zoukankan      html  css  js  c++  java
  • Android -- 自定义ViewGroup实现FlowLayout效果

    1,在开发的时候,常在我们的需求中会有这种效果,添加一个商品的一些热门标签,效果图如下:

    2,从上面效果可以看得出来,这是一个自定义的ViewGroup,然后实现换行效果,让我们一起来实现一下

    • 自定义属性

      从上面的效果来看,我们需要动态的设置每个lable的宽度和高度,所以我们编写如下的自定义属性

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="FlowLayout">
            <!--    标签之间的间距-->
            <attr name="lineSpace" format="dimension"/>
            <!--    每一行之间的间距-->
            <attr name="rowSpace" format="dimension"/>
        </declare-styleable>
    </resources>
    

      在布局文件中使用

    <?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:flowlayout="http://schemas.android.com/apk/res-auto"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent">
    <com.qianmo.flowlayout.FlowLayout
        android:id="@+id/flowLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dip"
        flowlayout:lineSpace="20dip"
        flowlayout:rowSpace="10dip"/>
    </LinearLayout>
    

      在类中获取自定义属性

    public class FlowLayout extends ViewGroup {
        private static String TAG = "FlowLayout";
    
        //自定义属性
        private int LINE_SPACE;
        private int ROW_SPACE;
    
        //放置标签的集合
        private List<String> lables;
        private List<String> lableSelects;
    
        public FlowLayout(Context context) {
            this(context, null);
        }
    
        public FlowLayout(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            //获取自定义属性
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
            LINE_SPACE = a.getDimensionPixelSize(R.styleable.FlowLayout_lineSpace, 10);
            ROW_SPACE = a.getDimensionPixelSize(R.styleable.FlowLayout_rowSpace, 10);
            a.recycle();
    
        }
    }
    • 初始化数据数据源

      向FlowLayout类中添加数据

     /**
         * 添加标签
         *
         * @param lables 标签集合
         * @param isAdd  是否添加
         */
        public void setLables(List<String> lables, boolean isAdd) {
            if (this.lables == null) {
                this.lables = new ArrayList<>();
            }
            if (this.lableSelects == null) {
                this.lableSelects = new ArrayList<>();
            }
            if (isAdd) {
                this.lables.addAll(lables);
            } else {
                this.lables.clear();
                this.lables = lables;
            }
            if (lables != null && lables.size() > 0) {
                for (final String lable : lables) {
                    final TextView tv = new TextView(getContext());
                    tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                            LayoutParams.WRAP_CONTENT));
                    tv.setText(lable);
                    tv.setTextSize(20);
                    tv.setBackgroundResource(R.drawable.shape_item_lable_bg);
                    tv.setTextColor(Color.BLACK);
                    tv.setGravity(Gravity.CENTER);
                    tv.setPadding(12, 5, 12, 5);
    
                    //判断是否选中
                    if (lableSelects.contains(lable)) {
                        tv.setSelected(true);
                        tv.setTextColor(getResources().getColor(R.color.tv_blue));
                    } else {
                        tv.setSelected(false);
                        tv.setTextColor(getResources().getColor(R.color.tv_gray));
                    }
    
                    //点击之后选中标签
                    tv.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            tv.setSelected(tv.isSelected() ? false : true);
                            if (tv.isSelected()) {
                                tv.setTextColor(getResources().getColor(R.color.tv_blue));
                                lableSelects.add(lable);
                            } else {
                                tv.setTextColor(getResources().getColor(R.color.tv_gray));
                                lableSelects.remove(lable);
                            }
                        }
                    });
    
                    //添加到容器中
                    addView(tv);
                }
            }
        }
    

      下面的代码是textview的背景选择器

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android" >
        <!--选中效果-->
        <item android:state_selected="true">
            <shape >
                <solid android:color="#ffffff" />
                <stroke android:color="@color/tv_blue"
                        android:width="2px"/>
                <corners android:radius="10000dip"/>
            </shape>
        </item>
        <!--默认效果-->
        <item>
            <shape >
                <solid android:color="#ffffff" />
                <stroke android:color="@color/divider_gray"
                        android:width="2px"/>
                <corners android:radius="10000dip"/>
            </shape>
        </item>
    </selector>
    
    • 重写onMeasure方法

      本布局在宽度上是使用的建议的宽度(填充父窗体或者具体的size),如果需要wrap_content的效果,还需要重新计算,当然这种需求是非常少见的,所以直接用建议宽度即可;布局的高度就得看其中的标签需要占据多少行(row ),那么高度就为row * 单个标签的高度+(row -1) * 行距,代码如下:

     /**
         * 通过测量子控件高度,来设置自身控件的高度
         * 主要是计算
         *
         * @param widthMeasureSpec
         * @param heightMeasureSpec
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //测量所有子view的宽高
            measureChildren(widthMeasureSpec, heightMeasureSpec);
    
            //获取view的宽高测量模式
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            //这里的宽度建议使用match_parent或者具体值,当然当使用wrap_content的时候没有重写的话也是match_parent所以这里的宽度就直接使用测量的宽度
            int width = widthSize;
    
            int height;
            //判断宽度
            if (heightMode == MeasureSpec.EXACTLY) {
                height = heightSize;
            } else {
                int row = 1;
                int widthSpace = width; //宽度剩余空间
                for (int i = 0; i < getChildCount(); i++) {
                    View view = getChildAt(i);
                    //获取标签宽度
                    int childW = view.getMeasuredWidth();
                    //判断剩余宽度是否大于此标签宽度
                    if (widthSpace >= childW) {
                        widthSpace -= childW;
                    } else {
                        row++;
                        widthSpace = width - childW;
                    }
                    //减去两边间距
                    widthSpace -= LINE_SPACE;
                }
                //获取子控件的高度
                int childH = getChildAt(0).getMeasuredHeight();
                //测算最终所需要的高度
                height = (childH * row) + (row - 1) * ROW_SPACE;
            }
    
            //保存测量高度
            setMeasuredDimension(width, height);
        }
    
    • 重写OnLayout方法

      onLayout(boolean changed, int l, int t, int r, int b)方法是一个抽象方法,自定义ViewGroup时必须实现它,用于给布局中的子控件分配位置,其中的参数l,t,r,b分别代表本ViewGroup的可用空间(除去margin和padding后的剩余空间)的左、上、右、下的坐标(相对于自身),相当于一个约束,如果子控件摆放的位置超过这个范围,超出的部分将不可见。

     /**
         * 摆放子view
         *
         * @param changed
         * @param l
         * @param t
         * @param r
         * @param b
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int row = 0;
            int right = 0;
            int bottom = 0;
            for (int i = 0; i < getChildCount(); i++) {
                View chileView = getChildAt(i);
                int childW = chileView.getMeasuredWidth();
                int childH = chileView.getMeasuredHeight();
                right += childW;
                bottom = (childH + ROW_SPACE) * row + childH;
                if (right > (r - LINE_SPACE)) {
                    row++;
                    right = childW;
                    bottom = (childH + ROW_SPACE) * row + childH;
                }
                chileView.layout(right - childW, bottom - childH, right, bottom);
                right += LINE_SPACE;
            }
        }
    

      看一下实现的效果图

      

      ok,这样我们就全部实现了,需要源码的同学可以在这里去下载

  • 相关阅读:
    Sql Server 2008卸载后再次安装一直报错
    listbox 报错 Cannot have multiple items selected when the SelectionMode is Single.
    Sql Server 2008修改Sa密码
    学习正则表达式
    Sql Server 查询第30条数据到第40条记录数
    Sql Server 复制表
    Sql 常见面试题
    Sql Server 简单查询 异步服务器更新语句
    jQuery stop()用法以及案例展示
    CSS3打造不断旋转的CD封面
  • 原文地址:https://www.cnblogs.com/wjtaigwh/p/6626517.html
Copyright © 2011-2022 走看看