web-dev-qa-db-ja.com

ビューの最大サイズに従って複数行TextViewのテキストサイズを自動調整する方法

テキストビュー内のテキストを自動調整する方法を探してきました。私の検索を通じて、私は次のような多くの解決策を見つけました:

しかし、他の多くのように、これらは私の問題を解決しません。複数行でTextViewを使用すると、期待どおりに機能しません。

基本的に私の目標はこれです:

Phase 1Phase 2Phase 3Phase 4

ご覧のように、テキストは幅、高さに基づいてサイズ変更され、改行にも注意を払って、複数行のテキストビューを作成します。書体を変更することもできます。

これを解決するための私のアイデアの1つは次のようなものでした。

int size = CONSTANT_MAX_SIZE;
TextView tv = (TextView) findViewById(R.id.textview1)
while(Math.abs(tv.getMeasuredHeight()) >= TEXTVIEW_MAX_HEIGHT) {
    size--;
    tv.setTextSize(size);
    tv.measure(MeasureSpec.UNSPECIFIED,MeasureSpec.UNSPECIFIED);
    i++;
}

CONSTANT_MAX_SIZEは、フォントの最大サイズを定義する定数です(TextViewのテキストサイズプロパティ)。

TEXTVIEW_MAX_HEIGHTは、テキストビューの最大サイズを定義する定数です。

これは、テキストビューのテキストが変更されるたびに呼び出されました。

Textview xmlは次のようなものでした。

<TextView
     Android:id="@+id/textview1"
     Android:layout_width="200dp"
     Android:layout_height="wrap_content"
     Android:singleLine="false"
     Android:inputType="textMultiLine"
     Android:text=""
     Android:textSize="150sp" />

幅はXMLで制限されるため、Androidを調整すると必要に応じて自動的に複数行が作成されるため、ビューの高さのみを考慮する必要があります。

これは潜在的な解決策ですが、完全に機能するわけではありませんが(それからはほど遠い)、サイズ変更(テキストを削除するとき)をサポートしていません。

提案やアイデアはありますか?

21
nunofmendes

この問題の解決策を待つ間、私は実験してそれを理解しようとしました。

この問題に最も近い解決策は、ペイントの方法に基づいていました。

基本的にペイントには、 'breaktext'と呼ばれるメソッドがあり、次のことを行います。

public int breakText(CharSequence text、int start、int end、boolean measureForwards、float maxWidth、float [] measuredWidth)

APIレベル1で追加されました

テキストを測定し、測定された幅がmaxWidthを超えると早期に停止します。測定された文字数を返し、測定された幅がnullでない場合は、測定された実際の幅を返します。

私はそれをPaint 'getTextBounds'と組み合わせます:

public void getTextBounds(文字列テキスト、int start、int end、Rect境界)

APIレベル1で追加されました

(呼び出し元によって割り当てられた)境界で、すべての>文字を囲む最小の四角形を、暗黙の原点(0,0)で返します。

これで、指定した幅と高さに収まる文字数を取得できます。

Whileを使用すると、測定する文字列から削除文字を移動し続け、(while(index <string.length)を使用して)行数を取得し、getTextBoundsで取得した高さを乗算できます。

さらに、行間のスペースを表す2行ごとに可変高さを追加する必要があります(これはgetTextBoundsではカウントされません)。

コード例として、複数行テキストの高さを知る関数は次のようなものです:

public int getHeightOfMultiLineText(String text,int textSize, int maxWidth) {
    Paint = new TextPaint();
    Paint.setTextSize(textSize);
    int index = 0;
    int linecount = 0;
    while(index < text.length()) {
        index += Paint.breakText(text,index,text.length,true,maxWidth,null);
        linecount++;
    }

    Rect bounds = new Rect();
    Paint.getTextBounds("Yy", 0, 2, bounds);
    // obtain space between lines
    double lineSpacing = Math.max(0,((lineCount - 1) * bounds.height()*0.25)); 

    return (int)Math.floor(lineSpacing + lineCount * bounds.height());

注:maxWidth変数はピクセル単位です

次に、しばらくしてこのメ​​ソッドを呼び出して、その高さの最大フォントサイズを決定する必要があります。コード例は次のようになります。

textSize = 100;
int maxHeight = 50;
while(getHeightOfMultiLineText(text,textSize,maxWidth) > maxHeight)
  textSize--;

残念ながら、これは私が知る限りでは、上の画像からアスペクトを達成することができた唯一の方法でした。

これがこの障害を克服しようとする人にとって役立つことを願っています。

17
nunofmendes

enter image description hereenter image description hereenter image description here

Phamducgiamの答えが少し改善されました。表示後のskretchsではなく、すでにフィットしたサイズのテキストが表示されます。 textSize 100を開始するため、エディターでは醜く見えますが、実行時には正常に機能します。ここにコードがあります:

import Android.content.Context;
import Android.text.TextPaint;
import Android.util.AttributeSet;
import Android.util.TypedValue;
import Android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FontFitTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // start decreasing font size from 100
        setTextSize(TypedValue.COMPLEX_UNIT_PX, 100);

        // calculate dimensions. don't forget about padding
        final float maxWidth = getWidth()-(getPaddingLeft()+getPaddingRight());
        final float maxHeight = getHeight()-(getPaddingTop()+getPaddingBottom());

        if (maxWidth < 1.0f || maxHeight < 1.0f) {
            return;
        }

        CharSequence text = getText();
        int lineCount = getLineCount(maxWidth, text);
        float height = getHeight(lineCount);
        // keep decreasing font size until it fits
        while (height > maxHeight) {
            final float textSize = getTextSize();
            setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
            height = getHeight(getLineCount(maxWidth, getText()));
        }
        // show fitted textView
        requestLayout();
    }

    private float getHeight(int lineCount) {
        return lineCount * getLineHeight() + (lineCount > 0 ? (lineCount - 1) * getPaint().getFontSpacing() : 0);
    }

    private int getLineCount(float maxWidth, CharSequence text) {
        int lineCount = 0;
        int index = 0;
        final TextPaint Paint = getPaint();
        while (index < text.length()) {
            index += Paint.breakText(text, index, text.length(), true, maxWidth, null);
            lineCount++;
        }
        return lineCount;
    }
}
2

あなたはこの問題についてかなり良い解決策を持っていますが、私はこれを別の視点から見てみました。ロジックは、少なくとも新しいAndroid開発者にとっては、よりシンプルで理解しやすいものだと思います。このアイデアは、大きな文字列のスペースの間に「\ n」文字を挿入することに基づいています。次に、i textViewで編集した文字列を表示テキストとして設定します。これがコードです。

private String insertNewLineCharAtString(String str, int step){
    StringBuilder sb = new StringBuilder();
    int spaceCounter = 0;
    String[] strArray = str.split(" ");
    for(int i = 0; i < strArray.length; i++){



        if(spaceCounter == step){
            sb.append('\n');
            sb.append(strArray[i]);
            spaceCounter = 0;
        }else{
            sb.append(" "+strArray[i]);
        }
        spaceCounter++;


    }
    return sb.toString();
}

パラメータは単純です。 Strは編集する文字列で、変数stepはメソッドが '/ n'文字を挿入するスペースの数を定義します。次に、このようなものを呼び出すだけです:

myTextView.setText(insertNewLineCharactersAtString(yourDisplayingStr);

これがTextPaint、Rects、および数学関数を使いたくない多くの人に役立つことを願っています。

1
karvoynistas

あなたの解決策をありがとう、あなたはスーパーマンです! TextViewの派生クラスにコードを実装しました。コードは、Xamarin AndroidのC#コードに変換されます。必要に応じて、簡単にJavaに変換できます(Javaの最初のコンストラクターを削除します)。

public class AutoFitTextView : TextView
{
    public AutoFitTextView(System.IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer)
        : base(javaReference, transfer)
    {
    }

    public AutoFitTextView(Context context)
        : base(context)
    {

    }

    public AutoFitTextView(Context context, IAttributeSet attrs)
        : base(context, attrs)
    {

    }


    public void ResizeFontSize()
    {
        int textSize = 50;
        int maxHeight = this.Height;
        while (GetHeightOfMultiLineText(this.Text, textSize, this.Width) > maxHeight)
        {
            textSize--;
        }
        float scaleFactor = Context.Resources.DisplayMetrics.ScaledDensity;
        float additionalFactor = 1.2f;

        TextSize = ((float)(textSize / (additionalFactor * scaleFactor)));
    }


    private int GetHeightOfMultiLineText(string text, int textSize, int maxWidth)
    {
        TextPaint Paint = new TextPaint();
        Paint.TextSize = textSize;
        int index = 0;
        int lineCount = 0;
        while (index < text.Length)
        {
            index += Paint.BreakText(text, index, text.Length, true, maxWidth, null);
            lineCount++;
        }

        Rect bounds = new Rect();
        Paint.GetTextBounds("Yy", 0, 2, bounds);
        // obtain space between lines
        double lineSpacing = Math.Max(0, ((lineCount - 1) * bounds.Height() * 0.25));

        return (int)Math.Floor(lineSpacing + lineCount * bounds.Height());
    }

}

ここにAXMLコードがあります

        <touchtest.AutoFitTextView
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:id="@+id/tvLabel2"
        Android:singleLine="false"
        Android:inputType="textMultiLine"
        Android:text="I am here To test this text" />
1
Xusan

nunofmendesからのアイデア を使用して、テキストのサイズを自動的に変更し、複数の行をサポートするTextViewの派生クラスを作成しました。

import Android.content.Context;
import Android.os.Handler;
import Android.text.Layout;
import Android.text.TextPaint;
import Android.text.TextUtils.TruncateAt;
import Android.util.AttributeSet;
import Android.util.TypedValue;

public class AutoResizeTextView extends TextView {

    private Handler measureHandler = new Handler();
    private Runnable requestLayout = new Runnable() {
        @Override
        public void run() {
            requestLayout();
        }
    };

    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public AutoResizeTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AutoResizeTextView(Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec,
                             final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        final float maxWidth = getWidth();
        final float maxHeight = getHeight();
        if (maxWidth < 1.0f || maxHeight < 1.0f) {
            return;
        }

        int index = 0;
        int lineCount = 0;
        CharSequence text = getText();
        final TextPaint Paint = getPaint();
        while (index < text.length()) {
            index += Paint.breakText(text, index, text.length(), true, maxWidth, null);
            lineCount++;
        }
        final float height = lineCount * getLineHeight() + (lineCount > 0 ?
                (lineCount - 1) * Paint.getFontSpacing() : 0);
        if (height > maxHeight) {
            final float textSize = getTextSize();
            setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
            measureHandler.post(requestLayout);
        }
    }
}
1
phamducgiam