自定义View那些事

最近与朋友聊天,朋友吐槽说去面试的时候自定义View方面的问题问了好久,我说不是很正常吗,最基本的。 把好久之前学Android 自定义View整理了一下。顺便写了一个demo。

稍微总结了一下四个点:

  1. 布局与绘制、基本回调方法、熟练使用View的api
  2. 事件分发
  3. 动画
  4. 细节

布局与绘制、基本回调、熟练使用View的api

需要做到的是measurelayoutdraw方法的熟练掌握,这就需要掌握View的绘制流程逻辑。熟悉常用的回调方法。熟悉常用View的api。

measure

用来测量View的大小,流程是DecorView->ViewGroup->View自定义ViewGroup的时候需要确保所有可见View调用了measure。里面有一些细节知识,比如注意View的Padding 值。

有两个关键的地方需要掌握:DecorView的测量和普通View的测量。

DecorView(根View)的测量,可以参考ViewRootImpl.measureHierarchyViewRootImpl.getRootMeasureSpec。可以看到getRootMeasureSpec中计算DecorView高度是根据DecorView设置的大小和window 的大小。这里是判断DecorView设置大小之后才计算的,与测量普通的View(非DecorView)是不一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

普通View的测量,可以参考ViewGroup.getChildMeasureSpecView的大小主要根据ViewGroupMeasureSpec和设置的大小决定的。这里就不贴代码了。

layout过程

layout用来确定View(主要是在自定义ViewGroup时的子View)的位置,流程也是DecorView->ViewGroup->View。可以查看demo 中的onLayout用来设置各个子View的位置。

draw过程

重点需要知道绘制的顺序。

  1. Draw the background
  2. If necessary, save the canvas’ layers to prepare for fading
  3. Draw view’s content
  4. Draw children
  5. If necessary, draw the fading edges and restore layers
  6. Draw decorations (scrollbars for instance)

常用的回调方法

onAttachedToWindow

onDetachedFromWindow

onFinishInflate

数量掌握每一个回调里面应该处理什么内容。

常用View的api

scrollBy

scrollTo

invalidate

requestLayout

还有demo 中的bringToFront。对View的api熟悉可以在很多时候为自己节约很多时间。

事件分发

基本的事件分发的流程是Activity->ViewGroup->View。伪代码如下:

1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result;
if (onInterceptTouchEvent(ev)){
result = onTouchEvent(ev);
} else {
result = child.dispatchTouchEvent(ev);
}
return result;
}

处理事件分发时一个需要注意的是滑动冲突的问题,这里就需要熟练地掌握事件分发的流程。可以参考Demo 中的一个滑动冲突问题,DragScrollView中是一个DragView,当长按选中一个item 的时候就会拦截DragScrollView的滚动事件,解决的办法是在DragViewdispatchTouchEvent调用getParent().requestDisallowInterceptTouchEvent(true)拦截了DragScrollView的事件。

动画

动画目前有两种主要的实现:View动画和属性动画(5.0以后的过渡动画这里没有考虑)。需要熟练的掌握不同动画的原理,在不同的需求中可以做出比较好的选择。demo 中使用到的是View动画。

细节

主要是交互、适配等等。

save && restore

主要关注的是onSaveInstanceStateonRestoreInstanceState。流程是:Activity->ViewGroup->View(这里去掉了Fragment)

在demo 中,用户排序完一个顺序以后,后台处理其他事情然后Activity被回收了,再回来发现需要重新排序,这样肯定不好。可以参考demo(demo 里面处理的是Activity)。

交互

android 中的长按一般代表什么操作;双击应该怎么处理;是向左还是向右滑动等等。比如demo 中长按给了用户一个震动的反馈。