Android-高级-UI-进阶之路-(三)-理解-View-工作原理并带你入自定义-View-门

本文详细解析了Android中View的工作流程,包括measure、layout和draw过程,以及如何自定义View。在measure过程中,通过getChildMeasureSpec计算子View的测量规格。自定义View时,需要关注wrap_content支持、padding处理和线程使用等注意事项。文章还给出了自定义View的实例,强调了处理wrap_content和padding的重要性。
摘要由CSDN通过智能技术生成

final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//得到子元素的 MeasureSpec
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);
    //对子View 开始进行 measure
    child.measure(childWidthMeasureSpec,

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

开源分享完整内容戳这里

childHeightMeasureSpec);
}
复制代码

上述方法首先会拿到子 View 的 LayoutParams 布局中定义的参数,然后根据父容器的 MeasureSpec 、 子元素的 LayoutParams 、子元素的 padding 等然后拿到对子元素的测量规格,可以看 getChildMeasureSpec 代码具体实现:

//ViewGroup.java
//padding:指父容器中已占用的大小
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//拿到在父容器中可用的最大的尺寸值
int size = Math.max(0, specSize - padding);

int resultSize = 0;
int resultMode = 0;
//根据父容器的测量规格和 View 本身的 LayoutParams 来确定子元素的 MeasureSpec
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);
}
复制代码

这一段代码虽然比较多,但是还是比较容易理解,它主要的工作就是先拿到在父容器中可用的尺寸,然后根据父元素的测量规格和子元素中 LayoutParams 参数来决定当前子 View 的 MeasureSpec。

上面的代码,如果用一张表格来表示的话,应该更好理解,请看下表:

parentSpecMode / childLaoutParams EXACTLY(精准模式) AT_MOST(最大模式) UNSPECIFIED(精准模式)
dp/px EXACTLY childSize EXACTLY childSize EXACTLY childSize
match_parent EXACTLY parentSize AT_MOST parentSize UNSPECIFIED 0
Warap_content AT_MOST parentSize AT_MOST parentSize UNSPECIFIED 0

通过此表可以更加清晰的看出,只要提供父容器的 MeasureSpec 和子元素的 LayoutParams, 就可以快速的确定出子元素的 MeasureSpec 了,有了 MeasureSpec 就可以进一步确定出子元素测量后的大小了。

View 工作流程

View 的工作流程主要是指 measure、layout 、draw 这三个流程,即 测量 -> 布局 -> 绘制,其中 measure 确定 View 测量宽高,layout 确定 View 的最终宽高和四个顶点的位置,而 draw 则将 View 绘制到屏幕上。

在讲解 View 的绘制流程之前,我们有必要知道 View 的 measure 何时触发,其实如果对 Activity 生命周期源码有所了解的应该知道,在 onCreate 生命周期中,我们做了 setContentView 把 XML 中的节点转为 View 树的过程,然后在 onResume 可以交互的状态,开始触发绘制工作,可以说 Activity 的 onResume 是开始绘制 View 的入口也不为过,下面看入口代码:

//ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {

/**

  • 1. 最终会调用 Activity onResume 生命周期函数
    /
    r = performResumeActivity(token, clearHide, reason);

    if (a.mVisibleFromClient) {
    if (!a.mWindowAdded) {
    a.mWindowAdded = true;
    /
    *
  • 2. 调用 ViewManager 的 addView 方法
    */
    wm.addView(decor, l);
    } else {

} else {

}
}
复制代码

通过上面代码我们知道,首先会调用注释 1 performResumeActivity 方法,其内部会执行 Activity onResume 生命周期方法, 然后会执行将 Activity 所有 View 的父类 DecorView 添加到 Window 的过程,我们看注释 2 代码它调用的是 ViewManager#addView 方法,在讲解 WindowManager 源码的时候,我们知道了 WindowManager 继承了 ViewManager 然后它们的实现类就是 WindowManagerImpl 所以我们直接看它内部 addView 实现:

//WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
/**

  • 委托给 WindowManagerGlobal 来处理 addView
    */
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    复制代码

内部处理又交给了 WindowManagerGlobal 对象,我们继续跟踪,代码如下:

//WindowManagerGlobal.java
/**
*

  • @param view DecorView
  • @param params
  • @param display
  • @param parentWindow
    */
    public void addView(View view, ViewGroup.LayoutParams params,
    Display display, Window parentWindow) {
    if (view == null) {
    throw new IllegalArgumentException(“view must not be null”);
    }
    if (display == null) {
    throw new IllegalArgumentException(“display must not be null”);
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
    throw new IllegalArgumentException(“Params must be WindowManager.LayoutParams”);
    }

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
/**

  • 1. 根据 WindowManager.LayoutParams 的参数来对添加的子窗口进行相应的调整
    */
    parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {

    }

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值