android 布局按钮分部,Android进阶 (布局绘制流程 二 setContentView源码解读) v1.0

本文承接上文 view 绘制流程 一。上文跟随源码分析了 setContentVIew() 后的部分 关于 PhoneWindow 和 Layoutinfate 部分的源码解析。该篇文章本人将继续学习和总结 ,并简单提及到经典面试题。本文大概阅读时间:30分钟 Andorid SDK :28.0 。

上文 分析道 PhoneWindow 的 setContentView 进行了分部解读

@Override

public void setContentView(int layoutResID) {

if (mContentParent == null) {

// view绘制流程 一 第一节

installDecor();

} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

mContentParent.removeAllViews();

}

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,

getContext());

transitionTo(newScene);

} else {

// view 绘制流程 一 第四节

mLayoutInflater.inflate(layoutResID, mContentParent);

}

// 该篇我们顺序从该方法继续研究学习。!!!!!!

mContentParent.requestApplyInsets();

final Callback cb = getCallback();

if (cb != null && !isDestroyed()) {

cb.onContentChanged();

}

mContentParentExplicitlySet = true;

}

我们正式开始!

ViewRootImpl

// view 类

public void requestApplyInsets() {

requestFitSystemWindows();

}

public void requestFitSystemWindows() {

if (mParent != null) {

mParent.requestFitSystemWindows();

}

}

这里的mParent是ViewParent的具体实现ViewRootImpl,所以调用的是ViewRootImpl里的requestFitSystemWindows方法。

// ViewRootImpl 类

@Override

public void requestFitSystemWindows() {

// 检查线程 是否是在主线程

checkThread();

mApplyInsetsRequested = true;

// 视图绘制

scheduleTraversals();

}

ViewRootImpl.scheduleTraversals()

void scheduleTraversals() {

// 没有正在绘制的 任务才能进入

if (!mTraversalScheduled) {

mTraversalScheduled = true;

mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

// 发送一个消息

mChoreographer.postCallback(

Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

if (!mUnbufferedInputDispatch) {

scheduleConsumeBatchedInput();

}

...

}

}

// Runnable实现

final class TraversalRunnable implements Runnable {

@Override

public void run() {

// 绘制方法

doTraversal();

}

}

void doTraversal() {

if (mTraversalScheduled) {

mTraversalScheduled = false;

mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {

Debug.startMethodTracing("ViewAncestor");

}

// 最重要的地方 执行view 的绘制流程

performTraversals();

if (mProfile) {

Debug.stopMethodTracing();

mProfile = false;

}

}

}

performTraversals()

private void performTraversals() {

...

// 第一次加载View,需要调整窗口大小,需要适应系统窗口,视图显示状态改变,

// 视图布局参数不为空,强制窗口重新布局。才可能执行测量

if (mFirst || windowShouldResize || insetsChanged ||

viewVisibilityChanged || params != null || mForceNextWindowRelayout) {

...

// 窗口没有停止,或者通知需要绘制

if (!mStopped || mReportNextDraw) {

...

if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()

|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||

updatedConfiguration) {

...

// 1.第一步:测量

// Ask host how big it wants to be

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

...

}

}

} else {

...

}

...

if (didLayout) {// 执行布局

// 2.第二步:布局

performLayout(lp, mWidth, mHeight);

...

}

...

// 如果没有取消绘制,并且不是新的Surface,那么执行绘制

if (!cancelDraw && !newSurface) {

...

// 3.第三步:绘制

performDraw();

} else {// 如果取消了绘制或者是新的Surface,那么要重新测量、布局和绘制

...

}

mIsInTraversal = false;

}

重要的方法就是在上述的代码中 通过 performMeasure(), performLayout() ,performDraw() 三个方法,方法内也分别调用了 View的 measure layout 和 draw 方法。这也印证了我们 在编写自定义View的时候 需要默认实现的三个方法的由来,也说明了三个方法的顺序。

View 绘制相关面试题

事件分发

前边的文章已经提到过,这里就放两张图片,一图胜千言

触摸事件分发 流程图参考文章

d83852ec58b3

触摸事件分发 流程图

d83852ec58b3

硬件到Android 触摸事件 参考文章

Dialog 和 Activity 点击事件问题

案例:当Dialog 现实的时候点击 Activity 部分按钮是没有反映的,为什么呢?

我们先看下 Dialog的初始化源码

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {

if (createContextThemeWrapper) {

if (themeResId == ResourceId.ID_NULL) {

final TypedValue outValue = new TypedValue();

context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);

themeResId = outValue.resourceId;

}

mContext = new ContextThemeWrapper(context, themeResId);

} else {

mContext = context;

}

mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

//新建PhoneWindow

final Window w = new PhoneWindow(mContext);

mWindow = w;

//将回调注入到window当中

w.setCallback(this);

w.setOnWindowDismissedCallback(this);

w.setOnWindowSwipeDismissedCallback(() -> {

if (mCancelable) {

cancel();

}

});

w.setWindowManager(mWindowManager, null, null);

w.setGravity(Gravity.CENTER);

mListenersHandler = new ListenersHandler(this);

}

这里创建好 phoneWindow 后setContentView() 设置布局,这里就绕到了 上篇文章的 源码顺序中,通过 installDecor() ,创建 DecorView 将我们传入的布局id 带入,通过 xml pull 方式迭代读取 view 最后返回一个view 对象,并将其添加到 DecorView中。

public void show() {

...

mWindowManager.addView(mDecor, l);

...

}

public void addView(View view, ViewGroup.LayoutParams params,

Display display, Window parentWindow) {

...

//创建ViewRootImpl

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

try {

// setView

root.setView(view, wparams, panelParentView);

} catch (RuntimeException e) {

...

}

}

}

Dialog 通过show() 的方式,将DecorView 传给 WindowManager实现类 。 经过底层会调用到ViewRootImpl的ViewPostImeInputStage里面来。但是因为ViewRootImpl存在着多个,InputManagerService 会根据Z轴的高度,获取最近一个窗口,然后执行对应ViewRootImpl里面ViewPostImeInputStage的监听方法->执行mView.dispatchPointerEvent(event)。所以点击事件便在最上层 的Dialog中消费了。

绘制卡顿源码追踪

由于这里 源码层级更多,不是一篇两篇能够说清楚的,推荐一个大佬的 博文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值