1. 程式人生 > >TextView 設定行高並垂直居中

TextView 設定行高並垂直居中

需求: TextView 多行文字可以設定行高(如20dp),每行文字垂直居中。

效果如下:

多行文字,設定行高,垂直居中

實現思路:通過設定 TextView 的 lineSpacingExtralineSpacingMultiplier 來實現。

  • lineSpacingMultiplier 的值為行間距的倍數,預設值為 1.0f。
  • lineSpacingExtra 值為具體的行間距值,如20dp。
  • 垂直居中靠設定 paddingTop 和 paddingBottom

TextView 相關

TextView 內部除了繼承自 View 的相關屬性和 measure、layout、draw步驟,還包括:
- Layout

: TextView 的文字排版、折行策略以及文字繪製都是在 Layout 裡面完成的,TextView 的自身測量也受 Layout 的影響。Layout 是 TextView 執行setText方法後,由 TextView 內部建立的例項,並不能由外部提供。Layout有三個子類,BoringLayout、DynamicLayout、StaticLayout。
- TransformationMethod: 用來處理最終的顯示結果的類,例如顯示密碼的時候把密碼轉換成圓點。這個類並不直接影響 TextView 內部儲存的 Text ,隻影響顯示的結果。
- MovementMethod: 用來處理 TextView 內部事件響應的類,可以針 對TextView 內文字的某一個區域做軟鍵盤輸入或者觸控事件的響應。
- Drawables
: TextView 的靜態內部類,用來處理和儲存 TextView 的 CompoundDrawables ,包括 TextView 的上下左右的 Drawable 以及錯誤提示的 Drawable。
- Spans: Spans 並不是特定的某一個類或者實現了某一個介面的類。它可以是任意型別,Spans實際上做的事情是在 TextView 的內部的 text 的某一個區域做標記。其中有部 分Spans 可以影響TextView的繪製和測量,如 ImageSpan、BackgroundColorSpan、AbsoluteSizeSpan。還有可以響應點選事件的ClickableSpan。
- Editor
: TextView作為可編輯文字控制元件的時候(EditText),使用Editor來處理文字的區域選擇處理和判斷、拼寫檢查、彈出文字選單等。
- InputConnection: EditText 的文字輸入部分是在 TextView 中完成的。而 InputConnection 是軟鍵盤和TextView之間的橋樑,所有的軟鍵盤的輸入文字、修改文字和刪除文字都是通過 InputConnection 傳遞給 TextView 的。

TextView 預設文字的上下邊距

TextView 的 textSize 屬性代表的意義是字型的大小,體現為字型高度,一般單位是 sp, sp 代表的字型大小根據手機設定的文字大小有關,預設的 1sp = 1dp。但是Android 系統會預設的給文字增加一點邊框。
預設效果

渲染圖

Android 提供了一個 setIncludeFontPadding 方法.用來設定 TextView 是否在頂部和底部保留一些空隙,預設為 ture 。如果我們設定為 false 的話可能會導致某些語言顯示的不完整, 如 Arabic Kannada 。在 StaticLayoutsetIncludePad 方法提到。

/**
 * Set whether to include extra space beyond font ascent and descent (which is
 * needed to avoid clipping in some languages, such as Arabic and Kannada). The
 * default is {@code true}.
 *
 * @param includePad whether to include padding
 * @return this builder, useful for chaining
 * @see android.widget.TextView#setIncludeFontPadding
 */
public Builder setIncludePad(boolean includePad) {
    mIncludePad = includePad;
    return this;
}

通過 android:includeFontPadding="false" 可以去掉一定的邊距值但是不能完全去掉。還少達不到
文字高度精確,所以不通過過設定 lineSpacingMultiplier 來改變, lineSpacingMultiplier 為 0 , 那麼多行文字就都變成一行了。
右邊的綠色背景的效果

最終結果是:

lineSpacingMultiplier = 0
lineSpacingExtra = 行高
paddingTop = paddingBottom = (行高-字型大小)* 0.5

計算paddingTop

設定高度

封裝成控制元件。這裡繼承了EditText, 預設的編輯文字時的行高會改變,所以在文字變化時需要重新設定.

package xyz.hanks.note.ui.widget;


import android.content.Context;
import android.graphics.Canvas;
import android.text.Editable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.widget.EditText;

import xyz.hanks.note.R;

/**
 * 每一行的文字垂直居中
 * Created by hanks on 16/7/2.
 */
public class LineTextView extends EditText {

    private float ITEM_HEIGHT = 125;
    boolean reLayout = false;
    TextWatcher textWatcher;

    public LineTextView(Context context) {
        this(context,null);
    }

    public LineTextView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public LineTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        addTextChangedListener(new android.text.TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                if (textWatcher != null) {
                    textWatcher.beforeTextChanged(charSequence, i, i1, i2);
                }
            }
            @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                float add = ITEM_HEIGHT;
                setLineSpacing(0f, 1f);
                setLineSpacing(add, 0);
                setIncludeFontPadding(false);
                setGravity(Gravity.CENTER_VERTICAL);
                int top = (int) ((add - getTextSize()) * 0.5f);
                setPadding(getPaddingLeft(), top, getPaddingRight(), -top);
                if (textWatcher != null) {
                    textWatcher.onTextChanged(charSequence, i, i1, i2);
                }
            }
            @Override public void afterTextChanged(Editable editable) {
                if (textWatcher != null) {
                    textWatcher.afterTextChanged(editable);
                }
            }
        });

    }

    @Override protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!reLayout) {
            reLayout = true; 
            setIncludeFontPadding(false);
            setGravity(Gravity.CENTER_VERTICAL);
            setLineSpacing(ITEM_HEIGHT, 0);
            int top = (int) ((ITEM_HEIGHT - getTextSize()) * 0.5f);
            setPadding(getPaddingLeft(), top, getPaddingRight(), -top);
            requestLayout();
            invalidate();
        }
    }

    public void addTextWatcher(TextWatcher textWatcher) {
        this.textWatcher = textWatcher;
    }

    public interface TextWatcher {
        void beforeTextChanged(CharSequence var1, int var2, int var3, int var4);
        void onTextChanged(CharSequence var1, int var2, int var3, int var4);
        void afterTextChanged(Editable var1);
    }
}

參考連結: