前言:
好久沒有發博客了,一直加班加到吐血,也是沒誰了,最近也是互聯網寒冬期,各大廠也都在裁員,提高自己纔是正道啊。
一、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();
}
}