美文网首页
自定义ViewGroup实现流式布局

自定义ViewGroup实现流式布局

作者: itfitness | 来源:发表于2020-04-19 22:01 被阅读0次

    目录

    目录

    前言

    趁着周末有空自己写了个流式布局,在这记录一下方便以后使用。

    实现效果

    代码展示

    public class FlowLayout extends ViewGroup {
        private int mHItemSpec = 20;//Item横向的间距
        private int mVItemSpec = 10;//Item竖向的间距
        private OnItemSelectListener onItemSelectListener;
        private ArrayList<ArrayList<View>> allLines = new ArrayList<>();
        private ArrayList<Integer> allHeight = new ArrayList<>();
    
        public OnItemSelectListener getOnItemSelectListener() {
            return onItemSelectListener;
        }
    
        public void setOnItemSelectListener(OnItemSelectListener onItemSelectListener) {
            this.onItemSelectListener = onItemSelectListener;
        }
    
        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);
        }
    
        /**
         * 设置数据
         */
        public void setDatas(List<? extends ModuleImpl> datas,Class itemViewClazz){
            removeAllViews();
            for(int i = 0 ; i < datas.size() ; i ++ ){
                final ModuleImpl module = datas.get(i);
                final View itemView = getClazzView(itemViewClazz);
                if(itemView instanceof ItemViewImpl){
                    ItemViewImpl itemImpl = (ItemViewImpl) itemView;
                    itemImpl.setItemText(module.getItemText());
                }
                final int finalI = i;
                itemView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if(onItemSelectListener!=null){
                            onItemSelectListener.onItemSelect(finalI,module);
                        }
                        updateItemStatus(finalI);
                    }
                });
                addView(itemView);
            }
        }
    
        /**
         * 更新itemView的状态
         */
        private void updateItemStatus(int selectPos){
            for(int  i = 0 ; i < getChildCount() ; i++){
                View child = getChildAt(i);
                if(child instanceof ItemViewImpl){
                    ItemViewImpl itemImpl = (ItemViewImpl) child;
                    if(selectPos == i){
                        itemImpl.setSelect(true);
                    }else {
                        itemImpl.setSelect(false);
                    }
                }
            }
        }
        /**
         * 通过反射实例化View
         * @param clazz
         */
        private View getClazzView(Class clazz){
            View itemView = null;
            try {
                Constructor c = clazz.getConstructor(Context.class);//获取有参构造
                itemView = (View) c.newInstance(getContext());    //通过有参构造创建对象
            }catch (Exception e){
            }
            return itemView;
        }
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            allLines.clear();
            allHeight.clear();
    
            int paddingLeft = getPaddingLeft();
            int paddingRight = getPaddingRight();
            int paddingTop = getPaddingTop();
            int paddingBottom = getPaddingBottom();
    
            int selfWidth = MeasureSpec.getSize(widthMeasureSpec);//当前流式布局最大的宽度
            int selfHeight = MeasureSpec.getSize(heightMeasureSpec);//当前流式布局最大的高度
    
            int needWidth = 0;//需要的宽度
            int needHeight = 0;//需要的高度
    
            int lineWidth = 0;//记录每一行的宽度
            int lineHeight = 0;//记录每一行的高度
    
            ArrayList<View> lineViews = new ArrayList<>();//存储每一行的View
            //度量子View
            for(int i = 0 ; i < getChildCount() ; i ++){
                View childView = getChildAt(i);
                LayoutParams layoutParams = childView.getLayoutParams();
                //获取子控件的MeasureSpec
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, layoutParams.width);
                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, layoutParams.height);
                childView.measure(childWidthMeasureSpec,childHeightMeasureSpec);//对子控件进行度量
                //获取子控件度量后的宽高
                int childMeasuredWidth = childView.getMeasuredWidth();
                int childMeasuredHeight = childView.getMeasuredHeight();
                //如果一行的宽度超过了本布局的宽度则需要换行
                if(lineWidth + childMeasuredWidth + mHItemSpec > selfWidth){
                    needWidth = Math.max(needWidth,lineWidth);//计算出所有行中最大的宽度来作为需要的宽度
                    needHeight += lineHeight+mVItemSpec;//需要的高度为所有行的高度和
                    allLines.add(lineViews);//存储每一行的View方便布局时使用
                    allHeight.add(lineHeight);//存储每一行的高度方便布局的时候使用
                    lineWidth = 0;
                    lineHeight = 0;
                    lineViews = new ArrayList<>();
                }
                lineWidth += childMeasuredWidth + mHItemSpec;
                //每一行的高度取每一行最高控件的高度
                lineHeight = Math.max(childMeasuredHeight,lineHeight);
                lineViews.add(childView);
            }
            //加入最后一行
            if(lineViews.size()>0){
                allLines.add(lineViews);
                allHeight.add(lineHeight);
                needHeight += lineHeight+mVItemSpec;
            }
            //获取FlowLayout自身的MeasureSpec Mode
            int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
            int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
            //根据FlowLayout自身的MeasureSpec Mode来判断使用哪个宽高
            int realWidth = modeWidth == MeasureSpec.EXACTLY ? selfWidth : needWidth;
            int realHeight = modeHeight == MeasureSpec.EXACTLY ? selfHeight : needHeight;
            setMeasuredDimension(realWidth,realHeight);
        }
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            //开始的left根据本布局的padding来计算的
            int curtL = getPaddingLeft();
            int curtT = getPaddingTop();
            for(ArrayList<View> widhViews:allLines){
                for(View childView:widhViews){
                    int right = childView.getMeasuredWidth() + curtL;
                    int bottom = childView.getMeasuredHeight() + curtT;
                    childView.layout(curtL,curtT,right,bottom);
                    curtL += childView.getMeasuredWidth() + mHItemSpec;
                }
                //每一行结束后需要增加行高
                curtT += allHeight.get(allLines.indexOf(widhViews)) + mVItemSpec;
                //每一行结束需要left要重置
                curtL = getPaddingLeft();
            }
        }
    
        /**
         * 实体类需要继承的接口
         */
        public interface ModuleImpl{
            String getItemText();
        }
        /**
         * ItemView选择或未选择的状态和文字
         */
        public interface ItemViewImpl{
            void setSelect(boolean isSelect);
            void setItemText(String text);
        }
        /**
         * 当item选择的回调
         * @param <T>
         */
        public interface OnItemSelectListener<T extends ModuleImpl>{
            void onItemSelect(int position,T data);
        }
    }
    

    使用方法

    1. 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:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <Button
            android:id="@+id/bt"
            android:text="添加View"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <com.itfitness.flowlayout.widget.FlowLayout
            android:id="@+id/fl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </com.itfitness.flowlayout.widget.FlowLayout>
    </LinearLayout>
    
    1. java代码中
    public class MainActivity extends AppCompatActivity {
        private FlowLayout flowLayout;
        private Button button;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            flowLayout = findViewById(R.id.fl);
            button = findViewById(R.id.bt);
            //给FlowLayout设置item点击事件
            flowLayout.setOnItemSelectListener(new FlowLayout.OnItemSelectListener<FlowLayoutItemModule>() {
                @Override
                public void onItemSelect(int position, FlowLayoutItemModule data) {
                    Toast.makeText(MainActivity.this, data.getItemText()+"=="+position, Toast.LENGTH_SHORT).show();
                }
            });
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //随机生成整数作为item数量
                    int i = new Random().nextInt(20);
                    Toast.makeText(MainActivity.this, i+"", Toast.LENGTH_SHORT).show();
                    //给FlowLayout设置数据,其中FlowLayoutItem是一个实现了FlowLayout.ItemViewImpl接口的自定义控件
                    flowLayout.setDatas(getFlowDatas(i), FlowLayoutItem.class);
                }
            });
        }
        /**
         * 获取数据
         * @return
         */
        private List<FlowLayout.ModuleImpl> getFlowDatas(int size){
            List<FlowLayout.ModuleImpl> datas = new ArrayList<>();
            for(int i = 0 ; i < size ; i++){
                FlowLayoutItemModule flowLayoutItemModule = new FlowLayoutItemModule();
                flowLayoutItemModule.setName("我是Item"+i);
                datas.add(flowLayoutItemModule);
            }
            return datas;
        }
    }
    

    特别注意

    给FlowLayout填充数据的实体类需要实现FlowLayout.ModuleImpl接口,而作为FlowLayout的itemView也需要实现FlowLayout.ItemViewImpl接口。

    案例源码

    https://github.com/myml666/Flowlayout

    相关文章

      网友评论

          本文标题:自定义ViewGroup实现流式布局

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