自定义ViewGroup实现流式布局

2020-04-19  本文已影响0人  itfitness

目录

目录

前言

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

实现效果

代码展示

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

上一篇下一篇

猜你喜欢

热点阅读