Android imitate Jingdong search history custom ViewGroup

In the history of Jingdong search, if the textview line is not displayed completely, it will wrap.

First picture! ! !

As shown in the figure, the custom viewgroup realizes the historical effect of Jingdong search.

 Detailed explanation of custom ViewGroup

First, let's talk about the implementation principle and the steps to customize the viewgroup implementation:

  1. Override onMesure () method to calculate the height and
  2. Override onLayout () method to calculate the placement of child views

Detailed onMesure method

The onMesure method is to calculate the total height of the current control after the child view is placed. In our example, the logic for calculating the height is to traverse the child view, and then the width of the child view has been accumulated. If the accumulated width of the child view is greater than the total width of the viewgroup, then The previous subview should be displayed on a new line.

First, use the measureChildren (widthMeasureSpec, heightMeasureSpec) method to trigger the onMesure method of the child view to calculate the width and height.

Then get the display mode and width and height of the viewgroup through the MeasureSpec class.

When it comes to display modes, it is necessary to explain that display modes are divided into three categories:

  1. MeasureSpec.EXACTLY: This is equivalent to setting match_parent or fixed dp value in xml.
  2. MeasureSpec.AT_MOST: This is equivalent to setting wrap_content in xml.
  3. MeasureSpec.UNSPECIFIED: This is equivalent to not setting the width and height.

Then we can know through the display mode. In our example of Jingdong, it is certainly impossible to set wrap_content, so we only need to measure MeasureSpec.EXACTLY. In the case of height, we usually give wrap_content, so the height only handles MeasureSpec .AT_MOST is enough. In other cases, we can simply setMeasuredDimension (0, 0).

Directly on the onMesure () code, there are comments, you can refer to the method to obtain the height

@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);
            }
        }
    }

Detailed onLayout method

We know that the onlayout method is to calculate the placement of child views in the viewgroup, then we can get the up, down, left, and right coordinate points of each child view. Our default initial coordinates are the left, top, right, and bottom of the parent element viewgroup. Then as long as the width and height values ​​of the child elements are known, plus the initial coordinates of our viewgroup, you can master view.layout (left, top, right, bottom); this method can be used to set the position of the coordinate points of the child veiw.

We code:

 @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;
                }
            }
        }
    }

Through the above explanation, we generally understand the two steps of the custom ViewGroup. OnMesure and onLayout methods are rewritten to calculate the width and height of the control and the position of the child view.

Welfare is here! ! ! ! ! ! ! ! The entire code is as follows!

/**
 * 自定义搜索历史容器
 * 实现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);
        }
    }
}

 

If you can help everyone, look forward to supporting bloggers, thank you!

Published 10 original articles · praised 7 · 10,000+ views

Guess you like

Origin blog.csdn.net/no_loafer/article/details/105539028