Android流布局实现筛选界面

简介: Android流布局实现筛选界面

说起流布局,几乎每个应用都会使用到,比如一些场景:标签、搜索历史、筛选等等,用的地方还是蛮多的,那么一般我们实现流布局的方法有哪些呢?一般常见的有以下几种方法。


1、自定义ViewGrop

2、使用google出的FlexboxLayout;github入口

3、RecyclerView

注: demo.apk 点击下载查看

使用RecyclerView实现流布局是一般使用FlexboxLayoutManager来配合使用来实现。当然还有其他一些使用方式,每种方法只要能实现我们的需求都是可以的,这里先看下我们需要实现的效果!

从上面效果可以看出,我们需要实现的流布局有以下需求。


流布局基本需求:


1、流布局支持每个子按钮宽度一致,即支持自适和平均分配;


2、流布局支持折叠和展开;


3、流布局支持设置一行显示几个及显示的行数(在折叠情况下显示的行数);


4、流布局支持自定义选中及默认UI(按钮的背景及文字的样式);


效果增加的需求:


1、流布局支持展示、单选、多选;


2、不限按钮默认选中(支持默认选中);


3、当选中其他选项(不限除外),不限按钮取消选中。


4、当其他选中都被取消到没有选中的时候,不限选中。


5、数据网络动态获取显示,支持自定义判断是否选中的参数。一般实现使用的是索引,但是实际需求一般是ID。


...


基本需求就是这些,为了更加灵活的实现这些需求,选择自定义ViewGrop方式来实现这个功能。


首先定义需要设置的配置的属性

<declare-styleable name="FlowLayout">
    <!--支持折叠 -->
    <attr name="flow_fold" format="boolean" />
    <!--折叠时的行数(支持折叠情况下) -->
    <attr name="flow_foldLines" format="integer" />
    <!--平均分配 -->
    <attr name="flow_equally" format="boolean" />
    <!--平局分配一行的数量 -->
    <attr name="flow_equally_count" format="integer" />
    <!--水平间距 -->
    <attr name="flow_horizontalSpacing" format="dimension" />
    <!--竖直间距 -->
    <attr name="flow_verticalSpacing" format="dimension" />
    <!--左右对齐 -->
    <attr name="flow_gravity">
        <enum name="LEFT" value="0"></enum>
        <enum name="RIGHT" value="1"></enum>
    </attr>
</declare-styleable>

然后创建一个FlowLayout继承ViewGrop

package com.zwl.mybossdemo.filter.flow;
 
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
 
import com.zwl.mybossdemo.R;
 
/**
 * 流布局
 * 1、是否折叠
 * 2、折叠行数
 * 3、对其方式
 * 4、平均宽度及数量
 * 5、水平竖直间隔
 */
public class FlowLayout extends ViewGroup {
 
    //默认折叠状态
    private static final boolean DEFAULT_FOLD = false;
    //折叠的行数
    private static final int DEFAULT_FOLD_LINES = 1;
    //左对齐
    private static final int DEFAULT_GRAVITY_LEFT = 0;
    //右对齐
    private static final int DEFAULT_GRAVITY_RIGHT = 1;
 
    //是否折叠,默认false不折叠
    private boolean mFold;
    //折叠行数
    private int mFoldLines = 1;
    //对齐 默认左对齐
    private int mGravity = DEFAULT_GRAVITY_LEFT;
    //折叠状态
    private Boolean mFoldState;
    //是否平均
    private boolean mEqually;
    //一行平局数量
    private int mEquallyCount;
    //水平距离
    private int mHorizontalSpacing;
    //竖直距离
    private int mVerticalSpacing;
 
    private OnFoldChangedListener mOnFoldChangedListener;
 
    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);
        mFold = a.getBoolean(R.styleable.FlowLayout_flow_fold, DEFAULT_FOLD);
        mFoldLines = a.getInt(R.styleable.FlowLayout_flow_foldLines, DEFAULT_FOLD_LINES);
        mGravity = a.getInt(R.styleable.FlowLayout_flow_gravity, DEFAULT_GRAVITY_LEFT);
        mEqually = a.getBoolean(R.styleable.FlowLayout_flow_equally, true);
        mEquallyCount = a.getInt(R.styleable.FlowLayout_flow_equally_count, 0);
        mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.FlowLayout_flow_horizontalSpacing, dp2px(4));
        mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.FlowLayout_flow_verticalSpacing, dp2px(4));
        a.recycle();
    }
 
 
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //当设置折叠 折叠数设置小于0直接隐藏布局
        if (mFold && mFoldLines <= 0) {
            setVisibility(GONE);
            changeFold(true, true);
            return;
        }
        //获取mode 和 size
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 
        final int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
        //判断如果布局宽度抛去左右padding小于0,也不能处理了
        if (layoutWidth <= 0) {
            return;
        }
 
        //这里默认宽高默认值默认把左右,上下padding加上
        int width = getPaddingLeft() + getPaddingRight();
        int height = getPaddingTop() + getPaddingBottom();
 
        //初始一行的宽度
        int lineWidth = 0;
        //初始一行的高度
        int lineHeight = 0;
 
        //测量子View
        measureChildren(widthMeasureSpec, heightMeasureSpec);
 
        int[] wh = null;
        int childWidth, childHeight;
        int childWidthMeasureSpec = 0, childHeightMeasureSpec = 0;
        //行数
        int line = 0;
        //折叠的状态
        boolean newFoldState = false;
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = getChildAt(i);
            //这里需要先判断子view是否被设置了GONE
            if (view.getVisibility() == GONE) {
                continue;
            }
 
            //如果设置是平局显示
            if (mEqually) {
                //这里只要计算一次就可以了
                if (wh == null) {
                    //取子view最大的宽高
                    wh = getMaxWH();
                    //求一行能显示多少个
                    int oneRowItemCount = (layoutWidth + mHorizontalSpacing) / (mHorizontalSpacing + wh[0]);
                    //当你设置了一行平局显示多少个
                    if (mEquallyCount > 0) {
                        //判断当你设定的数量小于计算的数量时,使用设置的,所以说当我们计算的竖直小于设置的值的时候这里并没有强制设置设定的值
                        //如果需求要求必须按照设定的来,这里就不要做if判断,直接使用设定的值,但是布局显示会出现显示不全或者...的情况。
                        if (oneRowItemCount > mEquallyCount) {
                            oneRowItemCount = mEquallyCount;
                        }
                    }
                    // 根据上面计算的一行显示的数量来计算一个的宽度
                    int newWidth = (layoutWidth - (oneRowItemCount - 1) * mHorizontalSpacing) / oneRowItemCount;
                    wh[0] = newWidth;
                    //重新获取子view的MeasureSpec
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(wh[0], MeasureSpec.EXACTLY);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(wh[1], MeasureSpec.EXACTLY);
                }
                childWidth = wh[0];
                childHeight = wh[1];
                //重新测量子view的大小
                getChildAt(i).measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
            // 自适显示
            else {
                childWidth = view.getMeasuredWidth();
                childHeight = view.getMeasuredHeight();
            }
            if (i == 0) {//第一行
                lineWidth = getPaddingLeft() + getPaddingRight() + childWidth;
                lineHeight = childHeight;
            } else {
                //判断是否需要换行
                //换行
                if (lineWidth + mHorizontalSpacing + childWidth > widthSize) {
                    line++;//行数增加
                    width = Math.max(lineWidth, width);// 取最大的宽度
                    //这里判断是否设置折叠及行数是否超过了设定值
                    if (mFold && line >= mFoldLines) {
                        line++;
                        height += lineHeight;
                        newFoldState = true;
                        break;
                    }
                    //重新开启新行,开始记录
                    lineWidth = getPaddingLeft() + getPaddingRight() + childWidth;
                    //叠加当前高度,
                    height += mVerticalSpacing + lineHeight;
                    //开启记录下一行的高度
                    lineHeight = childHeight;
                }
                //不换行
                else {
                    lineWidth = lineWidth + mHorizontalSpacing + childWidth;
                    lineHeight = Math.max(lineHeight, childHeight);
                }
            }
            // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较
            if (i == count - 1) {
                line++;
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }
        }
        //根据计算的值重新设置
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
                heightMode == MeasureSpec.EXACTLY ? heightSize : height);
        //折叠状态
        changeFold(line > mFoldLines, newFoldState);
    }
 
 
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int layoutWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        if (layoutWidth <= 0) {
            return;
        }
        int childWidth, childHeight;
        //需要加上top padding
        int top = getPaddingTop();
        final int[] wh = getMaxWH();
        int lineHeight = 0;
        int line = 0;
 
        //左对齐
        if (mGravity == DEFAULT_GRAVITY_LEFT) {
            //左侧需要先加上左边的padding
            int left = getPaddingLeft();
            for (int i = 0, count = getChildCount(); i < count; i++) {
                final View view = getChildAt(i);
                //这里一样判断下显示状态
                if (view.getVisibility() == GONE) {
                    continue;
                }
                //如果设置的平均 就使用最大的宽度和高度 否则直接自适宽高
                if (mEqually) {
                    childWidth = wh[0];
                    childHeight = wh[1];
                } else {
                    childWidth = view.getMeasuredWidth();
                    childHeight = view.getMeasuredHeight();
                }
                //第一行开始摆放
                if (i == 0) {
                    view.layout(left, top, left + childWidth, top + childHeight);
                    lineHeight = childHeight;
                } else {
                    //判断是否需要换行
                    if (left + mHorizontalSpacing + childWidth > layoutWidth + getPaddingLeft()) {
                        line++;
                        if (mFold && line >= mFoldLines) {
                            break;
                        }
                        //重新起行
                        left = getPaddingLeft();
                        top = top + mVerticalSpacing + lineHeight;
                        lineHeight = childHeight;
                    } else {
                        left = left + mHorizontalSpacing;
                        lineHeight = Math.max(lineHeight, childHeight);
                    }
                    view.layout(left, top, left + childWidth, top + childHeight);
                }
                //累加left
                left += childWidth;
            }
        } 
        //右对齐
        else {
            int paddingLeft = getPaddingLeft();
            int right = layoutWidth + paddingLeft;// 相当于getMeasuredWidth() -  getPaddingRight();
 
            for (int i = 0, count = getChildCount(); i < count; i++) {
                final View view = getChildAt(i);
                if (view.getVisibility() == GONE) {
                    continue;
                }
                //如果设置的平均 就使用最大的宽度和高度 否则直接自适宽高
                if (mEqually) {
                    childWidth = wh[0];
                    childHeight = wh[1];
                } else {
                    childWidth = view.getMeasuredWidth();
                    childHeight = view.getMeasuredHeight();
                }
                if (i == 0) {
                    view.layout(right - childWidth, top, right, top + childHeight);
                    lineHeight = childHeight;
                } else {
                    //判断是否需要换行
                    if (right - childWidth - mHorizontalSpacing < paddingLeft) {
                        line++;
                        if (mFold && line >= mFoldLines) {
                            break;
                        }
                        //重新起行
                        right = layoutWidth + paddingLeft;
                        top = top + mVerticalSpacing + lineHeight;
                        lineHeight = childHeight;
                    } else {
                        right = right - mHorizontalSpacing;
                        lineHeight = Math.max(lineHeight, childHeight);
                    }
                    view.layout(right - childWidth, top, right, top + childHeight);
                }
                right -= childWidth;
            }
        }
 
 
    }
 
    /**
     * 取最大的子view的宽度和高度
     *
     * @return
     */
    private int[] getMaxWH() {
        int maxWidth = 0;
        int maxHeight = 0;
        for (int i = 0, count = getChildCount(); i < count; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                continue;
            }
            maxWidth = Math.max(maxWidth, view.getMeasuredWidth());
            maxHeight = Math.max(maxHeight, view.getMeasuredHeight());
        }
        return new int[]{maxWidth, maxHeight};
    }
 
    /**
     * 折叠状态改变回调
     *
     * @param canFold
     * @param newFoldState
     */
    private void changeFold(boolean canFold, boolean newFoldState) {
        if (mFoldState == null || mFoldState != newFoldState) {
            if (canFold) {
                mFoldState = newFoldState;
            }
            if (mOnFoldChangedListener != null) {
                mOnFoldChangedListener.onFoldChanged(canFold, newFoldState);
            }
        }
        if (mOnFoldChangedListener != null) {
            mOnFoldChangedListener.onFoldChanging(canFold, newFoldState);
        }
    }
 
    /**
     * 设置是否折叠
     *
     * @param fold
     */
    public void setFold(boolean fold) {
        mFold = fold;
        if (mFoldLines <= 0) {
            setVisibility(fold ? GONE : VISIBLE);
            changeFold(true, fold);
        } else {
            requestLayout();
        }
    }
 
    /**
     * 折叠切换,如果之前是折叠状态就切换为未折叠状态,否则相反
     */
    public void toggleFold() {
        setFold(!mFold);
    }
 
 
    /**
     * dp->px
     *
     * @param dp
     * @return
     */
    private int dp2px(int dp) {
        return (int) (getContext().getResources().getDisplayMetrics().density * dp);
    }
 
 
    /**
     * 设置折叠状态回调
     *
     * @param listener
     */
    public void setOnFoldChangedListener(OnFoldChangedListener listener) {
        mOnFoldChangedListener = listener;
    }
 
    public interface OnFoldChangedListener {
        /**
         * 折叠状态回调
         *
         * @param canFold 是否可以折叠,true为可以折叠,false为不可以折叠
         * @param fold    当前折叠状态,true为折叠,false为未折叠
         */
        void onFoldChanged(boolean canFold, boolean fold);
 
 
        /**
         * 折叠状态时时回调
         *
         * @param canFold 是否可以折叠,true为可以折叠,false为不可以折叠
         * @param fold    当前折叠状态,true为折叠,false为未折叠
         */
        void onFoldChanging(boolean canFold, boolean fold);
    }
}

上面的FlowLayout基本实现了流布局的基本需求,下面来实现业务要求的需求。TagFlowLayout 继承我们的FlowLayout来实现其他的业务需求。

package com.zwl.mybossdemo.filter.flow;
 
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
 
import com.zwl.mybossdemo.R;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * @author zwl
 * @describe TAG 布局
 * @date on 2020/6/26
 */
public class TagFlowLayout extends FlowLayout implements TagFlowAdapter.OnDataChangedListener {
 
    public final static int TAG_MODE_SHOW = 0; //标签
    public final static int TAG_MODE_SINGLE = 1;//单选
    public final static int TAG_MODE_MULTIPLE = 2;//多选
    private int mTagMode = TAG_MODE_SHOW;
 
    private Context mContext;
 
    private TagFlowAdapter tagAdapter;
 
    private OnCheckChangeListener onCheckChangeListener;
    private OnTagClickListener onTagClickListener;
    private TagFlowContainer mutexTagFlowView;//互斥选项
 
    public TagFlowLayout(Context context) {
        this(context, null);
    }
 
    public TagFlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout);
        mTagMode = typedArray.getInt(R.styleable.TagFlowLayout_flow_mode, TAG_MODE_SHOW);
        typedArray.recycle();
    }
 
 
    public void setTagAdapter(TagFlowAdapter tagAdapter) {
        this.tagAdapter = tagAdapter;
        this.tagAdapter.setOnDataChangedListener(this);
        updateTagView();
    }
 
    /**
     * 设置模式(单选,标签,多选)
     *
     * @param mTagMode
     */
    public void setTagMode(int mTagMode) {
        this.mTagMode = mTagMode;
    }
 
    @Override
    public void onChanged() {
        updateTagView();
    }
 
 
    private void updateTagView() {
        removeAllViews();
        if (null == tagAdapter) return;
        int count = tagAdapter.getCount();
 
        TagFlowContainer tagContainer;
        for (int i = 0; i < count; i++) {
            tagContainer = new TagFlowContainer(mContext);
            View tagView = tagAdapter.getView(tagContainer, tagAdapter.getItem(i), i);
            //允许我们的CHECKED状态向下传递
            tagView.setDuplicateParentStateEnabled(true);
            tagContainer.addView(tagView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            tagContainer.setTag(tagAdapter.isCheckContent(tagAdapter.getItem(i), i));
            tagContainer.setChecked(tagAdapter.isChecked(tagAdapter.isCheckContent(tagAdapter.getItem(i), i)));
            tagContainer.setOnClickListener(new TagClickListener(i));
            addView(tagContainer);
        }
        mutexTagFlowView = getViewByTag(tagAdapter.getMutexCheck());
    }
 
 
    /**
     * 设置互斥选中还是不选中
     *
     * @param isCheck
     */
    public void setMutexTagFlowView(boolean isCheck) {
        if (tagAdapter.getMutexCheck() != null && mutexTagFlowView != null) {
            if (isCheck) {
                tagAdapter.addChecked(tagAdapter.getMutexCheck());
            } else {
                tagAdapter.removeMutexCheck();
            }
            mutexTagFlowView.setChecked(isCheck);
        }
    }
 
 
    /**
     * 根据tag获取对应的View
     *
     * @param o
     * @return
     */
    public TagFlowContainer getViewByTag(Object o) {
        if (o == null) return null;
        int count = tagAdapter.getCount();
        for (int i = 0; i < count; i++) {
            TagFlowContainer childAt = (TagFlowContainer) getChildAt(i);
            if (childAt.getTag() != null && childAt.getTag() == o) {
                return childAt;
            }
        }
        return null;
    }
 
 
    /**
     * 点击事件处理
     */
    private class TagClickListener implements View.OnClickListener {
        private int position;
 
        public TagClickListener(int i) {
            this.position = i;
        }
 
        @Override
        public void onClick(View v) {
            TagFlowContainer tagFlowContainer = (TagFlowContainer) v;
            if (onTagClickListener != null) {
                if (onTagClickListener.onTagClick(tagFlowContainer, position)) {
                    return;
                }
            }
            //单选
            if (mTagMode == TAG_MODE_SINGLE) {
                //如果已经是选中的就不需要操作
                if (tagFlowContainer.isChecked()) {
                    return;
                } else {
                    clearAllCheckedState();//清空所有view上的状态
                    tagAdapter.removeAllChecked();//清空选中的集合
                    tagAdapter.addChecked(tagAdapter.isCheckContent(tagAdapter.getItem(position), position));
                }
            }
            //多选
            else if (mTagMode == TAG_MODE_MULTIPLE) {
                boolean isMutex = tagAdapter.getMutexCheck() != null &&
                        tagAdapter.isCheckContent(tagAdapter.getItem(position), position) == tagAdapter.getMutexCheck();
                //之前是选中状态
                if (tagFlowContainer.isChecked()) {
                    if (isMutex) return;//如果是互斥项而且是已经选中了直接return
                    tagAdapter.removeChecked(tagAdapter.isCheckContent(tagAdapter.getItem(position), position));
                    if (tagAdapter.getCheckedList().size() == 0) {
                        setMutexTagFlowView(true);
                    }
                }
                //之前是未选中状态
                else {
                    //如果是点击了互斥的
                    if (isMutex) {
                        tagAdapter.removeAllChecked();//清空选中的集合
                        clearAllCheckedState();//清空所有view上的状态
                    }
                    //点击的不是互斥的
                    else {
                        setMutexTagFlowView(false);
                    }
                    tagAdapter.addChecked(tagAdapter.isCheckContent(tagAdapter.getItem(position), position));
                }
            }
            //纯展示
            else {
                return;
            }
            tagFlowContainer.toggle();
            if (onCheckChangeListener != null)
                onCheckChangeListener.onCheckChange(tagFlowContainer.isChecked(), position);
 
        }
    }
 
 
    /**
     * 单选模式 清空所有选中状态
     */
    private void clearAllCheckedState() {
        if (mTagMode == TAG_MODE_SINGLE || mTagMode == TAG_MODE_MULTIPLE) {
            int count = getChildCount();
            for (int i = 0; i < count; i++) {
                TagFlowContainer childAt = (TagFlowContainer) getChildAt(i);
                childAt.setChecked(false);
            }
        }
    }
 
    /**
     * 重置
     */
    public void reset() {
        clearAllCheckedState();
        if (tagAdapter != null) {
            tagAdapter.removeAllChecked();
        }
        setMutexTagFlowView(true);
    }
 
    /**
     * 设置选中
     *
     * @param pos
     */
    public void setChecked(Object... pos) {
        tagAdapter.addChecked(pos);
    }
 
 
    /**
     * 删除
     *
     * @param pos
     */
    public void removeChecked(Object... pos) {
        tagAdapter.removeChecked(pos);
    }
 
 
    /**
     * 是否包含
     *
     * @param pos
     */
    public boolean containsChecked(Object... pos) {
        return tagAdapter.containsChecked(pos);
    }
 
 
    /**
     * 后去所有选中的对象  按照对象数据顺序取
     *
     * @return
     */
    public List getCheckedItems() {
        ArrayList items = new ArrayList();
        for (int i = 0; i < tagAdapter.getCount(); i++) {
            if (tagAdapter.isChecked(tagAdapter.isCheckContent(tagAdapter.getItem(i), i))) {
                items.add(tagAdapter.getItem(i));
            }
        }
        return items;
    }
 
    /**
     * 去掉设置的互斥数据
     *
     * @return
     */
    public List getCheckedItemsFilter() {
        ArrayList items = new ArrayList();
        for (int i = 0; i < tagAdapter.getCount(); i++) {
            if (tagAdapter.isCheckContent(tagAdapter.getItem(i), i) != tagAdapter.getMutexCheck() &&
                    tagAdapter.isChecked(tagAdapter.isCheckContent(tagAdapter.getItem(i), i))) {
                items.add(tagAdapter.getItem(i));
            }
        }
        return items;
    }
 
 
    public TagFlowAdapter getAdapter() {
        return tagAdapter;
    }
 
    public Object getItem(int position) {
        return tagAdapter.getItem(position);
    }
 
 
    public void setOnCheckChangeListener(OnCheckChangeListener onCheckChangeListener) {
        this.onCheckChangeListener = onCheckChangeListener;
    }
 
    public void setOnTagClickListener(OnTagClickListener onTagClickListener) {
        this.onTagClickListener = onTagClickListener;
    }
 
 
    public interface OnCheckChangeListener {
        void onCheckChange(boolean isCheck, int position);
    }
 
    public interface OnTagClickListener {
        boolean onTagClick(View view, int position);
    }
}

其中使用到的TagFlowContainer

/**
 * @author zwl
 * @describe tag标签容器(处理选中效果)
 * @date on 2020/6/26
 */
public class TagFlowContainer extends FrameLayout implements Checkable {
 
    private boolean isChecked;
    private static final int[] CHECK_STATE = new int[]{android.R.attr.state_checked};
 
    public TagFlowContainer(@NonNull Context context) {
        this(context, null);
    }
 
    public TagFlowContainer(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    public TagFlowContainer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
 
 
    public View getTagView() {
        return getChildAt(0);
    }
 
    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        int[] states = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(states, CHECK_STATE);
        }
        return states;
    }
 
    @Override
    public void setChecked(boolean checked) {
        if (this.isChecked != checked) {
            this.isChecked = checked;
            refreshDrawableState();
        }
    }
 
    @Override
    public boolean isChecked() {
        return isChecked;
    }
 
    @Override
    public void toggle() {
        setChecked(!isChecked);
    }
}
package com.zwl.mybossdemo.filter.flow;
 
import android.view.View;
 
import java.util.HashSet;
import java.util.List;
 
/**
 * @author zwl
 * @describe tag 适配器
 * @date on 2020/6/26
 */
public abstract class TagFlowAdapter<T, S> {
    private List<T> datas;
 
    private HashSet<S> checkedList = new HashSet();
 
    private S mutexCheck;
 
    private OnDataChangedListener onDataChangedListener;
 
    public TagFlowAdapter() {
    }
 
    /**
     * 构造
     * @param datas 数据
     */
    public TagFlowAdapter(List<T> datas) {
        this.datas = datas;
    }
 
    /**
     * 构造
     * @param datas   数据
     * @param checkes 选中数据
     */
    public TagFlowAdapter(List<T> datas, S... checkes) {
        this.datas = datas;
        addChecked(checkes);
    }
 
    /**
     * 构造
     * @param datas      数据
     * @param mutexCheck 互斥数据(不限)设置后这个数据就和其他数据互斥
     */
    public TagFlowAdapter(List<T> datas, S mutexCheck) {
        this.datas = datas;
        setMutexCheck(mutexCheck);
    }
 
    /**
     * 构造
     * @param datas      数据
     * @param mutexCheck 互斥数据(不限)设置后这个数据就和其他数据互斥
     * @param checkes    选中数据
     */
    public TagFlowAdapter(List<T> datas, S mutexCheck, S... checkes) {
        this.datas = datas;
        setMutexCheck(mutexCheck);
        addChecked(checkes);
    }
 
    /**
     * 设置互斥数据
     * @param mutexCheck
     */
    public void setMutexCheck(S mutexCheck) {
        this.mutexCheck = mutexCheck;
    }
 
 
    /**
     * 添加选中数据
     * @param checkes
     */
    public void addChecked(S... checkes) {
        for (S s : checkes) {
            checkedList.add(s);
        }
    }
 
    /**
     * 判断数据是否被选中
     * @param s
     * @return
     */
    public boolean isChecked(S s) {
        return checkedList.contains(s);
    }
 
    /**
     * 数据的数量
     * @return
     */
    public int getCount() {
        return this.datas == null ? 0 : this.datas.size();
    }
 
    /**
     * 获取数据
     * @return
     */
    public List<T> getDatas() {
        return datas;
    }
 
    /**
     * 获取指定位置的数据
     * @param position
     * @return
     */
    public T getItem(int position) {
        return this.datas.get(position);
    }
 
    /**
     * 获取互斥(不限)数据
     * @return
     */
    public S getMutexCheck() {
        return mutexCheck;
    }
 
    /**
     * 获取当前选中的数据
     * @return
     */
    public HashSet getCheckedList() {
        return checkedList;
    }
 
    /**
     * 移除选中数据
     * @param o
     * @return
     */
    public boolean removeChecked(S o) {
        boolean b = checkedList.remove(o);
        return b;
    }
 
    /**
     * 移除互斥数据
     */
    public void removeMutexCheck() {
        if (mutexCheck != null && containsChecked(mutexCheck)) {
            checkedList.remove(mutexCheck);
        }
    }
 
    /**
     * 移除选中数据
     * @param checkes
     */
    public void removeChecked(S... checkes) {
        for (S s : checkes) {
            checkedList.remove(s);
        }
    }
 
    /**
     * 选中数据会否包含数据
     * @param s
     * @return
     */
    public boolean containsChecked(S s) {
        return checkedList.contains(s);
    }
 
 
    /**
     * 移除所有数据
     */
    public void removeAllChecked() {
        checkedList.clear();
    }
 
    public void setCheckedList(HashSet<S> checkedList) {
        this.checkedList = checkedList;
    }
 
 
    public abstract View getView(TagFlowContainer parent, T item, int position);
 
    public abstract S isCheckContent(T item, int position);
 
    public void notifyDataChanged() {
        if (this.onDataChangedListener != null) {
            this.onDataChangedListener.onChanged();
        }
 
    }
 
    void setOnDataChangedListener(OnDataChangedListener listener) {
        this.onDataChangedListener = listener;
    }
 
    interface OnDataChangedListener {
        void onChanged();
    }
 
}

下面使用的时候只要设置个适配器传入数据,不限数据,选中数据

filterList.setTagAdapter(new TagFlowAdapter<FilterBean, String>(filterGrop.filters, FilterBean.UNLIMITED, new String[]{FilterBean.UNLIMITED}) {
    @Override
    public View getView(TagFlowContainer parent, FilterBean item, int position) {
        TextView textView = (TextView) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_filter_tag, parent, false);
        textView.setText("" + getItem(position).name);
        textView.setGravity(Gravity.CENTER);
        return textView;
    }
 
    @Override
    public String isCheckContent(FilterBean item, int position) {
        return item.id;
    }
});

主要逻辑代码看下注释就可以了,下面附上项目github源码

相关文章
|
1月前
|
XML Android开发 数据安全/隐私保护
10. 【Android教程】网格布局 GridLayout
10. 【Android教程】网格布局 GridLayout
26 1
|
1月前
|
Android开发
08. 【Android教程】相对布局 RelativeLayout
08. 【Android教程】相对布局 RelativeLayout
16 0
|
2月前
|
Android开发
定制Android关机界面
定制Android关机界面
19 0
|
24天前
|
编解码 安全 Android开发
探索iOS与Android开发的差异:从界面到性能
【6月更文挑战第10天】在移动应用开发的广阔天地中,iOS和Android两大平台各占山头,它们在设计理念、用户体验、性能优化等方面展现出独特的魅力。本文将深入探讨这两大系统在开发过程中的主要差异,从用户界面设计到性能调优,揭示各自背后的技术逻辑与创新策略,为开发者提供全面的视角和实用的开发指南。
|
2天前
Android-自定义流布局标签
Android-自定义流布局标签
|
28天前
|
XML Android开发 数据格式
【Android UI】使用RelativeLayout与TableLayout实现登录界面
【Android UI】使用RelativeLayout与TableLayout实现登录界面
30 5
|
7天前
|
Android开发
深入了解 Android 中的 FrameLayout 布局
深入了解 Android 中的 FrameLayout 布局
6 0
|
7天前
|
Android开发 开发者
深入了解 Android 中的 RelativeLayout 布局
深入了解 Android 中的 RelativeLayout 布局
9 0
|
7天前
|
Android开发 UED 开发者
Android 中的 LinearLayout 布局
Android 中的 LinearLayout 布局
9 0
|
2月前
|
存储 传感器 Android开发
构建高效Android应用:从优化布局到提升性能
【5月更文挑战第13天】 在竞争激烈的移动应用市场中,一个高效的Android应用不仅需要具备直观的用户界面和丰富的功能,还要确保流畅的性能和快速的响应时间。本文将深入探讨如何通过优化布局设计、减少资源消耗以及利用系统提供的API来提升Android应用的性能。我们将分析布局优化的策略,讨论内存使用的常见陷阱,并介绍异步处理和电池寿命的考量。这些技术的综合运用将帮助开发者构建出既美观又高效的Android应用。