2024年Android自定义ViewGroup实战-----流式布局,15个经典面试问题及答案例子

Android高级架构师

由于篇幅问题,我呢也将自己当前所在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,还有一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记。

  • 330页PDF Android学习核心笔记(内含上面8大板块)

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

  • Android BAT部分大厂面试题(有解析)

好了,以上便是今天的分享,希望为各位朋友后续的学习提供方便。觉得内容不错,也欢迎多多分享给身边的朋友哈。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

/**

 * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高 

 */  

@Override  

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  

{  

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);  

    // 获得它的父容器为它设置的测量模式和大小  

    int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);  

    int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);  

    int modeWidth = MeasureSpec.getMode(widthMeasureSpec);  

    int modeHeight = MeasureSpec.getMode(heightMeasureSpec);  



    Log.e(TAG, sizeWidth + "," + sizeHeight);  



    // 如果是warp_content情况下,记录宽和高  

    int width = 0;  

    int height = 0;  

    /** 

     * 记录每一行的宽度,width不断取最大宽度 

     */  

    int lineWidth = 0;  

    /** 

     * 每一行的高度,累加至height 

     */  

    int lineHeight = 0;  



    int cCount = getChildCount();  



    // 遍历每个子元素  

    for (int i = 0; i < cCount; i++)  

    {  

        View child = getChildAt(i);  

        // 测量每一个child的宽和高  

        measureChild(child, widthMeasureSpec, heightMeasureSpec);  

        // 得到child的lp  

        MarginLayoutParams lp = (MarginLayoutParams) child  

                .getLayoutParams();  

        // 当前子空间实际占据的宽度  

        int childWidth = child.getMeasuredWidth() + lp.leftMargin  

                + lp.rightMargin;  

        // 当前子空间实际占据的高度  

        int childHeight = child.getMeasuredHeight() + lp.topMargin  

                + lp.bottomMargin;  

        /** 

         * 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行 

         */  

        if (lineWidth + childWidth > sizeWidth-getPaddingLeft()-getPaddingRight())  

        {  

            width = Math.max(lineWidth, width);// 取最大的  

            lineWidth = childWidth; // 重新开启新行,开始记录  

            // 叠加当前高度,  

            height += lineHeight;  

            // 开启记录下一行的高度  

            lineHeight = childHeight;  

        } else  

        // 否则累加值lineWidth,lineHeight取最大高度  

        {  

            lineWidth += childWidth;  

            lineHeight = Math.max(lineHeight, childHeight);  

        }  

        // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较  

        if (i == cCount - 1)  

        {  

            width = Math.max(width, lineWidth);  

            height += lineHeight;  

        }  



    }  

    setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth  

            : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight  

            : height);  



}  

首先得到其父容器传入的测量模式和宽高的计算值,然后遍历所有的childView,使用measureChild方法对所有的childView进行测量。然后如果父ViewGroup的宽和高设置为wrap\_content,我们通过测量计算得到所有childView的宽和高。最后根据测量模式,如果是MeasureSpec.EXACTLY则直接使用父ViewGroup传入的宽和高,否则设置为上面自己计算的宽和高。



其中第48行是考虑到假如我们父ViewGroup使用了padding这一属性的话,那么我们就得减去这个值。



第50行,原文中写的是 width = Math.max(lineWidth, childWidth);// 取最大的  这样会有个问题,假如第一行最宽,后面逐渐变小,那么我们取的width也就有问题了,故修改过来。  

还有第63到第67行,因为在计算最后一个控件时,不管换行不换行,我们都没有比较最后一个控件的宽(假如没换行)和将最后一个控件的高度加上去(假如是换行了)



  



**4.onLayout()方法**



onLayout中完成对所有childView的位置以及大小的指定  



/**

 * 存储所有的View,按行记录 

 */  

private List<List<View>> mAllViews = new ArrayList<List<View>>();  

/** 

 * 记录每一行的最大高度 

 */  

private List<Integer> mLineHeight = new ArrayList<Integer>();  

@Override  

protected void onLayout(boolean changed, int l, int t, int r, int b)  

{  

    mAllViews.clear();  

    mLineHeight.clear();  



    int width = getWidth();  



    int lineWidth = 0;  

    int lineHeight = 0;  

    // 存储每一行所有的childView  

    List<View> lineViews = new ArrayList<View>();  

    int cCount = getChildCount();  

    // 遍历所有的孩子  

    for (int i = 0; i < cCount; i++)  

    {  

        View child = getChildAt(i);  

        MarginLayoutParams lp = (MarginLayoutParams) child  

                .getLayoutParams();  

        int childWidth = child.getMeasuredWidth();  

        int childHeight = child.getMeasuredHeight();  



        // 如果已经需要换行  

        if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width-getPaddingLeft()-getPaddingRight())  

        {  

            // 记录这一行所有的View以及最大高度  

            mLineHeight.add(lineHeight);  

            // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView  

            mAllViews.add(lineViews);  

            lineWidth = 0;// 重置行宽  

            lineViews = new ArrayList<View>();  

        }  

        /** 

         * 如果不需要换行,则累加 

         */  

        lineWidth += childWidth + lp.leftMargin + lp.rightMargin;  

        lineHeight = Math.max(lineHeight, childHeight + lp.topMargin  

                + lp.bottomMargin);  

        lineViews.add(child);  

    }  

    // 记录最后一行  

    mLineHeight.add(lineHeight);  

    mAllViews.add(lineViews);  



    int left = getPaddingLeft();

int top = getPaddingTop();

    // 得到总行数  

    int lineNums = mAllViews.size();  

    for (int i = 0; i < lineNums; i++)  

    {  

        // 每一行的所有的views  

        lineViews = mAllViews.get(i);  

        // 当前行的最大高度  

        lineHeight = mLineHeight.get(i);  



        Log.e(TAG, "第" + i + "行 :" + lineViews.size() + " , " + lineViews);  

        Log.e(TAG, "第" + i + "行, :" + lineHeight);  



        // 遍历当前行所有的View  

        for (int j = 0; j < lineViews.size(); j++)  

        {  

            View child = lineViews.get(j);  

            if (child.getVisibility() == View.GONE)  

            {  

                continue;  

            }  

            MarginLayoutParams lp = (MarginLayoutParams) child  

                    .getLayoutParams();  



            //计算childView的left,top,right,bottom  

            int lc = left + lp.leftMargin;  

            int tc = top + lp.topMargin;  

            int rc =lc + child.getMeasuredWidth();  

            int bc = tc + child.getMeasuredHeight();  



            Log.e(TAG, child + " , l = " + lc + " , t = " + t + " , r ="  

                    + rc + " , b = " + bc);  



            child.layout(lc, tc, rc, bc);  

              

            left += child.getMeasuredWidth() + lp.rightMargin  

                    + lp.leftMargin;  

        }  

        left = getPaddingLeft();  

        top += lineHeight;  

    }  



}  

allViews的每个Item为每行所有View的List集合。



mLineHeight记录的为每行的最大高度。



23-48行,遍历所有的childView,用于设置allViews的值,以及mLineHeight的值。



57行,根据allViews的长度,遍历所有的行数



67-91行,遍历每一行的中所有的childView,对childView的left , top , right , bottom 进行计算,和定位。



92-93行,重置left和top,准备计算下一行的childView的位置。



好了,到此完成了所有的childView的绘制区域的确定,到此,我们的FlowLayout的代码也结束了



  



由于考虑到了padding,第32行要减去padding的大小,53、54行初始的left和top值也要相应的变化,同理第92行  



其中第28-29行,利用的是view.getMeasureWidth()方法,为什么不利用view.getWidth()方法呢,其实在这个例子中,你用这个也没什么影响。但是最好是选择前者,其中下图是两者的最直接的区别的显示,我就不解释了。  

![](https://img-blog.csdn.net/20161114114218085?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)  



左边是getHeight,右边是getMeasureHeight



  



**5.布局文件如下**



<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context="com.example.mydemo2.MainActivity" >



<com.example.mydemo2.MyFlowLayout

    android:id="@+id/myFlowLayout"

    android:layout_width="match_parent"

    android:layout_height="wrap_content" >



    <TextView

        style="@style/MyFlowLayout_viewStyle"

        android:text="Hello" />



    <TextView

       style="@style/MyFlowLayout_viewStyle"

        android:text="Android" />



    <TextView

        style="@style/MyFlowLayout_viewStyle"

        android:text="我是FlowLayout" />



    <TextView

         style="@style/MyFlowLayout_viewStyle"

        android:text="一个" />



    <TextView

        style="@style/MyFlowLayout_viewStyle"

        android:text="自定义的ViewGroup" />

    <TextView

        style="@style/MyFlowLayout_viewStyle"

        android:text="Hello" />



    <TextView

       style="@style/MyFlowLayout_viewStyle"

        android:text="Android" />



    <TextView

        style="@style/MyFlowLayout_viewStyle"

        android:text="我是FlowLayout" />



    <TextView

         style="@style/MyFlowLayout_viewStyle"

        android:text="一个" />



    <TextView

        style="@style/MyFlowLayout_viewStyle"

        android:text="自定义的ViewGroup" />

    <TextView

        style="@style/MyFlowLayout_viewStyle"

        android:text="Hello" />



    <TextView

       style="@style/MyFlowLayout_viewStyle"

        android:text="Android" />

最后

感谢您的阅读,在文末给大家准备一个福利。本人从事Android开发已经有十余年,算是一名资深的移动开发架构师了吧。根据我的观察发现,对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。

当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。

当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

[外链图片转存中…(img-9fkIWIq2-1715685826906)]

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值