Android 自定義View學習(十)——View的測量方法onMeasure()學習

學習資料


在整個系列的第一篇Android自定義View學習(一)——準備中急凰,簡單學習來了測量涉及到的onMeasure()方法我碟,本篇進行補充學習竹观。由于目前水平還很低,測量過程涉及到的源碼沒有能力去分析妻献,只是記錄一下學習怎么調用方法喇辽,很多地方都是"知其然而不知其所以然",不能對調用的方法本身原理進行說明姚炕。愛哥是從源碼進行分析摊欠,寫的非常好,可以去深入學習愛哥的博客


1. onMeasure() 測量方法 <p>

在之前的學習中柱宦,很多地方都用到了這個方法些椒,而且?guī)缀趺看问褂茫a都是不變的掸刊,套路是固定的

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

    int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(300, 300);
    } else if (wSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(300, hSpecSize);
    } else if (hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(wSpecSize, 300);
    }
}

偶爾做出的改動便是300這個數值

這個方法內免糕,主要是對于MeasureSpec.AT_MOST這個模式,也就是針對在布局xml文件控件的寬高寫wrap_content時的處理石窑。無論是MeasureSpec.EXACTLY(match_parent)還是MeasureSpec.AT_MOST,這兩種模式都是根據當前控件以及所在的父控件大小共同來確定的蚓炬。

如果不進行重寫這個方法松逊,直接引用這個當前的自定義控件,無論是wrap_content還是match_parent都會沾滿全屏肯夏,原因下面嘗試分析


1.1 嘗試分析原因<p>

UI界面架構圖

在一個Activity中经宏,在onCreate()方法中,都會調用setContentView(@LayoutRes int layoutResID)這樣一個方法熄捍,layoutResId就是Activityxml布局文件烛恤。

例如母怜,在MainActivity中余耽,setContentView(R.layout.main_activity)。在R.layout.main_activity中苹熏,最外層的布局往往會是一個LinearLayout(或者RelativeLayout)碟贾。之前認為這個最外層的布局就是一個MainActivity的根布局栓始,而對應的LinearLayout(或者RelativeLayout)便是父容器判没。但根據上面的圖可以知道,MainActivity的最外層布局是添加到了ContentView中据途,這個ContentView可以通過LinearLayout.getParent()獲得干发,返回的結果是一個idandroid:id="@android:id/content"FramLayout朱巨,這個android:id="@android:id/content才是Activity的根布局,R.layout.main_activity包含在其中

除了ContentView外枉长,手機屏幕上展示的還有ContentView上面的TitleView冀续,TitleView便是ActionBar或者5.0ToolBar。也就是說必峰,屏幕真正可見的根ViewDecorView洪唐。

每個Activity都包含有一個Window對象,通常是PhoneWindow吼蚁。當ActivityonCreate()方法中調用了setContentView()方法后凭需,ActivityManagerService會回調onResume()方法,系統(tǒng)會把整個DecorView添加進PhoneWindow


上面大概敘述了一下UI界面架構圖的關系。DecorView的大小粒蜈,無論什么時候顺献,默認都是全屏的。而DecorVew包含兩部分枯怖,TitleViewContentView滚澜,除去TitleView,屏幕可見的就是ContentView嫁怀。ContentView包含了Activityxml布局文件中最外層Layout设捐,自定義View則是在這個Layout中。而整個Activity的繪制過程是從最外層的DecorView開始的塘淑,由外向內萝招。最終確定自定義View的大小,需要根據由MeasureSpec.getMode(widthMeasureSpec)得到的SpecMode當做判斷依據來進行確定

  • AT_MOST 最大值模式存捺,
    對應 LayoutParams.WARP_CONTENT槐沼。由父容器指定了一個可用大小SpecSize,自定義View的大小不能超過這個SpecSize值捌治,而這個SpecSize也不會超過窗口的值
  • EXACTLY 精準模式
    對應 LayoutParams.MATCH_PARENT或者固定大小值岗钩。父容器已經檢測出View所需大小的值SpecSize,最終大小也就是窗口的大小或者固定值肖油。

一開始兼吓,很不理解warp_content,怎么就對應的是最大值模式森枪,而平常經常用到這個模式的TextView或者Button中视搏,理解為包裹內容。那是因為在TextView中县袱,都已經做了針對處理浑娜,之后使得這個模式才具備包裹內容的特性 。這個wrap_content模式本身并不是已經具備包裹內容的特性


如果不對上面的onMeasure()方法進行重寫式散,無論是warp_content還是match_parent筋遭,SpecSzie的大小都是窗口的大小,也就是自定義View所在的布局大小暴拄,而布局的寬高一般默認是match_parent漓滔,顯示在屏幕上就是DecorView中的ContentView的大小。

重寫了onMeasure()方法揍移,在AT_MOST也就是wrap_content時次和,確定了300,也就是父容器給指定了SpecSize300px


不曉得整個敘述清不清楚那伐,回頭以后自己還能不能看明白踏施。石蔗。。

雖然指定了wrap_content的大小畅形,但還會有問題养距,padding并未處理


2. 處理Padding <p>

先根據上面的onMeasure()方法,創(chuàng)建一個針對可以處理warp_content過的PaddingView

代碼很簡單日熬,就是繪制一個Bitmap棍厌,以Bitmap的寬高作為wrap_content最大值

public class PaddingView extends View {
    private Paint mPaint;

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

    /**
     * 初始化
     */
    private void init() {
        //畫筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.parseColor("#FF4081"));
    }

    /**
     * 繪制
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪制底色 輔助用
        canvas.drawColor(Color.CYAN);
        //繪制Bitmap
        final float left = 0;
        final float top = 0;
        canvas.drawBitmap(mBitmap, left, top, null);
    }

    /**
     * 測量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        
        int resultWidth = wSpecSize;
        int resultHeight = hSpecSize;

        if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
            resultWidth = mBitmap.getWidth();
            resultHeight = mBitmap.getHeight();
        } else if (wSpecMode == MeasureSpec.AT_MOST) {
            resultWidth = mBitmap.getWidth();
            resultHeight = hSpecSize;
        } else if (hSpecMode == MeasureSpec.AT_MOST) {
            resultWidth = wSpecSize;
            resultHeight = mBitmap.getHeight();
        }
        // 取Bitmap寬高和窗口寬高的較小的一個
        resultWidth = Math.min(resultWidth, wSpecSize);
        resultHeight = Math.min(resultHeight, hSpecSize);
        setMeasuredDimension(resultWidth, resultHeight);
    }
}

布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.szlk.customview.custom.PaddingView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />

</RelativeLayout>
以Bitmap的寬高作為wrap_content

測試的圖片有點巧合,寬度和手機屏幕差不多

簡單修改代碼竖席, 在布局文件加入android:padding="50dp"耘纱,發(fā)現并沒有效果。因為測量的時候毕荐,并沒有加入padding的大小的考慮束析。

onMeasure()onDraw()進行簡單修改:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    int resultWidth = wSpecSize;
    int resultHeight = hSpecSize;

    if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
        resultWidth = mBitmap.getWidth() + getPaddingLeft() + getPaddingRight();
        resultHeight = mBitmap.getHeight() + getPaddingTop() + getPaddingBottom();
    } else if (wSpecMode == MeasureSpec.AT_MOST) {
        resultWidth =  mBitmap.getWidth() + getPaddingLeft() + getPaddingRight();
        resultHeight = hSpecSize;
    } else if (hSpecMode == MeasureSpec.AT_MOST) {
        resultWidth = wSpecSize;
        resultHeight = mBitmap.getHeight() + getPaddingTop() + getPaddingBottom();
    }
     // 取Bitmap寬高和窗口寬高的較小的一個
    resultWidth = Math.min(resultWidth, wSpecSize);
    resultHeight = Math.min(resultHeight, hSpecSize);
    setMeasuredDimension(resultWidth, resultHeight);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //繪制底色 輔助用
    canvas.drawColor(Color.CYAN);
    //繪制Bitmap
    final float left = getPaddingLeft();
    final float top = getPaddingTop();
    canvas.drawBitmap(mBitmap, left, top, null);
}
paddig=50dp

padding的效果是有了,可照片太大了憎亚,控件resultWidth = mBitmap.getWidth() + getPaddingLeft() + getPaddingRight()的大小员寇,超過了手機屏幕寬度,導致控件內容顯示不全第美〉妫可以考慮使用Matrix進行優(yōu)化


2.1 使用Matrix進行優(yōu)化 <p>

主要是利用Matrix.setRectToRect()方法,不了解的同學什往,可以去Android 自定義View學習(八)——Matrix知識學習看一下 : )

public class PaddingView extends View {
    private Bitmap mBitmap;
    private Matrix mMatrix ;
    private RectF srcRectF;
    private RectF dstRectF;
    public PaddingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        mMatrix = new Matrix();
        //截取用來顯示的Bitmap有效區(qū)域
        srcRectF = new RectF(0,0,mBitmap.getWidth(),mBitmap.getHeight());
        //顯示Bitmap的底板
        dstRectF = new RectF();
    }



    /**
     * 繪制
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪制底色 輔助用
        canvas.drawColor(Color.CYAN);
        //繪制Bitmap
        final float left = getPaddingLeft();
        final float top = getPaddingTop();
        //設置Bitmap的縮放模式
        mMatrix.setRectToRect(srcRectF,dstRectF, Matrix.ScaleToFit.FILL);
        //利用后乘平移對開始繪制位置進行改變 這里不可以使用setTranslate()
        mMatrix.postTranslate(left,top);
        canvas.drawBitmap(mBitmap, mMatrix, null);
    }

    /**
     * 測量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        //將最終結果寬高設置為窗口大小 這樣省的判斷MeasureSpec.EXACTLY模式的情況
        int resultWidth = wSpecSize;
        int resultHeight = hSpecSize;

        if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
            resultWidth = mBitmap.getWidth() + getPaddingLeft() + getPaddingRight();
            resultHeight = mBitmap.getHeight() + getPaddingTop() + getPaddingBottom();
        } else if (wSpecMode == MeasureSpec.AT_MOST) {
            resultWidth =  mBitmap.getWidth() + getPaddingLeft() + getPaddingRight();
            resultHeight = hSpecSize;
        } else if (hSpecMode == MeasureSpec.AT_MOST) {
            resultWidth = wSpecSize;
            resultHeight = mBitmap.getHeight() + getPaddingTop() + getPaddingBottom();
        }
        //取Bitmap寬高和窗口寬高的較小的一個
        resultWidth = Math.min(resultWidth, wSpecSize);
        resultHeight = Math.min(resultHeight, hSpecSize);
        //確定dstRectF的范圍
        dstRectF.left= dstRectF.top = 0;
        dstRectF.right = resultWidth-getPaddingRight()-getPaddingLeft();
        dstRectF.bottom = resultHeight-getPaddingBottom()-getPaddingTop();
        setMeasuredDimension(resultWidth, resultHeight);
    }
}

Matrix進行優(yōu)化

雖然圖片有些變形扳缕,這樣無論多大的圖片都可以填充滿整個控件。有點類似ImageViewandroid:scaleType="centerCrop"這個屬性恶守,當然想要完全實現這個屬性的特點第献,還需要對srcRectF進行優(yōu)化贡必,截取Bitmap的中間正方形部分來顯示就可以了

Margin外邊距兔港,封裝在LayoutParams內交由父容器統(tǒng)一處理


3. 最后 <p>

主要是學習1.1原因分析那里。理解UI界面架構圖仔拟,這個圖看起來并不復雜衫樊,但涉及的東西卻非常多。算是先了解一下View的工作原理利花,View最復雜的過程就是測量科侈。

本人很菜,有錯誤炒事,請指出

共勉 : )

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末臀栈,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子挠乳,更是在濱河造成了極大的恐慌权薯,老刑警劉巖姑躲,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異盟蚣,居然都是意外死亡黍析,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門屎开,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阐枣,“玉大人,你說我怎么就攤上這事奄抽“剑” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵逞度,是天一觀的道長宪哩。 經常有香客問我,道長第晰,這世上最難降的妖魔是什么锁孟? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮茁瘦,結果婚禮上品抽,老公的妹妹穿的比我還像新娘。我一直安慰自己甜熔,他們只是感情好圆恤,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著腔稀,像睡著了一般盆昙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上焊虏,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天淡喜,我揣著相機與錄音,去河邊找鬼诵闭。 笑死炼团,一個胖子當著我的面吹牛,可吹牛的內容都是我干的疏尿。 我是一名探鬼主播瘟芝,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼褥琐!你這毒婦竟也來了锌俱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤敌呈,失蹤者是張志新(化名)和其女友劉穎贸宏,沒想到半個月后贩汉,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡锚赤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年匹舞,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片线脚。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡赐稽,死狀恐怖,靈堂內的尸體忽然破棺而出浑侥,到底是詐尸還是另有隱情姊舵,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布寓落,位于F島的核電站括丁,受9級特大地震影響,放射性物質發(fā)生泄漏伶选。R本人自食惡果不足惜史飞,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仰税。 院中可真熱鬧构资,春花似錦、人聲如沸陨簇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽河绽。三九已至己单,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耙饰,已是汗流浹背纹笼。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留榔幸,地道東北人允乐。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像削咆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蠢笋,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內容