自定义ViewGroup(二)

作者: ReturnYHH | 来源:发表于2017-01-15 20:37 被阅读82次

    上篇主要是简单的介绍下自定义viewGroup的用途以及方法,这篇写个小例子试试水,先几张效果图

    模仿微信朋友圈显示图片,可以看到,根据图片数量的不同,显示的方式也不一样,下面来看看实现的思路


    思路

    1,测量viewGroup,根据子view的数量来控制viewGroup的宽高
    (一):如果子view的数量少于或者等于3的时候,就显示一行,宽就是子view宽的总和,高则是子view的高
    (二):如果子view大于3并且小于等于6,分两种情况,一种是子view数量是4,如果是4,那么宽则是子view宽的2倍,高是子view高的2倍,如果不是4,那么就显示两行,一行是3,一行是2或者3,那么宽则是取最大,也就是子view宽的3倍,高是子view的2倍
    (三):如果子view大于6了,那么宽就是子view的3倍,高是子view的3倍
    2,测量完成之后,就到了子view的摆放了
    (一):如果子view数量少于或者等于3,则摆放一行,从左到右
    (二):如果子view大于3并且小于等于6,如果是4,则两行,上下各2个,否则就第一行3个,第二行2个或者3个
    (三):如果子view数量大于6,则3行,前2行3个,最后一行1个,2个或者3个

    先看看代码:


    onMeasure:

     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //总宽度和高度
            int totalWidth = 0;
            int totalHeight = 0;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
                // 得到child的lp
                MarginLayoutParams lp = (MarginLayoutParams) child
                        .getLayoutParams();
                // 当前子空间实际占据的宽度
                int childWidth = child.getMeasuredWidth() + lp.leftMargin
                        + lp.rightMargin;
                // 当前子空间实际占据的高度
                int childHeight = child.getMeasuredHeight() + lp.topMargin
                        + lp.bottomMargin;
                if (getChildCount() <= 3) {
                    totalWidth += childWidth;
                    totalHeight = childHeight;
                } else if (getChildCount() <= 6) {
                    if (getChildCount() == 4) {
                        totalWidth = childWidth * 2;
                        totalHeight = childHeight * 2;
                    } else {
                        totalWidth = childWidth * 3;
                        totalHeight = childHeight * 2;
                    }
                } else if (getChildCount() <= 9) {
                    totalWidth = childWidth * 3;
                    totalHeight = childHeight * 3;
                }
            }
            setMeasuredDimension(totalWidth + getPaddingLeft() + getPaddingRight(),
                    totalHeight + getPaddingTop() + getPaddingBottom());
        }
    

    不难理解,首先我们定义出两个变量,分别记录总宽度和高度,然后,我们遍历拿到所有子view的宽高,根据思路来进行测量,测量完成之后,在来看看如何摆放子view


    onLayout

    @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            //onLayout会被调用多次,为了预防重叠
            mAllViews.clear();
            mLineHeight.clear();
    
            //获取总宽度
            int width = getWidth();
    
            //单行宽度和当行高度
            int lineWidth = 0;
            int lineHeight = 0;
            // 存储每一行所有的childView
            List<View> childViews = new ArrayList<>();
            int childCount = getChildCount();
            // 遍历所有的子view
            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 + lp.leftMargin + lp.rightMargin + lineWidth > width) {
                    // 记录这一行所有的View以及最大高度
                    mLineHeight.add(lineHeight);
                    // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
                    mAllViews.add(childViews);
                    lineWidth = 0;// 重置行宽
                    childViews = new ArrayList<>();
                }
    
                //  如果不需要换行,则累加
                lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
                lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
                        + lp.bottomMargin);
                childViews.add(child);
            }
            // 记录最后一行
            mLineHeight.add(lineHeight);
            mAllViews.add(childViews);
    
            int left = getPaddingLeft();
            int top = getPaddingTop();
            // 得到总行数
            int lineNum = mAllViews.size();
            for (int i = 0; i < lineNum; i++) {
                // 每一行的所有的views
                childViews = mAllViews.get(i);
                // 当前行的最大高度
                lineHeight = mLineHeight.get(i);
    
                // 遍历当前行所有的子View
                for (int j = 0; j < childViews.size(); j++) {
                    View child = childViews.get(j);
                    if (child.getVisibility() != View.GONE) {
                        MarginLayoutParams lp = (MarginLayoutParams) child
                                .getLayoutParams();
    
                        //计算childView的left,top,right,bottom
                        int childLeft = left + lp.leftMargin;
                        int childTop = top + lp.topMargin;
                        int childRight = childLeft + child.getMeasuredWidth();
                        int childBottom = childTop + child.getMeasuredHeight();
    
                        child.layout(childLeft, childTop, childRight, childBottom);
    
                        left += child.getMeasuredWidth() + lp.rightMargin
                                + lp.leftMargin;
                    }
                }
                //换行后,重新从第一个开始,高度累加
                left = getPaddingTop();
                top += lineHeight;
            }
    
        }
    

    我们定义两个list来存储所有的子view和每一行的高度,然后,我们继续遍历子view,如果当行所有子view的宽累加大于了布局的宽度,我们就换行,否则就继续累加,最后把所有的行数和子view添加到我们的list中,最后通过遍历list来将子view拿出来,确定left,top,right,bottom之后,就可以进行布局了,这里要注意的是,如果需要换行了,那么子view的位置是从第一个开始,所以我们需要将left重置,并且高度增加,现在看看怎么用,分两种用法,一种就是静态布局,一种是动态布局,我们在开发中,一般都是使用动态布局方式


    静态用法

    静态用法就是写在xml之中,view的数量手动去添加

    <com.example.administrator.myactivity.MyView
            android:id="@+id/myView"
            android:layout_width="wrap_content"
            android:background="@color/colorAccent"
            android:layout_height="wrap_content">
    
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@mipmap/ic_launcher" />
    
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@mipmap/ic_launcher" />
    
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@mipmap/ic_launcher" />
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@mipmap/ic_launcher" />
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@mipmap/ic_launcher" />
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@mipmap/ic_launcher" />
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@mipmap/ic_launcher" />
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@mipmap/ic_launcher" />
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:src="@mipmap/ic_launcher" />
        </com.example.administrator.myactivity.MyView>
    

    这也是我效果图的用法


    动态用法

    动态用法则是根据服务器返回的数据来添加子view,这里我模拟一下
    首先我们定义一个item布局,里面就一个ImageView

    <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:src="@mipmap/ic_launcher">
    
    </ImageView>
    

    然后我们在activity中,动态的添加数据

    public class MainActivity extends AppCompatActivity {
        MyView myView;
        private int[] images = {R.mipmap.ic_launcher, R.mipmap.ic_launcher, R.mipmap.ic_launcher};
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            myView = (MyView) findViewById(R.id.myView);
            setData();
        }
    
        private void setData() {
            LayoutInflater inflater = LayoutInflater.from(this);
            for (int i = 0; i < images.length; i++) {
                ImageView imageView = (ImageView) inflater.inflate(R.layout.item, myView, false);
                imageView.setImageResource(images[i]);
                myView.addView(imageView);
            }
        }
    }
    

    这样就完成了动态添加数据,好了,到这里就完成了模仿微信朋友圈显示图片效果,最后附上整个自定义viewGroup的代码:

    package com.example.administrator.myactivity;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    import android.view.ViewGroup;
    
    
    import java.util.ArrayList;
    import java.util.List;
    
    
    /**
     * Created by Pan_ on 2015/2/2.
     */
    public class MyView extends ViewGroup {
    
    
        /**
         * 存储所有的View,按行记录
         */
        private List<List<View>> mAllViews = new ArrayList<>();
        /**
         * 记录每一行的最大高度
         */
        private List<Integer> mLineHeight = new ArrayList<>();
    
        public MyView(Context context) {
            super(context);
        }
    
        public MyView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //总宽度和高度
            int totalWidth = 0;
            int totalHeight = 0;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
                // 得到child的lp
                MarginLayoutParams lp = (MarginLayoutParams) child
                        .getLayoutParams();
                // 当前子空间实际占据的宽度
                int childWidth = child.getMeasuredWidth() + lp.leftMargin
                        + lp.rightMargin;
                // 当前子空间实际占据的高度
                int childHeight = child.getMeasuredHeight() + lp.topMargin
                        + lp.bottomMargin;
                if (getChildCount() <= 3) {
                    totalWidth += childWidth;
                    totalHeight = childHeight;
                } else if (getChildCount() <= 6) {
                    if (getChildCount() == 4) {
                        totalWidth = childWidth * 2;
                        totalHeight = childHeight * 2;
                    } else {
                        totalWidth = childWidth * 3;
                        totalHeight = childHeight * 2;
                    }
                } else if (getChildCount() <= 9) {
                    totalWidth = childWidth * 3;
                    totalHeight = childHeight * 3;
                }
            }
            setMeasuredDimension(totalWidth + getPaddingLeft() + getPaddingRight(),
                    totalHeight + getPaddingTop() + getPaddingBottom());
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            //onLayout会被调用多次,为了预防重叠
            mAllViews.clear();
            mLineHeight.clear();
    
            //获取总宽度
            int width = getWidth();
    
            //单行宽度和当行高度
            int lineWidth = 0;
            int lineHeight = 0;
            // 存储每一行所有的childView
            List<View> childViews = new ArrayList<>();
            int childCount = getChildCount();
            // 遍历所有的子view
            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 + lp.leftMargin + lp.rightMargin + lineWidth > width) {
                    // 记录这一行所有的View以及最大高度
                    mLineHeight.add(lineHeight);
                    // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
                    mAllViews.add(childViews);
                    lineWidth = 0;// 重置行宽
                    childViews = new ArrayList<>();
                }
    
                //  如果不需要换行,则累加
                lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
                lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
                        + lp.bottomMargin);
                childViews.add(child);
            }
            // 记录最后一行
            mLineHeight.add(lineHeight);
            mAllViews.add(childViews);
    
            int left = getPaddingLeft();
            int top = getPaddingTop();
            // 得到总行数
            int lineNum = mAllViews.size();
            for (int i = 0; i < lineNum; i++) {
                // 每一行的所有的views
                childViews = mAllViews.get(i);
                // 当前行的最大高度
                lineHeight = mLineHeight.get(i);
    
                // 遍历当前行所有的子View
                for (int j = 0; j < childViews.size(); j++) {
                    View child = childViews.get(j);
                    if (child.getVisibility() != View.GONE) {
                        MarginLayoutParams lp = (MarginLayoutParams) child
                                .getLayoutParams();
    
                        //计算childView的left,top,right,bottom
                        int childLeft = left + lp.leftMargin;
                        int childTop = top + lp.topMargin;
                        int childRight = childLeft + child.getMeasuredWidth();
                        int childBottom = childTop + child.getMeasuredHeight();
    
                        child.layout(childLeft, childTop, childRight, childBottom);
    
                        left += child.getMeasuredWidth() + lp.rightMargin
                                + lp.leftMargin;
                    }
                }
                //换行后,重新从第一个开始,高度累加
                left = getPaddingTop();
                top += lineHeight;
            }
    
        }
    
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new MarginLayoutParams(getContext(), attrs);
        }
    }
    

    这里补充一点,为什么我们需要设置LayoutParams 呢,因为我们每一个layout对应一个LayoutParams ,比如LinereanLayout对应的就是LinereanLayout的LayoutParams ,RelativeLayout对应的是RelativeLayout的LayoutParams,所以,我们在写LayoutParams 的时候,会发现有很多layout的LayoutParams ,那怎么知道我需要哪一个呢?这时候就看你的父view是哪个布局,就对应哪个LayoutParams ,我们在自定义viewGroup的时候,因为考虑到子view有Margin的属性,所以,我们设置他对应的LayoutParams 就是MarginLayoutParams,好了,这篇文章到这里就结束了,快到新年了,最后祝大家新年快乐~~

    相关文章

      网友评论

        本文标题:自定义ViewGroup(二)

        本文链接:https://www.haomeiwen.com/subject/usvsbttx.html