Android 仿京东搜索历史之自定义ViewGroup

仿照京东搜索历史中,如果textview一行显示不全则进行换行。

先上图!!!

如图所示,自定义viewgroup实现京东搜索历史效果。

 自定义ViewGroup详解

首先我们来讲一下实现原理,自定义viewgroup实现的步骤:

  1. 重写onMesure() 方法计算子view的高度和
  2. 重写onLayout() 方法计算子view的摆放位置

onMesure方法详解

onMesure方法是计算当前控件摆放子view后的总高度,我们的例子中计算高度的逻辑为,遍历子view,然后子view的宽度一直累加,如果子view的累加宽度大于viewgroup的总宽度,那么就应该把上一个子view换行显示。

首先通过measureChildren(widthMeasureSpec, heightMeasureSpec)方法来触发子view的onMesure方法来计算宽度高度。

然后通过MeasureSpec类获取viewgroup的显示模式跟宽高。

讲到显示模式,有必要讲解一下,显示模式分为三类:

  1. MeasureSpec.EXACTLY:这种就相当于xml中设置了match_parent或者固定dp值。
  2. MeasureSpec.AT_MOST:这种就相当于xml中设置了wrap_content。
  3. MeasureSpec.UNSPECIFIED:这种就相当于没有设置宽高。

那么通过显示模式我们可以知道,我们京东的例子,宽度肯定不可能设置wrap_content,所以我们只处MeasureSpec.EXACTLY这种情况就可以了,高度的话,我们给定的一般是wrap_content,所以高度只处理MeasureSpec.AT_MOST就可以了,其它情况直接我们给定setMeasuredDimension(0, 0)即可。

直接上onMesure()的代码,都有注释,大家可以参考一下获取高度的方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        count++;
        Log.e("lwd", "onmesure:" + count);
        //将所有子view重新调用onMesure方法测量
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //获取测量模式跟测量高度
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);
        int childCount = getChildCount();
        if (childCount == 0){
            //如果没有子view,则容易没有存在的意义
            setMeasuredDimension(0, 0);
        }else{
            viewMap = new HashMap<>();
            //这里先处理宽度为match_parent跟固定值的情况,因为我们的容器肯定不为wrap_content
            if (wMode == MeasureSpec.EXACTLY){
                //计算容器高度,根据子view的宽度相加是否大于容器的宽度,进行计算总高度
                int totalHeight = 0;
                int curWidth = 0;
                switch (hMode){
                    case MeasureSpec.EXACTLY:
                        break;
                    case MeasureSpec.AT_MOST://wrap_content时进行计算
                        int row = 0;
                        for (int i = 0; i < childCount; i++){
                            View child = getChildAt(i);
                            int childW = child.getMeasuredWidth();
                            int childH = child.getMeasuredHeight();
                            if (childW >= wSize){
                                //如果子view宽度大于等于容器的宽度,则高度累计,为一行
                                totalHeight += childH;
                                //当前width置为0
                                curWidth = 0;
                                addChild(viewMap, row, i);
                                //行数增加
                                row++;
                                totalHeight += MARGIN_VETICAL;
                            }else{
                                //如果子view小于容器宽度时,则计算子view的宽度和是否大于等于容器宽度,如果大于等于的话则进行换行
                                curWidth += childW;
                                if (i != 0){
                                    //第一次计算不加margin
                                    curWidth += MARGIN_HORIZTAL;//加上横向间距
                                }
                                if (curWidth > wSize){
                                    totalHeight += childH;
                                    curWidth = childW;
                                    row++;
                                    totalHeight += MARGIN_VETICAL;
                                }
                                addChild(viewMap, row, i);
                                if (totalHeight == 0){
                                    totalHeight += MARGIN_VETICAL;
                                    totalHeight += childH;
                                }
                            }
                        }
                        setMeasuredDimension(wSize, totalHeight);
                        break;
                    case MeasureSpec.UNSPECIFIED:
                        //如果没有设置高度的话,则容易没有存在的意义
                        setMeasuredDimension(0, 0);
                        break;
                }
            }else {
                //如果没有宽度的话,则容易没有存在的意义
                setMeasuredDimension(0, 0);
            }
        }
    }

onLayout方法详解

我们知道onlayout方法是计算子view在viewgroup中的摆放位置的,那么我们获取到各个子view的上下左右坐标点即可,我们默认的初始坐标为父元素viewgroup的left,top,right,bottom,那么子元素只要宽高值知道,加上我们viewgroup的初始坐标即可,大家掌握view.layout(left,top,right,bottom);这个方法即可,分别是设置子veiw的坐标点位置的。

我们上代码:

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (!viewMap.isEmpty()){
            Iterator<Integer> iterator = viewMap.keySet().iterator();
            int curX = l;
            int curY = t;
            int lastRowH = 0;
            while (iterator.hasNext()){
                int row = iterator.next();
                curY += MARGIN_VETICAL;
                ArrayList<Integer> indexs = viewMap.get(row);
                if (indexs != null && indexs.size() != 0){
                    for (int i = 0; i < indexs.size(); i++){
                        View child = getChildAt(indexs.get(i));
                        lastRowH = child.getMeasuredHeight();
                        int childWidth = child.getMeasuredWidth();
                        if (i != 0){
                            curX += MARGIN_HORIZTAL;
                        }
                        child.layout(curX, curY, curX + childWidth, curY + lastRowH);
                        curX += childWidth;
                    }
                    curY += lastRowH;
                    curX = l;
                    lastRowH = 0;
                }
            }
        }
    }

通过上面的讲解,我们大体明白了自定义ViewGroup的要领俩步走,onMesure跟onLayout方法重写,计算控件的宽高跟子view的位置。

福利来了!!!!!!!!全部代码如下!

/**
 * 自定义搜索历史容器
 * 实现textview自动换行排列
 * create by liwedong at 2020.04.15
 */
public class SearchLayout extends ViewGroup {
    private int count;
    //设置水平垂直间距
    private int MARGIN_HORIZTAL;
    private int MARGIN_VETICAL;
    private HashMap<Integer, ArrayList<Integer>> viewMap;

    public SearchLayout(Context context) {
        super(context);
        init(context);
    }

    public SearchLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    /**
     * 初始化
     * @param context
     */
    private void init(Context context) {
        MARGIN_HORIZTAL = DisplayMetricsUtils.setDp(context, 30);
        MARGIN_VETICAL = DisplayMetricsUtils.setDp(context, 30);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        count++;
        Log.e("lwd", "onmesure:" + count);
        //将所有子view重新调用onMesure方法测量
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //获取测量模式跟测量高度
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);
        int childCount = getChildCount();
        if (childCount == 0){
            //如果没有子view,则容易没有存在的意义
            setMeasuredDimension(0, 0);
        }else{
            viewMap = new HashMap<>();
            //这里先处理宽度为match_parent跟固定值的情况,因为我们的容器肯定不为wrap_content
            if (wMode == MeasureSpec.EXACTLY){
                //计算容器高度,根据子view的宽度相加是否大于容器的宽度,进行计算总高度
                int totalHeight = 0;
                int curWidth = 0;
                switch (hMode){
                    case MeasureSpec.EXACTLY:
                        break;
                    case MeasureSpec.AT_MOST://wrap_content时进行计算
                        int row = 0;
                        for (int i = 0; i < childCount; i++){
                            View child = getChildAt(i);
                            int childW = child.getMeasuredWidth();
                            int childH = child.getMeasuredHeight();
                            if (childW >= wSize){
                                //如果子view宽度大于等于容器的宽度,则高度累计,为一行
                                totalHeight += childH;
                                //当前width置为0
                                curWidth = 0;
                                addChild(viewMap, row, i);
                                //行数增加
                                row++;
                                totalHeight += MARGIN_VETICAL;
                            }else{
                                //如果子view小于容器宽度时,则计算子view的宽度和是否大于等于容器宽度,如果大于等于的话则进行换行
                                curWidth += childW;
                                if (i != 0){
                                    //第一次计算不加margin
                                    curWidth += MARGIN_HORIZTAL;//加上横向间距
                                }
                                if (curWidth > wSize){
                                    totalHeight += childH;
                                    curWidth = childW;
                                    row++;
                                    totalHeight += MARGIN_VETICAL;
                                }
                                addChild(viewMap, row, i);
                                if (totalHeight == 0){
                                    totalHeight += MARGIN_VETICAL;
                                    totalHeight += childH;
                                }
                            }
                        }
                        setMeasuredDimension(wSize, totalHeight);
                        break;
                    case MeasureSpec.UNSPECIFIED:
                        //如果没有设置高度的话,则容易没有存在的意义
                        setMeasuredDimension(0, 0);
                        break;
                }
            }else {
                //如果没有宽度的话,则容易没有存在的意义
                setMeasuredDimension(0, 0);
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (!viewMap.isEmpty()){
            Iterator<Integer> iterator = viewMap.keySet().iterator();
            int curX = l;
            int curY = t;
            int lastRowH = 0;
            while (iterator.hasNext()){
                int row = iterator.next();
                curY += MARGIN_VETICAL;
                ArrayList<Integer> indexs = viewMap.get(row);
                if (indexs != null && indexs.size() != 0){
                    for (int i = 0; i < indexs.size(); i++){
                        View child = getChildAt(indexs.get(i));
                        lastRowH = child.getMeasuredHeight();
                        int childWidth = child.getMeasuredWidth();
                        if (i != 0){
                            curX += MARGIN_HORIZTAL;
                        }
                        child.layout(curX, curY, curX + childWidth, curY + lastRowH);
                        curX += childWidth;
                    }
                    curY += lastRowH;
                    curX = l;
                    lastRowH = 0;
                }
            }
        }
    }

    public void addChild(HashMap<Integer, ArrayList<Integer>> map, int row, int index){
        ArrayList<Integer> columns = map.get(row);
        if (columns != null && columns.size() != 0){
            columns.add(index);
            map.put(row, columns);
        }else{
            ArrayList<Integer> integers = new ArrayList<>();
            integers.add(index);
            map.put(row, integers);
        }
    }
}

如果帮到大家,望点赞关注支持博主,谢谢!

发布了10 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/no_loafer/article/details/105539028