android 布局测试,android基础-view的测量,布局,绘制

知识点

view的测量

view的布局

view的绘制

android中的view显示方式主要就是测量出大小→决定在哪个位置→最后进行绘制

一、view的测量

view的测量是通过强大的MeasureSpec类帮助测量的,而关于该类起初我们只要了解它是一个32位的int值,其中高2位是用于标识当前view的测量模式,低30位就是用于记录view的大小。更多关于该类的知识可以查看官方文档MeasureSpec

view的测量模式有三种:

EXACTLY : 就是当我们指定view的大小或者使用适配父控件的情况 例如:

android:layout_width="100dp" /android:layout_width="match_parent"

AT_MOST : 该模式一般是控件适应自身内容大小,但不能超过父控件的大小 例如:

android:layout_width="wrap_content"

UNSPECIFIED : 该模式标识不限制view的大小,要多大就多大,一般在自定义view的时候使用

在view的测量中,系统默认的大小计算方法如下(API 27):

/**

* Utility to return a default size. Uses the supplied size if the

* MeasureSpec imposed no constraints. Will get larger if allowed

* by the MeasureSpec.

*

* @param size Default size for this view

* @param measureSpec Constraints imposed by the parent

* @return The size this view should be.

*/

public static int getDefaultSize(int size, int measureSpec) {

int result = size;

int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {

case MeasureSpec.UNSPECIFIED:

result = size;

break;

case MeasureSpec.AT_MOST:

case MeasureSpec.EXACTLY:

result = specSize;

break;

}

return result;

}

可以看出,默认的测量方式为,如果测量模式是UNSPECIFIED ,则采用系统默认大小,其余为measureSpec中所测量的大小,根据这个测量思路我们在自定义view的时候就可以采用自己的测量方式

举个例子:

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int resultWidth;

int resultHeight;

int specWMode = MeasureSpec.getMode(widthMeasureSpec);

int specWSize = MeasureSpec.getSize(widthMeasureSpec);

resultWidth=myMeasure(specWMode,specWSize, Dp2PxUtil.dip2px(mContext,200));

int specHMode = MeasureSpec.getMode(heightMeasureSpec);

int specHSize = MeasureSpec.getSize(heightMeasureSpec);

resultHeight=myMeasure(specHMode,specHSize,Dp2PxUtil.dip2px(mContext,300));

setMeasuredDimension(resultWidth,resultHeight);

}

/**

*

* @param specMode 测量模式

* @param specSize 测量大小

* @param result 在非精确测量模式中用来约束的大小

* @return

*/

private int myMeasure(int specMode,int specSize,int result){

if(specMode==MeasureSpec.EXACTLY){

result=specSize;

}else if(specMode==MeasureSpec.AT_MOST){

result=Math.min(specSize,result);

}else {

}

return result;

}

测试结果:

结果

备注

;

c5785cf1fd57

指定大小为100dp*100dp.png

设置自定义view的宽高为100dp*100dp

c5785cf1fd57

填充父布局.png

设置自定义view的宽高为match_parent

c5785cf1fd57

自适应.png

设置自定义view的宽高为wrap_content(实际根据我们的测量方法设置的是宽高为200dp*300dp)

c5785cf1fd57

不重写.png

如果没有重写自定义view中的onMeasure方法,并且设置宽高为wrap_content的时候,也是填充父布局,原因是这种情况下view的specSize是parentSize,而parentSize是父容器中目前可以使用的大小,也就是父容器当前剩余的空间大小,具体可以查看ViewGroup中的getChildMeasureSpec方法

二、view的布局

与view布局相关主要就有两个方法:

layout(int l, int t, int r, int b) :

onLayout(boolean changed, int left, int top, int right, int bottom) :

2.1 onLayout

该方法在自定义view中一般不需用重写,其常用viewgroup通知子view进行布局Layout的时候使用

2.2 layout

view中源码如下(API 27)

public void layout(int l, int t, int r, int b) {

if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {

onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);

mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;

}

int oldL = mLeft;

int oldT = mTop;

int oldB = mBottom;

int oldR = mRight;

boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {

onLayout(changed, l, t, r, b);

if (shouldDrawRoundScrollbar()) {

if(mRoundScrollbarRenderer == null) {

mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);

}

} else {

mRoundScrollbarRenderer = null;

}

mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

ListenerInfo li = mListenerInfo;

if (li != null && li.mOnLayoutChangeListeners != null) {

ArrayList listenersCopy =

(ArrayList)li.mOnLayoutChangeListeners.clone();

int numListeners = listenersCopy.size();

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

listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);

}

}

}

mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;

mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {

mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;

notifyEnterOrExitForAutoFillIfNeeded(true);

}

}

我们不必要知道该方法的全部流程以及作用,主要看下面这行代码:

boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

这行代码涉及了三个方法isLayoutModeOptical,setOpticalFrame,setFrame。isLayoutModeOptical看注释说是判断是否使用光学边界,这个我们也可以不管,主要看setOpticalFrame,setFrame这两个方法

我们先看setOpticalFrame:

private boolean setOpticalFrame(int left, int top, int right, int bottom) {

Insets parentInsets = mParent instanceof View ?

((View) mParent).getOpticalInsets() : Insets.NONE;

Insets childInsets = getOpticalInsets();

return setFrame(

left + parentInsets.left - childInsets.left,

top + parentInsets.top - childInsets.top,

right + parentInsets.left + childInsets.right,

bottom + parentInsets.top + childInsets.bottom);

}

可以看到最终return的方法也是setFrame,那么我们就往这方法里面看看

protected boolean setFrame(int left, int top, int right, int bottom) {

boolean changed = false;

if (DBG) {

Log.d("View", this + " View.setFrame(" + left + "," + top + ","

+ right + "," + bottom + ")");

}

if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {

changed = true;

// Remember our drawn bit

int drawn = mPrivateFlags & PFLAG_DRAWN;

int oldWidth = mRight - mLeft;

int oldHeight = mBottom - mTop;

int newWidth = right - left;

int newHeight = bottom - top;

boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

// Invalidate our old position

invalidate(sizeChanged);

mLeft = left;

mTop = top;

mRight = right;

mBottom = bottom;

mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

mPrivateFlags |= PFLAG_HAS_BOUNDS;

if (sizeChanged) {

sizeChange(newWidth, newHeight, oldWidth, oldHeight);

}

if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {

// If we are visible, force the DRAWN bit to on so that

// this invalidate will go through (at least to our parent).

// This is because someone may have invalidated this view

// before this call to setFrame came in, thereby clearing

// the DRAWN bit.

mPrivateFlags |= PFLAG_DRAWN;

invalidate(sizeChanged);

// parent display list may need to be recreated based on a change in the bounds

// of any child

invalidateParentCaches();

}

// Reset drawn bit to original value (invalidate turns it off)

mPrivateFlags |= drawn;

mBackgroundSizeChanged = true;

mDefaultFocusHighlightSizeChanged = true;

if (mForegroundInfo != null) {

mForegroundInfo.mBoundsChanged = true;

}

notifySubtreeAccessibilityStateChangedIfNeeded();

}

return changed;

}

这个方法我们也不用全部理解,主要看到它进行了当前位置参数和传进来的位置参数是否相等,如果不相等就进行重新绘制,看到这里,我们就知道了layout()方法如果传进去的位置和之前的位置参数不一样就会重新绘制该view,那么在viewgroup需要定位子view位置的时候,我们就可以调用每个子view的layout方法来重新给子view设置位置

三、view的绘制

当有了view的大小以及view的位置信息之后,我们就可以在屏幕上绘制该view了, view的绘制比较简单,我们可以直接重写onDraw方法

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

}

该方法会传递一个Canvas(画布)过来,我们只需要创建一个Paint(画笔)之类的在该画布上进行绘制就可以了

总结

view的测量,布局,绘制是基础中比较重要的,因为后续的一些复杂的特效,动画都可以由自定义view去实现,理解清楚其基本的绘制流程对后续开发会很有帮助

参考文章

《Android群英传》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值