Android 自定義ViewGroup(一)

自定義viewgroup洲胖,這個(gè)東西可以說(shuō)簡(jiǎn)單也簡(jiǎn)單,說(shuō)復(fù)雜也復(fù)雜。主要是因?yàn)橛玫剿詮?fù)習(xí)了一下沪悲,那就順便做個(gè)筆記。

暫時(shí)只講簡(jiǎn)單的用法

一.重要方法

(1)onMeasure 設(shè)置viewgroup的大小
(2)onLayout 設(shè)置如何擺放子View
(3)generateLayoutParams 設(shè)置LayoutParams

最重要的是前面兩個(gè)方法桩警,所以說(shuō)viewgroup很簡(jiǎn)單可训,你只需要知道在onMeasure 和 onLayout中寫(xiě)什么內(nèi)容就行。

注:這里說(shuō)的自定義viewgroup是值直接繼承ViewGroup捶枢,而不是繼承各種Layout之類已封裝好的ViewGroup握截。

1.確定自己要做的viewgroup是怎么樣的

首先要確認(rèn)自己要做出怎樣的viewgroup,因?yàn)閛nMeasure和onLayout 可以說(shuō)是關(guān)聯(lián)很小的烂叔,他們是配合使用才能出現(xiàn)自己想要的效果谨胞,如果不注意細(xì)節(jié)的話很容易弄錯(cuò),所以要先確定自己想做出來(lái)的viewgroup是怎樣的蒜鸡,才開(kāi)始做胯努。

2.onMeasure

首先要記好自定義onMeasure的流程,他會(huì)先調(diào)用onMeasure再調(diào)用onLayout逢防,而onMeasure會(huì)調(diào)用多次叶沛,這個(gè)以后講。

(1)onMeasure做的事很簡(jiǎn)單忘朝,就是測(cè)量ViewGroup的大小灰署,準(zhǔn)確來(lái)說(shuō)是根據(jù)子View來(lái)測(cè)量viewgroup的大小。所以在onMeasure方法里面一般會(huì)用measureChildren去測(cè)量子view的大小。
(2)onMeasure方法中一般要分兩種情況去測(cè)量溉箕,viewgroup在xml中定義時(shí)晦墙,寬高是不是wrap_content
你想想,如果viewgroup固定寬高或者填充父布局的話肴茄,那實(shí)際中的寬高肯定是你定義的晌畅,但是如果是wrap_content的話,你就需要自己去設(shè)置寬高讓它包裹所有子View寡痰,所以自定義viewgroup的onMeasure中會(huì)分兩種情況去setMeasuredDimension寬高

2.onLayout

設(shè)置完viewgroup的寬高之后抗楔,就要去擺放子view。

(1)擺放子View的規(guī)則是拦坠,設(shè)置這個(gè)view的左上角的點(diǎn)在viewgroup的位置:
child.layout(left, top, right,bottom);
而根據(jù)這個(gè)坐標(biāo)點(diǎn)和寬高谓谦,我們就能在viewgroup中正確的擺放子view
(2)需要注意的是如果子view超出了viewgroup所onMeasure(設(shè)置好大小)的部分贪婉,那部分不會(huì)顯示出來(lái)反粥。
(3)獲取子view的方法View child = getChildAt(i); 得到的view一般是addview時(shí)添加view的順序,但是還有特殊情況疲迂,這個(gè)過(guò)后再解釋才顿。

3.generateLayoutParams

設(shè)置LayoutParams,那么LayoutParams是什么東西尤蒿,一般我們給viewgroup添加view都會(huì)用到LayoutParams郑气。
翻譯過(guò)來(lái)就是布局參數(shù),通俗點(diǎn)說(shuō)就是能獲取到布局一些特定的屬性腰池,比如說(shuō)布局的邊距什么的尾组。反正你正著想,在創(chuàng)建view時(shí)LayoutParams設(shè)置的屬性示弓,在自定義Viewgroup中都能拿到讳侨。

這個(gè)類系統(tǒng)有很多子類,包括如果你牛逼的話你可以依照谷歌的這種做法奏属,可以自定義LayoutParams跨跨,所以具體情況再說(shuō)。

二.demo

逼逼了這么多囱皿,還是應(yīng)該拿個(gè)例子來(lái)說(shuō)勇婴,比如說(shuō)流式布局
流式布局可以用自定義viewgroup來(lái)實(shí)現(xiàn),雖然它也可以用recyclerview來(lái)實(shí)現(xiàn)嘱腥,但是它的性質(zhì)和RelativeLayout這些布局一樣耕渴,應(yīng)該是一個(gè)viewgroup。

我就找了網(wǎng)上一個(gè)來(lái)說(shuō)啊齿兔,因?yàn)槲覒械脤?xiě)算法橱脸。

public class BerFlowLayout extends ViewGroup {

    //存儲(chǔ)所有子View
    private List<List<View>> mAllChildViews = new ArrayList<>();
    //每一行的高度
    private List<Integer> mLineHeight = new ArrayList<>();

    public BerFlowLayout(Context context) {
        super(context);
    }

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

    public BerFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //父控件傳進(jìn)來(lái)的寬度和高度以及對(duì)應(yīng)的測(cè)量模式
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        //如果當(dāng)前ViewGroup的寬高為wrap_content的情況
        int width = 0;//自己測(cè)量的 寬度
        int height = 0;//自己測(cè)量的高度
        //記錄每一行的寬度和高度
        int lineWidth = 0;
        int lineHeight = 0;

        //獲取子view的個(gè)數(shù)
        int childCount = getChildCount();
        for(int i = 0;i < childCount; i ++){
            View child = getChildAt(i);
            //測(cè)量子View的寬和高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            //得到LayoutParams
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            //子View占據(jù)的寬度
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            //子View占據(jù)的高度
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            //換行時(shí)候
            if(lineWidth + childWidth > sizeWidth){
                //對(duì)比得到最大的寬度
                width = Math.max(width, lineWidth);
                //重置lineWidth
                lineWidth = childWidth;
                //記錄行高
                height += lineHeight;
                lineHeight = childHeight;
            }else{//不換行情況
                //疊加行寬
                lineWidth += childWidth;
                //得到最大行高
                lineHeight = Math.max(lineHeight, childHeight);
            }
            //處理最后一個(gè)子View的情況
            if(i == childCount -1){
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }
        }
        //wrap_content
        setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width,
                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mAllChildViews.clear();
        mLineHeight.clear();
        //獲取當(dāng)前ViewGroup的寬度
        int width = getWidth();

        int lineWidth = 0;
        int lineHeight = 0;
        //記錄當(dāng)前行的view
        List<View> lineViews = new ArrayList<View>();
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            //如果需要換行
            if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width) {
                //記錄LineHeight
                mLineHeight.add(lineHeight);
                //記錄當(dāng)前行的Views
                mAllChildViews.add(lineViews);
                //重置行的寬高
                lineWidth = 0;
                lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
                //重置view的集合
                lineViews = new ArrayList();
            }
            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
            lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);
            lineViews.add(child);
        }
        //處理最后一行
        mLineHeight.add(lineHeight);
        mAllChildViews.add(lineViews);

        //設(shè)置子View的位置
        int left = 0;
        int top = 0;
        //獲取行數(shù)
        int lineCount = mAllChildViews.size();
        for (int i = 0; i < lineCount; i++) {
            //當(dāng)前行的views和高度
            lineViews = mAllChildViews.get(i);
            lineHeight = mLineHeight.get(i);
            for (int j = 0; j < lineViews.size(); j++) {
                View child = lineViews.get(j);
                //判斷是否顯示
                if (child.getVisibility() == View.GONE) {
                    continue;
                }
                MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                int cLeft = left + lp.leftMargin;
                int cTop = top + lp.topMargin;
                int cRight = cLeft + child.getMeasuredWidth();
                int cBottom = cTop + child.getMeasuredHeight();
                //進(jìn)行子View進(jìn)行布局
                child.layout(cLeft, cTop, cRight, cBottom);
                left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            }
            left = 0;
            top += lineHeight;
        }
    }

    /**
     * 與當(dāng)前ViewGroup對(duì)應(yīng)的LayoutParams
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

}

1.onMeasure

先看onMeasure豌蟋,看看它怎么測(cè)量整體父布局的主到。這里循環(huán)處理子view泼差,先獲取到子view的大小

            //子View占據(jù)的寬度
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            //子View占據(jù)的高度
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

然后判斷換不換行徘层,如果這個(gè)子view的寬度加上前面累計(jì)起來(lái)的比父布局的寬度寬组砚,那就加一行吻商。

其實(shí)這里的onMeasure意圖很容易看懂,它的作用就是根據(jù)子view來(lái)決定高度糟红,所以為什么我之前說(shuō)要先弄清楚你想做怎么樣的效果艾帐,比如這里,我想做的效果就是流式布局的效果盆偿,那這個(gè)布局的高度肯定是根據(jù)有多少行來(lái)動(dòng)態(tài)決定的吧柒爸,而這里的計(jì)算就是覺(jué)得這個(gè)高度的過(guò)程。

1.onLayout

這個(gè)他這里寫(xiě)得有點(diǎn)麻煩事扭,應(yīng)該是可以再縮短一些的捎稚。

如果累加的寬度+當(dāng)前子view的寬度+間距 > 一行的寬度,則換行

if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width) {
}

換行就設(shè)置累計(jì)寬度為0: lineWidth = 0;
mAllChildViews就是它存儲(chǔ)的第一個(gè)裝view的二維數(shù)組求橄。

然后再對(duì)每一行進(jìn)行操作 for (int i = 0; i < lineCount; i++) {...}今野,然后對(duì)left 和top 進(jìn)行疊加操作。

其實(shí)我覺(jué)得這里可以在最上面的循環(huán)中就直接child.layout對(duì)子View進(jìn)行布局罐农,不用兩次循環(huán)条霜。他這里的思路是第一次大循環(huán)來(lái)獲取行數(shù)并保存二維數(shù)組,第二次大循環(huán)再設(shè)置子view位置涵亏。

3.MarginLayoutParams

這里寫(xiě)的

/**
     * 與當(dāng)前ViewGroup對(duì)應(yīng)的LayoutParams
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

是為了設(shè)置子view和子view間通過(guò)margin設(shè)置的間距宰睡。

4.調(diào)用

如果在xml中寫(xiě)子布局,可以直接用气筋,這時(shí)設(shè)置generateLayoutParams會(huì)默認(rèn)調(diào)用3個(gè)種的這個(gè)方法拆内,你不用去關(guān)系LayoutParams。

@Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

但是如果是動(dòng)態(tài)去添加view宠默,你就需要自己去寫(xiě)MarginLayoutParams矛纹,那么可以這樣寫(xiě)。

                ViewGroup.LayoutParams lp = new ViewGroup.
                        LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(lp);
                mlp.setMargins(10,10,10,10);

                textView.setLayoutParams(mlp);

三.總結(jié)

最后做個(gè)小結(jié)吧光稼。你可以看成自定義ViewGroup不難或南,它就要求你會(huì)用兩個(gè)方法去測(cè)量和擺放,難的是什么呢艾君?難的是你要怎么去寫(xiě)算法來(lái)完成你這個(gè)viewgroup的實(shí)現(xiàn)采够,也就是兩個(gè)方法中具體的代碼實(shí)現(xiàn),還有就是這兩個(gè)方法連起來(lái)的效果和與LayoutParams配合的效果冰垄,主要是onMeasure和onLayout的配合蹬癌,要非常的注意細(xì)節(jié),比如你在onLayout設(shè)置間距,但是在onMeasure沒(méi)有去開(kāi)辟這個(gè)間距所需要的空間逝薪,那就會(huì)出問(wèn)題隅要,這種寫(xiě)多就會(huì)清楚了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末董济,一起剝皮案震驚了整個(gè)濱河市步清,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌虏肾,老刑警劉巖廓啊,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異封豪,居然都是意外死亡谴轮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)吹埠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)第步,“玉大人,你說(shuō)我怎么就攤上這事缘琅〈菩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵胯杭,是天一觀的道長(zhǎng)驯杜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)做个,這世上最難降的妖魔是什么鸽心? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮居暖,結(jié)果婚禮上顽频,老公的妹妹穿的比我還像新娘。我一直安慰自己太闺,他們只是感情好糯景,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著省骂,像睡著了一般蟀淮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钞澳,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天怠惶,我揣著相機(jī)與錄音,去河邊找鬼轧粟。 笑死策治,一個(gè)胖子當(dāng)著我的面吹牛脓魏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播通惫,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼茂翔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了履腋?” 一聲冷哼從身側(cè)響起珊燎,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎府树,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體料按,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奄侠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了载矿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垄潮。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闷盔,靈堂內(nèi)的尸體忽然破棺而出弯洗,到底是詐尸還是另有隱情,我是刑警寧澤逢勾,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布牡整,位于F島的核電站,受9級(jí)特大地震影響溺拱,放射性物質(zhì)發(fā)生泄漏逃贝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一迫摔、第九天 我趴在偏房一處隱蔽的房頂上張望沐扳。 院中可真熱鬧,春花似錦句占、人聲如沸沪摄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杨拐。三九已至,卻和暖如春擂啥,著一層夾襖步出監(jiān)牢的瞬間戏阅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工啤它, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奕筐,地道東北人舱痘。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像离赫,于是被迫代替她去往敵國(guó)和親芭逝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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