Android 热门标签布局(模仿boos招聘的多选效果)

效果图 

宽度填满效果图
宽度填满效果图
宽度不填满效果图
宽度不填满效果图

 实现上图布局,关键是重写ViewGroup的 onMeasure 和 onLayout方法

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法:作用是确定ViewGroup的宽高。
  • onLayout(boolean changed, int left, int top, int right, int bottom) 方法:作用是设置ViewGroup里的View的位置

实现思路

  1. 在onMeasure(int w, int h)方法里,获取ViewGroup的可用宽度,使用getChildCount()获取view的数量,循环获取view的对象并使用measureChild(child, widthMeasureSpec, heightMeasureSpec)测量view的宽高,通过getMeasuredWidth()getMeasuredHeight()获取view的测量宽高,当view的测量宽度累加大于ViewGroup的可用宽度时,就表示ViewGroup需要添加一行来放置多出来的view
  2. 在onLayout(boolean changed, int left, int top, int right, int bottom)方法里,根据onMeasure(int w, int h)方法处理后获得的总行数(此时,一行有多少个view和一行的高度都已知),通过view.layout(left,top,right,bottom)来放置view的位置

实现的代码

package com.wudengwei.tagflowlayout;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by wudengwei
 * on 2018/12/4
 */
public class TagFlowLayout extends ViewGroup {
    /*保存所有的view*/
    private List<Line> mLines;
    /*正在使用的行*/
    private Line mCurrentLine;
    /*tag之间的间隔*/
    private int tagSpace;
    /*line之间的间隔*/
    private int lineSpace;
    /*是否让所有的line的使用宽度等于最大宽度*/
    private boolean fillAllLineWidth = false;

    public void setFillAllLineWidth(boolean fillAllLineWidth) {
        this.fillAllLineWidth = fillAllLineWidth;
    }
    /*tag点击事件回调*/
    private OnItemClickListener mOnItemClickListener;

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

    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);
        mLines = new ArrayList<>();
        tagSpace = dp2px(context,10f);
        lineSpace = dp2px(context,10f);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.e("onMeasure",""+widthMeasureSpec);
        /*清空*/
        mLines.clear();
        mCurrentLine = null;
        /*总宽度*/
        int totalWidth = MeasureSpec.getSize(widthMeasureSpec);
        /*最大可以使用的宽度*/
        int maxWidth = totalWidth - getPaddingLeft() - getPaddingRight();
        for (int i=0;i<getChildCount();i++) {
            View child = getChildAt(i);
            /*测量孩子的宽高*/
            measureChild(child,widthMeasureSpec,heightMeasureSpec);
            /*无论如何,必须存在一个当前使用的line*/
            if (mCurrentLine == null) {
                mCurrentLine = new Line(maxWidth,tagSpace);
                mCurrentLine.fillWidth = fillAllLineWidth;
                mCurrentLine.addView(child);
                mLines.add(mCurrentLine);
            } else {
                /*当前的line可以添加view就添加,否则再创建一个line(下一行)*/
                if (mCurrentLine.canAddView(child)) {
                    mCurrentLine.addView(child);
                } else {
                    mCurrentLine = new Line(maxWidth,tagSpace);
                    mCurrentLine.fillWidth = fillAllLineWidth;
                    mCurrentLine.addView(child);
                    mLines.add(mCurrentLine);
                }
            }
        }
        /*总高度*/
        int totalHeight = getPaddingTop() + getPaddingBottom();
        for (Line line : mLines) {
            totalHeight = totalHeight + line.height;
        }
        totalHeight = totalHeight + (mLines.size()-1) * lineSpace;
        /*设置viewGroup的大小*/
        setMeasuredDimension(totalWidth,totalHeight);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        Log.e("onLayout",""+changed);
        int marginTop = getPaddingTop();
        int marginLeft = getPaddingLeft();
        /*设置line中的所有view的位置*/
        for (int i=0;i<mLines.size();i++){
            Line line = mLines.get(i);
            /*最后一行不填满宽度*/
            if (i == mLines.size()-1) {
                line.fillWidth = false;
            }
            line.layout(marginLeft,marginTop);
            marginTop += lineSpace + line.height;
        }
        /*添加点击事件*/
        int index = 0;
        for (Line line : mLines) {
            for (int i=0;i<line.viewList.size();i++) {
                final int position = i + index;
                View view = line.viewList.get(i);
                view.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if (mOnItemClickListener != null)
                            mOnItemClickListener.click(view,position);
                    }
                });
            }
            index = index + line.viewList.size();
        }
    }

    /**
     * 保存一行的view
     */
    private class Line {
        private List<View> viewList;
        private int usedWidth;//已经使用的宽度
        private int maxWidth;//最大可以使用的宽度
        private int viewSpace;//view之间的间隔
        private int height;//高度
        private boolean fillWidth = false;//是否设置所有view的宽度和≈maxWidth

        public Line(int maxWidth, int viewSpace) {
            this.maxWidth = maxWidth;
            this.viewSpace = viewSpace;
            viewList = new ArrayList<>();
        }

        /*在可以添加view的前提下使用*/
        public void addView(View view) {
            int viewWidth = view.getMeasuredWidth();
            int viewHeight = view.getMeasuredHeight();
            if (viewList.size() == 0) {
                /*如果view的宽度大于最大可用宽度,强制设置为最大宽度*/
                usedWidth = viewWidth > maxWidth ? maxWidth : viewWidth;
                height = viewHeight;
            } else {
                usedWidth = usedWidth + viewSpace + viewWidth;
                height = viewHeight > height ? viewHeight : height;
            }
            viewList.add(view);
        }

        /*判断viewList是否可以添加view*/
        public boolean canAddView(View view) {
            int viewWidth = view.getMeasuredWidth();
            if (viewList.size() == 0)
                return true;
            /*已经使用的宽度加上viewWidth后,小于或等于表示可以添加*/
            return viewWidth + viewSpace + usedWidth <= maxWidth;
        }

        public void layout(int marginLeft, int marginTop) {
            /*avgWidth是为了让line的view铺满maxWidth,右边不会有空白*/
            int avgWidth = (int) ((maxWidth - usedWidth) * 1f / viewList.size());
            //计算控件的上下左右的位置
            for(View view : viewList) {
                /*获取view的测量宽度*/
                int viewWidth = view.getMeasuredWidth();
                int viewHeight = view.getMeasuredHeight();
                /*重新设置view的宽度,让所有view的宽度和≈maxWidth*/
                if(avgWidth > 0 && fillWidth){
                    /*按照给定的宽高设置view的大小*/
                    int specWidth = MeasureSpec.makeMeasureSpec(viewWidth + avgWidth,MeasureSpec.EXACTLY);
                    int specHeight = MeasureSpec.makeMeasureSpec(viewHeight,MeasureSpec.EXACTLY);
                    view.measure(specWidth,specHeight);
                    // 重新获取宽度和高度
                    viewWidth = view.getMeasuredWidth();
                    viewHeight = view.getMeasuredHeight();
                }
                /*extraTop为了让view垂直居中而取的参数(因为viewHeight的大小不固定)*/
                int extraTop = (int) ((height - viewHeight)*0.5f);

                int left = marginLeft;
                int top = marginTop + extraTop;
                int right = left + viewWidth;
                int bottom = top + viewHeight;
                //摆放每一个孩子的位置
                view.layout(left,top,right,bottom);
                marginLeft += viewWidth + viewSpace;
            }
        }
    }

    public interface OnItemClickListener {
        void click(View view, int position);
    }

    /*dp转px*/
    private int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }
}

本文代码下载

参考:https://blog.csdn.net/wk843620202/article/details/51673696

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值