自定義ViewGroup—回顧

自定ViewGroup要比自定義View要復雜一點箱蝠,因為自定義ViewGroup不僅測量自身還要測量子元素和及重寫onLayout()來一次排列子View卖宠。下面這篇文章是關于自定義ViewGroup的一些基本知識巧娱,這些主要內(nèi)容來自《android開發(fā)藝術探索》赌躺,在文章最后又這本書的網(wǎng)上版本。

image

目錄

  • ViewGroup的measure過程
  • onMeasure()函數(shù)
  • onLayout()函數(shù)
  • 對Padding和Margin的處理
  • 在Activity中獲取View的寬高

ViewGroup的measure過程

ViewGroup是一個抽象類徐勃,他沒有重寫View的onMeasure()方法事示。因此并沒有定義具體的測量過程,具體的測量過程交給了他的子類來完成僻肖,比如:LinearLayout肖爵、RelativeLayout等。ViewGroup提供了一個measureChildren的方法來測量子View:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            //測量子元素
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

下面為measureChild()的源碼:

protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    //獲取子元素寬度MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    //獲取子元素高度MeasureSpec        
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
    //子元素進行測量
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

measureChild()方法會先獲取子View的LayoutParams參數(shù)臀脏,然后再通過getChildMeasureSpec()獲取子View的寬高MeasureSpec劝堪,最后將獲取到的MeasureSpec傳遞給view的()方法進行測量。具體的執(zhí)行過程我在《View的繪制流程》這篇文章中介紹過揉稚,這里就不在多少說了秒啦。

onMeasure()方法

因為ViewGroup沒有重寫View的onMeasure方法,我們在自定義的時候集成了ViewGruppo成了View的子類搀玖,因此要寫自己布局的測量過則余境。那我上篇文章《自定義ViewGroup—FlowLayout》中的部分代碼為例:

@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   ...
                          //##1
    //循環(huán)遍歷子View
    for (int i = 0; i < getChildCount(); i++) {
        View childView = getChildAt(i);

        //先測量子View
        measureChild(childView, widthMeasureSpec, widthMeasureSpec);
        ....
        //計算剩余空間
        remaining = widthSize - usedWidth;
                        //##2
        //判斷子view的測量寬度是否大于剩余空間,如果大于則另起一行
        if (childWidth > remaining) {
            //另起一行灌诅,已用寬度應該為零
            usedWidth = 0;
            //添加View
            mLineView = new LineView();
            mLineViewList.add(mLineView);
        }
        mLineView.addView(childView);
        //已用寬度累加
        usedWidth += childWidth;
        mLineView.setTotalWidth(usedWidth);
    }
                     //##3
    for (int i = 0; i < mLineViewList.size(); i++) {
        //總高度=所有行數(shù)相加
        totalHeight += mLineViewList.get(i).mHeight;
    }
               
    //父容器的總高度=上下padding的高度芳来,在最后額外加一個底部marginBottom
    totalHeight += getPaddingTop() + getPaddingBottom() + marginBottom;
                     //##4
    setMeasuredDimension(widthSize, heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight);
}

上面代碼中主要是測量FlowLayout的高度。它是通過遍歷子View(//##1)計算剩余寬度猜拾,再通過子View的寬度和剩余寬度比較來判斷是否換行(//##2)即舌。FlowLayout的高度如果是在warp_cotent模式下高度就為子View的行數(shù)乘上子View的高度(//##3),最后通過setMeasuredDimension()計算View的寬高并保存起來关带。

onLayout()函數(shù)

onLayout()函數(shù)式是ViewGroup的一個抽象函數(shù)侥涵,ViewGroup的子類必須實現(xiàn)該函數(shù)沼撕,用于定義View的擺放規(guī)則。

注:該方法有一個指的探討的問題芜飘,下面onLayout()是被@Override注釋著的务豺,也就是這個方法是復用了View的onLayout()方法。那么問題來了嗦明,java中父類的方法是否能把子類重寫為抽象方法笼沥?這個問題我在好多技術群中向大神請教過,有的說能有的說不能娶牌。后來自己在項目中親自測試奔浅,發(fā)現(xiàn)可以重寫但是不能調(diào)用。如果讀者知道這個問題诗良,歡迎在下方留言汹桦。

@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);

該方法的調(diào)用是在View的layout被調(diào)用,我們可以查看ViewGroup的layout()放知道ViewGroup的layout過程是交給父類完成的鉴裹。

@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        //調(diào)用父類的layout方法
        super.layout(l, t, r, b);
    } else {
         transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

View的layout方法中調(diào)用了onLayout()方法

@SuppressWarnings({"unchecked"})
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);
       ...
    }
  ...
}

對Padding和Margin的處理

Padding的處理比較簡單舞骆,只需要getPaddingXXX()來獲取padding的值,在計算ViewGroup的寬高的時候?qū)⑵浼由霞纯?

paddingLeft = getPaddingLeft();
paddingTop = getPaddingTop();
paddingRight = getPaddingRight();
paddingBottom = getPaddingBottom();
//ViewGroup寬高計算
viewGroupWidth = paddingLeft + viewsWidth + paddingRight;
viewGroupHeight = paddingTop + viewsHeight + paddingBottom;

Margin的處理比較麻煩一點径荔,首先他要先從子View中獲取layoutParams屬性督禽,通過子View的LayoutParams屬性來獲取設置的Margin值。其layoutParams獲取方法為childView.getLayoutParams()总处。要注意下面兩點:

  1. 獲取的要是MarginLayoutParams()類型,記得做類型強轉(zhuǎn)狈惫。
  2. 重新generateLayoutParams(),返回類型為MarginLayoutParams類或他的子類鹦马。否則回報類型轉(zhuǎn)換異常

下面為實現(xiàn)代碼:

@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    //獲取Margin值
    MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
    marginLeft = Math.max(marginLeft, lp.rightMargin);
    marginTop = Math.max(marginTop, lp.topMargin);
    marginRight = Math.max(marginRight, lp.rightMargin);
    marginBottom = lp.bottomMargin;
    
    //計算子View四個坐標位置
    int cLeft = left + marginLeft;
    int cRight = left + childWidth + marginRight;
    int cTop = top + marginTop;
    int cBottom = top + childHeight + marginBottom;
    //設置View的具體位置
    childView.layout(cLeft, cTop, cRight, cBottom);
}
//重寫generateLayoutParams()
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new MarginLayoutParams(getContext(), attrs);
}

在Activity中獲取View的寬高

android中不能再Activity的生命周期onCreate()胧谈、onStart()onResume()生命周期中獲取到View的寬高菠红,這是因為Activity的生命周期和View的測量過程不是同步執(zhí)行的第岖。對于上面的問題有四種解決方案。下面為三種解決方法试溯,第四種方案比較復雜就沒寫出來蔑滓。

onWindowFoucusChanged()中獲取

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    int width = mFlowL_testCustom.getMeasuredWidth();
    int height = mFlowL_testCustom.getMeasuredHeight();
    Log.i(TAG, "onWindowFocusChanged()測量的寬為:" + width + "高為:" + height);
}

通過View的post方法,經(jīng)請求發(fā)送到消息隊列中執(zhí)行遇绞。

mFlowL_testCustom.post(new Runnable() {
    @Override
    public void run() {
        int width = mFlowL_testCustom.getMeasuredWidth();
        int height = mFlowL_testCustom.getMeasuredHeight();
        Log.i(TAG, "mFlowL_testCustom.post()測量的寬為:" + width + "高為:" + height);
    }
});

通過為ViewTreeObserver添加OnGlobalLayoutListener()來實現(xiàn)

ViewTreeObserver treeObserver=mFlowL_testCustom.getViewTreeObserver();
    treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            int width = mFlowL_testCustom.getMeasuredWidth();
            int height = mFlowL_testCustom.getMeasuredHeight();
            Log.i(TAG, "addOnGlobalLayoutListener()測量的寬為:" + width + "高為:" + height);
        }
});

總結(jié)

文章先寫到這里吧键袱!最近一直在堅持每天寫技術筆記,希望能慢慢將這種堅持當成一種習慣摹闽。最后祝所有看到這篇文章的人工作順利蹄咖,工資翻番。

參考

Android開發(fā)藝術探索完結(jié)篇——天道酬勤

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末付鹿,一起剝皮案震驚了整個濱河市澜汤,隨后出現(xiàn)的幾起案子蚜迅,更是在濱河造成了極大的恐慌,老刑警劉巖俊抵,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谁不,死亡現(xiàn)場離奇詭異,居然都是意外死亡徽诲,警方通過查閱死者的電腦和手機刹帕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谎替,“玉大人偷溺,你說我怎么就攤上這事∏幔” “怎么了挫掏?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秩命。 經(jīng)常有香客問我砍濒,道長,這世上最難降的妖魔是什么硫麻? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮樊卓,結(jié)果婚禮上拿愧,老公的妹妹穿的比我還像新娘。我一直安慰自己碌尔,他們只是感情好浇辜,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唾戚,像睡著了一般柳洋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叹坦,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天熊镣,我揣著相機與錄音,去河邊找鬼募书。 笑死绪囱,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的莹捡。 我是一名探鬼主播鬼吵,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼篮赢!你這毒婦竟也來了齿椅?” 一聲冷哼從身側(cè)響起琉挖,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涣脚,沒想到半個月后示辈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡涩澡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年顽耳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妙同。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡射富,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粥帚,到底是詐尸還是另有隱情胰耗,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布芒涡,位于F島的核電站柴灯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏费尽。R本人自食惡果不足惜赠群,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旱幼。 院中可真熱鬧查描,春花似錦、人聲如沸柏卤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缘缚。三九已至勾笆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桥滨,已是汗流浹背窝爪。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留齐媒,地道東北人酸舍。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像里初,于是被迫代替她去往敵國和親啃勉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

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