自定義View 實現(xiàn)流式布局FlowLayout

自定義View 最關注的有三個方法 measure(),layout(),draw();我們?nèi)崿F(xiàn)的時候一般只要重寫他們的模板方法, 即onMeasure(),onLayout,onDraw()。

根據(jù)View的類型 分為自定義View和自定義ViewGroup。自定義View 只需要關注 onDraw()方法,測量和布局是交給父布局處理的,所以自定義ViewGroup必須要實現(xiàn)onMeasure() 和 onLayout()兩個方法

因為我要自定義一個流式布局,所以需要自定義一個ViewGroup去實現(xiàn),先說一下大概流程,最主要的任務是測量也就是onMeasure方法,因為是一個ViewGroup,所以需要先測量所有的子View,根據(jù)子View的寬高決定自己的寬高。這也是一般自定義ViewGroup的流程,即先測量子View然后測量自己,當然也有先測量自己,再根據(jù)自己去測量布局子View的,比如ViewPager。

 onMeasure(){
        1.測量子View
       1.1 遍歷所有的 childView
       1.2 拿到 childView 的 layoutParams 把 childView 需要的尺寸轉化成 MeasureSpec
       1.3 childView.measure(widthMeasureSpec,heightMeasureSpec) 測量childView
       1.4 測量的過程中需要記錄行的最大值和高度的累加值 作為 FlawLayout 的寬和高,  還有換行的判斷
     
   2.測量自己
        根據(jù)測量childView得出的寬高,測量一下自己 
          setMeasureDimension()
    }
    
   onLayout(){
        最好在onMeasure()中記錄每一行的view,和每一行的height
        這樣我們就可以直接遍歷每一行的view 布局了view.layout(l,t,r,b),
    }

代碼實現(xiàn)

public class FlowLayoutView extends ViewGroup {
    public FlowLayoutView(Context context) {
        super(context);
    }

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

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

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

    List<List<View>> allLineViews = new ArrayList<>();
    List<Integer> lineHeights = new ArrayList<>();
    int mHorizontalSpacing = 30, mVerticalSpacing = 30;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        allLineViews.clear();
        lineHeights.clear();
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        int parentNeededHeight = 0;
        int parentNeededWidth = 0;
        int lineWidth = 0;
        int lineHeight = 0;
        //自己的尺寸
        int selfWidth = MeasureSpec.getSize(widthMeasureSpec);
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec);
        int childCount = getChildCount();
        List<View> lineViews = new ArrayList<>();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            if (childView.getVisibility() == GONE) {
                continue;
            }
            LayoutParams layoutParams = (LayoutParams) childView.getLayoutParams();
            //把xml中的尺寸轉換成測量的單位
            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, layoutParams.width);
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, layoutParams.height);
            //度量childView
            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

            //獲取childView度量后的尺寸
            int childMeasuredWidth = childView.getMeasuredWidth();
            int childMeasuredHeight = childView.getMeasuredHeight();
            //判斷是否換行
            if (paddingLeft + lineWidth + childMeasuredWidth + paddingRight > selfWidth) {
                //測量完一行,把這一行所有的view
                // 和這一行的高度 保存下來 方便layout
                allLineViews.add(lineViews);
                lineHeights.add(lineHeight);
                //記錄所需的最大寬高
                parentNeededWidth = Math.max(parentNeededWidth, lineWidth + paddingLeft + paddingRight);
                parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;

                //初始化下一行信息
                lineViews = new ArrayList<>();
                lineWidth = 0;
                lineHeight = 0;
            }
            //累加一行布局的寬高
            lineWidth = lineWidth + childMeasuredWidth + mHorizontalSpacing;
            lineHeight = Math.max(lineHeight, childMeasuredHeight);
            //保存一行view
            lineViews.add(childView);

            //最后一個view,結束測量
            if (i == childCount - 1) {
                allLineViews.add(lineViews);
                lineHeights.add(lineHeight);
                parentNeededWidth = Math.max(parentNeededWidth, lineWidth + paddingLeft + paddingRight);
                parentNeededHeight = parentNeededHeight + lineHeight + paddingTop + paddingBottom;
            }

        }
        //測量完所有的子View 后需要再重新測量自己
        // 1.獲取 父View給的測量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        // 2. 根據(jù)測量模式和子View每一行的寬高 決定自己的尺寸
        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;
        //測量自己,這個單位是具體的尺寸,因為所有的子View都測量完了,肯定能得出測量值了,所以這里直接用就行了,
        // 不用再計算MeasureSpec
        setMeasuredDimension(realWidth, realHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();
        int paddingBottom = getPaddingBottom();

        for (int i = 0; i < allLineViews.size(); i++) {
            int childTop = paddingTop;
            int childLeft = paddingLeft;
            List<View> views = allLineViews.get(i);
            for (View view : views) {
                /*
                 * view.getMeasuredWidth() 與view.getWidth()的區(qū)別
                 * 1.在onMeasure()的setMeasuredDimension(realWidth, realHeight);函數(shù)里
                 * setMeasuredWidth()。所以,在setMeasuredDimension(realWidth, realHeight)完成后,view.getMeasuredWidth()
                 * 才能獲取到值。所有在view的生命周期里 view.measure()后就能獲取measuredWidth了。
                 *
                 * 2. view.getWidth 在layout()之后才能獲取到值。width = mRight -mLeft得到的,mRight、mLeft
                 * 是在view.layout()里賦值的
                 * */
                int measuredWidth = view.getMeasuredWidth();
                int measuredHeight = view.getMeasuredHeight();
                int right = childLeft + measuredWidth;
                int bottom = childTop + measuredHeight;
                view.layout(childLeft, childTop, right, bottom);
                childLeft = right + mHorizontalSpacing;
            }
            paddingTop = childTop + lineHeights.get(i) + mVerticalSpacing;
        }


    }

    //如果要使用自定義LayoutParams,必須再ViewGroup 中重寫這兩個方法
    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

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

    public static class LayoutParams extends MarginLayoutParams {
        //在這里可以自定義layout_參數(shù)

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

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


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


    }

}

使用
方法一:在xml 中使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="歷史記錄"
        android:textColor="@android:color/black"
        android:textSize="16dp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
    <com.example.wangz.myapplication.view.FlowLayoutView
        android:id="@+id/flow_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/button"
        android:layout_marginTop="@dimen/dp_10"
        android:paddingLeft="20dp"
        android:paddingTop="20dp"
        android:paddingRight="20dp"
        android:paddingBottom="@dimen/dp_10">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="母嬰用品床上用品" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="15dp"
                    android:text="廚房 " />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="生活日用品調(diào)味料" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="@dimen/dp_10"
                    android:text="abcdefgh" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="手紙 " /
    </com.example.wangz.myapplication.view.FlowLayoutView>

</LinearLayout>

方法二 :代碼實現(xiàn)

public class MainActivity extends AppCompatActivity {

    String[] stringList = {"母嬰用品床上用品", "廚房", "生活日用品調(diào)味料", "abcdefgh", "手紙", "廚房", "衛(wèi)生紙"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FlowLayoutView flowLayoutView = findViewById(R.id.flow_layout);
        for (int i = 0; i < stringList.length; i++) {
            flowLayoutView.addView(getTextView(stringList[i]));
        }
    }

    private TextView getTextView(String text) {
        FlowLayoutView.LayoutParams layoutParams = new FlowLayoutView.LayoutParams(FlowLayoutView.LayoutParams.WRAP_CONTENT,
                FlowLayoutView.LayoutParams.WRAP_CONTENT);
        TextView textView = new TextView(this);
        textView.setLayoutParams(layoutParams);
        textView.setTextSize(12f);
        textView.setText(text);
        return textView;
    }

}
6b45700ef6d5fb155cf5c7dc3a4e931.jpg
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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