我们需要多次将TextView的字体自动调整到给定的边界。
可悲的是,即使有很多主题和帖子(以及建议的解决方案)都在谈论这个问题(例如here,here和here),但它们都没有真正奏效。
这就是为什么,我决定测试每一个,直到我找到真正的交易。
我认为来自这样一个textView的要求应该是:
应允许使用任何字体,字体,样式和字符集。
应同时处理宽度和高度
没有截断,除非文字由于限制而无法适应,我们已经 给它(例如:文本太长,可用尺寸太小)。但是,如果我们愿意,我们可以请求水平/垂直滚动条,仅适用于那些情况。
应允许多行或单行。如果是多线,请允许max& min lines。
计算不应该慢。使用循环找到最佳尺寸?至少要优化它,不要每次增加1个样本。
如果是多行,应该允许更喜欢调整大小或使用更多行,和/或允许使用“\ n”字符自行选择行。
我已经尝试过这么多样本(包括链接的那些,我已经写过了),而且我也尝试修改它们来处理这些情况,我已经谈过了,但没有一个真正有效。< / p>
我做了一个示例项目,让我可以直观地看到TextView是否自动适合。
目前,我的示例项目只是随机化文本(英文字母加上数字)和textView的大小,并让它保持单行,但即使这样也不适用于我的任何样本试过。
这是代码(也可用here):
res/layout/activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=".MainActivity">
<Button android:id="@+id/button1" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" android:text="Button" />
<FrameLayout android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_above="@+id/button1"
android:layout_alignParentLeft="true" android:background="#ffff0000"
android:layout_alignParentRight="true" android:id="@+id/container"
android:layout_alignParentTop="true" />
</RelativeLayout>
src/.../MainActivity.java
public class MainActivity extends Activity
{
private final Random _random =new Random();
private static final String ALLOWED_CHARACTERS ="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
@Override
protected void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ViewGroup container=(ViewGroup)findViewById(R.id.container);
findViewById(R.id.button1).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(final View v)
{
container.removeAllViews();
final int maxWidth=container.getWidth();
final int maxHeight=container.getHeight();
final FontFitTextView fontFitTextView=new FontFitTextView(MainActivity.this);
final int width=_random.nextInt(maxWidth)+1;
final int height=_random.nextInt(maxHeight)+1;
fontFitTextView.setLayoutParams(new LayoutParams(width,height));
fontFitTextView.setSingleLine();
fontFitTextView.setBackgroundColor(0xff00ff00);
final String text=getRandomText();
fontFitTextView.setText(text);
container.addView(fontFitTextView);
Log.d("DEBUG","width:"+width+" height:"+height+" text:"+text);
}
});
}
private String getRandomText()
{
final int textLength=_random.nextInt(20)+1;
final StringBuilder builder=new StringBuilder();
for(int i=0;i<textLength;++i)
builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
return builder.toString();
}
}
有人知道这个常见问题的解决方案吗?
即使是一个解决方案,其功能要少得多,我所写的内容,例如只有一定数量的文本行,并根据其大小调整字体,但从来没有出现过奇怪的故障,并且与可用空间相比,文本变得太大/太小。
由于这是一个非常重要的TextView,我决定发布一个库,以便每个人都可以轻松地使用它,并为此做出贡献,here。
答案 0 :(得分:143)
感谢MartinH的简单修复here,此代码还会处理android:drawableLeft
,android:drawableRight
,android:drawableTop
和android:drawableBottom
标记。
我的回答应该让你开心Auto Scale TextView Text to Fit within Bounds
我修改了你的测试用例:
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ViewGroup container = (ViewGroup) findViewById(R.id.container);
findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
container.removeAllViews();
final int maxWidth = container.getWidth();
final int maxHeight = container.getHeight();
final AutoResizeTextView fontFitTextView = new AutoResizeTextView(MainActivity.this);
final int width = _random.nextInt(maxWidth) + 1;
final int height = _random.nextInt(maxHeight) + 1;
fontFitTextView.setLayoutParams(new FrameLayout.LayoutParams(
width, height));
int maxLines = _random.nextInt(4) + 1;
fontFitTextView.setMaxLines(maxLines);
fontFitTextView.setTextSize(500);// max size
fontFitTextView.enableSizeCache(false);
fontFitTextView.setBackgroundColor(0xff00ff00);
final String text = getRandomText();
fontFitTextView.setText(text);
container.addView(fontFitTextView);
Log.d("DEBUG", "width:" + width + " height:" + height
+ " text:" + text + " maxLines:" + maxLines);
}
});
}
我在每个 android开发者的请求中发布代码:
最终效果:
示例布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp" >
<com.vj.widgets.AutoResizeTextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:ellipsize="none"
android:maxLines="2"
android:text="Auto Resized Text, max 2 lines"
android:textSize="100sp" /> <!-- maximum size -->
<com.vj.widgets.AutoResizeTextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:ellipsize="none"
android:gravity="center"
android:maxLines="1"
android:text="Auto Resized Text, max 1 line"
android:textSize="100sp" /> <!-- maximum size -->
<com.vj.widgets.AutoResizeTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Auto Resized Text"
android:textSize="500sp" /> <!-- maximum size -->
</LinearLayout>
和Java代码:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Build;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.widget.TextView;
public class AutoResizeTextView extends TextView {
private interface SizeTester {
/**
*
* @param suggestedSize
* Size of text to be tested
* @param availableSpace
* available space in which text must fit
* @return an integer < 0 if after applying {@code suggestedSize} to
* text, it takes less space than {@code availableSpace}, > 0
* otherwise
*/
public int onTestSize(int suggestedSize, RectF availableSpace);
}
private RectF mTextRect = new RectF();
private RectF mAvailableSpaceRect;
private SparseIntArray mTextCachedSizes;
private TextPaint mPaint;
private float mMaxTextSize;
private float mSpacingMult = 1.0f;
private float mSpacingAdd = 0.0f;
private float mMinTextSize = 20;
private int mWidthLimit;
private static final int NO_LINE_LIMIT = -1;
private int mMaxLines;
private boolean mEnableSizeCache = true;
private boolean mInitializedDimens;
public AutoResizeTextView(Context context) {
super(context);
initialize();
}
public AutoResizeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
private void initialize() {
mPaint = new TextPaint(getPaint());
mMaxTextSize = getTextSize();
mAvailableSpaceRect = new RectF();
mTextCachedSizes = new SparseIntArray();
if (mMaxLines == 0) {
// no value was assigned during construction
mMaxLines = NO_LINE_LIMIT;
}
}
@Override
public void setTextSize(float size) {
mMaxTextSize = size;
mTextCachedSizes.clear();
adjustTextSize();
}
@Override
public void setMaxLines(int maxlines) {
super.setMaxLines(maxlines);
mMaxLines = maxlines;
adjustTextSize();
}
public int getMaxLines() {
return mMaxLines;
}
@Override
public void setSingleLine() {
super.setSingleLine();
mMaxLines = 1;
adjustTextSize();
}
@Override
public void setSingleLine(boolean singleLine) {
super.setSingleLine(singleLine);
if (singleLine) {
mMaxLines = 1;
} else {
mMaxLines = NO_LINE_LIMIT;
}
adjustTextSize();
}
@Override
public void setLines(int lines) {
super.setLines(lines);
mMaxLines = lines;
adjustTextSize();
}
@Override
public void setTextSize(int unit, float size) {
Context c = getContext();
Resources r;
if (c == null)
r = Resources.getSystem();
else
r = c.getResources();
mMaxTextSize = TypedValue.applyDimension(unit, size,
r.getDisplayMetrics());
mTextCachedSizes.clear();
adjustTextSize();
}
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the lower text size limit and invalidate the view
*
* @param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
adjustTextSize();
}
private void adjustTextSize() {
if (!mInitializedDimens) {
return;
}
int startSize = (int) mMinTextSize;
int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
- getCompoundPaddingTop();
mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
- getCompoundPaddingRight();
mAvailableSpaceRect.right = mWidthLimit;
mAvailableSpaceRect.bottom = heightLimit;
super.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
efficientTextSizeSearch(startSize, (int) mMaxTextSize,
mSizeTester, mAvailableSpaceRect));
}
private final SizeTester mSizeTester = new SizeTester() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public int onTestSize(int suggestedSize, RectF availableSPace) {
mPaint.setTextSize(suggestedSize);
String text = getText().toString();
boolean singleline = getMaxLines() == 1;
if (singleline) {
mTextRect.bottom = mPaint.getFontSpacing();
mTextRect.right = mPaint.measureText(text);
} else {
StaticLayout layout = new StaticLayout(text, mPaint,
mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
mSpacingAdd, true);
// Return early if we have more lines
if (getMaxLines() != NO_LINE_LIMIT
&& layout.getLineCount() > getMaxLines()) {
return 1;
}
mTextRect.bottom = layout.getHeight();
int maxWidth = -1;
for (int i = 0; i < layout.getLineCount(); i++) {
if (maxWidth < layout.getLineWidth(i)) {
maxWidth = (int) layout.getLineWidth(i);
}
}
mTextRect.right = maxWidth;
}
mTextRect.offsetTo(0, 0);
if (availableSPace.contains(mTextRect)) {
// May be too small, don't worry we will find the best match
return -1;
} else {
// too big
return 1;
}
}
};
/**
* Enables or disables size caching, enabling it will improve performance
* where you are animating a value inside TextView. This stores the font
* size against getText().length() Be careful though while enabling it as 0
* takes more space than 1 on some fonts and so on.
*
* @param enable
* Enable font size caching
*/
public void enableSizeCache(boolean enable) {
mEnableSizeCache = enable;
mTextCachedSizes.clear();
adjustTextSize(getText().toString());
}
private int efficientTextSizeSearch(int start, int end,
SizeTester sizeTester, RectF availableSpace) {
if (!mEnableSizeCache) {
return binarySearch(start, end, sizeTester, availableSpace);
}
int key = getText().toString().length();
int size = mTextCachedSizes.get(key);
if (size != 0) {
return size;
}
size = binarySearch(start, end, sizeTester, availableSpace);
mTextCachedSizes.put(key, size);
return size;
}
private static int binarySearch(int start, int end, SizeTester sizeTester,
RectF availableSpace) {
int lastBest = start;
int lo = start;
int hi = end - 1;
int mid = 0;
while (lo <= hi) {
mid = (lo + hi) >>> 1;
int midValCmp = sizeTester.onTestSize(mid, availableSpace);
if (midValCmp < 0) {
lastBest = lo;
lo = mid + 1;
} else if (midValCmp > 0) {
hi = mid - 1;
lastBest = hi;
} else {
return mid;
}
}
// Make sure to return the last best.
// This is what should always be returned.
return lastBest;
}
@Override
protected void onTextChanged(final CharSequence text, final int start,
final int before, final int after) {
super.onTextChanged(text, start, before, after);
adjustTextSize();
}
@Override
protected void onSizeChanged(int width, int height, int oldwidth,
int oldheight) {
mInitializedDimens = true;
mTextCachedSizes.clear();
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight) {
adjustTextSize();
}
}
}
警告:强>
答案 1 :(得分:14)
我已经修改了M-WaJeEh的答案,以考虑侧面的复合画面。
getCompoundPaddingXXXX()
方法返回padding of the view + drawable space
。例如,请参阅:TextView.getCompoundPaddingLeft()
<强>问题:强> 这样可以修复文本可用TextView空间的宽度和高度。如果我们不考虑可绘制的大小,它将被忽略,文本将最终与drawable重叠。
更新了细分受众群adjustTextSize(String)
:
private void adjustTextSize(final String text) {
if (!mInitialized) {
return;
}
int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
mAvailableSpaceRect.right = mWidthLimit;
mAvailableSpaceRect.bottom = heightLimit;
int maxTextSplits = text.split(" ").length;
AutoResizeTextView.super.setMaxLines(Math.min(maxTextSplits, mMaxLines));
super.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
binarySearch((int) mMinTextSize, (int) mMaxTextSize,
mSizeTester, mAvailableSpaceRect));
}
答案 2 :(得分:7)
好的,我上周使用了大量重写我的代码,以精确适合您的测试。您现在可以复制1:1,它将立即起作用 - 包括setSingleLine()
。如果您想要获得极端值,请务必调整MIN_TEXT_SIZE
和MAX_TEXT_SIZE
。
融合算法如下所示:
for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
// Go to the mean value...
testSize = (upperTextSize + lowerTextSize) / 2;
// ... inflate the dummy TextView by setting a scaled textSize and the text...
mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
mTestView.setText(text);
// ... call measure to find the current values that the text WANTS to occupy
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int tempHeight = mTestView.getMeasuredHeight();
// ... decide whether those values are appropriate.
if (tempHeight >= targetFieldHeight) {
upperTextSize = testSize; // Font is too big, decrease upperSize
}
else {
lowerTextSize = testSize; // Font is too small, increase lowerSize
}
}
全班可以be found here.
现在结果非常灵活。这与xml中声明的相同,如下所示:
<com.example.myProject.AutoFitText
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:text="@string/LoremIpsum" />
...以及在测试中以编程方式构建。
我真的希望你现在可以使用它。您现在可以立即致电setText(CharSequence text)
来使用它。该课程处理极少数的例外,应该是坚如磐石的。该算法唯一支持不的是:
setMaxLines(x)
x >= 2
但是如果你愿意,我已经添加了大量的评论来帮助你建立这个!
请注意:
如果您只是正常使用它而不将其限制在一行,那么可能会出现您之前提到的断字。这是 Android功能,不 AutoFitText
的错误。 Android总是会破坏TextView太长的单词,这实际上非常方便。如果你想在这里进行干预,请从第203行开始查看我的评论和代码。我已经写了足够的分词和对你的认可,你今后需要做的只是将这些词分开,然后按你的意愿修改。
总之:您应该高度考虑重写您的测试以支持空间字符,如下所示:
final Random _random = new Random();
final String ALLOWED_CHARACTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
final int textLength = _random.nextInt(80) + 20;
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < textLength; ++i) {
if (i % 7 == 0 && i != 0) {
builder.append(" ");
}
builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
}
((AutoFitText) findViewById(R.id.textViewMessage)).setText(builder.toString());
这将产生非常美妙(和更现实)的结果 你会发现评论也可以帮助你开始这个问题。
祝你好运和最好的问候
答案 3 :(得分:4)
我的要求是
我引用了链接:Auto Scale TextView Text to Fit within Bounds(包括评论)以及DialogTitle.java
我发现提供的解决方案很简单但不会动态更改文本框的大小。它很棒
列表视图中选定的文本长度大于 ScalableTextView
中的现有文本长度。当选择长度小于ScalableTextView
中现有文本的文本时,它不会增加文本的大小,显示较小尺寸的文本。
我修改了ScalableTextView.java,根据文本长度重新调整文本大小。这是我的ScalableTextView.java
public class ScalableTextView extends TextView
{
float defaultTextSize = 0.0f;
public ScalableTextView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
setSingleLine();
setEllipsize(TruncateAt.END);
defaultTextSize = getTextSize();
}
public ScalableTextView(Context context, AttributeSet attrs)
{
super(context, attrs);
setSingleLine();
setEllipsize(TruncateAt.END);
defaultTextSize = getTextSize();
}
public ScalableTextView(Context context)
{
super(context);
setSingleLine();
setEllipsize(TruncateAt.END);
defaultTextSize = getTextSize();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final Layout layout = getLayout();
if (layout != null)
{
final int lineCount = layout.getLineCount();
if (lineCount > 0)
{
int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
while (ellipsisCount > 0)
{
final float textSize = getTextSize();
// textSize is already expressed in pixels
setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ellipsisCount = layout.getEllipsisCount(lineCount - 1);
}
}
}
}
}
快乐编码......
答案 4 :(得分:3)
警告,Android 3(Honeycomb)和Android 4.0(冰淇淋三明治)中的错误
Androids版本:3.1 - 4.04有一个bug,即TextView中的setTextSize()仅适用于第一次(第一次调用)。
Issue 22493: TextView height bug in Android 4.0 和 Issue 17343: button's height and text does not return to its original state after increase and decrease the text size on HoneyComb 中描述了该错误。
解决方法是在更改大小之前为分配给TextView的文本添加换行符:
final String DOUBLE_BYTE_SPACE = "\u3000";
textView.append(DOUBLE_BYTE_SPACE);
我在我的代码中使用它如下:
final String DOUBLE_BYTE_SPACE = "\u3000";
AutoResizeTextView textView = (AutoResizeTextView) view.findViewById(R.id.aTextView);
String fixString = "";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1
&& android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
fixString = DOUBLE_BYTE_SPACE;
}
textView.setText(fixString + "The text" + fixString);
我在文字的左右两侧加上这个“\ u3000”字符,让它保持居中。如果您将其对齐到左侧,则仅附加到右侧。当然它也可以嵌入AutoResizeTextView小部件,但我想在外面保留修复代码。
答案 5 :(得分:3)
现在有了这个问题的官方解决方案。使用Android O自动调整TextViews可在支持库26中使用,并向后兼容到Android 4.0。
https://developer.android.com/preview/features/autosizing-textview.html
我不确定管理员是否删除了包含此信息的https://stackoverflow.com/a/42940171/47680。
答案 6 :(得分:3)
我将解释这个属性如何逐步降低Android版本:
1-在项目gradle文件中导入android支持库26.x.x.如果IDE上没有支持库,它们将自动下载。
dependencies {
compile 'com.android.support:support-v4:26.1.0'
compile 'com.android.support:appcompat-v7:26.1.0'
compile 'com.android.support:support-v13:26.1.0' }
allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
} }
2-打开你的布局XML文件,并像你的TextView一样重构这个标签。这种情况是:当在系统上填写字体大小时,将文本拟合到可用宽度,而不是自动换行。
<android.support.v7.widget.AppCompatTextView
android:id="@+id/textViewAutoSize"
android:layout_width="match_parent"
android:layout_height="25dp"
android:ellipsize="none"
android:text="Auto size text with compatible lower android versions."
android:textSize="12sp"
app:autoSizeMaxTextSize="14sp"
app:autoSizeMinTextSize="4sp"
app:autoSizeStepGranularity="0.5sp"
app:autoSizeTextType="uniform" />
答案 7 :(得分:3)
从2018年6月开始,Android正式支持 Android 4.0(API级别14)及更高版本this feature。
使用Android 8.0(API级别26)及更高版本:
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize,
int autoSizeStepGranularity, int unit);
Android 8.0之前的Android版本(API级别26):
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(TextView textView,
int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)
查看我的详细信息answer。
答案 8 :(得分:1)
将文本视图转换为图像,并在边界内缩放图像。
以下是有关如何将视图转换为图片的示例: Converting a view to Bitmap without displaying it in Android?
问题是,你的文字不可选,但它应该可以解决问题。我没有尝试过,所以我不确定它的外观(因为缩放)。
答案 9 :(得分:0)
下面是avalancha TextView,增加了自定义Font的功能。
用法:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:foo="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="match_parent" >
<de.meinprospekt.androidhd.view.AutoFitText
android:layout_width="wrap_content"
android:layout_height="10dp"
android:text="Small Text"
android:textColor="#FFFFFF"
android:textSize="100sp"
foo:customFont="fonts/Roboto-Light.ttf" />
</FrameLayout>
不要忘记添加:xmlns:foo =“http://schemas.android.com/apk/res-auto”。字体应该在资产firectory
import java.util.ArrayList;
import java.util.List;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.TextView;
import de.meinprospekt.androidhd.R;
import de.meinprospekt.androidhd.adapter.BrochuresHorizontalAdapter;
import de.meinprospekt.androidhd.util.LOG;
/**
* https://stackoverflow.com/a/16174468/2075875 This class builds a new android Widget named AutoFitText which can be used instead of a TextView to
* have the text font size in it automatically fit to match the screen width. Credits go largely to Dunni, gjpc, gregm and speedplane from
* Stackoverflow, method has been (style-) optimized and rewritten to match android coding standards and our MBC. This version upgrades the original
* "AutoFitTextView" to now also be adaptable to height and to accept the different TextView types (Button, TextClock etc.)
*
* @author pheuschk
* @createDate: 18.04.2013
*
* combined with: https://stackoverflow.com/a/7197867/2075875
*/
@SuppressWarnings("unused")
public class AutoFitText extends TextView {
private static final String TAG = AutoFitText.class.getSimpleName();
/** Global min and max for text size. Remember: values are in pixels! */
private final int MIN_TEXT_SIZE = 10;
private final int MAX_TEXT_SIZE = 400;
/** Flag for singleLine */
private boolean mSingleLine = false;
/**
* A dummy {@link TextView} to test the text size without actually showing anything to the user
*/
private TextView mTestView;
/**
* A dummy {@link Paint} to test the text size without actually showing anything to the user
*/
private Paint mTestPaint;
/**
* Scaling factor for fonts. It's a method of calculating independently (!) from the actual density of the screen that is used so users have the
* same experience on different devices. We will use DisplayMetrics in the Constructor to get the value of the factor and then calculate SP from
* pixel values
*/
private float mScaledDensityFactor;
/**
* Defines how close we want to be to the factual size of the Text-field. Lower values mean higher precision but also exponentially higher
* computing cost (more loop runs)
*/
private final float mThreshold = 0.5f;
/**
* Constructor for call without attributes --> invoke constructor with AttributeSet null
*
* @param context
*/
public AutoFitText(Context context) {
this(context, null);
}
public AutoFitText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public AutoFitText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
//TextViewPlus part https://stackoverflow.com/a/7197867/2075875
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoFitText);
String customFont = a.getString(R.styleable.AutoFitText_customFont);
setCustomFont(context, customFont);
a.recycle();
// AutoFitText part
mScaledDensityFactor = context.getResources().getDisplayMetrics().scaledDensity;
mTestView = new TextView(context);
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// make an initial call to onSizeChanged to make sure that refitText is triggered
onSizeChanged(AutoFitText.this.getWidth(), AutoFitText.this.getHeight(), 0, 0);
// Remove the LayoutListener immediately so we don't run into an infinite loop
//AutoFitText.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
removeOnGlobalLayoutListener(AutoFitText.this, this);
}
});
}
public boolean setCustomFont(Context ctx, String asset) {
Typeface tf = null;
try {
tf = Typeface.createFromAsset(ctx.getAssets(), asset);
} catch (Exception e) {
LOG.e(TAG, "Could not get typeface: "+e.getMessage());
return false;
}
setTypeface(tf);
return true;
}
@SuppressLint("NewApi")
public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener){
if (Build.VERSION.SDK_INT < 16) {
v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
} else {
v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
}
/**
* Main method of this widget. Resizes the font so the specified text fits in the text box assuming the text box has the specified width. This is
* done via a dummy text view that is refit until it matches the real target width and height up to a certain threshold factor
*
* @param targetFieldWidth The width that the TextView currently has and wants filled
* @param targetFieldHeight The width that the TextView currently has and wants filled
*/
private void refitText(String text, int targetFieldWidth, int targetFieldHeight) {
// Variables need to be visible outside the loops for later use. Remember size is in pixels
float lowerTextSize = MIN_TEXT_SIZE;
float upperTextSize = MAX_TEXT_SIZE;
// Force the text to wrap. In principle this is not necessary since the dummy TextView
// already does this for us but in rare cases adding this line can prevent flickering
this.setMaxWidth(targetFieldWidth);
// Padding should not be an issue since we never define it programmatically in this app
// but just to to be sure we cut it off here
targetFieldWidth = targetFieldWidth - this.getPaddingLeft() - this.getPaddingRight();
targetFieldHeight = targetFieldHeight - this.getPaddingTop() - this.getPaddingBottom();
// Initialize the dummy with some params (that are largely ignored anyway, but this is
// mandatory to not get a NullPointerException)
mTestView.setLayoutParams(new LayoutParams(targetFieldWidth, targetFieldHeight));
// maxWidth is crucial! Otherwise the text would never line wrap but blow up the width
mTestView.setMaxWidth(targetFieldWidth);
if (mSingleLine) {
// the user requested a single line. This is very easy to do since we primarily need to
// respect the width, don't have to break, don't have to measure...
/*************************** Converging algorithm 1 ***********************************/
for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
// Go to the mean value...
testSize = (upperTextSize + lowerTextSize) / 2;
mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
mTestView.setText(text);
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
if (mTestView.getMeasuredWidth() >= targetFieldWidth) {
upperTextSize = testSize; // Font is too big, decrease upperSize
} else {
lowerTextSize = testSize; // Font is too small, increase lowerSize
}
}
/**************************************************************************************/
// In rare cases with very little letters and width > height we have vertical overlap!
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
if (mTestView.getMeasuredHeight() > targetFieldHeight) {
upperTextSize = lowerTextSize;
lowerTextSize = MIN_TEXT_SIZE;
/*************************** Converging algorithm 1.5 *****************************/
for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
// Go to the mean value...
testSize = (upperTextSize + lowerTextSize) / 2;
mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
mTestView.setText(text);
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
if (mTestView.getMeasuredHeight() >= targetFieldHeight) {
upperTextSize = testSize; // Font is too big, decrease upperSize
} else {
lowerTextSize = testSize; // Font is too small, increase lowerSize
}
}
/**********************************************************************************/
}
} else {
/*********************** Converging algorithm 2 ***************************************/
// Upper and lower size converge over time. As soon as they're close enough the loop
// stops
// TODO probe the algorithm for cost (ATM possibly O(n^2)) and optimize if possible
for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
// Go to the mean value...
testSize = (upperTextSize + lowerTextSize) / 2;
// ... inflate the dummy TextView by setting a scaled textSize and the text...
mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
mTestView.setText(text);
// ... call measure to find the current values that the text WANTS to occupy
mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int tempHeight = mTestView.getMeasuredHeight();
// int tempWidth = mTestView.getMeasuredWidth();
// LOG.debug("Measured: " + tempWidth + "x" + tempHeight);
// LOG.debug("TextSize: " + testSize / mScaledDensityFactor);
// ... decide whether those values are appropriate.
if (tempHeight >= targetFieldHeight) {
upperTextSize = testSize; // Font is too big, decrease upperSize
} else {
lowerTextSize = testSize; // Font is too small, increase lowerSize
}
}
/**************************************************************************************/
// It is possible that a single word is wider than the box. The Android system would
// wrap this for us. But if you want to decide fo yourself where exactly to break or to
// add a hyphen or something than you're going to want to implement something like this:
mTestPaint.setTextSize(lowerTextSize);
List<String> words = new ArrayList<String>();
for (String s : text.split(" ")) {
Log.i("tag", "Word: " + s);
words.add(s);
}
for (String word : words) {
if (mTestPaint.measureText(word) >= targetFieldWidth) {
List<String> pieces = new ArrayList<String>();
// pieces = breakWord(word, mTestPaint.measureText(word), targetFieldWidth);
// Add code to handle the pieces here...
}
}
}
/**
* We are now at most the value of threshold away from the actual size. To rather undershoot than overshoot use the lower value. To match
* different screens convert to SP first. See {@link http://developer.android.com/guide/topics/resources/more-resources.html#Dimension} for
* more details
*/
this.setTextSize(TypedValue.COMPLEX_UNIT_SP, lowerTextSize / mScaledDensityFactor);
return;
}
/**
* This method receives a call upon a change in text content of the TextView. Unfortunately it is also called - among others - upon text size
* change which means that we MUST NEVER CALL {@link #refitText(String)} from this method! Doing so would result in an endless loop that would
* ultimately result in a stack overflow and termination of the application
*
* So for the time being this method does absolutely nothing. If you want to notify the view of a changed text call {@link #setText(CharSequence)}
*/
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
// Super implementation is also intentionally empty so for now we do absolutely nothing here
super.onTextChanged(text, start, lengthBefore, lengthAfter);
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
if (width != oldWidth && height != oldHeight) {
refitText(this.getText().toString(), width, height);
}
}
/**
* This method is guaranteed to be called by {@link TextView#setText(CharSequence)} immediately. Therefore we can safely add our modifications
* here and then have the parent class resume its work. So if text has changed you should always call {@link TextView#setText(CharSequence)} or
* {@link TextView#setText(CharSequence, BufferType)} if you know whether the {@link BufferType} is normal, editable or spannable. Note: the
* method will default to {@link BufferType#NORMAL} if you don't pass an argument.
*/
@Override
public void setText(CharSequence text, BufferType type) {
int targetFieldWidth = this.getWidth();
int targetFieldHeight = this.getHeight();
if (targetFieldWidth <= 0 || targetFieldHeight <= 0 || text.equals("")) {
// Log.v("tag", "Some values are empty, AutoFitText was not able to construct properly");
} else {
refitText(text.toString(), targetFieldWidth, targetFieldHeight);
}
super.setText(text, type);
}
/**
* TODO add sensibility for {@link #setMaxLines(int)} invocations
*/
@Override
public void setMaxLines(int maxLines) {
// TODO Implement support for this. This could be relatively easy. The idea would probably
// be to manipulate the targetHeight in the refitText-method and then have the algorithm do
// its job business as usual. Nonetheless, remember the height will have to be lowered
// dynamically as the font size shrinks so it won't be a walk in the park still
if (maxLines == 1) {
this.setSingleLine(true);
} else {
throw new UnsupportedOperationException("MaxLines != 1 are not implemented in AutoFitText yet, use TextView instead");
}
}
@Override
public void setSingleLine(boolean singleLine) {
// save the requested value in an instance variable to be able to decide later
mSingleLine = singleLine;
super.setSingleLine(singleLine);
}
}
已知错误: 不适用于Android 4.03 - 字体不可见或非常小(原始avalancha也不起作用) 以下是该错误的解决方法:https://stackoverflow.com/a/21851239/2075875
答案 10 :(得分:0)
尝试将LayoutParams
和MaxWidth
以及MaxHeight
添加到TextView
。它将强制布局尊重父容器而不是溢出。
textview.setLayoutParams(new LayoutParams(LinearLayout.MATCH_PARENT,LinearLayout.WRAP_CONTENT));
int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);`
答案 11 :(得分:0)
试试这个
TextWatcher changeText = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
tv3.setText(et.getText().toString());
tv3.post(new Runnable() {
@Override
public void run() {
while(tv3.getLineCount() >= 3){
tv3.setTextSize((tv3.getTextSize())-1);
}
}
});
}
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void afterTextChanged(Editable s) { }
};
答案 12 :(得分:0)
如果您正在寻找更轻松的事情:
public MyTextView extends TextView{
public void resize(String text, float textViewWidth, float textViewHeight) {
Paint p = new Paint();
Rect bounds = new Rect();
p.setTextSize(1);
p.getTextBounds(text, 0, text.length(), bounds);
float widthDifference = (textViewWidth)/bounds.width();
float heightDifference = (textViewHeight);
textSize = Math.min(widthDifference, heightDifference);
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
答案 13 :(得分:0)
快速修复@Malachiasz描述的问题
我已通过在自动调整大小类中添加自定义支持来修复此问题:
public void setTextCompat(final CharSequence text) {
setTextCompat(text, BufferType.NORMAL);
}
public void setTextCompat(final CharSequence text, BufferType type) {
// Quick fix for Android Honeycomb and Ice Cream Sandwich which sets the text only on the first call
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
super.setText(DOUBLE_BYTE_WORDJOINER + text + DOUBLE_BYTE_WORDJOINER, type);
} else {
super.setText(text, type);
}
}
@Override
public CharSequence getText() {
String originalText = super.getText().toString();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
// We try to remove the word joiners we added using compat method - if none found - this will do nothing.
return originalText.replaceAll(DOUBLE_BYTE_WORDJOINER, "");
} else {
return originalText;
}
}
只需拨打yourView.setTextCompat(newTextValue)
而不是yourView.setText(newTextValue)
答案 14 :(得分:0)
从Android O开始,可以在xml:
中自动调整文本大小https://developer.android.com/preview/features/autosizing-textview.html
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="12sp"
app:autoSizeMaxTextSize="100sp"
app:autoSizeStepGranularity="2sp"
/>
Android O允许您指示TextView使文本大小 自动扩展或收缩以填充其布局 TextView的特性和边界。这个设置成功了 使用动态更容易在不同屏幕上优化文本大小 内容。
支持库26.0 Beta为自动调整提供全面支持 在Android之前运行Android版本的设备上的TextView功能 O.该库提供对Android 4.0(API级别14)和 更高。 android.support.v4.widget包中包含 TextViewCompat类用于访问向后兼容的功能 方式。
答案 15 :(得分:0)
在尝试使用Android官方Autosizing TextView后,我发现您的Android版本是否低于Android 8.0(API级别26),则需要使用android.support.v7.widget.AppCompatTextView
,并确保您的支持库版本高于26.0 .0。示例:
<android.support.v7.widget.AppCompatTextView
android:layout_width="130dp"
android:layout_height="32dp"
android:maxLines="1"
app:autoSizeMaxTextSize="22sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="2sp"
app:autoSizeTextType="uniform" />
更新:
根据@ android-developer的回复,我检查了AppCompatActivity
源代码,并在onCreate
中找到了这两行
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
和AppCompatDelegateImpl
的{{1}}
createView
它使用 if (mAppCompatViewInflater == null) {
mAppCompatViewInflater = new AppCompatViewInflater();
}
膨胀视图,当AppCompatViewInflater
createView时,它将对“ TextView”使用AppCompatTextView。
AppCompatViewInflater
在我的项目中,我不使用public final View createView(){
...
View view = null;
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
...
}
,因此我需要在xml中使用AppCompatActivity
。