前言:
好久没有发博客了,一直加班加到吐血,也是没谁了,最近也是互联网寒冬期,各大厂也都在裁员,提高自己才是正道啊。
一、ViewRoot和DecorView
ViewRoot对对应于ViewRootImpl类,他是连接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot来完成的。
View的绘制流程是从ViewRoot的performTraversals方法开始的,经过measure、layout和draw三个过程才将View绘制出来。
二、理解MeasureSpec
MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize
SpecMode指的是测量模式
SpecMode三种测量模式
UNSPECIFIED(未指定模式):父容器不对View有任何限制,要多大给多大,一般用于系统内部,表示一种测量的状态
EXACTLY(精确值模式):父容器检测出View需要的精确大小,View的最终大小就是SpecSize所指定的值,他对应于LayoutParams中的match_parent和具体的数值
AT_MOST(最大值模式):父容器指定一个可用大小,View的大小不能大于这个值。对应于LayoutParams中的wrap_content
三、View的工作流程
1、View的工作流程主要是指measure、layout、draw三大流程
measure(测量):确定View的测量宽/高
layout(布局):layout确定View的最终宽/高和四个顶点的位置
draw(绘制):负责将View绘制在屏幕上
2、measure测量过程:View的measure过程和ViewGrop的measure过程
(1)View的measure过程:直接通过measure方法就完成了测量过程
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)
}
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),
heightMeasureSpec))
}
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;
}
(2)ViewGroup的measure过程:完成自己的measure过程,还需要遍历去调用所有子元素的measure方法,各子元素递归去执行这个过程。ViewGroup是一个抽象类,没有重写View的onMeasure方法,但是他提供了一个measureChildren的方法
protected void measureChildren(int widthMeasureSpec,int heightMeasureSpec){
final int size = mChildrenCount;
final View[] children = mChildren;
for(int i = 0; i < size ; i++){
final View child = children[i];
if((child.mViewFlags & VISIBILITY_MASK) != GONE){
measureChild(child,widthMeasureSpec,heightMeasureSpec);
}
}
}
protected void measureChild(View child,int parentWidthMeasureSpec,int parentHeightMeasureSpec){
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidth - MeasureSpec,mPaddingLeft + mPaddingRight,lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeight - MeasureSpec,mPaddingTop + mPaddingBottom,lp.height);
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
3、layout过程:layout是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有子元素并调用layout方法,在layout方法中onLayout方法又会被调用。
protected void onLayout(boolean changed,int l,int t ,int r,int b){
if(mOrientation == VERTICAL){
layoutVertical(l,t,r,b);
}else{
layoutHorizontal(l,t,r,b);
}
}
private void layoutVertical(int left,int top,int right,int bottom){
final int count = getVirtualChildCount();
for(int i= 0;i<count;i++){
final View child = getVirtualChildAt(i);
if(child == null){
childTop += measureNullChild(i);
}else if(child.getVisibility()!= GONE){
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeidht();
final LinearLayout.LayoutParams lp = (LienarLayout.LayoutParams)child.getLayoutParams();
if(hasDividerBeforeChildAt(i)){
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child,childLeft,childTop + getLocationOffset(child),childWidth,childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocation - Offset(child);
i += getChildrenSkipCount(child,i);
}
}
}
private void setChildFrame(View child,int left,int top,int width,int height){
child.layout(left,top,left+width,top+height)
}
4、draw过程
(1)View绘制过程步骤:
绘制背景、绘制自己、绘制children、绘制装饰
四:自定义View
1、自定义View注意事项
(1)让View支持wrap_content
(2)让View支持padding
(3)尽量不要在View中使用Handler
(4)View中如果有线程或动画,需要及时停止
(5)View带有滑动嵌套时,需要处理好滑动冲突
2、
继承View重写onDraw方法,代码实例
public class CircleView extends View{
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 &&heigitSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(200,200);
}else if(widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(200,heightSpecSize);
}else if(heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,200);
}
}
@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);
}
}
继承ViewGroup派生特殊的Layout:这种方式用于实现自定义的布局,需要处理ViewGroup的测量、布局,并同时处理子元素的测量和布局过程
代码实例:
public class HorizontalScrollViewEx extends ViewGroup{
private int mChildrenSize;
private int mChildWidth;
private int mChildIndex;
//分别记录上次滑动的座标
private int mLastX = 0;
private int mLastY = 0;
//分别记录上次滑动的座标
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
public HorizontalScrollViewEx(Context context){
super(context);
init();
}
public HorizontalScrollViewEx(Context context,AttributeSet attrs){
super(context,attrs);
init();
}
public HorizontalScrollViewEx(Context context,AttributeSet attrs,int defStyle){
super(context,attrs,defStyle);
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;
}
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){
mChildIndex = xVelocity >0 ?mChildIndex - 1 : mChildindex + 1;
}else{
mChildIndex = (scrollX + mChildWidth / 2)/mChildWidth;
}
mChildIndex = Math.max(0,Math.min(mChildIndex,mChildrenSize - 1));
int dx = mChildIndex * mChildWidth - 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 measuredWidth = 0;
int measuredHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec,heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = 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);
measuredWidth = childView.getMeasuredWidth()*childCount;
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(measuredWidth,measuredHeight);
}else if(heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpaceSize,childView.getMeasuredHeight());
}else if(widthSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth()*childCount;
setMeasuredDimension(measuredWidth,heightSpaceSize);
}
}
@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();
mChildWidth = childWidth;
childView.layout(childLeft,0,childLeft + childWidth,childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
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.onDetachedFormWindow();
}
}