Android 自定義ViewGroup 流式布局

自定義View的基本方法

自定義View的最基本的三個(gè)方法分別是: onMeasure()、onLayout()、onDraw();
View在Activity中顯示出來(lái),要經(jīng)歷測(cè)量、布局和繪制三個(gè)步驟,分別對(duì)應(yīng)三個(gè)動(dòng)作:measure、layout和draw。

  • 測(cè)量:onMeasure()決定View的大??;
  • 布局:onLayout()決定View在ViewGroup中的位置;
  • 繪制:onDraw()決定繪制這個(gè)View。

自定義控件分類

  • 自定義View: 只需要重寫onMeasure()和onDraw()
  • 自定義ViewGroup: 則只需要重寫onMeasure()和onLayout()

自定義View基礎(chǔ)

View的分類

視圖View主要分為兩類

類別 解釋 特點(diǎn)
單一視圖 即一個(gè)View,如TextView 不包含子View
視圖組 即多個(gè)View組成的ViewGroup,如LinearLayout 包含子View

View類簡(jiǎn)介

  • View類是Android中各種組件的基類,如View是ViewGroup基類
  • View表現(xiàn)為顯示在屏幕上的各種視圖

Android中的UI組件都由View、ViewGroup組成。

  • View的構(gòu)造函數(shù):共有4個(gè)
// 如果View是在Java代碼里面new的,則調(diào)用第一個(gè)構(gòu)造函數(shù)
 public CarsonView(Context context) {
        super(context);
    }

// 如果View是在.xml里聲明的,則調(diào)用第二個(gè)構(gòu)造函數(shù)
// 自定義屬性是從AttributeSet參數(shù)傳進(jìn)來(lái)的
    public  CarsonView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

// 不會(huì)自動(dòng)調(diào)用
// 一般是在第二個(gè)構(gòu)造函數(shù)里主動(dòng)調(diào)用
// 如View有style屬性時(shí)
    public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //API21之后才使用
    // 不會(huì)自動(dòng)調(diào)用
    // 一般是在第二個(gè)構(gòu)造函數(shù)里主動(dòng)調(diào)用
    // 如View有style屬性時(shí)
    public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

AttributeSet與自定義屬性

系統(tǒng)自帶的View可以在xml中配置屬性,對(duì)于寫的好的自定義View同樣可以在xml中配置屬性,為了使自定義的View的屬性可以在xml中配置,需要以下4個(gè)步驟:

  1. 通過<declare-styleable>為自定義View添加屬性
  2. 在xml中為相應(yīng)的屬性聲明屬性值
  3. 在運(yùn)行時(shí)(一般為構(gòu)造函數(shù))獲取屬性值
  4. 將獲取到的屬性值應(yīng)用到View

代碼如下

package com.zthx.deviceui.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import com.zthx.deviceui.R;

import java.util.ArrayList;
import java.util.List;

/**
 * 類名稱: FlowLayout.java<p>
 * 類描述: 自定義流式ViewGroup<p>
 * 
 *
 * @author darryrzhong
 * @since 2019/12/11 9:37
 */
public class FlowLayout extends ViewGroup {

    /**
     * 每一行的子view
     * */
    private List<View> lineView;

    /**
     * 整個(gè)布局的所有行
     * */
    private List<List<View>> lines;

    /**
     * 每一行的高度
     * */
    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 init(){
        lineView = new ArrayList<>();
        lines = new ArrayList<>();
        heights = new ArrayList<>();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //測(cè)量自身
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取限制信息的 mode和 size
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //記錄當(dāng)前行的寬度個(gè)高度

        //寬度是當(dāng)前行子view的寬度之和
        int lineWidth = 0;
        //高度是當(dāng)前行所有子view中高度的最大值
        int lineHeight = 0;

        //整個(gè)流式布局的寬度和高度

        //所有行中寬度的最大值
        int flowLayoutWidth = 0;
        //所有行的高度疊加
        int flowLayoutHeight = 0;

        //初始化參數(shù)列表
        init();

        //遍歷所有的子view,對(duì)子view進(jìn)行measure,分配到具體的行
        int countView = getChildCount();
        for (int i = 0;i<countView;i++){
            View child = this.getChildAt(i);
            //測(cè)量子View 獲取到當(dāng)前子View的測(cè)量的寬度 /高度
            measureChild(child,widthMeasureSpec,heightMeasureSpec);
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //看下當(dāng)前的行的剩余的寬度是否可以容納下一個(gè)子View
            //如果放不下,換行 保存當(dāng)前行的所有子View,累加行高,
            //將當(dāng)前行的 高度 和寬度 置零
            if (lineWidth+childWidth > widthSize){
                //換行
              lines.add(lineView);
              lineView = new ArrayList<>();
              flowLayoutWidth = Math.max(flowLayoutWidth,lineWidth);
              flowLayoutHeight +=lineHeight;
              heights.add(lineHeight);
              lineHeight = 0;
              lineWidth = 0;
            }
            lineView.add(child);
            lineWidth +=childWidth;
            lineHeight = Math.max(lineHeight,childHeight);

        }

        //FlowLayout最終寬高
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY? widthSize:flowLayoutWidth,
                heightMode == MeasureSpec.EXACTLY?heightSize:flowLayoutHeight);

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int lineCount = lines.size();

         int currX = 0;
         int currY = 0;

         //所有的子view,一行一行的布局
         for (int i = 0;i<lineCount;i++){
             //取出一行
             List<View> lineViews = lines.get(i);
             //取出這一行的高度值
             int lineHeight = heights.get(i);
             int lineSize = lineViews.size();
             //遍歷當(dāng)前行的子View
             for (int j=0;j<lineSize;j++){
                 View child = lineViews.get(j);
                 int left = currX;
                 int top = currY;
                 int right = left+child.getMeasuredWidth();
                 int bottom = top+child.getMeasuredHeight();
                 child.layout(left,top,right,bottom);
                 //確定下一個(gè)view的left
                 currX +=child.getMeasuredWidth();
             }
             currY += lineHeight;
             currX = 0;
         }
    }


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

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

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

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return super.checkLayoutParams(p) && p instanceof LayoutParams;
    }

    public static class LayoutParams extends MarginLayoutParams{

        public int gravity = -1;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
            try {
                gravity = array.getInt(R.styleable.FlowLayout_android_gravity,-1);
            }finally {
                array.recycle();
            }

        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        @Override
        public String toString() {
            return "LayoutParams{" +
                    "gravity=" + gravity +
                    ", bottomMargin=" + bottomMargin +
                    ", leftMargin=" + leftMargin +
                    ", rightMargin=" + rightMargin +
                    ", topMargin=" + topMargin +
                    ", height=" + height +
                    ", layoutAnimationParameters=" + layoutAnimationParameters +
                    ", width=" + width +
                    '}';
        }
    }

}

布局如下:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools">

    <com.zthx.deviceui.view.FlowLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="55dp"
            android:text="view1" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="第二個(gè)view" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="三個(gè)三個(gè)view"
            android:layout_gravity="bottom"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="生活不止眼前的茍且,還有詩(shī)和遠(yuǎn)方" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="90dp"
            android:text="哈哈哈" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="不知道是誰(shuí)" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello hi ..." />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="啦啦啦啦啦" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="85dp"
            android:text="滴答滴答"
            android:layout_gravity="bottom"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="生活不止眼前的茍且,還有詩(shī)和遠(yuǎn)方" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="哈哈哈" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="啦啦啦啦啦" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="45dp"
            android:text="Hello hi ..." />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="他是誰(shuí),我是誰(shuí)" />

    </com.zthx.deviceui.view.FlowLayout>
</ScrollView>

效果如下

歡迎關(guān)注作者darryrzhong,更多干貨等你來(lái)拿喲.

請(qǐng)賞個(gè)小紅心!因?yàn)槟愕墓膭?lì)是我寫作的最大動(dòng)力!

更多精彩文章請(qǐng)關(guān)注

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容