基本概念
ViewRoot对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完成后,会将DecorView添加到View中。同时,会创建ViewRootImpl对象,并将ViewTootImpl对象和DecorView建立关联。
源码如下:
root = new ViewRootImpl(view,getContext(),dispaly);
root.setView(view,panelParentView);
View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个View绘制出来,其中measure用来测量View的宽和高,layout用来确定View在父容器中的位置,而draw则负责将View绘制在屏幕上。
performMeasure ——> measur ——> onMeasure
↓
preformLayout ——> layout ——> onLayout
↓
performDraw ——> draw ——> onDraw
MeasureSpec翻译为”测量规格”。MeasureSpec在很大程度上决定一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,父容器影响View的MeasureSpec。在测量过程中,系统会将View的LayoutParams根据这个measureSpec来测量出View的宽/高,不一定等于View的最终宽/高。
MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode是指测量模式,SpecSize是指某种测量模式下的规格大小。
部分源码:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public stataic int makeMeasureSpec(int size,int mode){
if( sUseBrokenMakeMeasureSpec){
retrurn size + mode ;
}else{
return (size & ~Mode_MASK)|(mode & MODE_MASK)
}
}
public static int getMode(int measureSpec){
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec){
rerurn (measureSpec & ~MODE_MASK);
}
MeaureSpec通过将SpecMode个SpecSize打包打包一个int值避免过多的对象内存分配,为了方便操作,其提供了打包和解包方法。SpecMode和SpecSize可以打包一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出原始的SpecMode和SpecSize,需要注意的是这里提到的MeasureSpec是指MeaureSpec所代表的int值,而非MeasureSpec本身。
SpecMode
UNSPECIFIED
父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态。
EXACTLY
父容器已经检测出View所要的精确的大小,这个时候View的最终大小就是SpecSize所指定的值。对应于LayoutParams中的match_parent和具体的数值这两种模式。
AT_MOST
父容器指定了一个可用大小的SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应LayoutParams中的warp_content。
系统内部是通过MeasureSpec来进行View的测量,但正常情况下使用View指定MeasureSpec,可以给View设置LayoutParams。在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后在根据这个MeasureSpec来确定View测量后的宽/高。 注意: MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。
另外,对于顶级的View也就是DecorView普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。
DecorView在ViewRootImpl中的measureHierarchy方法有如下一段代码,展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desiredWindowHeight是屏幕尺寸:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth,lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight,lp.height);
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
getRootMeasureSpec方法的实现:
private static int getRootMeasureSpec(int windowSize,int rootDimension){
int measureSpec;
switch(rootDimension){
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
break;
}
return measureSpec ;
}
DecorView的MeasureSpec的产生过程就很明确了,具体遵守如下规则,根据它的LayoutParams中的宽/高的参数划分。
View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制,其中measure确定View的测量宽/高,layout确定View的最高宽/高和四个顶点的位置,而draw则将View绘制到屏幕上。
measure过程要分情况来看,如果只是一个原始的View,那么通过measure方法就完成了其测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去请用所有子元素的measure方法,各个子元素
再递归去执行这个过程。
View的measure过程由measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View的measure方法中会调用View的onMeasure方法。
View的onMeasure方法:
protected void onMeasure(int widthMeasureSpec , int heightMeasureSpec){
setMearsureDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
}
View的最终的大小是在Layout阶段确定。
4.3View的工作原理一遍没理解多看几遍。
Draw过程就是将View绘制到屏幕上。
* (1)绘制背景backgroud。draw(canvas)*
* (2)绘制自己(onDraw)*
* (3)绘制children(dispatchDraw)*
* (4)绘制装饰(onDrawScrollBars)*
根据个人理解分为了四类:
* 1. 继承View重写onDraw*
这种方法主要用于实现一些不规则的效果,即这种效果不方便通过布局的组合方式来达到,往往需要静态或者动态地显示一些不规则的图形。很显然这需要通过绘制的方式来实现,及重写onDraw方法。采用这种方法需要自己支持wrap_content,并且padding也需要自己处理。
* 2. 继承ViewGroup派生特殊的Layout*
这种方法主要用于实现自定义的布局,即除了LinearLayout、RelativeLayout、FrameLayout这几种系统的布局之外,重新定义一中新布局,当某种效果看起来很像几种View组合在一起的时候,可以采用这种方法来实现。采用这种方式稍微复杂一些。需要合适地处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局过程。
* 3. 继承特定的View(比如TextView)*
一般是用于扩展某种已有的View功能,比如TextView,这种方法比较容易实现。这种方法不需要自己支持wrap_content和padding。
* 4. 继承特定的ViewGroup(比如LinearLayout)*
当某种效果看起来很像几种View组合在一起的时候,可以采用这种方法来实现。采用这种方法不需要自己处理ViewGroup的测量和布局这两个过程。需要注意这种方法和方法2的区别,一般来说方法2能实现的效果方法4都能实现,两者主要的差别在于方法2更接近底层。
* 1.让View支持wrap_content*
因为直接继承View或者ViewGroup的控件,如果不在onMeasure中对wrap_content做特殊处理,当外界在布局中使用wrap_content时就无法达到预期的效果。
* 2.如果有必要,让你的View支持padding*
因为直接继承View的控件,如果不在draw方法中处理padding,那么padding属性无法起作用的。另外,直接继承自ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响不然将导致padding和子元素的margin失效。
* 3.尽量不要在View在中使用Handler,没必要*
View内部本身就提供了post系列的方法,完全可以替代Handler的作用,当然除非你明确地要使用Handler来发送消息。
* 4.View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWinow*
如果线程或者动画需要停止时,onDetachedFromWindow是一个很好的时机。当包含此View的Activity退出或者当前View被remove时,View的onDetachedFromWindow方法会被调用,此方法对应的是onAttachedToWindow,当包含此View的Activity启动时,View的onAttachedToWindow方法会被调用。同时,当View变得不可见时,我们也需要停止线程和动画,如果不及时处理这种问题,可能会造成内存泄露。
* 5.View带有滑动嵌套情形时,需要处理好滑动冲突*
package view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import com.superdaxue.customviewdemo.R;
/** * Created by ZX_CC on 2016/4/26. */
public class CircleView extends View {
//WRAP_CONTENT 下的默认高度
private int mWidth = 100;
private int mHeight = 100;
private int mColor = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);
a.recycle();
init();
}
private void init() {
mPaint.setColor(mColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,mHeight);
}else if (widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,heightSpecSize);
}else if (heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,mHeight);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
int radius = Math.min(width,height)/2;
canvas.drawCircle(paddingLeft+width/2,paddingTop +height/2,radius,mPaint);
}
}
package view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
/** * Created by ZX_CC on 2016/4/26. */
public class HorizontalScrollViewEx extends ViewGroup {
private static final String TAG = "HorizontalScrollViewEx";
private int mChildrenSize ;
private int mChildrenWidth ;
private int mChildrenIndex ;
private Scroller mScroller ;
private VelocityTracker mVelocityTracker ;
//分别记录上次滑动坐标
private int mLastX = 0;
private int mLastY = 0;
//分别记录上次滑动的坐标(在onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
public HorizontalScrollViewEx(Context context) {
super(context);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
if (mScroller == null){
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted = false;
if ( !mScroller.isFinished() ){
mScroller.abortAnimation();
intercepted = true;
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)){
intercepted = true;
}else{
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
Log.e(TAG,"--intercepted = " + intercepted);
mLastX = x;
mLastY = y;
mLastXIntercept = x ;
mLastYIntercept = y ;
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if ( !mScroller.isFinished()){
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
scrollBy( - deltaX ,0);
break;
case MotionEvent.ACTION_UP :
int scrollX = getScrollX();
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50){
mChildrenIndex = xVelocity > 0 ? mChildrenIndex -1 : mChildrenIndex + 1;
}else {
mChildrenIndex = (scrollX + mChildrenWidth/2) /mChildrenWidth;
}
mChildrenIndex = Math.max(0,Math.min(mChildrenIndex,mChildrenIndex-1));
int dx = mChildrenIndex * mChildrenWidth - scrollX ;
smoothScrollBy(dx,0);
mVelocityTracker.clear();
break;
}
mLastX = x;
mLastY = y;
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = 0;
int measureHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec,heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0){
setMeasuredDimension(0,0);
}else if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureWidth = childView.getMeasuredWidth() * childCount;
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(measureWidth,measureHeight);
}else if (heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpaceSize,measureHeight);
}else if (widthSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measureWidth,heightSpaceSize);
}
}
// @Override
// protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// final int childCount = getChildCount();
//
// }
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount;
for (int i = 0; i < childCount; i ++){
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE){
final int childWidth = childView.getMeasuredWidth();
mChildrenWidth = childWidth;
//MarginLayoutParams lm = (MarginLayoutParams) childView.getLayoutParams();
// int m = min(lm.leftMargin,lm.rightMargin,lm.topMargin,lm.bottomMargin);
childView.layout(childLeft,0,childLeft + childWidth ,childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
private int min(int...params){
int min = params[0];
for (int p : params){
if (p < min){
min = p;
}
}
return min;
}
private void smoothScrollBy(int dx, int dy){
mScroller.startScroll(getScrollX(),0,dx,0,500);
invalidate();
}
@Override
public void computeScroll(){
if (mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
}