Androidに、テキストビュー内のテキストサイズを、占有するスペースに合わせて調整する方法はありますか?
例えば。 TableLayout
を使用し、各行に複数のTextView
sを追加しています。 TextView
sにテキストをラップさせたくないので、コンテンツのフォントサイズが小さくなることがわかります。
何か案は?
measureText
を試しましたが、列のサイズがわからないため、使用するのが面倒です。これは、フォントサイズを適切なものに変更するコードです
TableRow row = new TableRow(this);
for (int i=0; i < ColumnNames.length; i++) {
TextView textColumn = new TextView(this);
textColumn.setText(ColumnNames[i]);
textColumn.setPadding(0, 0, 1, 0);
textColumn.setTextColor(getResources().getColor(R.drawable.text_default));
row.addView(textColumn, new TableRow.LayoutParams());
}
table.addView(row, new TableLayout.LayoutParams());
以下のソリューションには、すべての提案が含まれています。それは元々Dunniによって投稿されたものから始まります。 gjpcのようなバイナリ検索を使用しますが、もう少し読みやすいです。また、グレムのバグ修正と私自身のバグ修正も含まれています。
import Android.content.Context;
import Android.graphics.Paint;
import Android.util.AttributeSet;
import Android.util.TypedValue;
import Android.widget.TextView;
public class FontFitTextView extends TextView {
public FontFitTextView(Context context) {
super(context);
initialise();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialise();
}
private void initialise() {
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
//max size defaults to the initially specified text size unless it is too small
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth)
{
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
float hi = 100;
float lo = 2;
final float threshold = 0.5f; // How close we have to be
mTestPaint.set(this.getPaint());
while((hi - lo) > threshold) {
float size = (hi+lo)/2;
mTestPaint.setTextSize(size);
if(mTestPaint.measureText(text) >= targetWidth)
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth);
this.setMeasuredDimension(parentWidth, height);
}
@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);
}
}
//Attributes
private Paint mTestPaint;
}
TextViewを拡張してこれを行うクラスを作成しました。提案されているとおり、measureTextを使用します。基本的に、最大テキストサイズと最小テキストサイズ(変更可能)があり、適合する最大サイズが見つかるまで、それらの間のサイズを1ずつ減少させます。特にエレガントではありませんが、他の方法は知りません。
コードは次のとおりです。
import Android.content.Context;
import Android.graphics.Paint;
import Android.util.AttributeSet;
import Android.widget.TextView;
public class FontFitTextView extends TextView {
public FontFitTextView(Context context) {
super(context);
initialise();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialise();
}
private void initialise() {
testPaint = new Paint();
testPaint.set(this.getPaint());
//max size defaults to the intially specified text size unless it is too small
maxTextSize = this.getTextSize();
if (maxTextSize < 11) {
maxTextSize = 20;
}
minTextSize = 10;
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth) {
if (textWidth > 0) {
int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
float trySize = maxTextSize;
testPaint.setTextSize(trySize);
while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) {
trySize -= 1;
if (trySize <= minTextSize) {
trySize = minTextSize;
break;
}
testPaint.setTextSize(trySize);
}
this.setTextSize(trySize);
}
}
@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);
}
}
//Getters and Setters
public float getMinTextSize() {
return minTextSize;
}
public void setMinTextSize(int minTextSize) {
this.minTextSize = minTextSize;
}
public float getMaxTextSize() {
return maxTextSize;
}
public void setMaxTextSize(int minTextSize) {
this.maxTextSize = minTextSize;
}
//Attributes
private Paint testPaint;
private float minTextSize;
private float maxTextSize;
}
これは speedplaneのFontFitTextView
ですが、テキストを合わせるために必要な場合はフォントサイズのみを縮小し、そうでない場合はフォントサイズを保持します。高さに合わせてフォントサイズを拡大することはありません。
public class FontFitTextView extends TextView {
// Attributes
private Paint mTestPaint;
private float defaultTextSize;
public FontFitTextView(Context context) {
super(context);
initialize();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
private void initialize() {
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
defaultTextSize = getTextSize();
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth) {
if (textWidth <= 0 || text.isEmpty())
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
// this is most likely a non-relevant call
if( targetWidth<=2 )
return;
// text already fits with the xml-defined font size?
mTestPaint.set(this.getPaint());
mTestPaint.setTextSize(defaultTextSize);
if(mTestPaint.measureText(text) <= targetWidth) {
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
return;
}
// adjust text size using binary search for efficiency
float hi = defaultTextSize;
float lo = 2;
final float threshold = 0.5f; // How close we have to be
while (hi - lo > threshold) {
float size = (hi + lo) / 2;
mTestPaint.setTextSize(size);
if(mTestPaint.measureText(text) >= targetWidth )
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth);
this.setMeasuredDimension(parentWidth, height);
}
@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 || h != oldh) {
refitText(this.getText().toString(), w);
}
}
}
Xmlでの使用方法の例を次に示します。
<com.your.package.activity.widget.FontFitTextView
Android:id="@+id/my_id"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:gravity="center"
Android:text="My Text"
Android:textSize="60sp" />
これにより、テキストが幅に収まる限り、フォントサイズは60spに維持されます。テキストが長い場合、フォントサイズが小さくなります。この場合、height=wrap_content
により、TextView
sの高さも変更されます。
バグを見つけたら、気軽に編集してください。
エミュレーターと電話で機能するが、Eclipseレイアウトエディターではあまり機能しない私のソリューションを次に示します。 kilakaのコードからインスピレーションを得ていますが、テキストのサイズはPaintから取得されるのではなく、measure(0, 0)
を呼び出すTextView自体を測定することから取得されます。
Javaクラス:
public class FontFitTextView extends TextView
{
private static final float THRESHOLD = 0.5f;
private enum Mode { Width, Height, Both, None }
private int minTextSize = 1;
private int maxTextSize = 1000;
private Mode mode = Mode.None;
private boolean inComputation;
private int widthMeasureSpec;
private int heightMeasureSpec;
public FontFitTextView(Context context) {
super(context);
}
public FontFitTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FontFitTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray tAttrs = context.obtainStyledAttributes(attrs, R.styleable.FontFitTextView, defStyle, 0);
maxTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_maxTextSize, maxTextSize);
minTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_minTextSize, minTextSize);
tAttrs.recycle();
}
private void resizeText() {
if (getWidth() <= 0 || getHeight() <= 0)
return;
if(mode == Mode.None)
return;
final int targetWidth = getWidth();
final int targetHeight = getHeight();
inComputation = true;
float higherSize = maxTextSize;
float lowerSize = minTextSize;
float textSize = getTextSize();
while(higherSize - lowerSize > THRESHOLD) {
textSize = (higherSize + lowerSize) / 2;
if (isTooBig(textSize, targetWidth, targetHeight)) {
higherSize = textSize;
} else {
lowerSize = textSize;
}
}
setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerSize);
measure(widthMeasureSpec, heightMeasureSpec);
inComputation = false;
}
private boolean isTooBig(float textSize, int targetWidth, int targetHeight) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
measure(0, 0);
if(mode == Mode.Both)
return getMeasuredWidth() >= targetWidth || getMeasuredHeight() >= targetHeight;
if(mode == Mode.Width)
return getMeasuredWidth() >= targetWidth;
else
return getMeasuredHeight() >= targetHeight;
}
private Mode getMode(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY)
return Mode.Both;
if(widthMode == MeasureSpec.EXACTLY)
return Mode.Width;
if(heightMode == MeasureSpec.EXACTLY)
return Mode.Height;
return Mode.None;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(!inComputation) {
this.widthMeasureSpec = widthMeasureSpec;
this.heightMeasureSpec = heightMeasureSpec;
mode = getMode(widthMeasureSpec, heightMeasureSpec);
resizeText();
}
}
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
resizeText();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh)
resizeText();
}
public int getMinTextSize() {
return minTextSize;
}
public void setMinTextSize(int minTextSize) {
this.minTextSize = minTextSize;
resizeText();
}
public int getMaxTextSize() {
return maxTextSize;
}
public void setMaxTextSize(int maxTextSize) {
this.maxTextSize = maxTextSize;
resizeText();
}
}
XML属性ファイル:
<resources>
<declare-styleable name="FontFitTextView">
<attr name="minTextSize" format="dimension" />
<attr name="maxTextSize" format="dimension" />
</declare-styleable>
</resources>
このクラスの最新バージョンについては、 my github を確認してください。それが誰かに役立つことを願っています。バグが見つかった場合、またはコードに説明が必要な場合は、Githubで問題を開いてください。
https://stackoverflow.com/users/234270/speedplane に感謝します。素晴らしい答えです!
以下は、高さも考慮し、フォントサイズを制限するためのmaxFontSize属性を備えた、彼の応答の改良版です(私の場合は便利だったので、共有したかったのです)。
package com.<your_package>;
import Android.content.Context;
import Android.content.res.TypedArray;
import Android.graphics.Paint;
import Android.graphics.Rect;
import Android.util.AttributeSet;
import Android.util.TypedValue;
import Android.widget.TextView;
public class FontFitTextView extends TextView
{
private Paint mTestPaint;
private float maxFontSize;
private static final float MAX_FONT_SIZE_DEFAULT_VALUE = 20f;
public FontFitTextView(Context context)
{
super(context);
initialise(context, null);
}
public FontFitTextView(Context context, AttributeSet attributeSet)
{
super(context, attributeSet);
initialise(context, attributeSet);
}
public FontFitTextView(Context context, AttributeSet attributeSet, int defStyle)
{
super(context, attributeSet, defStyle);
initialise(context, attributeSet);
}
private void initialise(Context context, AttributeSet attributeSet)
{
if(attributeSet!=null)
{
TypedArray styledAttributes = context.obtainStyledAttributes(attributeSet, R.styleable.FontFitTextView);
maxFontSize = styledAttributes.getDimension(R.styleable.FontFitTextView_maxFontSize, MAX_FONT_SIZE_DEFAULT_VALUE);
styledAttributes.recycle();
}
else
{
maxFontSize = MAX_FONT_SIZE_DEFAULT_VALUE;
}
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
//max size defaults to the initially specified text size unless it is too small
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth, int textHeight)
{
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
float hi = maxFontSize;
float lo = 2;
// final float threshold = 0.5f; // How close we have to be
final float threshold = 1f; // How close we have to be
mTestPaint.set(this.getPaint());
Rect bounds = new Rect();
while ((hi - lo) > threshold)
{
float size = (hi + lo) / 2;
mTestPaint.setTextSize(size);
mTestPaint.getTextBounds(text, 0, text.length(), bounds);
if (bounds.width() >= targetWidth || bounds.height() >= targetHeight)
hi = size; // too big
else
lo = size; // too small
// if (mTestPaint.measureText(text) >= targetWidth)
// hi = size; // too big
// else
// lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth, height);
this.setMeasuredDimension(parentWidth, height);
}
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after)
{
refitText(text.toString(), this.getWidth(), this.getHeight());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
if (w != oldw)
{
refitText(this.getText().toString(), w, h);
}
}
}
対応する/res/values/attr.xmlファイル:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FontFitTextView">
<attr name="maxFontSize" format="dimension" />
</declare-styleable>
</resources>
例:
<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:res-auto="http://schemas.Android.com/apk/res-auto"
Android:id="@+id/home_Layout"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:background="@drawable/background"
tools:ignore="ContentDescription" >
...
<com.<your_package>.FontFitTextView
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:singleLine="true"
Android:text="Sample Text"
Android:textSize="28sp"
res-auto:maxFontSize="35sp"/>
...
</RelativeLayout>
新しいmaxFontSize
属性を使用するには、例に示すようにxmlns:res-auto="http://schemas.Android.com/apk/res-auto"
を追加することを忘れないでください。
私も同じ問題を抱えていて、自分に合ったクラスを書いた。基本的に、静的レイアウトを使用してテキストを別のキャンバスに描画し、適切なフォントサイズが見つかるまで再測定しました。以下のトピックに掲載されているクラスをご覧ください。役に立てば幸いです。
次のことがうまく機能していることがわかりました。ループせず、高さと幅の両方を考慮します。ビューで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;
}
OnMeasureのわずかな変更:
@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);
}
RefitTextのバイナリ検索:
private void refitText(String text, int textWidth)
{
if (textWidth > 0)
{
int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
int trySize = (int)maxTextSize;
int increment = ~( trySize - (int)minTextSize ) / 2;
testPaint.setTextSize(trySize);
while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth))
{
trySize += increment;
increment = ( increment == 0 ) ? -1 : ~increment / 2;
if (trySize <= minTextSize)
{
trySize = (int)minTextSize;
break;
}
testPaint.setTextSize(trySize);
}
this.setTextSize( TypedValue.COMPLEX_UNIT_PX, trySize);
}
}
サードパーティのライブラリやウィジェットなしでこれを行うことができます。 APIレベル26のTextViewに組み込まれています。Android:autoSizeTextType="uniform"
をTextView
に追加し、高さを設定します。それで全部です。
https://developer.Android.com/guide/topics/ui/look-and-feel/autosizing-textview.html
<?xml version="1.0" encoding="utf-8"?>
<TextView
Android:layout_width="match_parent"
Android:layout_height="200dp"
Android:autoSizeTextType="uniform" />
互換性のためにTextViewCompat
を使用することもできます。
変更に対応
それ以外の場合、setTextSizeは値がSP単位であると想定するため、このようなテキストビューサイズを設定する必要があります。
setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);
そして、このコードを明示的に追加する必要がありました。
@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);
}
app:autoSizeTextType="uniform"
はAPIレベル26以降でのみ機能するため、後方互換性のためにAndroid:autoSizeTextType="uniform"
を使用します。
私はスピードプレーンの優れたソリューションの改善に取り組んでおり、これを思いつきました。テキストが垂直方向に正しく中央揃えされるようにマージンを設定するなど、高さを管理します。
これは同じ関数を使用して幅を取得しますが、最適に機能するようですが、高さがどこにも提供されないため、高さを取得するために別の関数を使用します。修正する必要があるいくつかの修正がありますが、私は目を楽しませながら、それを行う方法を見つけました。
import Android.content.Context;
import Android.graphics.Paint;
import Android.graphics.Rect;
import Android.util.AttributeSet;
import Android.util.TypedValue;
import Android.widget.TextView;
public class FontFitTextView extends TextView {
public FontFitTextView(Context context) {
super(context);
initialize();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
private void initialize() {
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
//max size defaults to the initially specified text size unless it is too small
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth,int textHeight)
{
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
float hi = Math.min(targetHeight,100);
float lo = 2;
final float threshold = 0.5f; // How close we have to be
Rect bounds = new Rect();
mTestPaint.set(this.getPaint());
while((hi - lo) > threshold) {
float size = (hi+lo)/2;
mTestPaint.setTextSize(size);
mTestPaint.getTextBounds(text, 0, text.length(), bounds);
if((mTestPaint.measureText(text)) >= targetWidth || (1+(2*(size+(float)bounds.top)-bounds.bottom)) >=targetHeight)
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX,(float) lo);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth,height);
this.setMeasuredDimension(parentWidth, height);
}
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
refitText(text.toString(), this.getWidth(),this.getHeight());
}
@Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
if (w != oldw) {
refitText(this.getText().toString(), w,h);
}
}
//Attributes
private Paint mTestPaint;
}
Googleはすでにこの機能を作成しています。
<TextView
Android:layout_width="match_parent"
Android:layout_height="200dp"
Android:autoSizeTextType="uniform" />
https://developer.Android.com/guide/topics/ui/look-and-feel/autosizing-textview.html
上記のDunniソリューションのバリエーションを使用しましたが、その特定のコードはうまくいきませんでした。特に、ビューのPaintオブジェクトの特性を持つようにPaintオブジェクトセットを使用して、measureText()を呼び出すと、ビューのPaintオブジェクトを直接呼び出すのと同じ値を返しません。おそらく、ビューの設定方法にいくつかの違いがあるため、動作が異なります。
私の解決策は、ビューのフォントサイズを複数回変更するとパフォーマンスが低下する可能性がある場合でも、ビューのペイントを直接使用することでした。
私はこれが正しい方法であるかどうかはわかりません...そのビューを取り、OnGlobalLayoutListener()をチェックしてtextview linecountを取得し、textSizeを設定します。
yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (textView.getLineCount()>=3) {
textView.setTextSize(20);
}else{
//add somthing
}
}
});
その非常にシンプルな数行のコード。
以前のポスターに触発されて、ソリューションを共有したいと思いました。前のフォントサイズに適用されるスケール係数を使用して、使用可能なスペースに合わせます。 TextViews onDrawメソッドの予期しない動作を防止することに加えて、単にテキストを独自に描画します。
public class FontFitTextView extends TextView {
// How much of the available space should be used in percent.
private static final float MARGINHEIGHT = 0.8f;
private static final float MARGINWIDTH = 0.8f;
private Paint paint;
private int viewWidth;
private int viewHeight;
private float textHeight;
private float textWidth;
public FontFitTextView(Context c) {
this(c, null);
}
public FontFitTextView(Context c, AttributeSet attrs) {
super(c, attrs);
initComponent();
}
// Default constructor override
public FontFitTextView(Context c, AttributeSet attrs, int defStyle) {
super(c, attrs, defStyle);
initComponent();
}
private void initComponent() {
Paint = new Paint();
Paint.setTextSize(30);
Paint.setTextAlign(Align.CENTER);
Paint.setAntiAlias(true);
}
public void setFontColor(int c) {
Paint.setColor(c);
}
private void calcTextSize(String s, Canvas c) {
float availableHeight = viewHeight;
float availableWidth = viewWidth;
// This value scales the old font up or down to match the available
// space.
float scale = 1.0f;
// Rectangle for measuring the text dimensions
Rect rect = new Rect();
float oldFontSize = Paint.getTextSize();
// Calculate the space used with old font size
Paint.getTextBounds(s, 0, s.length(), rect);
textWidth = rect.width();
textHeight = rect.height();
// find scale-value to fit the text horizontally
float scaleWidth = 1f;
if (textWidth > 0.0f) {
scaleWidth = (availableWidth) / textWidth * MARGINWIDTH;
}
// find scale-value to fit the text vertically
float scaleHeight = 1f;
if (textHeight > 0.0f) {
scaleHeight = (availableHeight) / textHeight * MARGINHEIGHT;
}
// We are always limited by the smaller one
if (scaleWidth < scaleHeight) {
scale = scaleWidth;
} else {
scale = scaleHeight;
}
// We apply the scale to the old font size to make it bigger or smaller
float newFontSize = (oldFontSize * scale);
Paint.setTextSize(newFontSize);
}
/**
* Calculates the Origin on the Y-Axis (width) for the text in this view.
*
* @return
*/
private float calcStartDrawingPosX() {
float left = getMeasuredWidth();
float centerY = left - (viewWidth / 2);
return centerY;
}
/**
* Calculates the Origin on the Y-Axis (height) for the text in this view.
*
* @return
*/
private float calcStartDrawingPosY() {
float bottom = getMeasuredHeight();
// The Paint only centers horizontally, Origin on the Y-Axis stays at
// the bottom, thus we have to lift the Origin additionally by the
// height of the font.
float centerX = bottom - (viewHeight / 2) + (textHeight / 2);
return centerX;
}
@Override
protected void onDraw(Canvas canvas) {
String text = getText().toString();
if (text.length() > 0) {
calcTextSize(text, canvas);
canvas.drawText(text, calcStartDrawingPosX(),
calcStartDrawingPosY(), Paint);
}
};
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
viewWidth = w;
viewHeight = h;
super.onSizeChanged(w, h, oldw, oldh);
}
}
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);
}
Textviewを特定の幅に収め、最小のtextsizeを達成できない場合は最後に省略記号「...」を追加する短いヘルパークラスを作成しました。
テキストが収まるか、最小テキストサイズに達するまで、テキストを小さくするだけであることに注意してください。大きなサイズでテストするには、helpメソッドを呼び出す前に、textsizeを大きな数値に設定します。
Pixelsを使用するため、dimenの値を使用している場合は、次のように呼び出すことができます。
float minTextSizePx = getResources().getDimensionPixelSize(R.dimen.min_text_size);
float maxTextWidthPx = getResources().getDimensionPixelSize(R.dimen.max_text_width);
WidgetUtils.fitText(textView, text, minTextSizePx, maxTextWidthPx);
これは私が使用するクラスです:
public class WidgetUtils {
public static void fitText(TextView textView, String text, float minTextSizePx, float maxWidthPx) {
textView.setEllipsize(null);
int size = (int)textView.getTextSize();
while (true) {
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text, 0, text.length(), bounds);
if(bounds.width() < maxWidthPx){
break;
}
if (size <= minTextSizePx) {
textView.setEllipsize(TextUtils.TruncateAt.END);
break;
}
size -= 1;
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
}
}
}
/* get your context */
Context c = getActivity().getApplicationContext();
LinearLayout l = new LinearLayout(c);
l.setOrientation(LinearLayout.VERTICAL);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0);
l.setLayoutParams(params);
l.setBackgroundResource(R.drawable.border);
TextView tv=new TextView(c);
tv.setText(" your text here");
/* set typeface if needed */
Typeface tf = Typeface.createFromAsset(c.getAssets(),"fonts/VERDANA.TTF");
tv.setTypeface(tf);
// LayoutParams lp = new LayoutParams();
tv.setTextColor(Color.parseColor("#282828"));
tv.setGravity(Gravity.CENTER | Gravity.BOTTOM);
// tv.setLayoutParams(lp);
tv.setTextSize(20);
l.addView(tv);
return l;
これは簡単な解決策です:
public void correctWidth(TextView textView, int desiredWidth)
{
Paint paint = new Paint();
Rect bounds = new Rect();
Paint.setTypeface(textView.getTypeface());
float textSize = textView.getTextSize();
Paint.setTextSize(textSize);
String text = textView.getText().toString();
Paint.getTextBounds(text, 0, text.length(), bounds);
while (bounds.width() > desiredWidth)
{
textSize--;
Paint.setTextSize(textSize);
Paint.getTextBounds(text, 0, text.length(), bounds);
}
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
AllCapsのような変換が設定されている場合、speedplaneのアプローチにはバグがあります。私はそれを修正し、次のコードを作成しました(申し訳ありませんが、私の評判ではこれをコメントとしてspeedplaneのソリューションに追加することはできません):
import Android.content.Context;
import Android.graphics.Paint;
import Android.util.AttributeSet;
import Android.util.TypedValue;
import Android.widget.TextView;
public class FontFitTextView extends TextView {
public FontFitTextView(Context context) {
super(context);
initialise();
}
public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialise();
}
private void initialise() {
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
//max size defaults to the initially specified text size unless it is too small
}
/* Re size the font so the specified text fits in the text box
* assuming the text box is the specified width.
*/
private void refitText(String text, int textWidth)
{
if (getTransformationMethod() != null) {
text = getTransformationMethod().getTransformation(text, this).toString();
}
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
float hi = 100;
float lo = 2;
final float threshold = 0.5f; // How close we have to be
mTestPaint.set(this.getPaint());
while((hi - lo) > threshold) {
float size = (hi+lo)/2;
if(mTestPaint.measureText(text) >= targetWidth)
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth);
this.setMeasuredDimension(parentWidth, height);
}
@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);
}
}
//Attributes
private Paint mTestPaint;
}
私はこのライブラリを見つけるまでずっと、私のプロジェクトでこの痛みを抱えていました:
compile 'me.grantland:autofittextview:0.2.+'
必要に応じてxmlを追加するだけで完了です。例えば:
<me.grantland.widget.AutofitTextView
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:singleLine="true"
Android:maxLines="2"
Android:textSize="40sp"
autofit:minTextSize="16sp"
/>