浅谈Android之Activity 窗口显示流程介绍(二)
来源:互联网 发布:免费制作网络结婚证 编辑:程序博客网 时间:2024/06/16 16:56
7.3 Activity Décorview布局(layout)
Measure确定Décor View以及child views的大小,layout则是确定child view在其parent view中的显示区域,只有layout结束,view的left,right,top,bottom值才会被设置,getWidth和getHeight两个函数才会返回view最终的宽高值
对FrameLayout来说,由于child view的大小确定了,那么再确定它们在parentview中的显示区域其实通过parent view的padding值,child view的margin值以及宽高就可以计算出
当然RelativeLayout和LinearLayout会相对复杂点,因为childviews之间会存在布局关联,本文只对FrameLayout的实现做简单介绍,至于RelativeLayout和LinearLayout大家可自行看代码分析
在ViewRootImpl中调用performMeasure完成对Décor View的measure后,接着调用
PerformLayout触发布局操作,其内部主要调用:
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
Host变量保存的就是Décor View,layout的四个变量依次对应left,top,right,bottom
FrameLayout和ViewGroup都没有对layout做处理,接着直接看View中的默认实现:
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);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)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;
}
这个函数先调用setFrame保存位置数据,然后调用onLayout进行布局操作,最后看这个view是否有设置layoutchangelistener,如果有,调用回调通知布局已经发生改变
先看setFrame:
//View.java
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
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) {
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
invalidateParentCaches();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
如果left,right,top,bottom值都没变,那就说明布局没变,返回changed为false,如果变了,则将对应的值都保存到mLeft,mRight,mTop, mBottom,然后调用invalidate通知view进行重绘
接着看onLayout的实现:
//FrameLayout.java
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
直接调用layoutChildren:
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
mForegroundBoundsChanged = true;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
逻辑很简单,通过拿到child view的measure width和measure height,然后根据gravity和margin值计算出child view在parent view中的left,right,top,bottom值,最后调用
child.layout
如果child view是ViewGroup,则重走本节流程,依次反复执行,直到全部child view都layout完成为止
7.4 Activity Décorview绘制(draw)
Layout结束后,每一个child view在其parent view的位置都已经固定,接下去就可以开始绘制child view的图形数据了
在ViewRootImpl中调用performLayout完成布局后,接着调用performDraw,其最终会调用drawSoftware,该函数会通过mSurface拿到Canvas,接着调用mView.draw()并传入canvas开始Décor view的绘制
由于Decor view的所有child views是共用Decor view这一块top canvas的,对每一个view来说,它们在绘制其child view或者content data时,最简单的当然是使用相对坐标,也就是将其左上角设置为坐标原点,这样就可以让view只需专注于child view或者content data的绘制,而无需关心坐标原点的调整以及恢复
如果把Décor view作为世界地图,那top canvas肯定拥有初始的单位矩阵, childview或者content data就是世界中的显示元素,元素显示位置和方式的调整,都是通过矩阵(matrix)的配置来实现的
后续View绘制代码的分析,为了简化逻辑以便于理解,我们假定系统不支持硬件加速,也就是mAttachInfo.mHardwareAccelerated为false
Draw(Canvas canvas)是View绘制的入口函数,它跟measure和layout一样,基于view tree做完整的递归调用,它会按顺序做如下事情
1) 绘制view的background
2) 调用onDraw绘制view的content
3) 调用dispatchDraw绘制view的children
4) 绘制scrollbar等
其中1, 2, 4都是基于传入的canvas做界面绘制,那canvas的坐标切换肯定是由第3步
dispatchDraw来完成了
dispatchDraw的默认实现是空的,也就是啥也没做,只有ViewGroup重新实现了改方法,也就是说,这个方法,只会ViewGroup有效
接着我们看其在ViewGroup中的实现:
//ViewGroup.java
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
……
int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
clipSaveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
// We will draw our child's animation, let's reset the flag
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
……
for (int i = 0; i < childrenCount; i++) {
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
……
}
由于mGroupFlags在initViewGroup时被设置了FLAG_CLIP_TO_PADDING,所以这里
clipToPadding肯定为true,接着调用canvas .clipRect根据padding和scroll值来对canvas的操作区域进行裁剪,接着遍历所有childview,依次调用drawChild
接着看drawChild的实现:
//ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
直接调用child.draw函数,这个函数也在View.java默认被实现,跟上头提过的draw(canvas)函数不同的是,它有三个参数,除了canvas外,还传入了parent viewgroup和drawing time:
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
boolean usingRenderNodeProperties = mAttachInfo != null &&mAttachInfo.mHardwareAccelerated;
boolean more = false;
final boolean childHasIdentityMatrix = hasIdentityMatrix();
final int flags = parent.mGroupFlags;
……
final boolean hasNoCache = cache == null || hasDisplayList;
final boolean offsetForScroll = cache == null && !hasDisplayList &&
layerType != LAYER_TYPE_HARDWARE;
int restoreTo = -1;
if (!usingRenderNodeProperties || transformToApply != null) {
restoreTo = canvas.save();
}
if (offsetForScroll) {
canvas.translate(mLeft - sx, mTop - sy);
} else {
if (!usingRenderNodeProperties) {
canvas.translate(mLeft, mTop);
}
if (scalingRequired) {
if (usingRenderNodeProperties) {
// TODO: Might not need this if we put everything inside the DL
restoreTo = canvas.save();
}
// mAttachInfo cannot be null, otherwise scalingRequired == false
final float scale = 1.0f / mAttachInfo.mApplicationScale;
canvas.scale(scale, scale);
}
}
……
if (!usingRenderNodeProperties) {
// apply clips directly, since RenderNode won't do it for this draw
if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN
&& cache == null) {
if (offsetForScroll) {
canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop));
} else {
if (!scalingRequired || cache == null) {
canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop);
} else {
canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
}
}
}
if (mClipBounds != null) {
// clip bounds ignore scroll
canvas.clipRect(mClipBounds);
}
}
……
if (!layerRendered) {
if (!hasDisplayList) {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
} else {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags);
}
}
} else if (cache != null) {
……
}
if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);
}
……
mRecreateDisplayList = false;
return more;
}
drawingTime和parent viewgroup的作用在下一节再介绍
由于在layout的时候,view在其parent view中的top-left坐标已经确定,所以这里的坐标切换相对来说就非常简单,首先调用canvas.save()备份canvas当前的matrix/clip数据,接着调用canvas.translate(mLeft,mTop)进行原点切换,最后调用draw(canvas)让view在当前canvas上进行内容绘制,绘制成功后,调用canvas.restoreToCount(restoreTo)恢复之前canvas备份的matrix/clip数据
7.5 ViewAnimation原理介绍
通过上一节我们知道,View在parent view中的位置或者展示方式是通过matrix来完成的,
那如果我在view每一次绘制的时候,按照一定的规律更改matrix的值,这样就可以达到view在parent view的一个序列图形显示效果,也就是说一个针对view的动画效果就出来了,这个好像是废话,动态视图不都是这么来的么^_^
Android提供了TranslateAnimation等类用来封装对每一帧对应matrix的计算
接下去通过代码做下简单介绍,为view设置并开始播放animation:
TranslateAnimation tAnim = new TranslateAnimation(0, 400, 0, 0);
tAnim.setDuration(2000);
view.startAnimation(tAnim);
创建TranslateAnimation,然后调用startAnimation开发播放
//View.java
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
先通过animation.setStartTime(Animation.START_ON_FIRST_FRAME)设置animation的启动时间为Animation.START_ON_FIRST_FRAME,也就是说,animation的第一帧被绘制的时间,即为start time
然后调用setAnimation(animation)将该animation设置为view的mCurrentAnimation中
最后调用invalidate(true)强制view重画。
View重画后,上一节说过,带有parent和drawingTime的draw函数会被调用:
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
……
Transformation transformToApply = null;
boolean concatMatrix = false;
……
final Animation a = getAnimation();
if (a != null) {
more = drawAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
} else {
……
}
concatMatrix |= !childHasIdentityMatrix;
……
float alpha = usingRenderNodeProperties ? 1 : (getAlpha() * getTransitionAlpha());
if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() ||
(mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
int transY = 0;
if (offsetForScroll) {
transX = -sx;
transY = -sy;
}
if (transformToApply != null) {
if (concatMatrix) {
if (usingRenderNodeProperties) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the transformation matrix,
// then redo the scroll translate to get the correct result.
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
……
}
……
if (a != null && !more) {
if (!hardwareAccelerated && !a.getFillAfter()) {
onSetAlpha(255);
}
parent.finishAnimatingView(this, a);
}
……
return more;
}
drawingTime,其实就是指这次draw操作的触发时间,也就是ViewRootImpl调用
drawSoftware的时间:
//ViewRootImpl.java
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
……
attachInfo.mDrawingTime = SystemClock.uptimeMillis();
……
return true;
}
至于parent ViewGroup,这边看到,其内部会有一个变量mChildTransformation保存当前某一child view的animation对应的transformation片段数据
在draw函数中,先通过调用drawAnimation并传入parent,animation和drawingTime来来得到当前的时间下对应的transformation片段数据,并保存到parent ViewGroup的
mChildTransformation中,然后drawAnimation内部还会判断在当前transformation片段数据执行完后,是否还存在animation片段数据?如果有,则会调用invalidate触发view的下一次绘制,这样才能保持animation不会中断
在draw函数,接着调用parent.getChildTransformation()获取当前的transformation数据,然后获取其Matrix并设置到canvas中
最后判断more是否为false,如果是false,说明animation已经结束,调用
parent.finishAnimatingView(this,a)执行animation结束相关扫尾代码
由于android View都是在主线程的完成绘制的,而主线程的负载又不是均衡的,所以会导致View animation的帧率无法得到保障,当然,对于短时的动画来说影响不大,但是对于长时间并且比较复杂的动画,建议还是使用Surface来绘制,然后开一条线程来按照你想要的帧率来触发重绘
- 浅谈Android之Activity 窗口显示流程介绍(二)
- 浅谈Android之Activity 窗口显示流程介绍(一)
- 浅谈Android之Activity Decor View创建流程介绍
- 浅谈Android之Activity相关介绍
- Android学习之路之浅谈Activity(窗口)
- 浅谈Android之Binder原理介绍(二)
- 浅谈Android之SurfaceFlinger相关介绍(二)
- Android 浅谈Sensor工作流程(二)
- 浅谈android 之 activity
- 浅谈Android之Activity触摸事件传输机制介绍
- 浅谈Android的Activity运行流程(生命周期)
- Android N 多窗口分析之freeform流程分析一(界面功能介绍)
- Android之Activity生命周期浅谈
- Activity启动流程源码分析之Launcher启动(二)
- Android Activity加载显示的基本流程
- 从问题单处理了解Toast系统窗口显示、Activity窗口创建、WindowManagerService对系统窗口组织排布(二)
- Activity窗口添加流程
- Android学习二之Activity(一)
- Redhat(linux)GDB与GCC版本兼容问题
- 绑定变量
- oracle ,mysql,sqlserver 字段设置默认值是否为空测试
- 如何在Web程序中实现定时运行的功能?
- 文章标题
- 浅谈Android之Activity 窗口显示流程介绍(二)
- C++ Null 指针
- oc总结 --oc基础语法相关知识
- noi-7890-阶乘和
- 31.URL 管理(2)
- MongoDB 定时备份
- 图解 Android 事件分发机制
- 何时使用(post&get)
- 带你一步一步揭开Rxjava2.0的神秘面纱(6)