final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
…
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
}
…
}
在代码中可以看到通过getRootMeasureSpec()方法获取了DecorView的MeasureSpec。
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can’t resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
getRootMeasureSpec()也不复杂,在方法中可以看出如果是LayoutParams.MATCH_PARENT,那么DecorView的大小就是Window的大小;如果是LayoutParams.WRAP_CONTENT,那么DecorView的大小不确定。
对于普通的View,MeasureSpec来自于父布局(ViewGroup)生成。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
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时与父布局的MeasureSpec以及padding相关,同时也与View本身的margin有关。
MeasureSpec中UNSPECIFIED的用途
UNSPECIFIED主要在一线父View不限制子View宽高的情况下使用,比如ScrollView
1.UNSPECIFIED会在ScrollView的measure方法里传给子View
2.子View收到UNSPECIFIED,会根据自己的实际内容大小来决定高度
3.UNSPECIFIED与AT_MOST的区别就是,它没有最大size限定这也说明UNSPECIFIED在ScrollView里很实用,因为ScrllView不需要限定子View的大小,它可以滚动嘛
实现自定义View主要需要解决以下3个问题
1.自定义控件的大小,也就是宽和高分别设置多少;
2.如果是 ViewGroup,如何合理安排其内部子 View 的摆放位置。
3.如何根据相应的属性将 UI 元素绘制到界面;
以上 3 个问题依次在如下 3 个方法中得到解决:
onMeasure,onLayout,onDraw
FlowLayout的onMeasure方法
因为自定义的控件是一个容器,onMeasure 方法会更加复杂一些。因为 ViewGroup 在测量自己的宽高之前,需要先确定其内部子 View 的所占大小,然后才能确定自己的大小。
如下所示:
//测量控件的宽和高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获得宽高的测量模式和测量值
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获得容器中子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”
/>
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
面试复习路线,梳理知识,提升储备
自己的知识准备得怎么样,这直接决定了你能否顺利通过一面和二面,所以在面试前来一个知识梳理,看需不需要提升自己的知识储备是很有必要的。
关于知识梳理,这里再分享一下我面试这段时间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)
资料获取方式:前往我的GitHub
- 架构师筑基必备技能
- Android高级UI与FrameWork源码
- 360°全方面性能调优
- 解读开源框架设计思想
- NDK模块开发
- 微信小程序
- Hybrid 开发与Flutter
知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结:
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
电子书和笔记,这些笔记将各个知识点进行了完美的总结:
[外链图片转存中…(img-TQTRmsNU-1710763491920)]
《960全网最全Android开发笔记》
[外链图片转存中…(img-WB80QUUk-1710763491921)]
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
[外链图片转存中…(img-ysa6BRFg-1710763491921)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。