手把手教你寫一個自定義 ViewGroup

手把手教你寫一個自定義 ViewGroup

關鍵字:kotlin、custom甘邀、ViewGroup

custom view group 書寫流程
1. override onLayout

首先琅攘,最直覺的做法,就是寫下 public class CustomViewGroup extends ViewGroup {} 松邪。對于 kotlin 呢坞琴,就是 class CustomViewGroup : ViewGroup {} 。然后逗抑,根據(jù)編譯器的提示剧辐,我們?nèi)崿F(xiàn) onLayout 方法。那么這個 onLayout 的作用是什么呢邮府?

簡單翻閱源碼荧关,發(fā)現(xiàn)在 View 的源碼下找到了答案:

public void layout(int l, int t, int r, int b) {
    // ...
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        // ...    
        onLayout(changed, l, t, r, b);
        // ...     
    }
    // ...

這段代碼告訴我們,onLayout 是在 layout 方法中調(diào)用的褂傀。從命名而角度來看忍啤,on 表示在什么什么時候。類似的紊服,我們寫自定義 view 的時候經(jīng)常寫的 onMeasure 也是在 measure 方法中調(diào)用的檀轨。那么聰明的讀者胸竞,請你告訴我,我們寫 activity 的時候?qū)懙?onCreate 方法参萄,是不是也是在 Activity 的 create 中調(diào)用的呢卫枝?

哈哈,當然不是了(請放下你們手中的西瓜刀)讹挎。onCreate 是在 Activity 中的 performCreate 方法中調(diào)用的校赤。不過,原理是類似的筒溃,onXxx 都是在實際做 xxx 的時候被調(diào)用马篮。而這些個 xxx 方法基本都是 final 的。

書歸正傳怜奖。這個 onLayout 的用途是什么呢浑测?onLayout 函數(shù)是在 layout 函數(shù)中調(diào)用的,自然就是負責處理布局相關的邏輯歪玲∏ㄑ耄看一下函數(shù)簽名:

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int)

四個參數(shù) left、top滥崩、right岖圈、bottom 相當于給我們畫了一個邊界,我們就在這個畫布上作畫钙皮,不能超出蜂科。之后我們在 onLayout 中的邏輯就是,如何排布我們的 CustomViewGroup 的 children 了短条。這個需要根據(jù)業(yè)務來导匣。如果僅僅為了測試的話,我們可以簡單的讓他們一個接一個排列不留空茸时。
在我們寫完了 onLayout 之后逐抑,運行項目。不出意外的話屹蚊,我們是看不到任何東西的厕氨。為什么?因為我們還少了非常關鍵的一個環(huán)節(jié):測量汹粤。

2. override onMeasure

編譯器只告訴了我們命斧,需要 override onLayout,但是沒有告訴我們需要 overrid onMeasure嘱兼。仔細想想国葬,在顯示生活中,如果我們想有一個 2d 的紙張來畫子 view 的話,那么汇四,我們只知道我的子 view 的排列方式還不夠接奈,必須要先告訴別人,我們的紙張的大小通孽,這樣我們才能作畫序宦。onMeasure 就做了向 CustomViewGroup 的 parent 描述我們需要的大小的工作。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

    val width = getSize(defaultSize = 300, measureSpec = widthMeasureSpec)
    val height = getSize(defaultSize = 300, measureSpec = heightMeasureSpec)

    measureChildren(widthMeasureSpec, heightMeasureSpec)

    setMeasuredDimension(width, height)
}

其中背苦,measureChildren(widthMeasureSpec, heightMeasureSpec) 完成了遞歸地 measure 子 view 的工作互捌。最后一句 setMeasuredDimension(width, height) 非常重要,每一個 onMeasure 方法都要去顯式地調(diào)用它行剂。

3. 上代碼
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

    val width = getSize(defaultSize = 300, measureSpec = widthMeasureSpec)
    val height = getSize(defaultSize = 300, measureSpec = heightMeasureSpec)

    measureChildren(widthMeasureSpec, heightMeasureSpec)

    setMeasuredDimension(width, height)
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    var currentTop = 0
    var currentLeft = 0
    var currentRowHeight = 0
    for (i in 0..childCount) {
        val child = getChildAt(i) ?: continue
        val childWidth = child.measuredWidth
        val childHeight = child.measuredHeight

        // child 的 width or height 超過了 parent 的 width 和 height秕噪,直接丟棄
        if (childWidth > measuredWidth
                || childHeight > measuredHeight) {
            continue
        }

        if (currentTop + childHeight > measuredHeight) {
            continue
        }

        if (currentLeft + childWidth > measuredWidth) {
            currentLeft = 0
            currentTop += currentRowHeight
            currentRowHeight = 0
        }

        if (currentLeft + childWidth <= measuredWidth
                && currentTop + childHeight <= measuredHeight) {

            child.layout(currentLeft, currentTop, currentLeft + childWidth, currentTop + childHeight)

            currentLeft += childWidth
            if (childHeight > currentRowHeight) {
                currentRowHeight = childHeight
            }
        }

    }
}


private fun getSize(defaultSize: Int, measureSpec: Int): Int {

    val size = MeasureSpec.getSize(measureSpec)
    val mode = MeasureSpec.getMode(measureSpec)

    when (mode) {
        MeasureSpec.EXACTLY -> {
            return size
        }

        MeasureSpec.AT_MOST -> {
            return Math.min(size, defaultSize)
        }

        MeasureSpec.UNSPECIFIED -> {
            return defaultSize
        }

        else -> {
            return defaultSize
        }
    }
}

代碼中,layout child 的關鍵的句子就是:

child.layout(currentLeft, currentTop, currentLeft + childWidth, currentTop + childHeight)
4. 小結(jié)

我們寫 CustomViewGroup 的時候厚宰,需要一次做這幾個事情:

  1. override onMeasure
    1. measureChildren(widthMeasureSpec, heightMeasureSpec)
    2. setMeasuredDimension(width, height)
  2. override onLayout
    1. 理清你需要的業(yè)務邏輯
    2. child.layout(currentLeft, currentTop, currentLeft + childWidth, currentTop + childHeight)
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腌巾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子铲觉,更是在濱河造成了極大的恐慌壤躲,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件备燃,死亡現(xiàn)場離奇詭異,居然都是意外死亡凌唬,警方通過查閱死者的電腦和手機并齐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來客税,“玉大人况褪,你說我怎么就攤上這事「埽” “怎么了测垛?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秧均。 經(jīng)常有香客問我食侮,道長,這世上最難降的妖魔是什么目胡? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任锯七,我火速辦了婚禮,結(jié)果婚禮上誉己,老公的妹妹穿的比我還像新娘眉尸。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布噪猾。 她就那樣靜靜地躺著霉祸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪袱蜡。 梳的紋絲不亂的頭發(fā)上丝蹭,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音戒劫,去河邊找鬼半夷。 笑死,一個胖子當著我的面吹牛迅细,可吹牛的內(nèi)容都是我干的巫橄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茵典,長吁一口氣:“原來是場噩夢啊……” “哼湘换!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起统阿,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤彩倚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扶平,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帆离,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年结澄,在試婚紗的時候發(fā)現(xiàn)自己被綠了哥谷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡麻献,死狀恐怖们妥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情勉吻,我是刑警寧澤监婶,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站齿桃,受9級特大地震影響惑惶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜短纵,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一集惋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧踩娘,春花似錦刮刑、人聲如沸喉祭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泛烙。三九已至,卻和暖如春翘紊,著一層夾襖步出監(jiān)牢的瞬間蔽氨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工帆疟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鹉究,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓踪宠,卻偏偏與公主長得像自赔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子柳琢,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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