android進階(四)-----View的工作原理

前言:

好久沒有發博客了,一直加班加到吐血,也是沒誰了,最近也是互聯網寒冬期,各大廠也都在裁員,提高自己纔是正道啊。

 

一、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();

}

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章