Android 自定義View 一(初體驗onDraw(),自定義屬性,onMeasue()方法,測量換行)

自定義控件對每個android程序猿來說是一項必須掌握的一項技巧嗅剖,在工作中難免遇到一些特殊的自定義控件范删,如果基礎(chǔ)知識不扎實根蟹,是一件非常崩潰的一件事脓杉,一大堆的網(wǎng)上資料復(fù)制黏貼。简逮。球散。。接下來為了鞏固一下知識和大家一起進(jìn)行一次愉快的自定義控件旅行……└(o)┘(老司機可以繞道哈)

</br>

一散庶、繼承View,重寫onDraw()方法蕉堰;

@先看一下官方文檔對View的介紹:

View這個類代表用戶界面組件的基本構(gòu)建塊凌净。View在屏幕上占據(jù)一個矩形區(qū)域,并負(fù)責(zé)繪制和事件處理屋讶。View是用于創(chuàng)建交互式用戶界面組件(按鈕冰寻、文本等)的基礎(chǔ)類。它的子類ViewGroup是所有布局的父類皿渗,它是一個可以包含其他view或者viewGroup并定義它們的布局屬性的看不見的容器斩芭。

@了解View類中一些重要的方法的使用時機

1、Constructors: (構(gòu)造方法)

一種形式的構(gòu)造方法是使用代碼創(chuàng)建的時候調(diào)用的(new View())乐疆,另一種形式是View被布局文件填充時被調(diào)用划乖;

2、onFinishInflate():(當(dāng)View和他的所有子控件被XML布局文件填充完成時被調(diào)用诀拭。)

這個方法里面可以完成一些初始化迁筛,比如初始化子控件;

3耕挨、onMeasure(int, int):(當(dāng)決定view和他的孩子的尺寸需求時被調(diào)用)

這個方法里面可以完成一些控件的測量细卧;

4、onLayout(boolean, int, int, int, int):(當(dāng)View給他的孩子分配大小和位置的時候調(diào)用)

這個方法用于擺放控件的位置筒占;

5贪庙、onSizeChanged(int, int, int, int):(當(dāng)view大小發(fā)生變化時調(diào)用)

這個方法用于控件的尺寸發(fā)生變化進(jìn)行變化后的賦值;

6翰苫、onDraw(Canvas canvas):(當(dāng)視圖應(yīng)該呈現(xiàn)其內(nèi)容時調(diào)用)

這個方法主要用于繪制控件止邮;

7、onAttachedToWindow():(當(dāng)視圖被連接到一個窗口時調(diào)用)
8奏窑、onDetachedFromWindow():(當(dāng)視圖從窗口分離時調(diào)用)
9导披、onWindowVisibilityChanged(int):(當(dāng)View的窗口的可見性發(fā)生改變時調(diào)用)

了解這幾個方法后,埃唯,自定義view的時候就可以在相應(yīng)的方法里面進(jìn)行操作了

@創(chuàng)建第一個自定義TextView

MyTextView繼承View撩匕,發(fā)現(xiàn)報錯,因為要覆蓋他的構(gòu)造方法(因為View中沒有參數(shù)為空的構(gòu)造方法)墨叛,View有四種形式的構(gòu)造方法止毕,其中四個參數(shù)的構(gòu)造方法是API 21才出現(xiàn),所以一般我們只需要重寫其他三個構(gòu)造方法即可漠趁。它們的參數(shù)不一樣分別對應(yīng)不同的創(chuàng)建方式扁凛,比如只有一個Context參數(shù)的構(gòu)造方法通常是通過代碼初始化控件時使用;而兩個參數(shù)的構(gòu)造方法通常對應(yīng)布局文件中控件被映射成對象時調(diào)用(需要解析屬性)闯传;通常我們讓這兩個構(gòu)造方法最終調(diào)用三個參數(shù)的構(gòu)造方法谨朝,然后在第三個構(gòu)造方法中進(jìn)行一些初始化操作(this()調(diào)用本地的方法)。

/**
 * Created by Dengxiao on 2016/12/23.
 */

public class MyTextView extends View {
    /**
     * 定義TextView相關(guān)的三個屬性(文字,顏色字币,大屑苑酢)
     */
    private String mTextStr;
    private int mTextColor;
    private int mTextSize;

    /**
     *繪制控制的區(qū)域和畫筆
     */
    private Rect mBound;
    private Paint mPaint;

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

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

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化字體的相關(guān)屬性
        mTextStr="hello world";
        mTextColor= Color.YELLOW;
        mTextSize=88;
        //初始化畫筆
        mPaint=new Paint();
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColor);
        //獲取繪制文本的寬和高
        mBound =new Rect();
        mPaint.getTextBounds(mTextStr,0,mTextStr.length(),mBound);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪制文本參數(shù) 計算x、y軸的起始坐標(biāo)
        int xStart = getWidth() / 2 - mBound.width() / 2;
        int yStart = getHeight()/2+mBound.height()/2;
        canvas.drawText(mTextStr,xStart,yStart,mPaint);
    }
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.dengxiao.customviewone.MainActivity">

    <com.dengxiao.customviewone.MyTextView
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:background="#f00"
         />
</RelativeLayout>

運行結(jié)果:


QQ圖片20161223115009.png
是不是叼炸天了纬朝,,一個View就實現(xiàn)了TextView骄呼,如果要繪制其他文字共苛,比如寫個happy,不想再MyTextView中修改mTextStr,為了方便想要在布局文件中直接更改文字蜓萄,這時候就用到了新的知識點:自定義屬性隅茎。

二、自定義屬性:

在res/values/下創(chuàng)建一個名為attrs.xml的文件嫉沽,然后定義如下屬性:(屬性詳解看下一篇Android自定義View二(深入了解自定義屬性))

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyTextView">
        <attr name="mTextStr" format="string"/>
        <attr name="mTextColor" format="color"/>
        <attr name="mTextSize" format="dimension"/>
    </declare-styleable>

</resources>

然后再布局中使用自定義屬性辟犀,記得一定要添加xmlns:app="http://schemas.android.com/apk/res-auto"

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.dengxiao.customviewone.MainActivity">

    <com.dengxiao.customviewone.MyTextView
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:background="#f00"
        app:mTextStr="happy"
        app:mTextColor="@color/colorPrimaryDark"
        app:mTextSize="15sp"
        />
</RelativeLayout>

在構(gòu)造方法中獲取自定義屬性值

 public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
       /* //初始化字體的相關(guān)屬性
        mTextStr = "hello";
        mTextColor = Color.YELLOW;
        mTextSize = 88;*/
        //獲取自定義屬性值
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTextView, defStyleAttr, 0);
        mTextStr= typedArray.getString(R.styleable.MyTextView_mTextStr);
        mTextColor=typedArray.getColor(R.styleable.MyTextView_mTextColor,Color.BLACK);
        mTextSize=typedArray.getDimension(R.styleable.MyTextView_mTextSize,88);
        //初始化畫筆
        mPaint = new Paint();
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColor);
        //獲取繪制文本的寬和高
        mBound = new Rect();
        mPaint.getTextBounds(mTextStr, 0, mTextStr.length(), mBound);
    }

運行結(jié)果

QQ圖片20161223140603.png

完美實現(xiàn)MyTextView的自定義屬性,绸硕,簡單吧L镁埂!2E濉出嘹!好吧,接下來讓做點小變化咬崔,税稼,多繪制一點文本app:mTextStr="Immediately to Christmas"運行結(jié)果


QQ圖片20161223141540.png

什么鬼?垮斯?郎仆??沒有像TextView控件那樣自動換行!控件太小兜蠕,不足以顯示辣么長的文本扰肌,我們將寬高改為wrap_content試試:


QQ圖片20161223141949.png

這又是什么鬼?牺氨?狡耻?不是wrap_content包裹控件么?猴凹?怎么就填充了屏幕夷狰??感覺很奇葩吧郊霎!不著急沼头,,下面介紹這個方法onMeasuer(),,了解了它,你就會明白其中的原理

三进倍、onMeasure()方法:

在學(xué)習(xí)onMasure方法之前土至,我們要先了解他的參數(shù)中的一個類MeasureSpec,知己知彼才能百戰(zhàn)百勝 猾昆。 跟蹤一下源碼陶因,發(fā)現(xiàn)它是View中的一個靜態(tài)內(nèi)部類,是由尺寸和模式組合而成的一個值垂蜗,用來描述父控件對子控件尺寸的約束楷扬,看看他的部分源碼,一共有三種模式贴见,然后提供了合成和分解的方法:

1烘苹、MeasureSpec:

/**
 * measurespec封裝了父控件對他的孩子的布局要求。
 * 一個measurespec由大小和模式片部。有三種可能的模式:
 */
public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    //父控件不強加任何約束給子控件镣衡,它可以是它想要任何大小。
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;  //0

    //父控件決定給孩子一個精確的尺寸
    public static final int EXACTLY     = 1 << MODE_SHIFT;  //1073741824

    //父控件會給子控件盡可能大的尺寸
    public static final int AT_MOST     = 2 << MODE_SHIFT;   //-2147483648
    /**
     * 根據(jù)給定的尺寸和模式創(chuàng)建一個約束規(guī)范
     */
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
            return 0;
        }
        return makeMeasureSpec(size, mode);
    }
    /**
     * 從約束規(guī)范中獲取模式
     */
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }
    /**
     * 從約束規(guī)范中獲取尺寸
     */
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}

這樣說起來還是有點抽象档悠,舉一個小栗子大家就知道這三種約束到底是什么意思廊鸥。重寫自定義View的onMeasure方法中打印一下他的參數(shù)(int widthMeasureSpec, int heightMeasureSpec)到底是個什么鬼**onMeasure方法會走多次,主要還是看第一次的測量效果

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//獲取寬的模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);//獲取高的模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//獲取寬的尺寸
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);//獲取高的尺寸
        Log.d("DX", "UNSPECIFIED==  0(控件不強加任何約束給子控件辖所,它可以是它想要任何大小)");
        Log.d("DX", "EXACTLY    ==  1073741824(父控件決定給孩子一個精確的尺寸)");
        Log.d("DX", "AT_MOST    ==  -2147483648(父控件會給子控件盡可能大的尺寸)");
        Log.d("DX", "寬的模式:" + widthMode);
        Log.d("DX", "高的模式:" + heightMode);
        Log.d("DX", "寬的尺寸:" + widthSize);
        Log.d("DX", "高的尺寸:" + heightSize);
    }
情形1黍图,讓按鈕包裹內(nèi)容:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.dengxiao.customviewone.MainActivity">

    <com.dengxiao.customviewone.MyTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#f00"
        app:mTextStr="Immediately to Christmas"
        app:mTextColor="@color/colorPrimaryDark"
        app:mTextSize="20sp"
        />

</RelativeLayout>
Log打印(會進(jìn)行多次測量,看第一次測量內(nèi)容就可以了)
DX: UNSPECIFIED==  0(控件不強加任何約束給子控件奴烙,它可以是它想要任何大小)
DX: EXACTLY    ==  1073741824(父控件決定給孩子一個精確的尺寸)
DX: AT_MOST    ==  -2147483648(父控件會給子控件盡可能大的尺寸)
DX: 寬的模式:-2147483648
DX: 高的模式:-2147483648
DX: 寬的尺寸:1080
DX: 高的尺寸:1458
以上可以看出在包裹內(nèi)容的時候高和寬的模式是(父控件會給子控件盡可能大的尺寸)
可以理解上面(不是wrap_content包裹控件么助被??怎么就填充了屏幕切诀?揩环?)
情形2,讓按鈕充滿父控件:
  <com.dengxiao.customviewone.MyTextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#f00"
        app:mTextStr="Immediately to Christmas"
        app:mTextColor="@color/colorPrimaryDark"
        app:mTextSize="20sp"
        />
Log打印(會進(jìn)行多次測量幅虑,看第一次測量內(nèi)容就可以了)
DX: UNSPECIFIED==  0(控件不強加任何約束給子控件丰滑,它可以是它想要任何大小)
DX: EXACTLY    ==  1073741824(父控件決定給孩子一個精確的尺寸)
DX: AT_MOST    ==  -2147483648(父控件會給子控件盡可能大的尺寸)
DX: 寬的模式:1073741824
DX: 高的模式:1073741824
DX: 寬的尺寸:1080
DX: 高的尺寸:1458
以上可以看出在充滿內(nèi)容的時候高和寬的模式是(父控件決定給孩子一個精確的尺寸)
情形3,給按鈕的寬設(shè)置具體值:
    <com.dengxiao.customviewone.MyTextView
        android:layout_width="200dip"
        android:layout_height="wrap_content"
        android:background="#f00"
        app:mTextStr="Immediately to Christmas"
        app:mTextColor="@color/colorPrimaryDark"
        app:mTextSize="20sp"
        />
Log打印(會進(jìn)行多次測量倒庵,看第一次測量內(nèi)容就可以了)
DX: UNSPECIFIED==  0(控件不強加任何約束給子控件褒墨,它可以是它想要任何大小)
DX: EXACTLY    ==  1073741824(父控件決定給孩子一個精確的尺寸)
DX: AT_MOST    ==  -2147483648(父控件會給子控件盡可能大的尺寸)
DX: 寬的模式:1073741824
DX: 高的模式:-2147483648
DX: 寬的尺寸:525
DX: 高的尺寸:1458
以上可以看出在給控件一個固定寬度的時候?qū)挼哪J绞牵ǜ缚丶Q定給孩子一個精確的尺寸)
通過分析以上三次Log日志,擎宝,可以看出在控件包裹的時候的模式是AT_MOST (父控件會給子控件盡可能大的尺寸) 郁妈,控件充滿的時候調(diào)用的模式是EXACTLY(父控件決定給孩子一個精確的尺寸)最后得到寬的尺寸是一樣的,绍申,有點難以理解噩咪,顾彰,看下面

通過上面對MeasureSpec的了解,我們現(xiàn)在就有能看懂View的onMeasure方法默認(rèn)是怎樣為控件測量大小的了 看View中onMeasure的源碼:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST://這里----
    case MeasureSpec.EXACTLY://這里----
        result = specSize;
        break;
    }
    return result;
}
根據(jù)父控件給予的約束(看上面源碼中----)胃碾,發(fā)現(xiàn)AT_MOST (相當(dāng)于wrap_content )和EXACTLY (相當(dāng)于match_parent )兩種情況返回的測量寬高都是specSize涨享,而這個specSize正是我們上面說的父控件剩余的寬高,所以默認(rèn)onMeasure方法中wrap_content 和match_parent 的效果是一樣的仆百,都是填充剩余的空間厕隧。這下就明白原理了吧!俄周!這個怎么解決呢看下面

2栏账、重寫onMeasure(),我們先忽略掉UNSPECIFIED 的情況(使用極少)栈源,只考慮AT_MOST 和EXACTLY 這兩種情況;

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//獲取寬的模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);//獲取高的模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//獲取寬的尺寸
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);//獲取高的尺寸
        Log.d("DX", "UNSPECIFIED==  0(控件不強加任何約束給子控件,它可以是它想要任何大小)");
        Log.d("DX", "EXACTLY    ==  1073741824(父控件決定給孩子一個精確的尺寸)");
        Log.d("DX", "AT_MOST    ==  -2147483648(父控件會給子控件盡可能大的尺寸)");
        Log.d("DX", "寬的模式:" + widthMode);
        Log.d("DX", "高的模式:" + heightMode);
        Log.d("DX", "寬的尺寸:" + widthSize);
        Log.d("DX", "高的尺寸:" + heightSize);
        int width;
        int height;
        //根據(jù)寬的模式進(jìn)行判斷并復(fù)制
        if (widthMode == MeasureSpec.EXACTLY) {
            //EXACTLY如果match_parent或者具體的值竖般,直接賦值
            width = widthSize;
        } else {
            //AT_MOST得到控件需要多大的尺寸
            float viewWidth = mBound.width();//文本的寬帶
            //控件的寬度就是文本的寬度加上兩邊的內(nèi)邊距甚垦。內(nèi)邊距就是padding值,在構(gòu)造方法執(zhí)行完就被賦值
            width = (int) (getPaddingLeft() + viewWidth + getPaddingRight());
        }
        //高度跟寬度的處理方式一樣
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            float textHeight = mBound.height();
            height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
        }
        //保存測量寬度和測量高度
        setMeasuredDimension(width, height);
    }
讓后將布局文件設(shè)置成wrap_content后運行結(jié)果
QQ圖片20170119152122.png
問題完美解決;恋瘛<枇痢!如果文字超過一行內(nèi)容會怎么辦挣郭?迄埃?驗證一下。布局文件:
 <com.dengxiao.customviewone.MyTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#f00"
        android:padding="10dp"
        app:mTextStr="北風(fēng)卷地白草折兑障,胡天八月即飛雪.hao忽如一夜春風(fēng)來侄非,千樹萬樹梨花開。"
        app:mTextColor="@color/colorPrimaryDark"
        app:mTextSize="25sp"
        />

運行結(jié)果

QQ圖片20170119153748.png

真如想象中的流译,逞怨,顯示不全,福澡,怎么辦叠赦,想想肯定是測量出現(xiàn)了問題,在onMeasure中高度出現(xiàn)了問題革砸,既然知道的問題的來源就好辦了除秀,,只需要在測量的時候算利,根據(jù)文字的總長度和控件的寬度册踩,就可以知道需要繪制幾行,然后將文本分割成小段放入集合中效拭,在onDraw方法中分別繪制棍好;

4仗岸、自動換行(以下代碼僅供參考)


/**
 * Created by Dengxiao on 2016/12/23.
 */

public class MyTextView extends View {
    private int textHeight;
    /**
     * 定義TextView相關(guān)的三個屬性(文字,顏色借笙,大邪遣馈)
     */
    private String mTextStr;
    private int mTextColor;
    private float mTextSize;
    private ArrayList<String> mTextList;

    /**
     * 繪制控制的區(qū)域和畫筆
     */
    private Rect mBound;
    private Paint mPaint;

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

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

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
       /* //初始化字體的相關(guān)屬性
        mTextStr = "hello";
        mTextColor = Color.YELLOW;
        mTextSize = 88;*/
        //獲取自定義屬性值
        mTextList = new ArrayList<>();
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTextView, defStyleAttr, 0);
        mTextStr = typedArray.getString(R.styleable.MyTextView_mTextStr);
        mTextColor = typedArray.getColor(R.styleable.MyTextView_mTextColor, Color.BLACK);
        mTextSize = typedArray.getDimension(R.styleable.MyTextView_mTextSize, 88);
        //初始化畫筆
        mPaint = new Paint();
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColor);
        //獲取繪制文本的寬和高
        mBound = new Rect();
        mPaint.getTextBounds(mTextStr, 0, mTextStr.length(), mBound);

      /*  Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        textHeight = (int) (Math.ceil(fontMetrics.descent -fontMetrics.ascent));*/

        textHeight = mBound.height();
        typedArray .recycle()


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       /* //單行繪制文本參數(shù) 計算x、y軸的起始坐標(biāo)
        int xStart = getWidth() / 2 - mBound.width() / 2;
        int yStart = getHeight() / 2 + mBound.height() / 2;
        canvas.drawText(mTextStr, xStart, yStart, mPaint);*/

        for (int i = 0; i < mTextList.size(); i++) {
            mPaint.getTextBounds(mTextList.get(i), 0, mTextList.get(i).length(), mBound);
            Log.v("DX", "mBound.h:" + mBound.height());
            Log.v("DX", "在X:" + (getWidth() / 2 - mBound.width() / 2) + "  Y:" + (getPaddingTop() + (textHeight * (i + 1)) + "  繪制:" + mTextList.get(i)));
            canvas.drawText(mTextList.get(i), (getWidth() / 2 - mBound.width() / 2), (getPaddingTop() + (textHeight * (i + 1))), mPaint);
        }
    }

    boolean isOneLine = true;//如果是一行為true
    int lineNum;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//獲取寬的模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);//獲取高的模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//獲取寬的尺寸
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);//獲取高的尺寸
        Log.d("DX", "UNSPECIFIED==  0(控件不強加任何約束給子控件业稼,它可以是它想要任何大小)");
        Log.d("DX", "EXACTLY    ==  1073741824(父控件決定給孩子一個精確的尺寸)");
        Log.d("DX", "AT_MOST    ==  -2147483648(父控件會給子控件盡可能大的尺寸)");
        Log.d("DX", "寬的模式:" + widthMode);
        Log.d("DX", "高的模式:" + heightMode);
        Log.d("DX", "寬的尺寸:" + widthSize);
        Log.d("DX", "高的尺寸:" + heightSize);

        float viewWidth = mBound.width();//文本的寬帶
        int width;
        int height;
        if (mTextList.size() == 0) {
            //計算文本的寬度
            int padding = getPaddingLeft() + getPaddingRight();
            int maxWidth = widthSize - padding;//文本顯示的最大寬帶(屏幕寬度-文本左右內(nèi)邊距)
            if (viewWidth < maxWidth) {
                lineNum = 1;
                mTextList.add(mTextStr);
            } else {
                //文本超過了一行
                isOneLine = false;
                //計算行數(shù)
                float lineCount = viewWidth / maxWidth;
                String s1 = String.valueOf(lineCount);
                if (s1.contains(".")) {
                    lineNum = Integer.parseInt(s1.substring(0, s1.indexOf("."))) + 1;
                } else {
                    lineNum = Integer.parseInt(lineCount + "");
                }
                int lineLenght = mTextStr.length() / lineNum;
                for (int i = 0; i < lineNum; i++) {
                    String lineStr;
                    if (mTextStr.length() <= lineLenght) {
                        lineStr = mTextStr.substring(0, mTextStr.length());
                    } else {
                        lineStr = mTextStr.substring(0, lineLenght);
                    }
                    mTextList.add(lineStr);
                    if (!TextUtils.isEmpty(mTextStr)) {
                        if (mTextStr.length() <= lineLenght) {
                            mTextStr = mTextStr.substring(0, mTextStr.length());
                        } else {
                            mTextStr = mTextStr.substring(lineLenght, mTextStr.length());
                        }
                    } else {
                        break;
                    }
                }
            }
        }
        //根據(jù)寬的模式進(jìn)行判斷并復(fù)制
        if (widthMode == MeasureSpec.EXACTLY) {
            //EXACTLY如果match_parent或者具體的值盗痒,直接賦值
            width = widthSize;
        } else {
            //AT_MOST得到控件需要多大的尺寸 如果是多行,說明控件寬度應(yīng)該填充父窗體
            width = isOneLine ? (int) (getPaddingLeft() + viewWidth + getPaddingRight()) : widthSize;

        }
        //高度跟寬度的處理方式一樣
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
//            float textHeight = mBound.height();這樣設(shè)置高度有偏差
            int lineHeight = (getPaddingTop() + textHeight +4+ getPaddingBottom());
            int allHeight = (getPaddingTop() +( textHeight +4)* lineNum + getPaddingBottom());
            height = isOneLine ? lineHeight : allHeight;
            ;
        }
        //保存測量寬度和測量高度
        setMeasuredDimension(width, height);
    }
}
運行結(jié)果
QQ圖片20170120143405.png
完整代碼就不貼了低散,個人建議自己手動來一遍俯邓,印象會非常的深刻的
如有什么問題,敬請?zhí)岢鋈酆牛指兄x稽鞭!希望越來越好,謝謝引镊!如果喜歡朦蕴,還請點擊start,喜歡支持一下了弟头,謝謝O(∩_∩)O~
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吩抓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子赴恨,更是在濱河造成了極大的恐慌疹娶,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伦连,死亡現(xiàn)場離奇詭異雨饺,居然都是意外死亡,警方通過查閱死者的電腦和手機惑淳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門沛膳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汛聚,你說我怎么就攤上這事锹安。” “怎么了倚舀?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵叹哭,是天一觀的道長。 經(jīng)常有香客問我痕貌,道長风罩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任舵稠,我火速辦了婚禮超升,結(jié)果婚禮上入宦,老公的妹妹穿的比我還像新娘。我一直安慰自己室琢,他們只是感情好乾闰,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盈滴,像睡著了一般涯肩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巢钓,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天病苗,我揣著相機與錄音,去河邊找鬼症汹。 笑死硫朦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的背镇。 我是一名探鬼主播咬展,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芽世!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诡壁,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤济瓢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妹卿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旺矾,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年夺克,在試婚紗的時候發(fā)現(xiàn)自己被綠了箕宙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡铺纽,死狀恐怖柬帕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狡门,我是刑警寧澤陷寝,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站其馏,受9級特大地震影響凤跑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜叛复,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一仔引、第九天 我趴在偏房一處隱蔽的房頂上張望扔仓。 院中可真熱鬧,春花似錦咖耘、人聲如沸翘簇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缘揪。三九已至,卻和暖如春义桂,著一層夾襖步出監(jiān)牢的瞬間找筝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工慷吊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留袖裕,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓溉瓶,卻偏偏與公主長得像急鳄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子堰酿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容