您的位置:首页 > 其它

View的工作原理(自定义View)

2016-01-20 15:42 393 查看
  为了更好的自定义View,还需要掌握View的底层工作原理,比如View的测量流程,布局流程以及绘制流程。为了更好的理解View的测量过程,我们还需要理解MeasureSpec。

 

一 MeasureSpec

(1).MeasureSpec

MeasureSpec代表了一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode 是指测量模式,而SpecSize是指在某种测量模式下的规格大小。MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配。为了方便操作,其提供了打包和解压方法。SpecMode和SpecSize也是一个int值,一组SpecMode和SpecSize可以打包为一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode 和SpecSize ,需要注意的是这里提到的MeasureSpec是指MeasureSpec所代表的int值,而不是它本身。

SpecMode有三类,每一类,都表示不同的含义。

 

UNSPECIFIED

父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态。

 

EXACTLY

 

父容器已经检测出View所需要的精确大小,这个时候,View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值两种模式。

 

AT_MOST

父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同的View的具体实现了。它对应于LayoutParams的wrap_content.

 

(2).MeasureSpec和LayoutParams的关系

系统内部是通过MeasureSpec来进行View的测量,但是正常情况下我们使用View指定MeasureSpec,尽管如此,我们还可以给View设置LayoutParams。在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasrueSpec确定View测量后的宽和高。需要注意的是,MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的尺寸。

 

只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定出了子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出了子元素的测量后的大小了。

 

二 View的工作原理

View的工作流程主要是指measure,layout,draw这三大流程,即测量,布局和绘制,其中measure确定了View测量的宽和高,layout确定了View的位置,而draw则将View绘制到屏幕上。

(1) .measure过程

(2) Layout过程

(3) Draw过程

//ps这一块太多,过一段时间再写,反正还没人看。。。。。。。。。

 

三 自定义View

自定义View的分类标准不唯一,而大体上有四类。

 

(1).继承View重写onDraw方法

这种方法主要用于实现一些不规则的效果,即折后在那个效果不方便通过布局的组合方式来达到,往往需要静态或者动态的显示一些不规则的图形,这需要通过绘制的方法来实现,即重写onDraw方法。采用这种方法需要自己支持wrap_content,并且padding也需要自己处理。

(2). 继承ViewGroup派生特殊的Layout

这种方法主要用于实现自定义布局,即除了LinearLayout,RelativeLayout,FrameLayout这几种系统布局之外,我们重新定义一种新的布局,需要核实的处理ViewGroup的测量,布局这两个过程,并同时处理子元素的测量和布局过程。

(3). 继承特定的View(比如TextView)

这种方法比较常见,一般是用于扩展那某种已有的View的功能,比如TextVeiw,这种方法比较容易实现,不需要自己支持wrap_content和padding。

(4). 继承特定的ViewGroup(比如LinearLayout)

这种方法比较常见,效果看起来就像集中View组合在一起的时候,可以采用这种方法实现,不需要自己处理ViewGroup的测量和布局过程。

 

自定义View有一下问题需要注意

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的作用

4.View中如果有线程或者动画,需要及时停止。

如果有线程或者动画需要停止时,那么onDetachedFromWindow是一个很好的时机。当包含View的Activity退出或者当前View被Remove时,View的onDetachedFromWindow方法会被调用,和此方法对应的是onAttachedToWindow.同时,当View变得不可见时,我们也需要停止线程和动画,如果不及时处理这种问题,有可能造成内存泄露。

5.View带滑动嵌套情形时,需要处理好滑动冲突。

最后 附一自定义View例子:

public class HorizontalScrollViewEx2 extends ViewGroup {
private static final String TAG = "HorizontalScrollViewEx2";

private int mChildrenSize;
private int mChildWidth;
private int mChildIndex;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;

// 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;

private Scroller mScroller;
private VelocityTracker mVelocityTracker;

public HorizontalScrollViewEx2(Context context) {
super(context);
init();
}

public HorizontalScrollViewEx2(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public HorizontalScrollViewEx2(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init();
}

private void init() {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
mLastX = x;
mLastY = y;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
return true;
}
return false;
} else {
return true;
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent action:" + event.getAction());
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;
Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY);
scrollBy(-deltaX, 0);
break;
}
case MotionEvent.ACTION_UP: {
int scrollX = getScrollX();
int scrollToChildIndex = scrollX / mChildWidth;
Log.d(TAG, "current index:" + scrollToChildIndex);
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();
Log.d(TAG, "index:" + scrollToChildIndex + " dx:" + dx);
break;
}
default:
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 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 (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);
} else {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(measuredWidth, measuredHeight);
}
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.d(TAG, "width:" + getWidth());
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.onDetachedFromWindow();
}
}

 

 

 

View部分,需要了解的,打听就是这些方面,有些不完善的地方,有空我再补充。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息