android onMeasure 实现
文章目录
- `View`的 `onMeasure`
- `onMeasure`要关注那些内容:
- 子控件的宽高能大于父控件吗?能
- 正常的处理逻辑
- `ViewGroup` 的 `onMeasure`
- `measureChild` 与 `measureChildWithMargins`的区别
View
的 onMeasure
先看普通 View
的测量方法
onMeasure
要关注那些内容:
子控件的宽高能大于父控件吗?能
<LinearLayout android:layout_width="400dp" android:layout_height="wrap_content" android:background="#3333" android:orientation="vertical"> <com.python.cat.potato.view.custom.ItemView android:layout_width="600dp" android:layout_height="wrap_content" android:background="#600f" /> <TextView android:id="@+id/tv_test" android:layout_width="700dp" android:layout_height="80dp" android:text="@string/task_count" /> LinearLayout>
View testView = view.findViewById(R.id.tv_test);testView.post(() -> { LogUtils.w("700dp==" + SizeUtils.dp2px(700)); LogUtils.w("testView " + testView.getWidth() + " , " + testView.getHeight()); ViewGroup parent = (ViewGroup) testView.getParent(); LogUtils.w("testView.parent " + parent.getWidth() + " , " + parent.getHeight());});
输出log:
2018-12-19 23:51:28.699 7096-7096/ W/LogUtils: 700dp==18372018-12-19 23:51:28.700 7096-7096/ W/LogUtils : testView 1838 , 2102018-12-19 23:51:28.700 7096-7096/W/LogUtils: testView.parent1050 , 236
通过日志可以看到系统控件并没有对这种情况进行处理,那么,自定义控件也就不用处理这种情况。
正常的处理逻辑
- 在
MeasureSpec.AT_MOST
的情况下,定义一个最小值,可以直接是0,或者是一个自定义的最小值,比如10dp. - 在其他情况下,使用父控件建议的宽高。
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// super.onMeasure(widthMeasureSpec, heightMeasureSpec); int fw = resolveMeasure(widthMeasureSpec, MIN_WIDTH); int fh = resolveMeasure(heightMeasureSpec, MIN_HEIGHT); LogUtils.d("fw=" + fw + " , " + fh); setMeasuredDimension(fw, fh);}private int resolveMeasure(int lenMeasureSpec, int minLength) { int len; int mode = MeasureSpec.getMode(lenMeasureSpec); int size = MeasureSpec.getSize(lenMeasureSpec); switch (mode) { case MeasureSpec.AT_MOST: len = minLength; break; default: len = size; break; } return len;}
在不重写 onLayout()
的前提下 ,setMeasuredDimension(fw, fh);
里面设置的大小,就是当前控件的尺寸大小了。
ViewGroup
的 onMeasure
对于容器而言,宽高往往跟里面的item
大小有关,而且,要考虑对item
设置margin
的支持。
measureChild
与 measureChildWithMargins
的区别
-
简单来说,就是一句话:
measureChildWithMargins
支持给child
设置margin
,而measureChild
不支持。 -
细分的话,通过这两个方法测量的
child
,在调用getMeasuredWidth/Height
的时候,获取的值是不一样的(前提是给child
设置了margin
)。
// 二者之间的关系可以用一行表达式来说明:int measuredChildWidth = child.lp.marginLeft + child.lp.marginRight + measureChildWithMarginsWidth;
这两个方法都是ViewGroup.java
里面的实现方法。其实源码很短,可以直接看一下。
/** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding. * The heavy lifting is done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param parentHeightMeasureSpec The height requirements for this view */ protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } /** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding * and margins. The child must have MarginLayoutParams The heavy lifting is * done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param widthUsed Extra space that has been used up by the parent * horizontally (possibly by other children of the parent) * @param parentHeightMeasureSpec The height requirements for this view * @param heightUsed Extra space that has been used up by the parent * vertically (possibly by other children of the parent) */ 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); }
只看宽度,高度同理。对于宽度,
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); // 这是 measureChild() 里面的宽度
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); // 这是 measureChildWithMargins() 里面的宽度设置
可以看出,对于 带margin
的测量方法,比起不带margin
而言,多了
+ lp.leftMargin + lp.rightMargin + widthUsed
这样的一个数值。那么,这里的 widthUsed
应该写多少呢?
我一开始也不知道这里应该写多少,但是对比一下上面的方法就知道了,下面这个方法的目的是支持child
设置margin
,那么,widthUsed
这个变量就没什么作用了,直接设置为0即可。
但是这样说似乎也没有很强的说服力,看一下很多系统自带的容器控件,在使用这个方法的时候,给widthUsed
赋值为多少。几乎全部是0,heightUsed
也是一样,全部是0。
比如FrameLayout
,ActionBarView
等。
好,那对于自定义的ViewGroup
的 onMeasure
就好办了。
比如,我们自定义一个线性布局,竖直排列的。那么,onMeasure
就可以这样写:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// super.onMeasure(widthMeasureSpec, heightMeasureSpec); int ws = MeasureSpec.getSize(widthMeasureSpec); int wm = MeasureSpec.getMode(widthMeasureSpec); int hs = MeasureSpec.getSize(heightMeasureSpec); int hm = MeasureSpec.getMode(heightMeasureSpec); int childCount = getChildCount(); int width = 0; int height = 0; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();// measureChild(child, widthMeasureSpec, heightMeasureSpec); measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); int measuredWidth = child.getMeasuredWidth(); int measuredHeight = child.getMeasuredHeight(); height += measuredHeight + lp.topMargin + lp.bottomMargin; width = measuredWidth + lp.leftMargin + lp.rightMargin; width = Math.min(width, ws); } int finalW = wm == MeasureSpec.EXACTLY ? ws : width; int finalH = hm == MeasureSpec.EXACTLY ? hs : height; com.apkfuns.logutils.LogUtils.i("measure: " + finalW + " , " + finalH); setMeasuredDimension(finalW, finalH); }
特别注意,这里面测量 child 的尺寸的目的还是为了设置自己的尺寸。
然后,既然测量的时候需要考虑 margin
,那么 布局的时候,也是一样。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { com.apkfuns.logutils.LogUtils.d(String.format("%s -- %s , %s , %s , %s", changed, l, t, r, b)); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();// LogUtils.d(lp); l = lp.leftMargin; t = t + lp.topMargin; r = l + child.getMeasuredWidth(); b = t + child.getMeasuredHeight(); child.layout(l, t, r, b); t += child.getMeasuredHeight() + lp.bottomMargin;// break; com.apkfuns.logutils.LogUtils.d("item width:" + (lp.rightMargin + lp.leftMargin + child.getMeasuredWidth())); } }
这样,就在布局 child
的时候,设置上了对应的margin
。
注意:以上只是实现了对margin
的支持,至于 padding
并没有。
完成代码(添加了对自身 padding
的支持):
public class LineLayout extends ViewGroup { public LineLayout(Context context) { this(context, null); } public LineLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LineLayout(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public LineLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); LogUtils.d("size w=" + w + " ,,, h=" + h); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// super.onMeasure(widthMeasureSpec, heightMeasureSpec); int ws = MeasureSpec.getSize(widthMeasureSpec); int wm = MeasureSpec.getMode(widthMeasureSpec); int hs = MeasureSpec.getSize(heightMeasureSpec); int hm = MeasureSpec.getMode(heightMeasureSpec); int childCount = getChildCount(); int width = 0; int height = 0; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();// measureChild(child, widthMeasureSpec, heightMeasureSpec); measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); int measuredWidth = child.getMeasuredWidth(); int measuredHeight = child.getMeasuredHeight(); height += measuredHeight + lp.topMargin + lp.bottomMargin; width = measuredWidth + lp.leftMargin + lp.rightMargin; width = Math.min(width, ws); } int finalW = wm == MeasureSpec.EXACTLY ? ws : width + getPaddingLeft() + getPaddingRight(); int finalH = hm == MeasureSpec.EXACTLY ? hs : height + getPaddingTop() + getPaddingBottom(); com.apkfuns.logutils.LogUtils.i("measure: " + finalW + " , " + finalH); setMeasuredDimension(finalW, finalH); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { com.apkfuns.logutils.LogUtils.d(String.format("%s -- %s , %s , %s , %s", changed, l, t, r, b)); t = getPaddingTop(); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();// LogUtils.d(lp); l = lp.leftMargin + getPaddingLeft(); t = t + lp.topMargin; r = l + child.getMeasuredWidth(); b = t + child.getMeasuredHeight(); child.layout(l, t, r, b); t += child.getMeasuredHeight() + lp.bottomMargin;// break; com.apkfuns.logutils.LogUtils.d("item width:" + (lp.rightMargin + lp.leftMargin + child.getMeasuredWidth())); } } @Override protected MarginLayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected LayoutParams generateLayoutParams(LayoutParams p) { return new MarginLayoutParams(p); }}
更多相关文章
- android POST请求(https)遇到的问题
- Android(安卓)第十一天重置版_新特性
- 解决WebView加载Https无法显示的问题
- android material design widget recyclerview
- android设置默认程序
- Android中父子窗体调用类似模式窗体应用
- Android(安卓)ListView拖动时,背景颜色会变成黑色
- Android(安卓)studio 3.0安装配置方法图文教程
- Android开发学习 之 五、基本界面控件