我正在开发一个项目,该项目需要我根据用户在按钮上的选择来显示一些动态文本。
我知道如何做文本显示部分,但我一直坚持如何在固定大小的按钮上正确显示不同的文本。
例如:“苹果”和“我有一个苹果”。怎样才能达到显示“Apple”时文字变大适合按钮,而“I have an Apple”文字变小可能变成三行的效果?
谢谢!
我正在开发一个项目,该项目需要我根据用户在按钮上的选择来显示一些动态文本。
我知道如何做文本显示部分,但我一直坚持如何在固定大小的按钮上正确显示不同的文本。
例如:“苹果”和“我有一个苹果”。怎样才能达到显示“Apple”时文字变大适合按钮,而“I have an Apple”文字变小可能变成三行的效果?
谢谢!
Android 8.0 支持Autosizing TextViews所以你只需要指定android:autoSizeTextType="uniform"
. 对于旧版本,您可以使用android.support.v7.widget.AppCompatTextView
with app:autoSizeTextType="uniform"
。
偶然地,它也适用于按钮,而旧版本只是使用它android.support.v7.widget.AppCompatButton
。
希望这有帮助。
看看这个问题Auto Scale TextView Text to Fit within Bounds。同样的技术应该适用于按钮。
(是的,它比看起来应该的要复杂得多。)
样式.xml:
<style name="Widget.Button.CustomStyle" parent="Widget.MaterialComponents.Button">
<item name="android:minHeight">50dp</item>
<item name="android:maxWidth">300dp</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">16sp</item>
<item name="backgroundTint">@color/white</item>
<item name="cornerRadius">25dp</item>
<item name="autoSizeTextType">uniform</item>
<item name="autoSizeMinTextSize">10sp</item>
<item name="autoSizeMaxTextSize">16sp</item>
<item name="autoSizeStepGranularity">2sp</item>
<item name="android:maxLines">1</item>
<item name="android:textColor">@color/colorPrimary</item>
<item name="android:insetTop">0dp</item>
<item name="android:insetBottom">0dp</item>
<item name="android:lineSpacingExtra">4sp</item>
<item name="android:gravity">center</item>
</style>
用法:
<com.google.android.material.button.MaterialButton
android:id="@+id/blah"
style="@style/Widget.Button.CustomStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="Your long text, to the infinity and beyond!!! Why not :)" />
[来源:https ://stackoverflow.com/a/59302886/421467 ]
我知道这个问题已经有几年了,但我想添加一个完整的解决方案以供将来参考。
此代码基于AutoFitTextView并已针对 Buttons 进行了调整。具体来说,它还考虑了文本宽度,以避免在调整大小时出现断字。
有关所有许可信息,请访问上述链接。
您至少需要 java 文件:
AutoSizeTextButton.java
public class AutoSizeTextButton extends android.support.v7.widget.AppCompatButton implements AutofitHelper.OnTextSizeChangeListener{
private AutofitHelper mHelper;
public AutoSizeTextButton(Context context) {
this(context, null, 0);
}
public AutoSizeTextButton(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public AutoSizeTextButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs, defStyleAttr);
}
private void init(AttributeSet attrs, int defStyleAttr) {
mHelper = AutofitHelper.create(this, attrs, defStyleAttr)
.addOnTextSizeChangeListener(this);
}
// Getters and Setters
/**
* {@inheritDoc}
*/
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
if (mHelper != null) {
mHelper.setTextSize(unit, size);
}
}
/**
* {@inheritDoc}
*/
@Override
public void setLines(int lines) {
super.setLines(lines);
if (mHelper != null) {
mHelper.setMaxLines(lines);
}
}
/**
* {@inheritDoc}
*/
@Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
if (mHelper != null) {
mHelper.setMaxLines(maxLines);
}
}
/**
* Returns the {@link AutofitHelper} for this View.
*/
public AutofitHelper getAutofitHelper() {
return mHelper;
}
/**
* Returns whether or not the text will be automatically re-sized to fit its constraints.
*/
public boolean isSizeToFit() {
return mHelper.isEnabled();
}
/**
* Sets the property of this field (sizeToFit), to automatically resize the text to fit its
* constraints.
*/
public void setSizeToFit() {
setSizeToFit(true);
}
/**
* If true, the text will automatically be re-sized to fit its constraints; if false, it will
* act like a normal View.
*
* @param sizeToFit
*/
public void setSizeToFit(boolean sizeToFit) {
mHelper.setEnabled(sizeToFit);
}
/**
* Returns the maximum size (in pixels) of the text in this View.
*/
public float getMaxTextSize() {
return mHelper.getMaxTextSize();
}
/**
* Set the maximum text size to the given value, interpreted as "scaled pixel" units. This size
* is adjusted based on the current density and user font size preference.
*
* @param size The scaled pixel size.
*
* @attr ref android.R.styleable#TextView_textSize
*/
public void setMaxTextSize(float size) {
mHelper.setMaxTextSize(size);
}
/**
* Set the maximum text size to a given unit and value. See TypedValue for the possible
* dimension units.
*
* @param unit The desired dimension unit.
* @param size The desired size in the given units.
*
* @attr ref android.R.styleable#TextView_textSize
*/
public void setMaxTextSize(int unit, float size) {
mHelper.setMaxTextSize(unit, size);
}
/**
* Returns the minimum size (in pixels) of the text in this View.
*/
public float getMinTextSize() {
return mHelper.getMinTextSize();
}
/**
* Set the minimum text size to the given value, interpreted as "scaled pixel" units. This size
* is adjusted based on the current density and user font size preference.
*
* @param minSize The scaled pixel size.
*
* @attr ref R.styleable#AutofitButton_minTextSize
*/
public void setMinTextSize(int minSize) {
mHelper.setMinTextSize(TypedValue.COMPLEX_UNIT_SP, minSize);
}
/**
* Set the minimum text size to a given unit and value. See TypedValue for the possible
* dimension units.
*
* @param unit The desired dimension unit.
* @param minSize The desired size in the given units.
*
* @attr ref R.styleable#AutofitButton_minTextSize
*/
public void setMinTextSize(int unit, float minSize) {
mHelper.setMinTextSize(unit, minSize);
}
/**
* Returns the amount of precision used to calculate the correct text size to fit within its
* bounds.
*/
public float getPrecision() {
return mHelper.getPrecision();
}
/**
* Set the amount of precision used to calculate the correct text size to fit within its
* bounds. Lower precision is more precise and takes more time.
*
* @param precision The amount of precision.
*/
public void setPrecision(float precision) {
mHelper.setPrecision(precision);
}
@Override
public void onTextSizeChange(float textSize, float oldTextSize) {
// do nothing
}
}
AutofitHelper.java
/**
* A helper class to enable automatically resizing a {@link android.widget.Button}`s {@code textSize} to fit
* within its bounds.
*
* @attr ref R.styleable.AutofitButton_sizeToFit
* @attr ref R.styleable.AutofitButton_minTextSize
* @attr ref R.styleable.AutofitButton_precision
*/
public class AutofitHelper {
private static final String TAG = "AutoFitTextHelper";
private static final boolean SPEW = false;
// Minimum size of the text in pixels
private static final int DEFAULT_MIN_TEXT_SIZE = 8; //sp
// How precise we want to be when reaching the target textWidth size
private static final float DEFAULT_PRECISION = 0.5f;
/**
* Creates a new instance of {@code AutofitHelper} that wraps a {@link android.widget.Button} and enables
* automatically sizing the text to fit.
*/
public static AutofitHelper create(Button view) {
return create(view, null, 0);
}
/**
* Creates a new instance of {@code AutofitHelper} that wraps a {@link android.widget.Button} and enables
* automatically sizing the text to fit.
*/
public static AutofitHelper create(Button view, AttributeSet attrs) {
return create(view, attrs, 0);
}
/**
* Creates a new instance of {@code AutofitHelper} that wraps a {@link android.widget.Button} and enables
* automatically sizing the text to fit.
*/
public static AutofitHelper create(Button view, AttributeSet attrs, int defStyle) {
AutofitHelper helper = new AutofitHelper(view);
boolean sizeToFit = true;
if (attrs != null) {
Context context = view.getContext();
int minTextSize = (int) helper.getMinTextSize();
float precision = helper.getPrecision();
TypedArray ta = context.obtainStyledAttributes(
attrs,
R.styleable.AutofitButton,
defStyle,
0);
sizeToFit = ta.getBoolean(R.styleable.AutofitButton_sizeToFit, sizeToFit);
minTextSize = ta.getDimensionPixelSize(R.styleable.AutofitButton_minTextSize,
minTextSize);
precision = ta.getFloat(R.styleable.AutofitButton_precision, precision);
ta.recycle();
helper.setMinTextSize(TypedValue.COMPLEX_UNIT_PX, minTextSize)
.setPrecision(precision);
}
helper.setEnabled(sizeToFit);
return helper;
}
/**
* Re-sizes the textSize of the TextView so that the text fits within the bounds of the View.
*/
private static void autofit(Button view, TextPaint paint, float minTextSize, float maxTextSize,
int maxLines, float precision) {
if (maxLines <= 0 || maxLines == Integer.MAX_VALUE) {
// Don't auto-size since there's no limit on lines.
return;
}
int targetWidth = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();
if (targetWidth <= 0) {
return;
}
CharSequence text = view.getText();
TransformationMethod method = view.getTransformationMethod();
if (method != null) {
text = method.getTransformation(text, view);
}
Context context = view.getContext();
Resources r = Resources.getSystem();
DisplayMetrics displayMetrics;
float size = maxTextSize;
float high = size;
float low = 0;
if (context != null) {
r = context.getResources();
}
displayMetrics = r.getDisplayMetrics();
paint.set(view.getPaint());
paint.setTextSize(size);
if ((maxLines == 1 && paint.measureText(text, 0, text.length()) > targetWidth)
|| getLineCount(text, paint, size, targetWidth, displayMetrics) > maxLines
|| getMaxWordWidth(text, paint, size, displayMetrics) > targetWidth) {
size = getAutofitTextSize(text, getMaxWord(text, paint), paint, targetWidth, maxLines, low, high, precision,
displayMetrics);
}
if (size < minTextSize) {
size = minTextSize;
}
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
}
/**
* Recursive binary search to find the best size for the text.
*/
private static float getAutofitTextSize(CharSequence text, String widestWord, TextPaint paint,
float targetWidth, int maxLines, float low, float high, float precision,
DisplayMetrics displayMetrics) {
float mid = (low + high) / 2.0f;
int lineCount = 1;
StaticLayout layout = null;
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mid,
displayMetrics));
if (maxLines != 1) {
layout = new StaticLayout(text, paint, (int)targetWidth, Layout.Alignment.ALIGN_NORMAL,
1.0f, 0.0f, true);
lineCount = layout.getLineCount();
}
if (SPEW) Log.d(TAG, "low=" + low + " high=" + high + " mid=" + mid +
" target=" + targetWidth + " maxLines=" + maxLines + " lineCount=" + lineCount);
if (lineCount > maxLines) {
// For the case that `text` has more newline characters than `maxLines`.
if ((high - low) < precision) {
return low;
}
return getAutofitTextSize(text, widestWord, paint, targetWidth, maxLines, low, mid, precision,
displayMetrics);
}
else if (lineCount < maxLines) {
return getAutofitTextSize(text, widestWord, paint, targetWidth, maxLines, mid, high, precision,
displayMetrics);
}
else {
float maxLineWidth = 0;
if (maxLines == 1) {
maxLineWidth = paint.measureText(text, 0, text.length());
} else {
for (int i = 0; i < lineCount; i++) {
if (layout.getLineWidth(i) > maxLineWidth) {
maxLineWidth = layout.getLineWidth(i);
}
}
}
float maxWordWidth = paint.measureText(widestWord, 0, widestWord.length());
if(maxWordWidth > maxLineWidth){
maxLineWidth = maxWordWidth;
}
if ((high - low) < precision) {
return low;
} else if (maxLineWidth > targetWidth) {
return getAutofitTextSize(text, widestWord, paint, targetWidth, maxLines, low, mid, precision,
displayMetrics);
} else if (maxLineWidth < targetWidth) {
return getAutofitTextSize(text, widestWord, paint, targetWidth, maxLines, mid, high, precision,
displayMetrics);
} else {
return mid;
}
}
}
private static int getLineCount(CharSequence text, TextPaint paint, float size, float width,
DisplayMetrics displayMetrics) {
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, size,
displayMetrics));
StaticLayout layout = new StaticLayout(text, paint, (int)width,
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
return layout.getLineCount();
}
private static int getMaxLines(Button view) {
int maxLines = -1; // No limit (Integer.MAX_VALUE also means no limit)
TransformationMethod method = view.getTransformationMethod();
if (method != null && method instanceof SingleLineTransformationMethod) {
maxLines = 1;
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// setMaxLines() and getMaxLines() are only available on android-16+
maxLines = view.getMaxLines();
}
return maxLines;
}
private static float getMaxWordWidth(CharSequence text, TextPaint paint, float size,
DisplayMetrics displayMetrics) {
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, size,
displayMetrics));
String maxWord = getMaxWord(text, paint);
return paint.measureText(maxWord, 0, maxWord.length());
}
private static String getMaxWord(CharSequence text, TextPaint paint) {
String textStr = text.toString();
textStr = textStr.replace("-", "- ");
String[] words = textStr.split("[ \u00AD\u200B]");
String maxWord = "";
float maxWordWidth = 0;
for (String word : words) {
float wordWidth = paint.measureText(word, 0, word.length());
if (wordWidth > maxWordWidth){
maxWordWidth = wordWidth;
maxWord = word;
}
}
return maxWord;
}
// Attributes
private Button mButton;
private TextPaint mPaint;
/**
* Original textSize of the TextView.
*/
private float mTextSize;
private int mMaxLines;
private float mMinTextSize;
private float mMaxTextSize;
private float mPrecision;
private boolean mEnabled;
private boolean mIsAutofitting;
private ArrayList<OnTextSizeChangeListener> mListeners;
private TextWatcher mTextWatcher = new AutofitTextWatcher();
private View.OnLayoutChangeListener mOnLayoutChangeListener =
new AutofitOnLayoutChangeListener();
private AutofitHelper(Button view) {
final Context context = view.getContext();
float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
mButton = view;
mPaint = new TextPaint();
setRawTextSize(view.getTextSize());
mMaxLines = getMaxLines(view);
mMinTextSize = scaledDensity * DEFAULT_MIN_TEXT_SIZE;
mMaxTextSize = mTextSize;
mPrecision = DEFAULT_PRECISION;
}
/**
* Adds an {@link OnTextSizeChangeListener} to the list of those whose methods are called
* whenever the {@link android.widget.Button}'s {@code textSize} changes.
*/
public AutofitHelper addOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
if (mListeners == null) {
mListeners = new ArrayList<OnTextSizeChangeListener>();
}
mListeners.add(listener);
return this;
}
/**
* Removes the specified {@link OnTextSizeChangeListener} from the list of those whose methods
* are called whenever the {@link android.widget.Button}'s {@code textSize} changes.
*/
public AutofitHelper removeOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
if (mListeners != null) {
mListeners.remove(listener);
}
return this;
}
/**
* Returns the amount of precision used to calculate the correct text size to fit within its
* bounds.
*/
public float getPrecision() {
return mPrecision;
}
/**
* Set the amount of precision used to calculate the correct text size to fit within its
* bounds. Lower precision is more precise and takes more time.
*
* @param precision The amount of precision.
*/
public AutofitHelper setPrecision(float precision) {
if (mPrecision != precision) {
mPrecision = precision;
autofit();
}
return this;
}
/**
* Returns the minimum size (in pixels) of the text.
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set the minimum text size to the given value, interpreted as "scaled pixel" units. This size
* is adjusted based on the current density and user font size preference.
*
* @param size The scaled pixel size.
*
* @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
*/
public AutofitHelper setMinTextSize(float size) {
return setMinTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
/**
* Set the minimum text size to a given unit and value. See TypedValue for the possible
* dimension units.
*
* @param unit The desired dimension unit.
* @param size The desired size in the given units.
*
* @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
*/
public AutofitHelper setMinTextSize(int unit, float size) {
Context context = mButton.getContext();
Resources r = Resources.getSystem();
if (context != null) {
r = context.getResources();
}
setRawMinTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
return this;
}
private void setRawMinTextSize(float size) {
if (size != mMinTextSize) {
mMinTextSize = size;
autofit();
}
}
/**
* Returns the maximum size (in pixels) of the text.
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the maximum text size to the given value, interpreted as "scaled pixel" units. This size
* is adjusted based on the current density and user font size preference.
*
* @param size The scaled pixel size.
*
* @attr ref android.R.styleable#TextView_textSize
*/
public AutofitHelper setMaxTextSize(float size) {
return setMaxTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
/**
* Set the maximum text size to a given unit and value. See TypedValue for the possible
* dimension units.
*
* @param unit The desired dimension unit.
* @param size The desired size in the given units.
*
* @attr ref android.R.styleable#TextView_textSize
*/
public AutofitHelper setMaxTextSize(int unit, float size) {
Context context = mButton.getContext();
Resources r = Resources.getSystem();
if (context != null) {
r = context.getResources();
}
setRawMaxTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
return this;
}
private void setRawMaxTextSize(float size) {
if (size != mMaxTextSize) {
mMaxTextSize = size;
autofit();
}
}
/**
* @see TextView#getMaxLines()
*/
public int getMaxLines() {
return mMaxLines;
}
/**
* @see TextView#setMaxLines(int)
*/
public AutofitHelper setMaxLines(int lines) {
if (mMaxLines != lines) {
mMaxLines = lines;
autofit();
}
return this;
}
/**
* Returns whether or not automatically resizing text is enabled.
*/
public boolean isEnabled() {
return mEnabled;
}
/**
* Set the enabled state of automatically resizing text.
*/
public AutofitHelper setEnabled(boolean enabled) {
if (mEnabled != enabled) {
mEnabled = enabled;
if (enabled) {
mButton.addTextChangedListener(mTextWatcher);
mButton.addOnLayoutChangeListener(mOnLayoutChangeListener);
autofit();
} else {
mButton.removeTextChangedListener(mTextWatcher);
mButton.removeOnLayoutChangeListener(mOnLayoutChangeListener);
mButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
}
}
return this;
}
/**
* Returns the original text size of the View.
*
* @see TextView#getTextSize()
*/
public float getTextSize() {
return mTextSize;
}
/**
* Set the original text size of the View.
*
* @see TextView#setTextSize(float)
*/
public void setTextSize(float size) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
/**
* Set the original text size of the View.
*
* @see TextView#setTextSize(int, float)
*/
public void setTextSize(int unit, float size) {
if (mIsAutofitting) {
// We don't want to update the TextView's actual textSize while we're autofitting
// since it'd get set to the autofitTextSize
return;
}
Context context = mButton.getContext();
Resources r = Resources.getSystem();
if (context != null) {
r = context.getResources();
}
setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
}
private void setRawTextSize(float size) {
if (mTextSize != size) {
mTextSize = size;
}
}
private void autofit() {
float oldTextSize = mButton.getTextSize();
float textSize;
mIsAutofitting = true;
autofit(mButton, mPaint, mMinTextSize, mMaxTextSize, mMaxLines, mPrecision);
mIsAutofitting = false;
textSize = mButton.getTextSize();
if (textSize != oldTextSize) {
sendTextSizeChange(textSize, oldTextSize);
}
}
private void sendTextSizeChange(float textSize, float oldTextSize) {
if (mListeners == null) {
return;
}
for (OnTextSizeChangeListener listener : mListeners) {
listener.onTextSizeChange(textSize, oldTextSize);
}
}
private class AutofitTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
// do nothing
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
autofit();
}
@Override
public void afterTextChanged(Editable editable) {
// do nothing
}
}
private class AutofitOnLayoutChangeListener implements View.OnLayoutChangeListener {
@Override
public void onLayoutChange(View view, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
autofit();
}
}
/**
* When an object of a type is attached to an {@code AutofitHelper}, its methods will be called
* when the {@code textSize} is changed.
*/
public interface OnTextSizeChangeListener {
/**
* This method is called to notify you that the size of the text has changed to
* {@code textSize} from {@code oldTextSize}.
*/
public void onTextSizeChange(float textSize, float oldTextSize);
}
}
然后在 values/attrs.xml中添加需要的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AutofitButton">
<!-- Minimum size of the text. -->
<attr name="minTextSize" format="dimension" />
<!-- Amount of precision used to calculate the correct text size to fit within its
bounds. Lower precision is more precise and takes more time. -->
<attr name="precision" format="float" />
<!-- Defines whether to automatically resize text to fit to the view's bounds. -->
<attr name="sizeToFit" format="boolean" />
</declare-styleable>
<!-- Your other attributes -->
</resources>
你完成了!您现在可以使用 AutoSizeTextButton 类。
<your.package.name.AutoSizeTextButton
android:layout_width="..."
android:layout_height="..."
android:maxLines="2" />
并且一定要加上android:maxLines
值大于0的属性,否则什么都做不了!
补充说明:
文本会缩小,直到最长的单词不换行就可以放入按钮(或达到最小大小)。单词必须用普通空格或连字符分隔。该算法还考虑 aSOFT HYPHEN
或 aZERO WIDTH SPACE
单词分隔符,但是我强烈建议在使用它们之前对其进行测试,因为按钮中使用的 Android 文本引擎会忽略这些字符(至少在 API 19 中),这可能会导致奇怪的自动换行.
没有内置的方法可以做到这一点,您将需要创建/使用自定义视图,以使内部文本适应其边界。别担心,这不是第一次被问到,请参阅Auto Scale TextView Text to Fit within Bounds提供的自定义视图以获取工作代码。
如果有人正在研究如何禁用自动文本大小,可以通过
<TextView
app:autoSizeTextType="none" <!-- disabled -->
将上面的行添加到您的文本视图