Android TagFlowLayout布局实现

假如想象 提交于 2020-03-01 14:02:21

一、简 介

由于移动互联网的发展,大数据技术的进步,app厂商或者犯罪分子会推广你喜欢的内容或者广告。对于各种类型的需求,通过关系数据库心亦不能满足需要,这种情况通过nosql数据库来存储用户兴趣。对于用户兴趣的标注,就是通过tag或者cookie等实现。在这方面,客户端需求也是很大,无论是供应链、内容还是地理定位,都有很大的需求。

这种标记方式比较常用

二、代码实现


public class TagFlowLayout extends ViewGroup {
            private final static String TAG = "TagFlowLayout";

            //自定义属性
            private int spacingBetweenItems;
            private int lineSpacing;

            private OnItemSelectedListener mOnItemSelectedListener;
            private OnItemRemovedlistener mOnItemRemovedListener;

            public TagFlowLayout(Context context) {
                this(context, null);
            }

            public TagFlowLayout(Context context, AttributeSet attrs) {
                this(context, attrs, 0);
            }

            public TagFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
                super(context, attrs, defStyleAttr);
                TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout);
                spacingBetweenItems = ta.getDimensionPixelSize(R.styleable.TagFlowLayout_leftAndRightSpace, 10);
        lineSpacing = ta.getDimensionPixelSize(R.styleable.TagFlowLayout_rowSpace, 10);
        ta.recycle();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //为所有的标签childView计算宽和高
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //获取高的模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //建议的高度
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //布局的宽度采用建议宽度(match_parent或者size),如果设置wrap_content也是match_parent的效果
        int width = MeasureSpec.getSize(widthMeasureSpec);

        int height;
        if (heightMode == MeasureSpec.EXACTLY) {
            //如果高度模式为EXACTLY(match_perent或者size),则使用建议高度
            height = heightSize;
        } else {
            //其他情况下(AT_MOST、UNSPECIFIED)需要计算计算高度
            int childCount = getChildCount();
            if (childCount <= 0) {
                height = 0;   //没有标签时,高度为0
            } else {
                int row = 1;  // 标签行数
                int widthSpace = width;// 当前行右侧剩余的宽度
                for (int i = 0; i < childCount; i++) {
                    View view = getChildAt(i);
                    //获取标签宽度
                    int childW = view.getMeasuredWidth();
                    Log.v(TAG, "标签宽度:" + childW + " 行数:" + row + "  剩余宽度:" + widthSpace);
                    if (widthSpace >= childW) {
                        //如果剩余的宽度大于此标签的宽度,那就将此标签放到本行
                        widthSpace -= childW;
                    } else {
                        row++;    //增加一行
                        //如果剩余的宽度不能摆放此标签,那就将此标签放入一行
                        widthSpace = width - childW;
                    }
                    //减去标签左右间距
                    widthSpace -= spacingBetweenItems;
                }
                //由于每个标签的高度是相同的,所以直接获取第一个标签的高度即可
                int childH = getChildAt(0).getMeasuredHeight();
                //最终布局的高度=标签高度*行数+行距*(行数-1)
                height = (childH * row) + lineSpacing * (row - 1);

                Log.v(TAG, "总高度:" + height + " 行数:" + row + "  标签高度:" + childH);
            }
        }

        //设置测量宽度和测量高度
        setMeasuredDimension(width, height);
    }



    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int row = 0;
        int right = 0;   // 标签相对于布局的右侧位置
        int botom;       // 标签相对于布局的底部位置

        if(getChildCount()<=0) return;

        final List<List<View>> mGroupViews = new ArrayList<>();
        List<View> rowsView = new ArrayList<>();
        mGroupViews.add(rowsView);

        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            int childW = childView.getMeasuredWidth();
            right += childW;
            if (right > (r - l)) {
                right = childW;
                rowsView = new ArrayList<>();
                mGroupViews.add(rowsView);
            }
            right += spacingBetweenItems;
            rowsView.add(childView);
        }

        for (List<View> lineViews : mGroupViews){

            int size = lineViews.size();

            int maxWidth = r-l;
            int maxLineViewsWidth = getLineChildrenSumWidth(lineViews);
            int dxWidth = maxWidth - maxLineViewsWidth-(size-1)*spacingBetweenItems;
            right = Math.max(0,dxWidth/2);

            for (int i=0;i<size;i++){

                View childView = lineViews.get(i);
                int childW = childView.getMeasuredWidth();
                int childH = childView.getMeasuredHeight();
                //右侧位置=本行已经占有的位置+当前标签的宽度
                right += childW;
                //底部位置=已经摆放的行数*(标签高度+行距)+当前标签高度
                botom = row * (childH + lineSpacing) + childH;
                childView.layout(right - childW, botom - childH, right, botom);
                right += spacingBetweenItems;
            }
            row++;
        }
    }

    private int getLineChildrenSumWidth(List<View> lineViews) {
        if(lineViews==null) return 0;
        int width = 0;
        for (int i=0;i<lineViews.size();i++){
            View childView = lineViews.get(i);
            width += childView.getMeasuredWidth();
        }

        return width;
    }

    private final ArrayList<TagBean> tags = new ArrayList<>();

    public void setTags(ArrayList<TagBean> tags) {
        if (tags == null || tags.isEmpty()) return;
        this.tags.clear();
        this.tags.addAll(tags);
        for (TagBean tag : this.tags) {
            addTag(tag,false);
        }
    }

    public void addTag(TagBean tagBean){
        addTag(tagBean,true);
    }
    public void addTag(TagBean tag,boolean addTag) {
        if(addTag){
            this.tags.add(tag);
        }
        final LayoutInflater inflater = LayoutInflater.from(getContext());
        final View view = inflater.inflate(R.layout.item_tag, this, false);
        view.setSelected(tag.isSelected());
        TextView text = view.findViewById(R.id.text);
        text.setText(tag.getContent());
        view.setTag(tag);
        handleViewClick(view);
        this.addView(view);
    }

    private void handleViewClick(View view) {
        if (view == null || view.getTag() == null) return;
        TagBean tag = ((TagBean) view.getTag());
        if (tag.isShowClose()) {
            View close = view.findViewById(R.id.close);
            close.setVisibility(VISIBLE);
            close.setOnClickListener((v) -> {
                removeTag(tag);
            if (mOnItemRemovedListener!= null){
                this.mOnItemRemovedListener.onItemRemoved(tag.getContent());
            }
            });
        } else {
            view.setOnClickListener((v) -> {
                tag.setSelected(!tag.isSelected());
                view.setSelected(tag.isSelected());

                if(mOnItemSelectedListener!=null){
                    this.mOnItemSelectedListener.onItemSelectedChanged(tag.isSelected(),tag,view);
                }
            });
        }
    }

    public void removeTag(TagBean tag) {
        this.tags.remove(tag);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childAt = getChildAt(i);
            if (childAt == null)
                continue;
            Object childTag = childAt.getTag();
            if (childTag == null)
                continue;
            if (childTag.equals(tag)) {
                this.removeViewAt(i);
                break;
            }
        }
    }

    public ArrayList<String> getSelectedTags() {
        if (tags.isEmpty()) return null;
        final ArrayList<String> list = new ArrayList<>();
        for (TagBean tag : this.tags) {
            if (!tag.isSelected())
                continue;
            list.add(tag.getContent());
        }
        return list;
    }

    public ArrayList<String> getAllTags() {
        if (tags.isEmpty()) return null;
        final ArrayList<String> list = new ArrayList<>();
        for (TagBean tag : this.tags) {
            list.add(tag.getContent());
        }
        return list;
    }

    public static class TagBean {
        private String content;
        private boolean selected;
        private boolean showClose;

        public TagBean(String content, boolean selected, boolean showClose) {
            this.content = content;
            this.selected = selected;
            this.showClose = showClose;
        }

        public String getContent() {
            return content;
        }

        public void setContent(String content) {
            this.content = content;
        }

        public boolean isSelected() {
            return selected;
        }

        public void setSelected(boolean selected) {
            this.selected = selected;
        }

        public boolean isShowClose() {
            return showClose;
        }

        public void setShowClose(boolean showClose) {
            this.showClose = showClose;
        }

        @Override
        public String toString() {
            return "TagBean{" +
                    "content='" + content + '\'' +
                    ", selected=" + selected +
                    '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            TagBean tagBean = (TagBean) o;
            return selected == tagBean.selected &&
                    showClose == tagBean.showClose &&
                    Objects.equals(content, tagBean.content);
        }

        @Override
        public int hashCode() {
            return Objects.hash(content, selected, showClose);
        }
    }


    public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
        this.mOnItemSelectedListener = onItemSelectedListener;
    }

    public interface OnItemSelectedListener{
        public void onItemSelectedChanged(boolean isSelected,TagBean bean,View tagView);
    }

    public void setOnItemRemovedlistener(OnItemRemovedlistener onItemRemovedlistener){
            this.mOnItemRemovedListener = onItemRemovedlistener;
    }

    public interface OnItemRemovedlistener{
        public void onItemRemoved(String s);
    }
}

属性定义

  <declare-styleable name="TagFlowLayout">
        <!--标签之间左右距离-->
        <attr name="leftAndRightSpace" format="dimension" />
        <!--标签行距-->
        <attr name="rowSpace" format="dimension" />
    </declare-styleable>

item_tag.xml布局定义

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/tag_outline_selector"
    android:gravity="center"
    android:orientation="horizontal"
    android:paddingLeft="15dp"
    android:paddingTop="8dp"
    android:paddingRight="12dp"
    android:paddingBottom="8dp">

    <TextView
        android:id="@+id/text"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="@color/tag_textcolor_selector"
        android:textSize="12sp"
        tools:text="Payment"
        android:maxLines="1"
        android:ellipsize="end"
        />

    <ImageView
        android:id="@+id/close"
        android:layout_width="18dp"
        android:layout_height="18dp"
        android:layout_marginLeft="5dp"
        android:paddingLeft="3dp"
        android:paddingRight="3dp"
        android:src="@drawable/ic_close"
        android:visibility="gone"
        tools:visibility="visible"
        />


</LinearLayout>

tag_outline_selector

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true">
        <shape>
            <corners android:radius="10000dip" />
            <solid android:color="@android:color/white" />
            <stroke android:width="1dp" android:color="@color/photo_detail_restore_color" />
        </shape>
    </item>
    <item android:state_selected="false">
        <shape>
            <corners android:radius="10000dip" />
            <solid android:color="@android:color/white" />
            <stroke android:width="1dp" android:color="#bfbfbf" />
        </shape>
    </item>
</selector>

效果一

效果二

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!