Android ViewGroup的自定義

案例:自定義一個(gè)ViewGroup谱姓,將子view進(jìn)行流式布局


image.png

流程:

  1. 測量子view的大小 → onMeasure
    測量每一個(gè)子view的寬和高瘟斜,待擺放的子view的寬度如果小于當(dāng)前行的剩余寬度則
    放進(jìn)入,否則將其擺放到下一行莱褒。
  2. 擺放子view的位置 → onLayout (必須)
    遍歷記錄的每一行所有子view和每一行的高對子view進(jìn)行擺放
  3. 對子view進(jìn)行繪制 → onDraw
    當(dāng)前案例無需重構(gòu)此方法
    布局如下圖


    image.png

public class FlowLayout extends ViewGroup {

    private int mHorizontalSpacing = dp2px(16); //每個(gè)item橫向間距
    private int mVerticalSpacing = dp2px(8); //每個(gè)item橫向間距

    private List<List<View>> allLines;// 記錄所有的行肚吏,一行一行的存儲
    private List<Integer> heights; // 記錄每一行的行高

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

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

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

    private void initMeasureParams() {
        allLines = new ArrayList<>();
        heights = new ArrayList<>();
    }

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

        initMeasureParams(); //此方法不能在構(gòu)造函數(shù)調(diào)用,生命周期

        int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //獲取當(dāng)前類控件的寬度
        //獲取內(nèi)間距以便通過getChildMeasureSpec方法獲取子view的度量規(guī)則
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int childCount = getChildCount(); //或者子view的數(shù)量
        int lineWidthUsed = 0;  //當(dāng)前行已占用了的寬度
        int lineHeight = 0;     //當(dāng)前行占用最大的高度

        List<View> lineViews = new ArrayList<>(); //記錄每一行所擺放的控件
        int parentNeededWidth = 0;  //當(dāng)前類控件至少所需要的寬度
        int parentNeededHeight = 0; //當(dāng)前類控件至少所需要的高度

        //遍歷子view進(jìn)行度量荤崇,以便能獲取度量之后的尺寸大小
        // childView.getMeasuredWidth()是取度量之后的大小拌屏,childView.getWidth()是取onLayout之后的大小,這里需要前者
        for (int i = 0; i < childCount; i++) {
            //獲取子View术荤;
            View childView = getChildAt(i);
            LayoutParams childLP = childView.getLayoutParams();
            //獲取子view的度量規(guī)則
            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,paddingLeft + paddingRight, childLP.width);
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);
            //度量子view倚喂、設(shè)置子View的尺寸
            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

            //確定是否換行
            int childMeasureWidth = childView.getMeasuredWidth();
            int childMeasureHeight = childView.getMeasuredHeight();
            if (childMeasureWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
                allLines.add(lineViews);
                heights.add(lineHeight);

                parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
                lineViews = new ArrayList<>();
                lineWidthUsed = 0;
                lineHeight = 0;

            }

            lineViews.add(childView);
            lineWidthUsed = lineWidthUsed + childMeasureWidth + mHorizontalSpacing;
            lineHeight = Math.max(lineHeight, childMeasureHeight);

            //最后一個(gè)子view所在的那一行需要手動(dòng)加入
            if(i == childCount - 1){
                allLines.add(lineViews);
                heights.add(lineHeight);
                parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);
            }
            
        }

        //確定流式布局自身的寬高
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec);

        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;
        setMeasuredDimension(realWidth,realHeight); //度量之后存儲該類控件自身寬高值,否則將觸發(fā)測量時(shí)出現(xiàn)異常
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int lineCount = allLines.size();
        int curX = getPaddingLeft(); //設(shè)置子view左邊的起始位置
        int curT = getPaddingTop();  //設(shè)置子view頂部的起始位置

        for (int i = 0; i < lineCount; i++) {
            List<View> lineViews = allLines.get(i);
            int lineHeight = heights.get(i);
            for (int j = 0; j < lineViews.size(); j++) {
                View view = lineViews.get(j);
                int left = curX;
                int top = curT;
                int right = left + view.getMeasuredWidth();
                int bottom = top + view.getMeasuredHeight();
                view.layout(left, top, right, bottom); //參數(shù)值是子view距離當(dāng)前控件的四個(gè)距離
                curX = right + mHorizontalSpacing;
            }
            curT = curT + lineHeight + mVerticalSpacing;
            curX = getPaddingLeft();
        }
    }

    public static int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
    }


}

總結(jié)知識點(diǎn):

度量規(guī)則MeasureSpec:
是view的內(nèi)部類瓣戚,里面定義了三個(gè)int類型的變量來表示三種模式:據(jù)傳入的SpecSize來確定SpecMode, 如果傳入的SpecSize是Match_parent或者是精確值端圈,SpecMode是EXACTY焦读,如果傳入的是Wrap_content的話,SpecMode是AT_MOST, 若不對VIew的大小做出限制的話舱权,比如listview吨灭、recycleview這些的SpecMode就是UNSPECIFIED;widthMeasureSpec和heightMeasureSpec是32位的int類型刑巧,其中包括了模式和尺寸大小,前兩位表示模式无畔,后30位表示尺寸大小啊楚。

//通過度量規(guī)則獲取尺寸大小
MeasureSpec.getSize(MeasureSpec)
//獲取子view的度量規(guī)則
getChildMeasureSpec(當(dāng)前view的度量規(guī)則,paddingLeft + paddingRight, 子view的LayoutParams的寬高)
//對子view進(jìn)行度量之后才能獲取度量之后的準(zhǔn)確值
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//在度量階段獲取子view的大小要取度量之后的值
int childMeasureWidth = childView.getMeasuredWidth();
// 獲取度量規(guī)則的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//度量之后存儲該類控件自身寬高值(度量自己),否則將觸發(fā)測量時(shí)出現(xiàn)異常
setMeasuredDimension(realWidth,realHeight);

//獲取該view到父view的距離
int left = getLeft();
//對子view進(jìn)行l(wèi)ayout擺放浑彰,方法參數(shù)是子view到父view的四個(gè)距離
view.layout(left, top, right, bottom); 
//MotionEvent中 get()和getRaw()的區(qū)別
//get() :觸摸點(diǎn)相對于其所在組件坐標(biāo)系的坐標(biāo)
//getRaw() :觸摸點(diǎn)相對于屏幕默認(rèn)坐標(biāo)系的坐標(biāo)
1585727817(1).jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恭理,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子郭变,更是在濱河造成了極大的恐慌颜价,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诉濒,死亡現(xiàn)場離奇詭異周伦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)未荒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進(jìn)店門专挪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人片排,你說我怎么就攤上這事寨腔。” “怎么了率寡?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵迫卢,是天一觀的道長。 經(jīng)常有香客問我冶共,道長乾蛤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任比默,我火速辦了婚禮幻捏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘命咐。我一直安慰自己篡九,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布醋奠。 她就那樣靜靜地躺著榛臼,像睡著了一般伊佃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沛善,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天航揉,我揣著相機(jī)與錄音,去河邊找鬼金刁。 笑死帅涂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尤蛮。 我是一名探鬼主播媳友,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼产捞!你這毒婦竟也來了醇锚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤坯临,失蹤者是張志新(化名)和其女友劉穎焊唬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體看靠,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赶促,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挟炬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芳杏。...
    茶點(diǎn)故事閱讀 38,683評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辟宗,靈堂內(nèi)的尸體忽然破棺而出爵赵,到底是詐尸還是另有隱情,我是刑警寧澤泊脐,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布空幻,位于F島的核電站,受9級特大地震影響容客,放射性物質(zhì)發(fā)生泄漏秕铛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一缩挑、第九天 我趴在偏房一處隱蔽的房頂上張望但两。 院中可真熱鬧,春花似錦供置、人聲如沸谨湘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽紧阔。三九已至坊罢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間擅耽,已是汗流浹背活孩。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乖仇,地道東北人憾儒。 一個(gè)月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像乃沙,于是被迫代替她去往敵國和親航夺。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評論 2 349

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