Android —— 自定义View中,你应该知道的知识点(1),2024年最新百度android面试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

//获得容器中子View的个数

int childCount = getChildCount();

//记录每一行View的总宽度

int totalLineWidth = 0;

//记录每一行最高View的高度

int perLineMaxHeight = 0;

//记录当前ViewGroup的总高度

int totalHeight = 0;

for (int i = 0; i < childCount; i++) {

View childView = getChildAt(i);

//对子View进行测量

measureChild(childView, widthMeasureSpec, heightMeasureSpec);

MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();

//获得子View的测量宽度

int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

//获得子View的测量高度

int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

if (totalLineWidth + childWidth > widthSize) {

//统计总高度

totalHeight += perLineMaxHeight;

//开启新的一行

totalLineWidth = childWidth;

perLineMaxHeight = childHeight;

} else {

//记录每一行的总宽度

totalLineWidth += childWidth;

//比较每一行最高的View

perLineMaxHeight = Math.max(perLineMaxHeight, childHeight);

}

//当该View已是最后一个View时,将该行最大高度添加到totalHeight中

if (i == childCount - 1) {

totalHeight += perLineMaxHeight;

}

}

//如果高度的测量模式是EXACTLY,则高度用测量值,否则用计算出来的总高度(这时高度的设置为wrap_content)

heightSize = heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight;

setMeasuredDimension(widthSize, heightSize);

}

上述 onMeasure 方法的主要目的有 2 个:

1.调用 measureChild 方法递归测量子 View;

2.通过叠加每一行的高度,计算出最终 FlowLayout 的最终高度 totalHeight。

FlowLayout的onLayout方法

上面的 FlowLayout 中的 onMeasure 方法只是计算出 ViewGroup 的最终显示宽高,但是并没有规定某一个子 View 应该显示在何处位置。要定义 ViewGroup 内部子 View 的显示规则,则需要复写并实现 onLayout 方法。

onLayout是一个抽象方法,也就是说每一个自定义 ViewGroup 都必须主动实现如何排布子 View,具体就是遍历每一个子 View,调用 child.(l, t, r, b) 方法来为每个子 View 设置具体的布局位置。四个参数分别代表左上右下的坐标位置,一个简易的 FlowLayout 实现如下:

@Override

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

mAllViews.clear();

mPerLineMaxHeight.clear();

//存放每一行的子View

List lineViews = new ArrayList<>();

//记录每一行已存放View的总宽度

int totalLineWidth = 0;

//记录每一行最高View的高度

int lineMaxHeight = 0;

/遍历所有View,将View添加到List<List>集合中******/

//获得子View的总个数

int childCount = getChildCount();

for (int i = 0; i < childCount; i++) {

View childView = getChildAt(i);

MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();

int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

if (totalLineWidth + childWidth > getWidth()) {

mAllViews.add(lineViews);

mPerLineMaxHeight.add(lineMaxHeight);

//开启新的一行

totalLineWidth = 0;

lineMaxHeight = 0;

lineViews = new ArrayList<>();

}

totalLineWidth += childWidth;

lineViews.add(childView);

lineMaxHeight = Math.max(lineMaxHeight, childHeight);

}

//单独处理最后一行

mAllViews.add(lineViews);

mPerLineMaxHeight.add(lineMaxHeight);

/遍历集合中的所有View并显示出来/

//表示一个View和父容器左边的距离

int mLeft = 0;

//表示View和父容器顶部的距离

int mTop = 0;

for (int i = 0; i < mAllViews.size(); i++) {

//获得每一行的所有View

lineViews = mAllViews.get(i);

lineMaxHeight = mPerLineMaxHeight.get(i);

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

View childView = lineViews.get(j);

MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();

int leftChild = mLeft + lp.leftMargin;

int topChild = mTop + lp.topMargin;

int rightChild = leftChild + childView.getMeasuredWidth();

int bottomChild = topChild + childView.getMeasuredHeight();

//四个参数分别表示View的左上角和右下角

childView.layout(leftChild, topChild, rightChild, bottomChild);

mLeft += lp.leftMargin + childView.getMeasuredWidth() + lp.rightMargin;

}

mLeft = 0;

mTop += lineMaxHeight;

}

}

一道滴滴面试题


之前在面试滴滴时碰到了这样一首题目,这个问题如果你如果理解了,相信你已经充分掌握了自定义View的measure过程

Activity内根布局LinearLayout,背景颜色为红色,宽高为wrap_content

内部包含View背影颜色为蓝色,宽高也为wrap_content

求界面颜色

<LinearLayout

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:background=“@color/red”

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

<View

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:background=“@color/blue”

/>

答案是蓝色

在下当时想当然的认为,既然都是wrap_content,界面颜色应该是白色。但是正确答案是蓝色

下面就来分析下具体原因

LinearLayout的onMeasure()

onMeasure()中比较简单,但是这里我们需要明确一下,这个方法的参数是什么含义:

MeasureSpec就不用多说了,记录当前View的尺寸和测量模式

另外明确一点,这里的MeasureSpec是父View的

/**

  • @param widthMeasureSpec horizontal space requirements as imposed by the parent.

  • @param heightMeasureSpec vertical space requirements as imposed by the parent.

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

if (mOrientation == VERTICAL) {

measureVertical(widthMeasureSpec, heightMeasureSpec);

} else {

measureHorizontal(widthMeasureSpec, heightMeasureSpec);

}

}

这里咱们就选measureVertical()追进去,方法里的边界条件非常的多,但其中对于子View的测量过程比较的简单,遍历所有的子View,挨个调用measureChildBeforeLayout()方法,而这个方法最终会走到ViewGroup中的measureChildWithMargins():

protected void measureChildWithMargins(View child,

int parentWidthMeasureSpec, int widthUsed,

int parentHeightMeasureSpec, int heightUsed) {

// 这个方法主要就是做了一件事情:通过子View的LayoutParams和父View的MeasureSpec来决定子View的MeasureSpec

final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin

  • widthUsed, lp.width);

final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin

  • heightUsed, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

生成子View的MeasureSpec

这部分逻辑主要在getChildMeasureSpec()方法中,我们直接追进去就好了:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

// 省略部分初始化代码

switch (specMode) {

case MeasureSpec.EXACTLY:

if (childDimension >= 0) {

resultSize = childDimension;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.MATCH_PARENT) {

resultSize = size;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

}

break;

case MeasureSpec.AT_MOST:

if (childDimension >= 0) {

resultSize = childDimension;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.MATCH_PARENT) {

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

}

break;

case MeasureSpec.UNSPECIFIED:

if (childDimension >= 0) {

resultSize = childDimension;

resultMode = MeasureSpec.EXACTLY;

} else if (childDimension == LayoutParams.MATCH_PARENT) {

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

resultMode = MeasureSpec.UNSPECIFIED;

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

resultMode = MeasureSpec.UNSPECIFIED;

}

break;

}

return MeasureSpec.makeMeasureSpec(resultSize, resultMode);

}

这部分代码,就是Google定的规则,也没什么好说的。总结起来就是《Android开发艺术探索》中的那张图:

看了这个,咱们就可以思考一下咱们开篇遇到的问题:父View(LinearLayout)是wrap_content,子View是wrap_parent,那么子View的MeasureSpec是什么样子?

有了上边的分析,我们很容易得出答案:parentSize + AT_MOST。因此咱们就知道这种场景下,子View的wrap_parent意味自己的宽高就是父View的宽高。那么此时父View的宽高是多少呢?

由于这里的父View已经是根View了,那么它的外边便是DecorView,而DecorView的MeasureSpec相对简单些,直接基于Window的宽高和自身的LayoutParams进行计算。

private static int getRootMeasureSpec(int windowSize, int rootDimension) {

int measureSpec;

switch (rootDimension) {

case ViewGroup.LayoutParams.MATCH_PARENT:

measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);

break;

case ViewGroup.LayoutParams.WRAP_CONTENT:

measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);

break;

default:

measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);

break;

}

return measureSpec;

}

public void setContentView(View view) {

setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

}

因此这种场景下,DecorView的MeasureSpec是屏幕宽高 + EXACTLY,那么父View(LinearLayout)的宽高就很明确了:parentSize + AT_MOST。

1.子View(TextView)的MeasureSpec是parentSize + AT_MOST

2.父View(LinearLayout)的MeasureSpec是parentSize + AT_MOST

3.DecorView的MeasureSpec是屏幕的size + EXACTLY

执行子View的measure()方法

最后

答应大伙的备战金三银四,大厂面试真题来啦!

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

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

07页Android开发相关源码解析》**

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

[外链图片转存中…(img-Jk7EFNBj-1713663787016)]

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

[外链图片转存中…(img-CJrW167Z-1713663787016)]

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-c4RycZxd-1713663787016)]

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值