最近一直在研究关于安卓中常用控件的源码实现,也参考了不少文章,希望通过自己的总结加深一下记忆,我会从一个view的绘制流程去分析这个控件
作为安卓中最常用的控件ListView,我觉很很有必要学习一下Google的大牛是如何实现这种比较复杂的控件,包括ListVIew的绘制流程,ListView的缓存机制,以及封装思想,对今后自己能早出更好的轮子有所帮助.
注 : 所有的源码都是来自安卓5.1版本.
本文将从以下角度对安卓中最常用的控件ListView进行分析
ListView的构造
我们先从一个类的最开始构造方法开始研究,第二行,ListView在初始化的时候,先执行了super(context, attrs, defStyleAttr, defStyleRes)
方法,ListView的父类是AbsListView
,所以我们先看下父类的初始化究竟做了什么
public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
//初始化AbsListView
super(context, attrs, defStyleAttr, defStyleRes);
...
}
- ListView 父类 AbsListView的构造
父类方法中调用了initAbsListView
进行ListView的初始化配置,之后就是拿到一些自定义属性
public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
//初始化设置一些额外属性
initAbsListView();
... 拿到自定义属性省略
}
initAbsListView()
这个方法中给ListView设置了一些初始化状态
private void initAbsListView() {
// Setting focusable in touch mode will set the focusable property to true
//可点击
setClickable(true);
//触摸可获取焦点
setFocusableInTouchMode(true);
//可以绘制
setWillNotDraw(false);
//对于透明的地方,显示最底层的背景
setAlwaysDrawnWithCacheEnabled(false);
//设置是否缓存卷动项
setScrollingCacheEnabled(true);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
// 事件处理相关变量初始化
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mOverscrollDistance = configuration.getScaledOverscrollDistance();
mOverflingDistance = configuration.getScaledOverflingDistance();
mDensityScale = getContext().getResources().getDisplayMetrics().density;
}
- 回到ListView的构造方法
可以看到在初始化状态之后,通过a.getDrawable(com.android.internal.R.styleable.ListView_divider);
拿到了分割线的样式,这就是是我们通过在style文件中复ListView_divider
可以自定义Item分割线的原因.而且还可以通过复写ListView_overScrollHeader
,ListView_overScrollFooter
设置头部和底部的drawble文件
public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
//初始化AbsListView
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.ListView, defStyleAttr, defStyleRes);
CharSequence[] entries = a.getTextArray(
com.android.internal.R.styleable.ListView_entries);
if (entries != null) {
setAdapter(new ArrayAdapter<CharSequence>(context,
com.android.internal.R.layout.simple_list_item_1, entries));
}
//获取item分割线 drawable 可以自定义
final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider);
if (d != null) {
// If a divider is specified use its intrinsic height for divider height
setDivider(d);
}
//头部样式
final Drawable osHeader = a.getDrawable(
com.android.internal.R.styleable.ListView_overScrollHeader);
if (osHeader != null) {
setOverscrollHeader(osHeader);
}
//脚步样式
final Drawable osFooter = a.getDrawable(
com.android.internal.R.styleable.ListView_overScrollFooter);
if (osFooter != null) {
setOverscrollFooter(osFooter);
}
// Use the height specified, zero being the default
// item分割线的高度
final int dividerHeight = a.getDimensionPixelSize(
com.android.internal.R.styleable.ListView_dividerHeight, 0);
if (dividerHeight != 0) {
setDividerHeight(dividerHeight);
}
mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
a.recycle();
}
最后总结一下,ListView在构造方法中,就是初始化了一些状态,并且将分割线等样式添加了进来,这就是我们可以通过在sylte.xml复写对应的样式达到修改分割线的原因.
onMeasure方法
在onMeasure方法中会根据我们自定义继承BaseAdapter的adpter.getCount
方法拿到所有item的数量,并且通过View child = obtainView(0, mIsScrap);
方法创建view,那么这个view是怎么创建的呢,进去看一下
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
//设置List 的Padding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
//step 1 getCount 得到adapter 中的 getCount 这里返回的是data 数据的长度
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
//循环
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
heightMode == MeasureSpec.UNSPECIFIED)) {
//step 2 getView 创建每个view
final View child = obtainView(0, mIsScrap);
//测量 子view
measureScrapChild(child, 0, widthMeasureSpec);
//获取childWidth childHeight
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, 0);
}
}
... 省略 以下是对ListView的测量并赋值给成员变量
}
- obtainView
可以看到最终也是调用了mAdapter.getView(position, scrapView, this);
创建child,getView中的参数scrapView 就是被回收的view对象,后面会讲到
View obtainView(int position, boolean[] isScrap) {
...
final View scrapView = mRecycler.getScrapView(position);
//获取到adapter中返回的convertView;
final View child = mAdapter.getView(position, scrapView, this);
...
//将getView 中返回的convertView 返回
return child;
}
总结一下.在onMeasure方法中,会通过我们设置进来的mAdpter的getCount方法拿到item的数量,通过getView的方法拿到我们创建的每一个view,当然ListVIew第一次创建的时候并没有mAdapter的存在,只有在setAdapter被我们调用过后才会执行这些方法,也就是说在setAdapter中一定会调用requestLayout
方法重新走一遍流程,这个下面会进行讲解.
onLayout方法
通过搜索发现ListView中并没有onLayout方法,那也就是说一定是在他的父类AbsListView
中,我们可以看到它调用了layoutChildren()
,从方法名看应该是对子view进行布局,这个layoutChildren是一个空实现方法,也就是说应该是通过AbsListView的子类ListVIew
和GridView
进行实现
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
//拿到view 数量
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
mRecycler.markChildrenDirty();
}
// 由子类ListView 和 GridView实现,是核心布局方法代码,也是listview与adapter交互数据
// 的主要入口函数
layoutChildren();
mInLayout = false;
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
// TODO: Move somewhere sane. This doesn't belong in onLayout().
if (mFastScroll != null) {
mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
}
}
- listView.layoutChildren()
这个方法比较长,我们具体看重点,这个方法中会判断是否通过adapter进行添加数据的操作,并通过fillXXX()
方法进行对ItemView的填充,并且有两个很重要的对象:
1.View[] mActiveViews
:存放的是当前ListView可以使用的待激活的子item view
2.ArrayList<View>[] mScrapViews
:存放的是在ListView滑动过程中滑出屏幕来回收以便下次利用的子item view
@Override
protected void layoutChildren() {
...
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
// 只有在调用adapter.notifyDatasetChanged()方法一直到layout()布局结束,
//dataChanged为true,默认为false,这里如果调用notifyDatasetChanged,就会将Item添加到ReyclerBin当中,这个
//ReyclerBin封装了这两个集合用来存放对应的符合条件的item,用来实现复用机制
//1.View[] mActiveViews : 存放的是当前ListView可以使用的待激活的子item view
//2.ArrayList<View>[] mScrapViews : 存放的是在ListView滑动过程中滑出屏幕来回收以便下次利用的子item view
if (dataChanged) {
// dataChanged为true,说明当前listview是有数据的了,把当前所有的item view
// 存放到RecycleBin对象的mScrapViews中保存
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
// dataChanged默认为false,第一次执行此方法走这里
//将view添加到 activeViews[] 中
recycleBin.fillActiveViews(childCount, firstPosition);
}
...
switch (mLayoutMode) {
...
default:
//一般情况下走这里
if (childCount == 0) {
// 第一次布局的时候,因为还没有setAdapter,没有走mAdpate.getCount方法,所以childCount必然为0
if (!mStackFromBottom) {
final int position = lookForSelectablePosition(0, true);
setSelectedPositionInt(position);
// 从上到上布局listview能显示得下的子view,具体的填充view的方法,下面讲到
sel = fillFromTop(childrenTop);
} else {
final int position = lookForSelectablePosition(mItemCount - 1, false);
setSelectedPositionInt(position);
sel = fillUp(mItemCount - 1, childrenBottom);
}
} else {
// 非第一次layout,也就说执行了nitifyDatasetChanged方法之后
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition,
oldSel == null ? childrenTop : oldSel.getTop());
} else if (mFirstPosition < mItemCount) {
// 通常情况走这里,fillSpecific()会调用fillUp()和fillDown()布局子view
sel = fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
} else {
sel = fillSpecific(0, childrenTop);
}
}
break;
}
//到这里,ListView中的view就被填充完毕.
...
//布局完成之后记录状态
mLayoutMode = LAYOUT_NORMAL;
mDataChanged = false;
}
总结一下,通过onLayout方法,就将item填充到了ListView中
Item的填充与Item的布局
我们刚才讲到,在layoutChidren中有几个以fill开头的方法就是具体的Item的填充方法,
- fillSpecific()
这个方法中会根据mStackFromBottom参数判断填充方向,通过fillUp
,fillDown
进行填充
private View fillSpecific(int position, int top) {
boolean tempIsSelected = position == mSelectedPosition;
View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
// Possibly changed again in fillUp if we add rows above this one.
mFirstPosition = position;
View above;
View below;
final int dividerHeight = mDividerHeight;
//根据填充方向,如果mStackFromBottom为false,表示从顶部向底部填充,true反之
//mStackFromBottom 可以通过 xml文件android:stackFromBottom="false"设置,默认为false
if (!mStackFromBottom) {
//具体填充方法
above = fillUp(position - 1, temp.getTop() - dividerHeight);
// This will correct for the top of the first view not touching the top of the list
adjustViewsUpOrDown();
//具体填充方法
below = fillDown(position + 1, temp.getBottom() + dividerHeight);
int childCount = getChildCount();
if (childCount > 0) {
correctTooHigh(childCount);
}
} else {
below = fillDown(position + 1, temp.getBottom() + dividerHeight);
// This will correct for the bottom of the last view not touching the bottom of the list
adjustViewsUpOrDown();
above = fillUp(position - 1, temp.getTop() - dividerHeight);
int childCount = getChildCount();
if (childCount > 0) {
correctTooLow(childCount);
}
}
if (tempIsSelected) {
return temp;
} else if (above != null) {
return above;
} else {
return below;
}
}
- fillDown
以fillDown()
举例
第一次进入nextTop就是padding,也就是最顶部的位置,通过一个while循环,只要nextTop没有超出end(ListView内容高度)就一直makeAndAddView()
创建view,nextTop在循环里会根据Item数量进行循环赋值,只要判断当前这个item的nextTop超出listView,就停止这个循环,通过这种方法就将可见view都填充出来了
//第一次进来pos = 0;
// nexttop 是 padding.top
private View fillDown(int pos, int nextTop) {
View selectedView = null;
//listView的高度
int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
//listView的高度-padding值
end -= mListPadding.bottom;
}
// while循环在listview范围内布局可见数量的子item view
// nextTop == mListPadding.top,可认为是listview的mPaddingTop
// end == mListPadding.bottom,可认为是listview的mPaddingBottom
// nextTop < end说明下一个要装载的item view的getTop()依然可见,那当然要布局到listview中
//这里未进入循环的时候nextTop == list.paddinttop 默认值
while (nextTop < end && pos < mItemCount) {
// is this the selected item?
//布局当前页面可以显示的view
boolean selected = pos == mSelectedPosition;
//重点,布局子view的方法
//参数,postion 每个Item的top值 paddingLeft值 是否被选中
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
//这里nextTop = child的top 加上 他的行高
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
- makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected)
listView就是通过这个方法调用obtainView(position, mIsScrap)
mAdapter.getView创建view,然后通过setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
这个方法进行对子view的布局,记住这些方法都在while循环中,
//布局当前页面可显示的子view
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
//获取到getView的每个view
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
//布局当前页面可显示的子view,这个方法中对view进行布局
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
- private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean recycled)
在这个方法中,通过拿到上面while循环传经来的参数,调用了子child的measure和layout方法进行测量和绘制,到此listView中可见区域的view就被填充出来了.
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean recycled) {
...
//如果需要测量,先测量子view
if (needToMeasure) {
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
mListPadding.left + mListPadding.right, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
//测量
child.measure(childWidthSpec, childHeightSpec);
} else {
cleanupLayoutState(child);
}
...
//对子view进行布局
child.layout(childrenLeft, childTop, childRight, childBottom);
...
}
上面就是ListView中item的填充,下面我们来看看setAdapter中究竟做了什么操作
setAdapter
setAdapter中通过mAdapter.registerDataSetObserver(mDataSetObserver);
注册一个AdapterDataSetObserver
订阅者,每当调用notifyDataSetChange
的时候,就会触发AdapterDataSetObserver
的onChanged
的方法,这个是观察者模式,不懂得可以参考下其他文章,这里就不多做赘述,这个方法最终调用requestLayout方法,也就是说我们每次setAdapter之后就会重新布局,这时候mAdapter不为空,就会走刚才所说的绘制流程.
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
//将一些成员变量还原设置为初始默认值
//mLayoutMode = LAYOUT_NORMAL
resetList();
// mRecycler的mScrapViews清空并执行listview.removeDetachedView
//mScrapViews 存放边界之外的view
mRecycler.clear();
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
// 如果listview有headerView或者FooterView则会生成包装adapter,生成一个含有HeaderView 和footerView的adapter
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
mOldSelectedPosition = INVALID_POSITION;//-1
mOldSelectedRowId = INVALID_ROW_ID;//Long.MIN_VALUE
// AbsListView#setAdapter will update choice mode states.
//给父亲 adblistView 设置 adapter
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
//调用adapter的getCount 得到条目个数
mItemCount = mAdapter.getCount();
checkFocus();
//注册观察者,这个观察者每当调用notifyDataSetChange的时候就会触发
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
// 设置listview的数据源类型,并在mRecycler中初始化对应个数的scrapViews list
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
// 会调用顶层viewRootImpl.performTraversals(),导致视图重绘,listview刷新
requestLayout();
}
notifyDataSetChanged
这个方法在BaseAdapter
中
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
这时候根据观察者模式,会调用订阅者AdapterDataSetObserver
的onChanged方法,上面提到过,最终还是会调用requestLayout进行重新布局
- onChanged
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
// 同样,最终调用viewRootImpl.performTraversals(),导致视图重绘,执行listview的
// measure layout 方法等
requestLayout();
}
ListView的回收机制
最后我们来看看ListView的复用机制
要想了解这方面,先要从ListView滑动开始看,滚动核心方法AbsListView
的trackMotionScroll
,在这个方法中实现了对ListView,Item的缓存
- trackMotionScroll
第一步 : 这个方法会先判断我们先在滑动的位置是否已经到最顶部,或者最底部,如果到了边界值,就不能再滑动了
第二步 : 拿手指向上移动,也就是下滑状态来说名:先遍历所有的item,如果发现这个item的底部还在可视范围之内,说明这个item还没有销毁,如果超出,则表示需要被缓存起来,也就是会加入到mRecycler的mScrapViews(超出屏幕的集合)中保存,这个集合之前有说过,专门用来保存超出屏幕的Item.并将划出的view通过detachViewsFromParent
从ListView中detach掉
第三步 : 判断是否有Item滚入了ListView中,如果滚入,调用fillGap
方法进行填充,这个方法中会调用之前说过的fillDown
或者fillUp
方法填充item,并添加到mActivated
(当前屏幕中Item的集合)中,这样就实现了Item的缓存.
//incrementalDeltaY 从上一个事件更改deltaY 即上一个deltaY
//deltaY Y轴偏移量
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
if (childCount == 0) {
return true;
}
....
//判断是否在最顶部且手指向下滑动,是的话即不能向下滑动了,表示到顶部了
//如果第一个item的positon 是 0 并且 第一个item的top==listPadding.top
final boolean cannotScrollDown = (firstPosition == 0 &&
firstTop >= listPadding.top && incrementalDeltaY >= 0);
//判断是否在最底部且手指向上滑动,是的话即不能向上滑动了,表示到底部了
final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
// listview无法滚动即返回
if (cannotScrollDown || cannotScrollUp) {
return incrementalDeltaY != 0;
}
// incrementalDeltaY<0说明手指是向上滑动的
final boolean down = incrementalDeltaY < 0;
...
int start = 0;
int count = 0;
//手指向上移动
if (down) {
int top = -incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
top += listPadding.top;
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
//底部大于等于 top 说明 还在当前视图范围内
if (child.getBottom() >= top) {
break;
} else {
// 最top的子view已经滑出listview,count 就是滑出去的view数
count++;
//拿到position
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
child.clearAccessibilityFocus();
// 将最顶部滑出的子view 加入到mRecycler的mScrapViews中保存
mRecycler.addScrapView(child, position);
}
}
}
} else {
...向下移动 省略 和上面逻辑相同
}
mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
mBlockLayoutRequests = true;
if (count > 0) {
// 将上面滑出的子view 从listview中detach掉
detachViewsFromParent(start, count);
mRecycler.removeSkippedScrap();
}
// invalidate before moving the children to avoid unnecessary invalidate
// calls to bubble up from the children all the way to the top
if (!awakenScrollBars()) {
invalidate();
}
//核心滚动便宜代码,根据incrementalDeltaY同步偏移所有的子view
offsetChildrenTopAndBottom(incrementalDeltaY);
if (down) {
mFirstPosition += count;
}
// 根据条件判断是否填充滑动进入listview的子view
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
//
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
//滚动过程判断需要加载填充滑动进的子view的处理部分
fillGap(down);
}
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
final int childIndex = mSelectedPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(mSelectedPosition, getChildAt(childIndex));
}
} else if (mSelectorPosition != INVALID_POSITION) {
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(INVALID_POSITION, getChildAt(childIndex));
}
} else {
mSelectorRect.setEmpty();
}
mBlockLayoutRequests = false;
invokeOnItemScrollListener();
return false;
}
以上就是对ListView绘制流程以及其中的观察者模式等等,后面我也会对ReyclerView 以及ViewPager进行源码分析,希望通过这种方式,更加深刻的了解各种View的实现,对以后自定义控件的编写提供更好的思想.