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源码

相关文章
|
2月前
|
ARouter Android开发
Android不同module布局文件重名被覆盖
Android不同module布局文件重名被覆盖
|
4月前
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
71 10
|
2月前
|
XML 数据可视化 Android开发
Android应用界面
Android应用界面中的布局和控件使用,包括相对布局、线性布局、表格布局、帧布局、扁平化布局等,以及AdapterView及其子类如ListView的使用方法和Adapter接口的应用。
35 0
Android应用界面
|
3月前
|
XML Android开发 UED
💥Android UI设计新风尚!掌握Material Design精髓,让你的界面颜值爆表!🎨
随着移动应用市场的蓬勃发展,用户对界面设计的要求日益提高。为此,掌握由Google推出的Material Design设计语言成为提升应用颜值和用户体验的关键。本文将带你深入了解Material Design的核心原则,如真实感、统一性和创新性,并通过丰富的组件库及示例代码,助你轻松打造美观且一致的应用界面。无论是色彩搭配还是动画效果,Material Design都能为你的Android应用增添无限魅力。
80 1
|
2月前
|
ARouter Android开发
Android不同module布局文件重名被覆盖
Android不同module布局文件重名被覆盖
154 0
|
4月前
|
编解码 Android开发
【Android Studio】使用UI工具绘制,ConstraintLayout 限制性布局,快速上手
本文介绍了Android Studio中使用ConstraintLayout布局的方法,通过创建布局文件、设置控件约束等步骤,快速上手UI设计,并提供了一个TV Launcher界面布局的绘制示例。
71 1
|
4月前
|
Android开发 iOS开发 C#
Xamarin.Forms:从零开始的快速入门指南——打造你的首个跨平台移动应用,轻松学会用C#和XAML构建iOS与Android通用界面的每一个步骤
【8月更文挑战第31天】Xamarin.Forms 是一个强大的框架,让开发者通过单一共享代码库构建跨平台移动应用,支持 iOS、Android 和 Windows。使用 C# 和 XAML,它简化了多平台开发流程并保持一致的用户体验。本指南通过创建一个简单的 “HelloXamarin” 应用演示了 Xamarin.Forms 的基本功能和工作原理。
106 0
|
5月前
|
Android开发 Kotlin
kotlin开发安卓app,如何让布局自适应系统传统导航和全面屏导航
使用`navigationBarsPadding()`修饰符实现界面自适应,自动处理底部导航栏的内边距,再加上`.padding(bottom = 10.dp)`设定内容与屏幕底部的距离,以完成全面的布局适配。示例代码采用Kotlin。
137 15
|
5月前
|
XML Android开发 UED
💥Android UI设计新风尚!掌握Material Design精髓,让你的界面颜值爆表!🎨
【7月更文挑战第28天】随着移动应用市场的发展,用户对界面设计的要求不断提高。Material Design是由Google推出的设计语言,强调真实感、统一性和创新性,通过模拟纸张和墨水的物理属性创造沉浸式体验。它注重色彩、排版、图标和布局的一致性,确保跨设备的统一视觉风格。Android Studio提供了丰富的Material Design组件库,如按钮、卡片等,易于使用且美观。
167 1
|
4月前
|
XML 数据可视化 API
Android经典实战之约束布局ConstraintLayout的实用技巧和经验
ConstraintLayout是Android中一款强大的布局管理器,它通过视图间的约束轻松创建复杂灵活的界面。相较于传统布局,它提供更高灵活性与性能。基本用法涉及XML定义约束,如视图与父布局对齐。此外,它支持百分比尺寸、偏移量控制等高级功能,并配有ConstraintSet和编辑器辅助设计。合理运用可显著提高布局效率及性能。
262 0