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

}

}

 

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