文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2022-06-06 13:54

关注

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

先上图!!!

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

 自定义ViewGroup详解

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

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

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

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

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

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

MeasureSpec.EXACTLY:这种就相当于xml中设置了match_parent或者固定dp值。

MeasureSpec.AT_MOST:这种就相当于xml中设置了wrap_content。
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 = 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 iterator = viewMap.keySet().iterator();
            int curX = l;
            int curY = t;
            int lastRowH = 0;
            while (iterator.hasNext()){
                int row = iterator.next();
                curY += MARGIN_VETICAL;
                ArrayList 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的位置。

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


public class SearchLayout extends ViewGroup {
    private int count;
    //设置水平垂直间距
    private int MARGIN_HORIZTAL;
    private int MARGIN_VETICAL;
    private HashMap<Integer, ArrayList> viewMap;
    public SearchLayout(Context context) {
        super(context);
        init(context);
    }
    public SearchLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(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 = 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 iterator = viewMap.keySet().iterator();
            int curX = l;
            int curY = t;
            int lastRowH = 0;
            while (iterator.hasNext()){
                int row = iterator.next();
                curY += MARGIN_VETICAL;
                ArrayList 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> map, int row, int index){
        ArrayList columns = map.get(row);
        if (columns != null && columns.size() != 0){
            columns.add(index);
            map.put(row, columns);
        }else{
            ArrayList integers = new ArrayList();
            integers.add(index);
            map.put(row, integers);
        }
    }
}

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


作者:no_loafer


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯