私はそれがそのgetHeightとgetWidthの境界内に収まるようにTextView
で折り返すテキストのサイズを変更するための最適な方法を探しています。私は単にテキストを折り返す方法を探しているのではありません。両方のテキストが折り返され、画面全体に収まるように十分に小さいことを確認したいのです。
StackOverflowで自動リサイズが必要なケースをいくつか見たことがありますが、ハックの解決策がある特別な場合、解決策がない場合、またはTextView
を十分小さくなるまで再帰的に再描画する方法のいずれかです。テキストを見るユーザは、再帰ごとに段階的に縮小します。
しかし、私がしていることを含まない良い解決策を見つけた人がいると確信しています。テキストを解析して測定し、テキストのサイズを変更し、適切な小さいサイズが見つかるまで繰り返します。
テキストをラップするためにTextView
はどのルーチンを使用しますか?テキストが十分に小さくなるかどうかを予測するために、それらを何らかの形で使用することはできませんでしたか。
tl; dr :getHeightとgetWidthの境界に収まるようにラップして、TextView
を自動サイズ変更するベストプラクティスの方法はありますか?
2018年6月から、AndroidはAndroid 4.0(APIレベル14)以降で正式にこの機能のサポートを開始しました。
次のところでチェックしてください。 TextViewの自動サイズ調整
Android 8.0(APIレベル26)以降:
<?xml version="1.0" encoding="utf-8"?>
<TextView
Android:layout_width="match_parent"
Android:layout_height="200dp"
Android:autoSizeTextType="uniform"
Android:autoSizeMinTextSize="12sp"
Android:autoSizeMaxTextSize="100sp"
Android:autoSizeStepGranularity="2sp" />
プログラム的に:
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize,
int autoSizeStepGranularity, int unit)
textView.setAutoSizeTextTypeUniformWithConfiguration(
1, 17, 1, TypedValue.COMPLEX_UNIT_DIP);
Android 8.0より前のAndroidバージョン(APIレベル26):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<TextView
Android:layout_width="match_parent"
Android:layout_height="200dp"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="12sp"
app:autoSizeMaxTextSize="100sp"
app:autoSizeStepGranularity="2sp" />
</LinearLayout>
プログラム的に:
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(
int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(textView, 1, 17, 1,
TypedValue.COMPLEX_UNIT_DIP);
注意:TextViewのレイアウト幅= "match_parent"または絶対サイズ!
私はモバイル開発者として、自動サイズ変更をサポートするネイティブなものが何も見つからなかったのは残念でした。私の検索では私にとってうまくいったことは何も起こらず、結局、私は週末のうち半分を過ごし、私自身の自動サイズ変更テキストビューを作成しました。私はここにコードを載せるつもりですし、うまくいけばそれは他の誰かに役立つでしょう。
このクラスは、高さを測定するために、元のテキストビューのテキストPaintを含む静的レイアウトを使用します。そこから、2フォントピクセルずつステップダウンして、収まるサイズになるまで再測定します。最後に、それでもテキストが収まらない場合は、省略記号を付けます。テキストをアニメートしてビューを再利用するという要件がありましたが、これは私が持っているデバイスではうまくいくようで、私のためには十分に速く動くようです。
/**
* DO WHAT YOU WANT TO PUBLIC LICENSE
* Version 2, December 2004
*
* Copyright (C) 2004 Sam Hocevar <[email protected]>
*
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT YOU WANT TO.
*/
import Android.content.Context;
import Android.text.Layout.Alignment;
import Android.text.StaticLayout;
import Android.text.TextPaint;
import Android.util.AttributeSet;
import Android.util.TypedValue;
import Android.widget.TextView;
/**
* Text view that auto adjusts text size to fit within the view.
* If the text size equals the minimum text size and still does not
* fit, append with an Ellipsis.
*
* @author Chase Colburn
* @since Apr 4, 2011
*/
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 20;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add Ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
* @param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
* @param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
* @return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
* @param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
* @return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add Ellipsis to text that overflows at the smallest text size
* @param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add Ellipsis to text that overflows at the smallest text size
* @return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if (mTextSize > 0) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
mMaxTextSize = mTextSize;
}
}
/**
* Resize text after measuring
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed || mNeedsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
* @param width
* @param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no text
if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
return;
}
if (getTransformationMethod() != null) {
text = getTransformationMethod().getTransformation(text, this);
}
// Get the text view's Paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while (textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append an Ellipsis
if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
TextPaint Paint = new TextPaint(textPaint);
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, Paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if (layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if (lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an Ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the Ellipsis
while (width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if (mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text Paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint Paint, int width, float textSize) {
// modified: make a copy of the original TextPaint object for measuring
// (apparently the object gets modified while measuring, see also the
// docs for TextView.getPaint() (which states to access it read-only)
TextPaint paintCopy = new TextPaint(Paint);
// Update the text Paint object
paintCopy.setTextSize(textSize);
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}
警告です。 Android 3.1 - 4.04に影響する重要な修正済みのバグがあり、すべてのAutoResizingTextViewウィジェットが機能しません。お読みください: https://stackoverflow.com/a/21851157/2075875
UPDATE: 以下のコードも、ここで説明されている ideal AutoScaleTextViewの要件を満たしています: Android用のTextViewの自動調整 そして勝者としてマークされています。
UPDATE 2: 追加されたmaxlinesのサポートは、APIレベル16より前ではうまく動作します。
Update 3: MartinHの簡単な修正 here のおかげで、Android:drawableLeft
、Android:drawableRight
、Android:drawableTop
およびAndroid:drawableBottom
タグのサポートが追加されました。
私の要求は少し違いました。私は整数をアニメーション化していたのでサイズを調整する効率的な方法を必要としていました、2秒でTextView
で0から〜4000まで、そしてそれに応じてサイズを調整したいと思いました。私の解決策は少し違った働きをします。最終結果は次のようになります。
そしてそれを生成したコード
<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 mInitiallized;
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;
}
mInitiallized = true;
}
@Override
public void setText(final CharSequence text, BufferType type) {
super.setText(text, type);
adjustTextSize(text.toString());
}
@Override
public void setTextSize(float size) {
mMaxTextSize = size;
mTextCachedSizes.clear();
adjustTextSize(getText().toString());
}
@Override
public void setMaxLines(int maxlines) {
super.setMaxLines(maxlines);
mMaxLines = maxlines;
reAdjust();
}
public int getMaxLines() {
return mMaxLines;
}
@Override
public void setSingleLine() {
super.setSingleLine();
mMaxLines = 1;
reAdjust();
}
@Override
public void setSingleLine(boolean singleLine) {
super.setSingleLine(singleLine);
if (singleLine) {
mMaxLines = 1;
} else {
mMaxLines = NO_LINE_LIMIT;
}
reAdjust();
}
@Override
public void setLines(int lines) {
super.setLines(lines);
mMaxLines = lines;
reAdjust();
}
@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(getText().toString());
}
@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;
reAdjust();
}
private void reAdjust() {
adjustTextSize(getText().toString());
}
private void adjustTextSize(String string) {
if (!mInitiallized) {
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);
}
String text = getText().toString();
int key = text == null ? 0 : text.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 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);
reAdjust();
}
@Override
protected void onSizeChanged(int width, int height, int oldwidth,
int oldheight) {
mTextCachedSizes.clear();
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight) {
reAdjust();
}
}
}
実際のところ解決策はGoogleの DialogTitle class ...にありますが、一般に認められているものほど効果的ではありませんが、はるかに単純で適応が簡単です。
public class SingleLineTextView extends TextView {
public SingleLineTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setSingleLine();
setEllipsize(TruncateAt.END);
}
public SingleLineTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setSingleLine();
setEllipsize(TruncateAt.END);
}
public SingleLineTextView(Context context) {
super(context);
setSingleLine();
setEllipsize(TruncateAt.END);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final Layout layout = getLayout();
if (layout != null) {
final int lineCount = layout.getLineCount();
if (lineCount > 0) {
final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
if (ellipsisCount > 0) {
final float textSize = getTextSize();
// textSize is already expressed in pixels
setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
}
}
私はChaseのソリューションから始めましたが、それが私のデバイスで期待通りに動作する前に2つのことを適応させなければなりませんでした(Galaxy Nexus、Android 4.1)。
レイアウトの測定にTextPaintのコピーを使用する TextView.getPaint() のドキュメントには読み取り専用で使用する必要があると記載されているので、測定にPaintオブジェクトを使用する両方の場所でコピーを作成しました。
// 1. in resizeText()
if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
TextPaint Paint = new TextPaint(textPaint);
// 2. in getTextHeight()
private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
// modified: make a copy of the original TextPaint object for measuring
// (apparently the object gets modified while measuring, see also the
// docs for TextView.getPaint() (which states to access it read-only)
TextPaint Paint = new TextPaint(originalPaint);
// Update the text Paint object
Paint.setTextSize(textSize);
...
文字サイズの設定に単位を追加する
// modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))
setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
これら2つの修正により、解決策は私にとって完璧に機能しています。元のソリューションが機能していなかったのはAndroid 4.xによるものかどうかわかりません。あなたがそれを実際に使っているのを見たり、それが本当にあなたのデバイスで動くかどうかをテストしたい場合は、私のフラッシュカードアプリ Flashcards ToGo を見てください。テキストは任意の長さにすることができ、フラッシュカードはさまざまなアクティビティで表示されますが、小さい場合もあれば大きい場合もあり、さらに横長モードと縦長モードの場合もあります。
私はChaseのAutoResizeTextViewクラスから始めて、縦と横の両方に合うように小さな変更を加えました。
私はまた、(Eclipseの)レイアウトエディタで、ややあいまいな条件下でNull Pointer Exceptionを引き起こすバグを発見しました。
変更1:テキストを垂直方向と水平方向の両方に合わせる
Chaseのオリジナルバージョンは、縦に収まるまでテキストサイズを縮小しますが、テキストをターゲットより広くすることができます。私の場合は、指定した幅に収まるようにテキストが必要でした。
この変更により、テキストが垂直方向と水平方向の両方に収まるまでサイズ変更されます。
resizeText(
int ,
int )
は次のように変更されます。
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
に:
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
int textWidth = getTextWidth(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(((textHeight >= height) || (textWidth >= width) ) && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
textWidth = getTextWidth(text, textPaint, width, targetTextSize);
}
次に、ファイルの最後にgetTextWidth()
ルーチンを追加します。それはほんの少し修正されたgetTextHeight()
です。高さと幅の両方を返す1つのルーチンにそれらを組み合わせる方がおそらくより効率的でしょう。
// Set the text size of the text Paint object and use a static layout to render text off screen before measuring
private int getTextWidth(CharSequence source, TextPaint Paint, int width, float textSize) {
// Update the text Paint object
Paint.setTextSize(textSize);
// Draw using a static layout
StaticLayout layout = new StaticLayout(source, Paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
layout.draw(sTextResizeCanvas);
return layout.getWidth();
}
変更2:Eclipse Android Layout EditorのEmptyStackExceptionを修正しました
かなりあいまいで非常に正確な条件下では、レイアウトエディタはレイアウトのグラフィック表示を表示できません。 com.Android.ide.Eclipse.adtに「EmptyStackException:null」例外がスローされます。
必要な条件は以下のとおりです。
- AutoResizeTextViewウィジェットを作成します
- そのウィジェットのスタイルを作成
- テキスト項目をスタイルで指定します。ウィジェット定義にない
のように:
res/layout/main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="fill_parent"
Android:layout_height="fill_parent"
Android:orientation="vertical" >
<com.ajw.DemoCrashInADT.AutoResizeTextView
Android:id="@+id/resizingText"
style="@style/myTextStyle" />
</LinearLayout>
res/values/myStyles.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="myTextStyle" parent="@Android:style/Widget.TextView">
<item name="Android:layout_height">wrap_content</item>
<item name="Android:layout_width">fill_parent</item>
<item name="Android:text">some message</item>
</style>
</resources>
これらのファイルで、編集時に グラフィカルレイアウト タブを選択すると、main.xml
が表示されます。
エラー!
EmptyStackException:null
例外の詳細がウィンドウ>ビューの表示>エラーログに記録されています
レイアウトのグラフィカルビューの代わりに。
もう長すぎる話を短くするために、これを次の行まで追跡しました(これもresizeText
で)。
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
問題は、特定の条件下ではmTextSizeが初期化されないことです。値は0です。
上記では、(Math.minの結果として)targetTextSize
はゼロに設定されています。
そのゼロは、textSize
引数としてgetTextHeight()
(およびgetTextWidth()
)に渡されます。になるとlayout.draw(sTextResizeCanvas);
私たちは例外を受けます。
resizeText()
とgetTextHeight()
でテストするよりもgetTextWidth()
の始めに(mTextSize == 0)
をテストするほうが効率的です。早くテストすることで、介在する作業をすべて節約できます。
これらのアップデートにより、(私のcrash-demoテストアプリのように)ファイルは次のようになります。
//
// from: http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds
//
//
package com.ajw.DemoCrashInADT;
import Android.content.Context;
import Android.graphics.Canvas;
import Android.text.Layout.Alignment;
import Android.text.StaticLayout;
import Android.text.TextPaint;
import Android.util.AttributeSet;
import Android.util.TypedValue;
import Android.widget.TextView;
/**
* Text view that auto adjusts text size to fit within the view. If the text
* size equals the minimum text size and still does not fit, append with an
* Ellipsis.
*
* 2011-10-29 changes by Alan Jay Weiner
* * change to fit both vertically and horizontally
* * test mTextSize for 0 in resizeText() to fix exception in Layout Editor
*
* @author Chase Colburn
* @since Apr 4, 2011
*/
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 20;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Off screen canvas for text size rendering
private static final Canvas sTextResizeCanvas = new Canvas();
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for
// resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add Ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes, set the force resize flag to true and reset the text
* size.
*/
@Override
protected void onTextChanged(final CharSequence text, final int start,
final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
*
* @param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
*
* @param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
*
* @return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
*
* @param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
*
* @return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add Ellipsis to text that overflows at the smallest text size
*
* @param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add Ellipsis to text that overflows at the smallest text
* size
*
* @return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
mMaxTextSize = mTextSize;
}
/**
* Resize text after measuring
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed || mNeedsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft()
- getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom()
- getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
*
* @param width
* @param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no
// text
// or if mTextSize has not been initialized
if (text == null || text.length() == 0 || height <= 0 || width <= 0
|| mTextSize == 0) {
return;
}
// Get the text view's Paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the
// default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize)
: mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
int textWidth = getTextWidth(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min
// text size, incrementally try smaller sizes
while (((textHeight > height) || (textWidth > width))
&& targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
textWidth = getTextWidth(text, textPaint, width, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append
// an Ellipsis
if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, textPaint, width,
Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
layout.draw(sTextResizeCanvas);
int lastLine = layout.getLineForVertical(height) - 1;
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the
// Ellipsis
while (width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1)
.toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
// Some devices try to auto adjust line spacing, so force default line
// spacing
// and invalidate the layout as a side effect
textPaint.setTextSize(targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if (mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text Paint object and use a static layout to
// render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint Paint, int width,
float textSize) {
// Update the text Paint object
Paint.setTextSize(textSize);
// Draw using a static layout
StaticLayout layout = new StaticLayout(source, Paint, width,
Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
layout.draw(sTextResizeCanvas);
return layout.getHeight();
}
// Set the text size of the text Paint object and use a static layout to
// render text off screen before measuring
private int getTextWidth(CharSequence source, TextPaint Paint, int width,
float textSize) {
// Update the text Paint object
Paint.setTextSize(textSize);
// Draw using a static layout
StaticLayout layout = new StaticLayout(source, Paint, width,
Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
layout.draw(sTextResizeCanvas);
return layout.getWidth();
}
}
最初のコードを投稿してくれて、Chaseに感謝します。私はそれがどのように機能したかを見るためにそれを読んで楽しんで、そして私はそれを追加することができてうれしい。
Android 4.xの場合の回避策:
私はAutoResizeTextViewを見つけました、そしてそれは私のAndroid 2.1エミュレータでとてもうまくいきます。私はそれがとても好きでした。しかし残念ながらそれは私自身の4.0.4携帯電話と4.1エミュレータで失敗しました。試したところ、xmlのAutoResizeTextViewクラスに次の属性を追加することで、簡単に解決できることがわかりました。
アンドロイド:ellipsize = "none"
Android:singleLine = "true"
上記の2行で、今AutoResizeTextViewは私の2.1と4.1エミュレータと私自身の4.0.4携帯電話で完全に働いています。
これがお役に立てば幸いです。 :-)
警告、Androidハニカムおよびアイスクリームサンドイッチのバグ
Androidのバージョン:3.1 - 4.04にはバグがあり、TextViewの中のsetTextSize()は一度だけ動作します(最初の呼び出し)。
バグはここで説明されています: http://code.google.com/p/Android/issues/detail?id=22493http://code.google.com/p/Android/issues/detail? id = 17343#c9
これを回避するには、サイズを変更する前に、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ウィジェットに埋め込むこともできますが、修正コードを外部に置いておきたいと思いました。
AppcompatTextViewは、Support Library 26.0から自動サイズ変更をサポートするようになりました。 Android OのTextViewも同じように機能します。 詳しい情報はここにあります。 簡単なデモアプリが見つかります ここ 。
<LinearLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:layout_width="match_parent"
Android:layout_height="wrap_content">
<TextView
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="12sp"
app:autoSizeMaxTextSize="100sp"
app:autoSizeStepGranularity="2sp"
/>
</LinearLayout>
ビューの境界にぴったり合うようにテキストのサイズを変更する必要がありました。 Chaseの解決策はテキストサイズを減らすだけで、これは十分なスペースがあればテキストも拡大する。
resizeText()
メソッドに見られるように、すべてを 速くて正確に にするには、繰り返しではなく二分法を使いました。だからこそMAX_TEXT_SIZE
オプションもあります。私もonoelleのヒントを含めました。
Android 4.4でテスト済み
/**
* DO WHAT YOU WANT TO PUBLIC LICENSE
* Version 2, December 2004
*
* Copyright (C) 2004 Sam Hocevar <[email protected]>
*
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT YOU WANT TO.
*/
import Android.content.Context;
import Android.text.Layout.Alignment;
import Android.text.StaticLayout;
import Android.text.TextPaint;
import Android.util.AttributeSet;
import Android.util.TypedValue;
import Android.widget.TextView;
/**
* Text view that auto adjusts text size to fit within the view.
* If the text size equals the minimum text size and still does not
* fit, append with an Ellipsis.
*
* @author Chase Colburn
* @since Apr 4, 2011
*/
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 26;
// Maximum text size for this text view
public static final float MAX_TEXT_SIZE = 128;
private static final int BISECTION_LOOP_WATCH_DOG = 30;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = MAX_TEXT_SIZE;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add Ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
* @param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
* @param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
* @return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
* @param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
* @return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add Ellipsis to text that overflows at the smallest text size
* @param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add Ellipsis to text that overflows at the smallest text size
* @return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if(mTextSize > 0) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
//mMaxTextSize = mTextSize;
}
}
/**
* Resize text after measuring
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if(changed || mNeedsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
* @param width
* @param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no text
if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
return;
}
// Get the text view's Paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// Bisection method: fast & precise
float lower = mMinTextSize;
float upper = mMaxTextSize;
int loop_counter=1;
float targetTextSize = (lower+upper)/2;
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
while(loop_counter < BISECTION_LOOP_WATCH_DOG && upper - lower > 1) {
targetTextSize = (lower+upper)/2;
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
if(textHeight > height)
upper = targetTextSize;
else
lower = targetTextSize;
loop_counter++;
}
targetTextSize = lower;
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// If we had reached our minimum text size and still don't fit, append an Ellipsis
if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
TextPaint paintCopy = new TextPaint(textPaint);
paintCopy.setTextSize(targetTextSize);
StaticLayout layout = new StaticLayout(text, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if(layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if(lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an Ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = paintCopy.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the Ellipsis
while(width < lineWidth + ellipseWidth) {
lineWidth = paintCopy.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if(mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text Paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
// modified: make a copy of the original TextPaint object for measuring
// (apparently the object gets modified while measuring, see also the
// docs for TextView.getPaint() (which states to access it read-only)
TextPaint Paint = new TextPaint(originalPaint);
// Update the text Paint object
Paint.setTextSize(textSize);
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, Paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}
2017年のgoogle IOカンファレンスで、googleがTextViewのautoSizeプロパティを導入しました
<Android.support.v7.widget.AppCompatTextView
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:text="@string/my_text"
app:autoSizeTextType="uniform"
app:autoSizeMaxTextSize="10sp"
app:autoSizeMinTextSize="6sp"
app:autoSizeStepGranularity="1sp"/>
私は永遠にこれを探していたので、ここでは見当たらない解決策をしばらく前に見つけたので、今後の参考のためにここでそれを書くつもりです。
注意:このコードはしばらく前にGoogle Android Lollipopダイヤラから直接取得したものですが、そのときに変更が加えられた場合は覚えていません。また、どのライセンスがこれに該当するのかわかりませんが、それがApache 2.0
であると考える理由があります。
クラスResizeTextView
、実際のView
public class ResizeTextView extends TextView {
private final int mOriginalTextSize;
private final int mMinTextSize;
private final static int sMinSize = 20;
public ResizeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mOriginalTextSize = (int) getTextSize();
mMinTextSize = (int) sMinSize;
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
ViewUtil.resizeText(this, mOriginalTextSize, mMinTextSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
ViewUtil.resizeText(this, mOriginalTextSize, mMinTextSize);
}
このResizeTextView
クラスは、私が理解していないようにTextViewとそのすべての子を拡張することができるので、EditTextも同様です。
メソッドresizeText(...)
を持つクラスViewUtil
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.Apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Android.graphics.Paint;
import Android.util.TypedValue;
import Android.widget.TextView;
public class ViewUtil {
private ViewUtil() {}
public static void resizeText(TextView textView, int originalTextSize, int minTextSize) {
final Paint paint = textView.getPaint();
final int width = textView.getWidth();
if (width == 0) return;
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalTextSize);
float ratio = width / Paint.measureText(textView.getText().toString());
if (ratio <= 1.0f) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
Math.max(minTextSize, originalTextSize * ratio));
}
}
}
あなたの見方を
<yourpackage.yourapp.ResizeTextView
Android:layout_width="match_parent"
Android:layout_height="64dp"
Android:gravity="center"
Android:maxLines="1"/>
それが役に立てば幸い!
これは、TextView自体にTextChangedListenedを追加して使用する簡単な解決策です。
expressionView = (TextView) findViewById(R.id.expressionView);
expressionView.addTextChangedListener(textAutoResizeWatcher(expressionView, 25, 55));
private TextWatcher textAutoResizeWatcher(final TextView view, final int MIN_SP, final int MAX_SP) {
return new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
final int widthLimitPixels = view.getWidth() - view.getPaddingRight() - view.getPaddingLeft();
Paint paint = new Paint();
float fontSizeSP = pixelsToSp(view.getTextSize());
Paint.setTextSize(spToPixels(fontSizeSP));
String viewText = view.getText().toString();
float widthPixels = Paint.measureText(viewText);
// Increase font size if necessary.
if (widthPixels < widthLimitPixels){
while (widthPixels < widthLimitPixels && fontSizeSP <= MAX_SP){
++fontSizeSP;
Paint.setTextSize(spToPixels(fontSizeSP));
widthPixels = Paint.measureText(viewText);
}
--fontSizeSP;
}
// Decrease font size if necessary.
else {
while (widthPixels > widthLimitPixels || fontSizeSP > MAX_SP) {
if (fontSizeSP < MIN_SP) {
fontSizeSP = MIN_SP;
break;
}
--fontSizeSP;
Paint.setTextSize(spToPixels(fontSizeSP));
widthPixels = Paint.measureText(viewText);
}
}
view.setTextSize(fontSizeSP);
}
};
}
private float pixelsToSp(float px) {
float scaledDensity = getResources().getDisplayMetrics().scaledDensity;
return px/scaledDensity;
}
private float spToPixels(float sp) {
float scaledDensity = getResources().getDisplayMetrics().scaledDensity;
return sp * scaledDensity;
}
このアプローチは、パラメータとして受け取ったMIN_SPとMAX_SPの範囲を考慮して、テキストに合わせて必要に応じてフォントサイズを増減します。
私はこれがあなたを助けることを願っています
import Android.content.Context;
import Android.graphics.Rect;
import Android.text.TextPaint;
import Android.util.AttributeSet;
import Android.widget.TextView;
/* Based on
* from http://stackoverflow.com/questions/2617266/how-to-adjust-text-font-size-to-fit-textview
*/
public class FontFitTextView extends TextView {
private static float MAX_TEXT_SIZE = 20;
public FontFitTextView(Context context) {
this(context, null);
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
float size = this.getTextSize();
if (size > MAX_TEXT_SIZE)
setTextSize(MAX_TEXT_SIZE);
}
private void refitText(String text, int textWidth) {
if (textWidth > 0) {
float availableWidth = textWidth - this.getPaddingLeft()
- this.getPaddingRight();
TextPaint tp = getPaint();
Rect rect = new Rect();
tp.getTextBounds(text, 0, text.length(), rect);
float size = rect.width();
if (size > availableWidth)
setTextScaleX(availableWidth / size);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
refitText(this.getText().toString(), parentWidth);
this.setMeasuredDimension(parentWidth, parentHeight);
}
@Override
protected void onTextChanged(final CharSequence text, final int start,
final int before, final int after) {
refitText(text.toString(), this.getWidth());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw) {
refitText(this.getText().toString(), w);
}
}
}
注:テキストサイズが20より大きい場合は、MAX_TEXT_SIZEを使用します。これが自分のビューに大きなフォントが適用されるのを許可したくない場合は、単に削除するだけです。
私の実装はもう少し複雑ですが、次のような利点があります。
/**
* Text view that auto adjusts text size to fit within the view. If the text
* size equals the minimum text size and still does not fit, append with an
* Ellipsis.
*
* Based on the original work from Chase Colburn
* <http://stackoverflow.com/a/5535672/305532>
*
* @author Thomas Keller <[email protected]>
*/
public class AutoResizeTextView extends TextView {
// in dip
private static final int MIN_TEXT_SIZE = 20;
private static final boolean SHRINK_TEXT_SIZE = true;
private static final char Ellipsis = '\u2026';
private static final float LINE_SPACING_MULTIPLIER_MULTILINE = 0.8f;
private static final float LINE_SPACING_MULTIPLIER_SINGLELINE = 1f;
private static final float LINE_SPACING_EXTRA = 0.0f;
private CharSequence mOriginalText;
// temporary upper bounds on the starting text size
private float mMaxTextSize;
// lower bounds for text size
private float mMinTextSize;
// determines whether we're currently in the process of measuring ourselves,
// so we do not enter onMeasure recursively
private boolean mInMeasure = false;
// if the text size should be shrinked or if the text size should be kept
// constant and only characters should be removed to hit the boundaries
private boolean mShrinkTextSize;
public AutoResizeTextView(Context context) {
this(context, null);
init(context, null);
}
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
init(context, attrs);
}
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
// the current text size is used as maximum text size we can apply to
// our widget
mMaxTextSize = getTextSize();
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoResizeTextView);
mMinTextSize = a.getFloat(R.styleable.AutoResizeTextView_minFontSize, MIN_TEXT_SIZE);
mShrinkTextSize = a.getBoolean(R.styleable.AutoResizeTextView_shrinkTextSize, SHRINK_TEXT_SIZE);
a.recycle();
}
}
@Override
public void setTextSize(float size) {
mMaxTextSize = size;
super.setTextSize(size);
}
/**
* Returns the original, unmodified text of this widget
*
* @return
*/
public CharSequence getOriginalText() {
// text has not been resized yet
if (mOriginalText == null) {
return getText();
}
return mOriginalText;
}
@Override
public void setText(CharSequence text, BufferType type) {
if (!mInMeasure) {
mOriginalText = text.toString();
}
super.setText(text, type);
}
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mInMeasure = true;
try {
int availableWidth = MeasureSpec.getSize(widthMeasureSpec) - getCompoundPaddingLeft()
- getCompoundPaddingRight();
int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - getCompoundPaddingTop()
- getCompoundPaddingBottom();
// Do not resize if the view does not have dimensions or there is no
// text
if (mOriginalText == null || mOriginalText.length() == 0 || availableWidth <= 0) {
return;
}
TextPaint textPaint = getPaint();
// start with the recorded max text size
float targetTextSize = mMaxTextSize;
String originalText = mOriginalText.toString();
String finalText = originalText;
Rect textSize = getTextSize(originalText, textPaint, targetTextSize);
boolean textExceedsBounds = textSize.height() > availableHeight || textSize.width() > availableWidth;
if (mShrinkTextSize && textExceedsBounds) {
// check whether all lines can be rendered in the available
// width / height without violating the bounds of the parent and
// without using a text size that is smaller than the minimum
// text size
float heightMultiplier = availableHeight / (float) textSize.height();
float widthMultiplier = availableWidth / (float) textSize.width();
float multiplier = Math.min(heightMultiplier, widthMultiplier);
targetTextSize = Math.max(targetTextSize * multiplier, mMinTextSize);
// measure again
textSize = getTextSize(finalText, textPaint, targetTextSize);
}
// we cannot shrink the height further when we hit the available
// height, but we can shrink the width by applying an Ellipsis on
// each line
if (textSize.width() > availableWidth) {
StringBuilder modifiedText = new StringBuilder();
String lines[] = originalText.split(System.getProperty("line.separator"));
for (int i = 0; i < lines.length; i++) {
modifiedText.append(resizeLine(textPaint, lines[i], availableWidth));
// add the separator back to all but the last processed line
if (i != lines.length - 1) {
modifiedText.append(System.getProperty("line.separator"));
}
}
finalText = modifiedText.toString();
// measure again
textSize = getTextSize(finalText, textPaint, targetTextSize);
}
textPaint.setTextSize(targetTextSize);
boolean isMultiline = finalText.indexOf('\n') > -1;
// do not include extra font padding (for accents, ...) for
// multiline texts, this will prevent proper placement with
// Gravity.CENTER_VERTICAL
if (isMultiline) {
setLineSpacing(LINE_SPACING_EXTRA, LINE_SPACING_MULTIPLIER_MULTILINE);
setIncludeFontPadding(false);
} else {
setLineSpacing(LINE_SPACING_EXTRA, LINE_SPACING_MULTIPLIER_SINGLELINE);
setIncludeFontPadding(true);
}
// according to
// <http://code.google.com/p/Android/issues/detail?id=22493>
// we have to add a unicode character to trigger the text centering
// in ICS. this particular character is known as "zero-width" and
// does no harm.
setText(finalText + "\u200B");
int measuredWidth = textSize.width() + getCompoundPaddingLeft() + getCompoundPaddingRight();
int measuredHeight = textSize.height() + getCompoundPaddingTop() + getCompoundPaddingBottom();
// expand the view to the parent's height in case it is smaller or
// to the minimum height that has been set
// FIXME: honor the vertical measure mode (EXACTLY vs AT_MOST) here
// somehow
measuredHeight = Math.max(measuredHeight, MeasureSpec.getSize(heightMeasureSpec));
setMeasuredDimension(measuredWidth, measuredHeight);
} finally {
mInMeasure = false;
}
}
private Rect getTextSize(String text, TextPaint textPaint, float textSize) {
textPaint.setTextSize(textSize);
// StaticLayout depends on a given width in which it should lay out the
// text (and optionally also split into separate lines).
// Therefor we calculate the current text width manually and start with
// a fake (read: maxmimum) width for the height calculation.
// We do _not_ use layout.getLineWidth() here since this returns
// slightly smaller numbers and therefor would lead to exceeded text box
// drawing.
StaticLayout layout = new StaticLayout(text, textPaint, Integer.MAX_VALUE, Alignment.ALIGN_NORMAL, 1f, 0f, true);
int textWidth = 0;
String lines[] = text.split(System.getProperty("line.separator"));
for (int i = 0; i < lines.length; ++i) {
textWidth = Math.max(textWidth, measureTextWidth(textPaint, lines[i]));
}
return new Rect(0, 0, textWidth, layout.getHeight());
}
private String resizeLine(TextPaint textPaint, String line, int availableWidth) {
checkArgument(line != null && line.length() > 0, "expected non-empty string");
int textWidth = measureTextWidth(textPaint, line);
int lastDeletePos = -1;
StringBuilder builder = new StringBuilder(line);
while (textWidth > availableWidth && builder.length() > 0) {
lastDeletePos = builder.length() / 2;
builder.deleteCharAt(builder.length() / 2);
// don't forget to measure the Ellipsis character as well; it
// doesn't matter where it is located in the line, it just has to be
// there, since there are no (known) ligatures that use this glyph
String textToMeasure = builder.toString() + Ellipsis;
textWidth = measureTextWidth(textPaint, textToMeasure);
}
if (lastDeletePos > -1) {
builder.insert(lastDeletePos, Ellipsis);
}
return builder.toString();
}
// there are several methods in Android to determine the text width, namely
// getBounds() and measureText().
// The latter works for us the best as it gives us the best / nearest
// results without that our text canvas needs to wrap its text later on
// again.
private int measureTextWidth(TextPaint textPaint, String line) {
return Math.round(textPaint.measureText(line));
}
}
[2012-11-21に改訂]
私は次のことがうまくいっていることがわかりました。ループせず、高さと幅の両方を考慮します。ビューでsetTextSizeを呼び出すときにPXユニットを指定することが重要です。
Paint paint = adjustTextSize(getPaint(), numChars, maxWidth, maxHeight);
setTextSize(TypedValue.COMPLEX_UNIT_PX,Paint.getTextSize());
これが、ビューからgetPaint()を渡して使用するルーチンです。実際の文字列から独立して幅を見積もるには、「ワイド」文字を含む10文字の文字列を使用します。
private static final String text10="OOOOOOOOOO";
public static Paint adjustTextSize(Paint paint, int numCharacters, int widthPixels, int heightPixels) {
float width = Paint.measureText(text10)*numCharacters/text10.length();
float newSize = (int)((widthPixels/width)*Paint.getTextSize());
Paint.setTextSize(newSize);
// remeasure with font size near our desired result
width = Paint.measureText(text10)*numCharacters/text10.length();
newSize = (int)((widthPixels/width)*Paint.getTextSize());
Paint.setTextSize(newSize);
// Check height constraints
FontMetricsInt metrics = Paint.getFontMetricsInt();
float textHeight = metrics.descent-metrics.ascent;
if (textHeight > heightPixels) {
newSize = (int)(newSize * (heightPixels/textHeight));
Paint.setTextSize(newSize);
}
return Paint;
}
これは私がまだ探している人のために私が見つけた他のものの列挙です:
1)これが解決策です それが収まるまでテキストビューを再帰的に再描画します。これは文字通りあなたのテキストが所定の位置に縮小されるのを見ることを意味しますが、少なくともそれが行われるときそれは収まります。コードを実装するには少し調整が必要になりますが、大部分はそこにあります。
2) this 、または this の中のdunniのクラスのようなカスタムソリューションを一緒にハックしてみることもできます。私は空白だけでラップするためにそれを必要とするのでそれはずっと面倒になりました...
3)あなたは検索を続けることができます - 私は私が数えることができるより多くの選択肢を試しました。 StaticLayoutに関するTedのアドバイスは私には報いていませんが、おそらくそこに何かがあります。 StaticLayout.getEllipsis(line)を使用して、テキストが画面からはみ出していないかどうかを判断しましたが、効果はありませんでした。それについての私の(現在未回答の)投稿を見てください ここ 。
特定の解決策が必要でした。私は自分のレイアウトにedittextとtextviewを持っています。テキストビューは高さと幅が固定されています。ユーザーがエディットテキストを入力し始めると、そのテキストは直ちにテキストビューに表示されます。 textfieldのテキストはtextviewに合うように自動的にサイズ変更されるべきです。それで私は私のために働くためにチェイスの解決策を更新しました。そのため、テキストビューでテキストが変わると、サイズ変更が始まります。私のものとChaseの解決策の違いは: ユーザーがいくつかの文字を削除してもサイズ変更は行われる 。誰かに役立つことを願っています。
public class TextFitTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 10;
// Maximum text size for this text view - if it is 0, then the text acts
// like match_parent
public static final float MAX_TEXT_SIZE = 0;
// Our ellipse string
private static final String mEllipsis = "...";
// Text size that is set from code. This acts as a starting point for
// resizing
private float mTextSize;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Max bounds for text size
private float mMaxTextSize = MAX_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add Ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Add Ellipsis to text that overflows at the smallest text size
private int heightLimit;
private int widthLimit;
// Default constructor override
public TextFitTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public TextFitTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public TextFitTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes resize the text size.
*/
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
// if we are adding new chars to text
if (before <= after && after != 1) {
resizeText(true);
// now we are deleting chars
} else {
resizeText(false);
}
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
@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;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
*
* @return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add Ellipsis to text that overflows at the smallest text size
*
* @param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add Ellipsis to text that overflows at the smallest text
* size
*
* @return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Get width and height limits
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (widthLimit == 0 && heightLimit == 0) {
widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with specified width and height
*
* @param width
* @param height
*/
public void resizeText(boolean increase) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no
// text
if (text == null || text.length() == 0 || heightLimit <= 0 || widthLimit <= 0 || mTextSize == 0) {
return;
}
// Get the text view's Paint object
TextPaint textPaint = getPaint();
// Get the required text height
int textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
// If the text length is increased
// Until we either fit within our text view or we had reached our min
// text size, incrementally try smaller sizes
if (increase) {
while (textHeight > heightLimit && mTextSize > mMinTextSize) {
mTextSize = Math.max(mTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
}
}
// text length has been decreased
else {
// if max test size is set then add it to while condition
if (mMaxTextSize != 0) {
while (textHeight < heightLimit && mTextSize <= mMaxTextSize) {
mTextSize = mTextSize + 2;
textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
}
} else {
while (textHeight < heightLimit) {
mTextSize = mTextSize + 2;
textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
}
}
mTextSize = textHeight > heightLimit ? mTextSize - 2 : mTextSize;
}
// If we had reached our minimum text size and still don't fit, append
// an Ellipsis
if (mAddEllipsis && mTextSize == mMinTextSize && textHeight > heightLimit) {
// Draw using a static layout
TextPaint Paint = new TextPaint(textPaint);
StaticLayout layout = new StaticLayout(text, Paint, widthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
mSpacingAdd, false);
// Check that we have a least one line of rendered text
if (layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut
// off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(heightLimit) - 1;
// If the text would not even fit on a single line, clear it
if (lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an Ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = Paint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the
// Ellipsis
while (widthLimit < lineWidth + ellipseWidth) {
lineWidth = Paint.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line
// spacing
// and invalidate the layout as a side effect
setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
}
// Set the text size of the text Paint object and use a static layout to
// render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
// Update the text Paint object
TextPaint Paint = new TextPaint(originalPaint);
Paint.setTextSize(textSize);
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, Paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd,
true);
return layout.getHeight();
}
}
このバージョンのトップアンサーを提供するとXamarin.Androidでコードを書く人のために C# に書き換えられます。私のためにうまくいった。
/**
* DO WHAT YOU WANT TO PUBLIC LICENSE
* Version 2, December 2004
*
* Copyright (C) 2004 Sam Hocevar <[email protected]>
*
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT YOU WANT TO.
*/
using System;
using Android.Content;
using Android.Runtime;
using Android.Text;
using Android.Util;
using Android.Widget;
using Java.Lang;
namespace App.GuestGuide.Droid.Controls
{
public class OnTextResizeEventArgs : EventArgs
{
public TextView TextView { get; set; }
public float OldSize { get; set; }
public float NewSize { get; set; }
}
/// <inheritdoc />
/// <summary>
/// Text view that auto adjusts text size to fit within the view.
/// If the text size equals the minimum text size and still does not
/// fit, append with an Ellipsis.
/// </summary>
public class AutoResizeTextView : TextView
{
/// <summary>
/// Minimum text size for this text view
/// </summary>
public static float MIN_TEXT_SIZE = 10;
/// <summary>
/// Our ellipse string
/// </summary>
private const string Ellipsis = "...";
private float _mMaxTextSize;
private float _mMinTextSize = MIN_TEXT_SIZE;
/// <summary>
/// Register subscriber to receive resize notifications
/// </summary>
public event EventHandler<OnTextResizeEventArgs> OnTextResize;
/// <summary>
/// Flag for text and/or size changes to force a resize
/// </summary>
private bool _needsResize;
/// <summary>
/// Text size that is set from code. This acts as a starting point for resizing
/// </summary>
private float _textSize;
/// <summary>
/// Text view line spacing multiplier
/// </summary>
private float _spacingMult = 1.0f;
/// <summary>
/// Text view additional line spacing
/// </summary>
private float _spacingAdd;
/// <summary>
/// Add Ellipsis to text that overflows at the smallest text size
/// </summary>
public bool ShouldAddEllipsis { get; set; }
/// <inheritdoc />
/// <summary>
/// Override the set text size to update our internal reference values
/// </summary>
public override float TextSize
{
get => base.TextSize;
set
{
base.TextSize = value;
_textSize = TextSize;
}
}
/// <summary>
/// Temporary upper bounds on the starting text size
/// </summary>
public float MaxTextSize
{
get => _mMaxTextSize;
// Set the upper text size limit and invalidate the view
set
{
_mMaxTextSize = value;
RequestLayout();
Invalidate();
}
}
/// <summary>
/// Lower bounds for text size
/// </summary>
public float MinTextSize
{
get => _mMinTextSize;
//Set the lower text size limit and invalidate the view
set
{
_mMinTextSize = value;
RequestLayout();
Invalidate();
}
}
public AutoResizeTextView(Context context) : this(context, null)
{
}
public AutoResizeTextView(Context context, IAttributeSet attrs) : this(context, attrs, 0)
{
}
public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
{
_textSize = TextSize;
}
public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
{
_textSize = TextSize;
}
protected AutoResizeTextView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
_textSize = TextSize;
}
/// <inheritdoc />
/// <summary>
/// When text changes, set the force resize flag to true and reset the text size.
/// </summary>
/// <param name="text"></param>
/// <param name="start"></param>
/// <param name="lengthBefore"></param>
/// <param name="lengthAfter"></param>
protected override void OnTextChanged(ICharSequence text, int start, int lengthBefore, int lengthAfter)
{
_needsResize = true;
// Since this view may be reused, it is good to reset the text size
ResetTextSize();
}
/// <inheritdoc />
/// <summary>
/// If the text view size changed, set the force resize flag to true
/// </summary>
/// <param name="w"></param>
/// <param name="h"></param>
/// <param name="oldw"></param>
/// <param name="oldh"></param>
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
if (w != oldw || h != oldh)
{
_needsResize = true;
}
}
public override void SetTextSize([GeneratedEnum] ComplexUnitType unit, float size)
{
base.SetTextSize(unit, size);
_textSize = TextSize;
}
/// <inheritdoc />
/// <summary>
/// Override the set line spacing to update our internal reference values
/// </summary>
/// <param name="add"></param>
/// <param name="mult"></param>
public override void SetLineSpacing(float add, float mult)
{
base.SetLineSpacing(add, mult);
_spacingMult = mult;
_spacingAdd = add;
}
/// <summary>
/// Reset the text to the original size
/// </summary>
public void ResetTextSize()
{
if (_textSize > 0)
{
base.SetTextSize(ComplexUnitType.Px, _textSize);
_mMaxTextSize = _textSize;
}
}
/// <inheritdoc />
/// <summary>
/// Resize text after measuring
/// </summary>
/// <param name="changed"></param>
/// <param name="left"></param>
/// <param name="top"></param>
/// <param name="right"></param>
/// <param name="bottom"></param>
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
if (changed || _needsResize)
{
var widthLimit = (right - left) - CompoundPaddingLeft - CompoundPaddingRight;
var heightLimit = (bottom - top) - CompoundPaddingBottom - CompoundPaddingTop;
ResizeText(widthLimit, heightLimit);
}
base.OnLayout(changed, left, top, right, bottom);
}
/// <summary>
/// Resize the text size with default width and height
/// </summary>
public void ResizeText()
{
var heightLimit = Height - PaddingBottom - PaddingTop;
var widthLimit = Width - PaddingLeft - PaddingRight;
ResizeText(widthLimit, heightLimit);
}
/// <summary>
/// Resize the text size with specified width and height
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
public void ResizeText(int width, int height)
{
ICharSequence text = null;
if (!string.IsNullOrEmpty(Text))
{
text = new Java.Lang.String(Text);
}
// Do not resize if the view does not have dimensions or there is no text
if (text == null || text.Length() == 0 || height <= 0 || width <= 0 || _textSize == 0)
{
return;
}
if (TransformationMethod != null)
{
text = TransformationMethod.GetTransformationFormatted(text, this);
}
// Get the text view's Paint object
var textPaint = Paint;
// Store the current text size
var oldTextSize = textPaint.TextSize;
// If there is a max text size set, use the lesser of that and the default text size
var targetTextSize = _mMaxTextSize > 0 ? System.Math.Min(_textSize, _mMaxTextSize) : _textSize;
// Get the required text height
var textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while (textHeight > height && targetTextSize > _mMinTextSize)
{
targetTextSize = System.Math.Max(targetTextSize - 2, _mMinTextSize);
textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append an Ellipsis
if (ShouldAddEllipsis && targetTextSize == _mMinTextSize && textHeight > height)
{
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
var Paint = new TextPaint(textPaint);
// Draw using a static layout
var layout = new StaticLayout(text, Paint, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, false);
// Check that we have a least one line of rendered text
if (layout.LineCount > 0)
{
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
var lastLine = layout.GetLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if (lastLine < 0)
{
Text = string.Empty;
}
// Otherwise, trim to the previous line and add an Ellipsis
else
{
var start = layout.GetLineStart(lastLine);
var end = layout.GetLineEnd(lastLine);
var lineWidth = layout.GetLineWidth(lastLine);
var ellipseWidth = textPaint.MeasureText(Ellipsis);
// Trim characters off until we have enough room to draw the Ellipsis
while (width < lineWidth + ellipseWidth)
{
lineWidth = textPaint.MeasureText(text.SubSequence(start, --end + 1));
}
Text = text.SubSequence(0, end) + Ellipsis;
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
SetTextSize(ComplexUnitType.Px, targetTextSize);
SetLineSpacing(_spacingAdd, _spacingMult);
var notifyArgs = new OnTextResizeEventArgs
{
TextView = this,
NewSize = targetTextSize,
OldSize = oldTextSize
};
// Notify the listener if registered
OnTextResize?.Invoke(this, notifyArgs);
// Reset force resize flag
_needsResize = false;
}
/// <summary>
/// Set the text size of the text Paint object and use a static layout to render text off screen before measuring
/// </summary>
/// <param name="source"></param>
/// <param name="Paint"></param>
/// <param name="width"></param>
/// <param name="textSize"></param>
/// <returns></returns>
private int GetTextHeight(ICharSequence source, TextPaint Paint, int width, float textSize)
{
// modified: make a copy of the original TextPaint object for measuring
// (apparently the object gets modified while measuring, see also the
// docs for TextView.getPaint() (which states to access it read-only)
// Update the text Paint object
var paintCopy = new TextPaint(Paint)
{
TextSize = textSize
};
// Measure using a static layout
var layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, true);
return layout.Height;
}
}
}
私の方法は:
public void changeTextSize(int initialSize, TextView tv) {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
double width = displayMetrics.widthPixels / displayMetrics.xdpi;
double height = displayMetrics.heightPixels / displayMetrics.ydpi;
Log.i("LOG", "The width of the tested emulator is: " + width);
Log.i("LOG", "The height of the tested emulator is: " + height);
double scale = Math.min(width / 2.25, height / 4.0); //See the logcat >>> width = 2.25 and heigt = 4.0
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, (int) (initialSize * scale));
}
例えば、次のとおりです。
changeTextSize(16, findViewById(R.id.myTextView));
changeTextSize(12, findViewById(R.id.myEditText));
これにはAndroid.text.StaticLayout
クラスを使用できます。それがTextView
が内部的に使っていることです。
私は上記の提案のいくつかを組み合わせて、二分法を使って拡大縮小するものを作りました。幅の範囲内で拡大縮小します。
/**
* DO WHAT YOU WANT TO PUBLIC LICENSE
* Version 2, December 2004
*
* Copyright (C) 2004 Sam Hocevar <[email protected]>
*
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT YOU WANT TO.
*/
import Android.content.Context;
import Android.text.Layout.Alignment;
import Android.text.StaticLayout;
import Android.text.TextPaint;
import Android.util.AttributeSet;
import Android.util.TypedValue;
import Android.widget.TextView;
/**
* Text view that auto adjusts text size to fit within the view. If the text
* size equals the minimum text size and still does not fit, append with an
* Ellipsis.
*
* @author Chase Colburn
* @since Apr 4, 2011
*/
public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 10;
// Minimum text size for this text view
public static final float MAX_TEXT_SIZE = 128;
private static final int BISECTION_LOOP_WATCH_DOG = 30;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for
// resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = MAX_TEXT_SIZE;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add Ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Default constructor override
public AutoResizeTextView(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes, set the force resize flag to true and reset the text
* size.
*/
@Override
protected void onTextChanged(final CharSequence text, final int start,
final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
*
* @param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
*
* @param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
*
* @return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
*
* @param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
*
* @return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add Ellipsis to text that overflows at the smallest text size
*
* @param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add Ellipsis to text that overflows at the smallest text
* size
*
* @return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if (mTextSize > 0) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
// mMaxTextSize = mTextSize;
}
}
/**
* Resize text after measuring
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
if (changed || mNeedsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft()
- getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom()
- getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
// Height and width with a padding as a percentage of height
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
*
* @param width
* @param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no
// text
if (text == null || text.length() == 0 || height <= 0 || width <= 0
|| mTextSize == 0) {
return;
}
// Get the text view's Paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// Bisection method: fast & precise
float lower = mMinTextSize;
float upper = mMaxTextSize;
int loop_counter = 1;
float targetTextSize = (lower + upper) / 2;
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
int textWidth = getTextWidth(text, textPaint, width, targetTextSize);
while (loop_counter < BISECTION_LOOP_WATCH_DOG && upper - lower > 1) {
targetTextSize = (lower + upper) / 2;
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
textWidth = getTextWidth(text, textPaint, width, targetTextSize);
if (textHeight > (height) || textWidth > (width))
upper = targetTextSize;
else
lower = targetTextSize;
loop_counter++;
}
targetTextSize = lower;
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// If we had reached our minimum text size and still don't fit, append
// an Ellipsis
if (mAddEllipsis && targetTextSize == mMinTextSize
&& textHeight > height) {
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
TextPaint paintCopy = new TextPaint(textPaint);
paintCopy.setTextSize(targetTextSize);
StaticLayout layout = new StaticLayout(text, paintCopy, width,
Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if (layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut
// off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if (lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an Ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = paintCopy.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the
// Ellipsis
while (width < lineWidth + ellipseWidth) {
lineWidth = paintCopy.measureText(text.subSequence(
start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line
// spacing
// and invalidate the layout as a side effect
setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if (mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text Paint object and use a static layout to
// render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint originalPaint,
int width, float textSize) {
// modified: make a copy of the original TextPaint object for measuring
// (apparently the object gets modified while measuring, see also the
// docs for TextView.getPaint() (which states to access it read-only)
TextPaint Paint = new TextPaint(originalPaint);
// Update the text Paint object
Paint.setTextSize(textSize);
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, Paint, width,
Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
// Set the text size of the text Paint object and use a static layout to
// render text off screen before measuring
private int getTextWidth(CharSequence source, TextPaint originalPaint,
int width, float textSize) {
// Update the text Paint object
TextPaint Paint = new TextPaint(originalPaint);
// Draw using a static layout
Paint.setTextSize(textSize);
StaticLayout layout = new StaticLayout(source, Paint, width,
Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return (int) layout.getLineWidth(0);
}
}
誰かがそれを必要とするならば、これはXamarin.Androidのためにしかし同じコードスニペットです。
/**
* DO WHAT YOU WANT TO PUBLIC LICENSE
* Version 1, December 2017
*
* Copyright (C) 2017 Nathan Westfall
*
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT YOU WANT TO.
*/
using Android.Content;
using Android.Runtime;
using Android.Widget;
using Android.Util;
using Android.Text;
using Java.Lang;
namespace My.Text
{
public class AutoResizeTextView : TextView
{
public const float MIN_TEXT_SIZE = 20;
public interface OnTextResizeListener
{
void OnTextResize(TextView textView, float oldSize, float newSize);
}
private const string mEllipsis = "...";
private OnTextResizeListener mTextResizeListener;
private bool mNeedsResize = false;
private float mTextSize;
private float mMaxTextSize = 0;
private float mMinTextSize = MIN_TEXT_SIZE;
private float mSpacingMult = 1.0f;
private float mSpacingAdd = 0.0f;
public bool AddEllipsis { get; set; } = true;
public AutoResizeTextView(Context context) : this(context, null) { }
public AutoResizeTextView(Context context, IAttributeSet attrs) : this(context, attrs, 0) { }
public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyle): base(context, attrs, defStyle)
{
mTextSize = TextSize;
}
protected override void OnTextChanged(ICharSequence text, int start, int lengthBefore, int lengthAfter)
{
base.OnTextChanged(text, start, lengthBefore, lengthAfter);
mNeedsResize = true;
ResetTextSize();
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
if (w != oldw || h != oldh)
mNeedsResize = true;
}
public void SetOnResizeListener(OnTextResizeListener listener)
{
mTextResizeListener = listener;
}
public override void SetTextSize([GeneratedEnum] ComplexUnitType unit, float size)
{
base.SetTextSize(unit, size);
mTextSize = TextSize;
}
public override void SetLineSpacing(float add, float mult)
{
base.SetLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
public void SetMaxTextSize(float maxTextSize)
{
mMaxTextSize = maxTextSize;
RequestLayout();
Invalidate();
}
public float GetMaxTextSize()
{
return mMaxTextSize;
}
public void SetMinTextSize(float minTextSize)
{
mMinTextSize = minTextSize;
RequestLayout();
Invalidate();
}
public float GetMinTextSize()
{
return mMinTextSize;
}
public void ResetTextSize()
{
if(mTextSize > 0)
{
base.SetTextSize(ComplexUnitType.Px, mTextSize);
mMaxTextSize = mTextSize;
}
}
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
if(changed || mNeedsResize)
{
int widthLimit = (right - left) - CompoundPaddingLeft - CompoundPaddingRight;
int heightLimit = (bottom - top) - CompoundPaddingBottom - CompoundPaddingTop;
ResizeText(widthLimit, heightLimit);
}
base.OnLayout(changed, left, top, right, bottom);
base.OnLayout(changed, left, top, right, bottom);
}
public void ResizeText()
{
int heightLimit = Height - PaddingBottom - PaddingTop;
int widthLimit = Width - PaddingLeft - PaddingRight;
ResizeText(widthLimit, heightLimit);
}
public void ResizeText(int width, int height)
{
var text = TextFormatted;
if (text == null || text.Length() == 0 || height <= 0 || width <= 0 || mTextSize == 0)
return;
if (TransformationMethod != null)
text = TransformationMethod.GetTransformationFormatted(TextFormatted, this);
TextPaint textPaint = Paint;
float oldTextSize = textPaint.TextSize;
float targetTextSize = mMaxTextSize > 0 ? System.Math.Min(mTextSize, mMaxTextSize) : mTextSize;
int textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
while(textHeight > height && targetTextSize > mMinTextSize)
{
targetTextSize = System.Math.Max(targetTextSize - 2, mMinTextSize);
textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
}
if(AddEllipsis && targetTextSize == mMinTextSize && textHeight > height)
{
TextPaint Paint = new TextPaint(textPaint);
StaticLayout layout = new StaticLayout(text, Paint, width, Layout.Alignment.AlignNormal, mSpacingMult, mSpacingAdd, false);
if(layout.LineCount > 0)
{
int lastLine = layout.GetLineForVertical(height) - 1;
if (lastLine < 0)
SetText("", BufferType.Normal);
else
{
int start = layout.GetLineStart(lastLine);
int end = layout.GetLineEnd(lastLine);
float lineWidth = layout.GetLineWidth(lastLine);
float ellipseWidth = textPaint.MeasureText(mEllipsis);
while (width < lineWidth + ellipseWidth)
lineWidth = textPaint.MeasureText(text.SubSequence(start, --end + 1).ToString());
SetText(text.SubSequence(0, end) + mEllipsis, BufferType.Normal);
}
}
}
SetTextSize(ComplexUnitType.Px, targetTextSize);
SetLineSpacing(mSpacingAdd, mSpacingMult);
mTextResizeListener?.OnTextResize(this, oldTextSize, targetTextSize);
mNeedsResize = false;
}
private int GetTextHeight(ICharSequence source, TextPaint Paint, int width, float textSize)
{
TextPaint paintCopy = new TextPaint(Paint);
paintCopy.TextSize = textSize;
StaticLayout layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.AlignNormal, mSpacingMult, mSpacingAdd, false);
return layout.Height;
}
}
}
キャンバスにテキストを描画したい場合に役立つかもしれない(Chaseのアイデアに基づいて)次のメソッドを作成しました。
private static void drawText(Canvas canvas, int xStart, int yStart,
int xWidth, int yHeigth, String textToDisplay,
TextPaint paintToUse, float startTextSizeInPixels,
float stepSizeForTextSizeSteps) {
// Text view line spacing multiplier
float mSpacingMult = 1.0f;
// Text view additional line spacing
float mSpacingAdd = 0.0f;
StaticLayout l = null;
do {
paintToUse.setTextSize(startTextSizeInPixels);
startTextSizeInPixels -= stepSizeForTextSizeSteps;
l = new StaticLayout(textToDisplay, paintToUse, xWidth,
Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
} while (l.getHeight() > yHeigth);
int textCenterX = xStart + (xWidth / 2);
int textCenterY = (yHeigth - l.getHeight()) / 2;
canvas.save();
canvas.translate(textCenterX, textCenterY);
l.draw(canvas);
canvas.restore();
}
これを使用することができます。任意のカスタムビューの任意のonDraw()メソッド内。
私はchaseとM-WaJeEhのコードを使っていますが、ここでいくつかの利点と欠点を見つけました。
チェイスから
利点:
- 1行のTextViewに最適です
不利益:
それがカスタムフォントで1行以上ある場合、テキストの一部は消えます
それが楕円を有効にするならば、それは楕円のためのスペースを準備しませんでした
カスタムフォント(書体)の場合は、サポートされていません
m-WaJeEh発の
利点:
- それは複数行に最適です
不利益:
heightをwrap-contentとして設定した場合、このコードは最小サイズから開始し、setSizeからではなく、限られた幅だけ縮小することで、最小サイズに縮小します。
カスタムフォント(書体)の場合は、サポートされていません
これはキックのためのさらに別の解決策です。おそらくあまり効率的ではありませんが、テキストの高さと幅の両方、およびマークアップされたテキストには対応しています。
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
{
if ((MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED)
&& (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED)) {
final float desiredWidth = MeasureSpec.getSize(widthMeasureSpec);
final float desiredHeight = MeasureSpec.getSize(heightMeasureSpec);
float textSize = getTextSize();
float lastScale = Float.NEGATIVE_INFINITY;
while (textSize > MINIMUM_AUTO_TEXT_SIZE_PX) {
// Measure how big the textview would like to be with the current text size.
super.onMeasure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
// Calculate how much we'd need to scale it to fit the desired size, and
// apply that scaling to the text size as an estimate of what we need.
final float widthScale = desiredWidth / getMeasuredWidth();
final float heightScale = desiredHeight / getMeasuredHeight();
final float scale = Math.min(widthScale, heightScale);
// If we don't need to shrink the text, or we don't seem to be converging, we're done.
if ((scale >= 1f) || (scale <= lastScale)) {
break;
}
// Shrink the text size and keep trying.
textSize = Math.max((float) Math.floor(scale * textSize), MINIMUM_AUTO_TEXT_SIZE_PX);
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
lastScale = scale;
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
これが私が取るアプローチです。とても簡単です。これはフォントサイズをゼロにするための逐次近似を使用しており、一般的に10回以内の反復でそれを把握することができます。 「activityWidth」を、テキストを表示するために使用しているビューの幅に置き換えます。この例では、画面の幅に対するプライベートフィールドとして設定されています。 198の初期フォントサイズは、メソッドが例外を生成した場合にのみ設定されます(実際には絶対に起こらないはずです)。
private float GetFontSizeForScreenWidth(String text)
{
float fontsize = 198;
try
{
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint.setColor(Color.RED);
Typeface typeface = Typeface.create("Helvetica", Typeface.BOLD);
Paint.setTypeface(typeface);
Paint.setTextAlign(Align.CENTER);
int lowVal = 0;
int highVal = 2000;
int currentVal = highVal;
/*
* Successively approximate the screen size until it is
* within 2 pixels of the maximum screen width. Generally
* this will get you to the closest font size within about 10
* iterations.
*/
do
{
Paint.setTextSize(currentVal);
float textWidth = Paint.measureText(text);
float diff = activityWidth - textWidth;
if ((diff >= 0) && (diff <= 2))
{
fontsize = Paint.getTextSize();
return fontsize;
}
if (textWidth > activityWidth)
highVal = currentVal;
else if (textWidth < activityWidth)
lowVal = currentVal;
else
{
fontsize = Paint.getTextSize();
return fontsize;
}
currentVal = (highVal - lowVal) / 2 + lowVal;
} while (true);
}
catch (Exception ex)
{
return fontsize;
}
}
TextViewを拡張し、以下のコードでonDrawをオーバーライドします。テキストの縦横比は維持されますが、スペースを埋めるようにサイズ調整されます。必要に応じてコードを簡単に修正して拡大することができます。
@Override
protected void onDraw(@NonNull Canvas canvas) {
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.drawableState = getDrawableState();
String text = getText().toString();
float desiredWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2;
float desiredHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2;
float textSize = textPaint.getTextSize();
for (int i = 0; i < 10; i++) {
textPaint.getTextBounds(text, 0, text.length(), rect);
float width = rect.width();
float height = rect.height();
float deltaWidth = width - desiredWidth;
float deltaHeight = height - desiredHeight;
boolean fitsWidth = deltaWidth <= 0;
boolean fitsHeight = deltaHeight <= 0;
if ((fitsWidth && Math.abs(deltaHeight) < 1.0)
|| (fitsHeight && Math.abs(deltaWidth) < 1.0)) {
// close enough
break;
}
float adjustX = desiredWidth / width;
float adjustY = desiredHeight / height;
textSize = textSize * (adjustY < adjustX ? adjustY : adjustX);
// adjust text size
textPaint.setTextSize(textSize);
}
float x = desiredWidth / 2f;
float y = desiredHeight / 2f - rect.top - rect.height() / 2f;
canvas.drawText(text, x, y, textPaint);
}
この解決策は私たちには有効です。
public class CustomFontButtonTextFit extends CustomFontButton
{
private final float DECREMENT_FACTOR = .1f;
public CustomFontButtonTextFit(Context context) {
super(context);
}
public CustomFontButtonTextFit(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomFontButtonTextFit(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
private synchronized void refitText(String text, int textWidth) {
if (textWidth > 0)
{
float availableWidth = textWidth - this.getPaddingLeft()
- this.getPaddingRight();
TextPaint tp = getPaint();
Rect rect = new Rect();
tp.getTextBounds(text, 0, text.length(), rect);
float size = rect.width();
while(size > availableWidth)
{
setTextSize( getTextSize() - DECREMENT_FACTOR );
tp = getPaint();
tp.getTextBounds(text, 0, text.length(), rect);
size = rect.width();
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
refitText(this.getText().toString(), parentWidth);
if(parentWidth < getSuggestedMinimumWidth())
parentWidth = getSuggestedMinimumWidth();
if(parentHeight < getSuggestedMinimumHeight())
parentHeight = getSuggestedMinimumHeight();
this.setMeasuredDimension(parentWidth, parentHeight);
}
@Override
protected void onTextChanged(final CharSequence text, final int start,
final int before, final int after)
{
super.onTextChanged(text, start, before, after);
refitText(text.toString(), this.getWidth());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
if (w != oldw)
refitText(this.getText().toString(), w);
}
}
怠惰なプログラマのためのChaseとonoelleのおかげで、TextViewの代わりにButtonに適応させた彼らの素晴らしいマージされたコードの実用的なバージョンをここに投稿させてください。
(ImageButtonsではなく)すべてのボタンをAutoResizeTextButtonsに置き換えれば、同じ退屈な問題が解決されます。
これがコードです。インポートを削除しました。
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* Version 2, December 2004
*
* Copyright (C) 2004 Sam Hocevar <[email protected]>
*
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
* made better by onoelle
* adapted for button by beppi
*/
/**
* Text Button that auto adjusts text size to fit within the view.
* If the text size equals the minimum text size and still does not
* fit, append with an Ellipsis.
*
* @author Chase Colburn
* @since Apr 4, 2011
*/
public class AutoResizeTextButton extends Button {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 20;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(Button textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add Ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
// Default constructor override
public AutoResizeTextButton(Context context) {
this(context, null);
}
// Default constructor when inflating from XML file
public AutoResizeTextButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public AutoResizeTextButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
}
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
* @param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
@Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
@Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
* @param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
* @return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
* @param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
* @return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add Ellipsis to text that overflows at the smallest text size
* @param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add Ellipsis to text that overflows at the smallest text size
* @return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if(mTextSize > 0) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
mMaxTextSize = mTextSize;
}
}
/**
* Resize text after measuring
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if(changed || mNeedsResize) {
int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with default width and height
*/
public void resizeText() {
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
resizeText(widthLimit, heightLimit);
}
/**
* Resize the text size with specified width and height
* @param width
* @param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no text
if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
return;
}
// Get the text view's Paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
// If we had reached our minimum text size and still don't fit, append an Ellipsis
if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
TextPaint Paint = new TextPaint(textPaint);
StaticLayout layout = new StaticLayout(text, Paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if(layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if(lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an Ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the Ellipsis
while(width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
setText(text.subSequence(0, end) + mEllipsis);
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
// textPaint.setTextSize(targetTextSize);
// modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))
setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if(mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
// Set the text size of the text Paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
// modified: make a copy of the original TextPaint object for measuring
// (apparently the object gets modified while measuring, see also the
// docs for TextView.getPaint() (which states to access it read-only)
// Update the text Paint object
TextPaint Paint = new TextPaint(originalPaint);
Paint.setTextSize(textSize);
// Measure using a static layout
StaticLayout layout = new StaticLayout(source, Paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}
使用法:
何も変更せずに、通常のButtonの代わりにAutoResizeTextButtonをxmlの中に配置します。 onCreate()の中に(例えば)置く:
myButton = (AutoResizeTextButton)getView().findViewById(id.myButton);
myButton.setMinTextSize(8f);
myButton.resizeText();
こちらのScalableTextView.Java
を参照してください Android用自動フィットTextView 。テキストの長さに基づいてTextViewを縮小および拡大するためのコードを追加しました