版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
本文最后附一 TableView 自定义控件源码,大神写的。个人添加了详细的注释,如果对 ListView 复用思想熟悉可直接跳过,下载源码进行学习。
注:这边 ListView 的源码采用 API 25 进行学习。
一、效果
注:这边 ListView 的源码采用 API 25 进行学习。
实现上面这个效果,要求右下角内容不仅支持上下滑动,也要支持左右滑动。ListView 只支持上下滑动,没有左右滑动这个功能。如果为了支持左右滑动,采用 ScrollView 嵌套 ListView ,在功能上是可以实现的。但是,Listview 有复用机制,ScrollView 没有,ScrollView 会把当前行所有的 Item 创建出来(哪怕不显示),当列数比较多的时候,会占用大量内存,甚至 OOM。
Listview 采用了复用机制,只创建当前显示的 Item,而且可进行复用,大大优化了内存。我们在这分析 ListView 的复用机制,模仿实现一个 TableView,在水平跟竖直方向上都可进行复用。
二、ListView 复用机制
1.复用机制概述
Android 提供 getView 方法中有一个 convertView 参数来实现复用。当回收池没有 View 的时候,convertView 为空,当回收池有 View 的时候,convertView 不为空。
ListView 初始化的时候,ListView 在 Item 中加载 View,获取的 convertView 为空,所以每次都去创建,按下图例子,第一次创建了4个。当用户滑动列表时,Item1 由可见变为不可见的时候,会把改行的 View 存放到回收池。最后加载 Item5,这时候回收池有 View 存在, convertView 不为空,把这个 View 拿出来复用。
复用可以避免每一行去创建 java 对象以及加载 Item 的 xml 布局,这都是很耗资源的。
2.复用机制源码
控件主要分为 View 和 ViewGroup 两类,很明显,ListView 属于 ViewGroup。既然 ListView 最终继承于 ViewGroup,那么 ListView 肯定重写了 onLayout 方法来定义子 View 如何进行摆放。但是点进去源码会先 ListView 没有 onLayout 方法,onLayout 是在 ListView 的父类 AbsListView 中进行重写。
AbsListView 的 onLayout:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
//mRecycler 就是回收池
mRecycler.markChildrenDirty();
}
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);
}
}
很明显,重点是 layoutChildren 这个方法,看名字就知道是对子 View 进行布局。点进去会发现在 AbsListView 中该方法是一个空方法。这是因为列表的有很多, AbsListView 把具体怎么摆放让列表自己决定,但是 AbsListView 又要对 onLayout 方法进行一些处理,所以让子类通过重写 layoutChildren 这个方法进行实现摆放。
listView 的 layoutChildren:
@Override
protected void layoutChildren() {
...
//一般情况下,没有对 mLayoutMode 进行赋值的话,mLayoutMode 的值是 LAYOUT_NORMAL,走 default。
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition, mSpecificTop);
break;
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
case LAYOUT_SPECIFIC:
final int selectedPosition = reconcileSelectedPosition();
sel = fillSpecific(selectedPosition, mSpecificTop);
/**
* When ListView is resized, FocusSelector requests an async selection for the
* previously focused item to make sure it is still visible. If the item is not
* selectable, it won't regain focus so instead we call FocusSelector
* to directly request focus on the view after it is visible.
*/
if (sel == null && mFocusSelector != null) {
final Runnable focusRunnable = mFocusSelector
.setupFocusIfValid(selectedPosition);
if (focusRunnable != null) {
post(focusRunnable);
}
}
break;
case LAYOUT_MOVE_SELECTION:
sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
break;
default:
//第一次调用的时候,还没有摆放任何子 View,childCount 为 0。
if (childCount == 0) {
if (!mStackFromBottom) {
final int position = lookForSelectablePosition(0, true);
setSelectedPositionInt(position);
sel = fillFromTop(childrenTop);
} else {
final int position = lookForSelectablePosition(mItemCount - 1, false);
setSelectedPositionInt(position);
//fillUp 是很关键的一块代码,由上往下填充。
sel = fillUp(mItemCount - 1, childrenBottom);
}
} else {
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition,
oldSel == null ? childrenTop : oldSel.getTop());
} else if (mFirstPosition < mItemCount) {
sel = fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
} else {
sel = fillSpecific(0, childrenTop);
}
}
break;
}
...
}
ListView 的 fillUp:
//pos:当前绘制是第几个 Item
//nextBottom :每一个 Item 中的 View 的下边界。
private View fillUp(int pos, int nextBottom) {
View selectedView = null;
int end = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end = mListPadding.top;
}
//复用机制的核心,控制这一页的绘制
//end 是当前 LietView 最下边的坐标,即判断下一个 Item 的 Buttom 是否超出 ListView
while (nextBottom > end && pos >= 0) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
//返回的 View 是下一个 Child
View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
nextBottom = child.getTop() - mDividerHeight;
if (selected) {
selectedView = child;
}
pos--;
}
mFirstPosition = pos + 1;
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
在 while 循环中对 ListView 进行填充,通过 nextBottom > end 来判断当前页填充完成后,不再继续填充。这样每次就只会绘制当前显示页的内容,不会把 LstView 全部绘制出来。
ListView 的 makeAndAddView
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
if (!mDataChanged) {
// Try to use an existing view for this position.
final View activeView = mRecycler.getActiveView(position);
if (activeView != null) {
// Found it. We're reusing an existing child, so it just needs
// to be positioned like a scrap view.
setupChild(activeView, position, y, flow, childrenLeft, selected, true);
return activeView;
}
}
// Make a new view for this position, or convert an unused view if
// possible.
final View child = obtainView(position, mIsScrap);
// This needs to be positioned and measured.
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
setupChild 方法添加上一个 Child 。
这边重点看一下 obtainView 方法,该方法在 ListView 没有实现,是在其父类 AbsListView 中实现。
AbsListView 的 obtainView
View obtainView(int position, boolean[] outMetadata) {
...
//mRecycler 就是一个回收池,当前回收池有 View 则返回 View,没有则为空。
final View scrapView = mRecycler.getScrapView(position);
//最终调用 adapter 的 getView 方法
final View child = mAdapter.getView(position, scrapView, this);
...
}
源码值分析重要的部分,到这里,已经把上面说的 LiestView 复用机制对应的代码都找到了。
最后再顺便看一下回收池 RecycleBin,RecycleBin 是一个 AbsListView 的内部类。
RecycleBin 类
class RecycleBin {
...
/**
* Unsorted views that can be used by the adapter as a convert view.
*/
private ArrayList<View>[] mScrapViews;
View getScrapView(int position) {
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}
...
}
这边回收池回收的内容主要是存放在 mScrapViews,mScrapViews 是一个是一个集合数组,因为 ListView 可能有多种不同样式的 Item,所以为不同的 Item 建立一个集合,当 getScrapView 的时候先去调用 getItemViewType 去判断样式,然后从对应的回收池集合中取出对应的 View。
三、TableView 架构
由上面分析可以,复用机制主要是利用了一个回收池进行 View 的复用。在这边,也先搭建一个简单的回收池。
Recycler.java
public class Recycler {
private ArrayList<View>[] views;
public Recycler(int type){
views = new ArrayList[type];
for (int i = 0; i < type; i ++) {
views[i] = new ArrayList<>();
}
}
public void addRecycledView (View view, int type){
views[type].add(view);
}
public View getRecycledView (int type){
if (views[type].size() != 0) {
return views[type].remove(0);
}
return null;
}
}
适配器 BaseTableAdapter
public interface BaseTableAdapter {
/*
* ListView 的 Adapter 中只有 getCount()一个方法
* BaseTableAdapter 这边获取行的个数同时,也要获取列的个数
*/
int getRowCount();
int getColumnCount();
/*
* ListView 的 Adapter 中 getView(int position, View convertView, ViewGroup parent) 方法只有三个参数
* BaseTableAdapter 的 getView 方法需要同时传入 View 的行与列
*/
View getView(int row, int column, View convertView, ViewGroup parent);
public int getWidth(int column);
public int getHeight(int row);
public int getItemViewType(int row, int column);
}
TableView.java
public class TableView extends ViewGroup{
public TableView(Context context) {
this(context, null);
}
public TableView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TableView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
}
public void setAdapter (BaseTableAdapter adapter) {
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TableView tableView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tableView = (TableView)findViewById(R.id.tableView);
tableView.setAdapter(new MyAdapter());
}
class MyAdapter implements BaseTableAdapter {
@Override
public int getRowCount() {
return 0;
}
@Override
public int getColumnCount() {
return 0;
}
@Override
public View getView(int row, int column, View convertView, ViewGroup parent) {
return null;
}
@Override
public int getWidth(int column) {
return 0;
}
@Override
public int getHeight(int row) {
return 0;
}
@Override
public int getItemViewType(int row, int column) {
return 0;
}
}
}
这样初步搭建了一个 TableView 的架构,采用 Adapter 适配器,跟 ListView 十分相似。
四、TableView 布局
接下来这边进行 TableView 的 实现,作为一个自定义 ViewGroup,onMeasure 与 onLayout 这两个方法的重新肯定是核心内容。
TableView
public class TableView extends ViewGroup{
private BaseTableAdapter adapter;
//每一列宽度,每一行高度
private int[] widths;
private int[] heights;
//行与列的个数
private int rowCount;
private int columnCount;
//触摸点的坐标
private int currentX;
private int currentY;
private int width;
private int height;
//四个区域对应的 View 存储
private View headView;
private List<View> rowViewList;
private List<View> columnViewList;
private List<List<View>> bodyViewTable;
//回收池
private Recycler recycler;
//滑动的偏移量
private int scrollX;
private int scrollY;
//当前页显示的第一行第一列
private int firstRow;
private int firstColumn;
public TableView(Context context) {
this(context, null);
}
public TableView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TableView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//初始化显示的四块区域
this.headView = null;
this.rowViewList = new ArrayList<View>();
this.columnViewList = new ArrayList<View>();
this.bodyViewTable = new ArrayList<List<View>>();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int w;
final int h;
if (adapter != null) {
//获取行与列的个数
this.rowCount = adapter.getRowCount();
this.columnCount = adapter.getColumnCount();
//记录每一列的宽度
widths = new int[columnCount + 1];
for (int i = -1; i < columnCount; i++) {
widths[i + 1] = adapter.getWidth(i);
}
//记录每一行的高度
heights = new int[rowCount + 1];
for (int i = -1; i < rowCount; i++) {
heights[i + 1] = adapter.getHeight(i);
}
//这边先大体获取一下宽高,细节还需要处理
if (widthMode == MeasureSpec.AT_MOST) {
w = Math.min(widthSize, sumArray(widths));
} else {
w = widthSize;
}
if (heightMode == MeasureSpec.AT_MOST) {
h = Math.min(heightSize, sumArray(heights));
} else {
h = heightSize;
}
} else {
if (heightMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
w = 0;
h = 0;
} else {
w = widthSize;
h = heightSize;
}
}
setMeasuredDimension(w, h);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (adapter != null) {
width = r - l;
height = b - t;
int left, top, right, bottom;
//获取左上角的 View
headView = makeAndSetup(-1, -1, 0, 0, widths[0], heights[0]);
//获取右上角的 View
left = widths[0] - scrollX;
//只绘制当前页
for (int i = firstColumn; i < columnCount && left < width; i++) {
right = left + widths[i + 1];
final View view = makeAndSetup(-1, i, left, 0, right, heights[0]);
rowViewList.add(view);
left = right;
}
//获取左下角的 View
top = heights[0] - scrollY;
//只绘制当前页
for (int i = firstRow; i < rowCount && top < height; i++) {
bottom = top + heights[i + 1];
final View view = makeAndSetup(i, -1, 0, top, widths[0], bottom);
columnViewList.add(view);
top = bottom;
}
//获取右下角的 View,为了方便看,没有与上面的放在一起
top = heights[0] - scrollY;
//只绘制当前页
for (int i = firstRow; i < rowCount && top < height; i++) {
bottom = top + heights[i + 1];
left = widths[0] - scrollX;
List<View> list = new ArrayList<View>();
for (int j = firstColumn; j < columnCount && left < width; j++) {
right = left + widths[j + 1];
final View view = makeAndSetup(i, j, left, top, right, bottom);
list.add(view);
left = right;
}
bodyViewTable.add(list);
top = bottom;
}
}
}
//获取一个 View
private View makeAndSetup(int row, int column, int left, int top, int right, int bottom) {
final View view = obtainView(row, column, right - left, bottom - top);
view.layout(left, top, right, bottom);
return view;
}
//真正去获取 View
private View obtainView(int row, int column, int width, int height) {
//获取当前控件类型
int itemType = adapter.getItemViewType(row, column);
//获取当前 itemType 类型的回收池 View,可能为空
View recycledView = recycler.getRecycledView(itemType);
View view = adapter.getView(row, column, recycledView, this);
//view 肯定不为空,为 view 绑定三个标签
view.setTag(R.id.tag_type_view, itemType);
view.setTag(R.id.tag_row, row);
view.setTag(R.id.tag_column, column);
view.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
addTableView(view, row, column);
return view;
}
private void addTableView(View view, int row, int column) {
addView(view);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
currentX = (int) ev.getX();
currentY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//设置 move 事件时候进行拦截,不让子 View 消耗 move 事件
intercept = true;
break;
}
return super.onInterceptTouchEvent(ev);
}
public void setAdapter (BaseTableAdapter adapter) {
this.adapter = adapter;
this.recycler = new Recycler(adapter.getViewTypeCount());
scrollX = 0;
scrollY = 0;
firstColumn = 0;
firstRow = 0;
requestLayout();
}
//计算数组总和
private int sumArray(int array[]) {
return sumArray(array, 0, array.length);
}
private int sumArray(int array[], int firstIndex, int count) {
int sum = 0;
count += firstIndex;
for (int i = firstIndex; i < count; i++) {
sum += array[i];
}
return sum;
}
}
在 onLayout 方法中,获取各个区域的 View 的时候, for 循环要是把判断条件的 && 号后面去掉的话,则会把后方不需要显示的 Item 也加载出来,造成占用大量内存。
MainActivity
public class MainActivity extends AppCompatActivity {
private TableView tableView;
Handler handler = new Handler();
int row=10;
int colmun=10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tableView = (TableView)findViewById(R.id.tableView);
tableView.setAdapter(new MyAdapter(this));
//这边暂时还没有实现滑动,用handl重新加载,来模拟一下,看复用是否生效
handler.postDelayed(new Runnable() {
@Override
public void run() {
tableView.setAdapter(new MyAdapter(MainActivity.this));
}
},1500);
handler.postDelayed(new Runnable() {
@Override
public void run() {
row=10000;
colmun=1000;
tableView.setAdapter(new MyAdapter(MainActivity.this));
}
},8000);
}
//与 ListView 的适配器一样使用
class MyAdapter implements BaseTableAdapter {
LayoutInflater inflater;
public MyAdapter(Context context){
inflater = LayoutInflater.from(context);
}
@Override
public int getRowCount() {
return row;
}
@Override
public int getColumnCount() {
return colmun;
}
@Override
public View getView(int row, int column, View convertView, ViewGroup parent) {
//从回收池中获取的 View
if (convertView == null) {
convertView= inflater.inflate(getLayout(row, column), parent, false);
}
TextView textView= (TextView) convertView.findViewById(R.id.text1);
textView.setText(row+"行 "+column+"列 ");
return convertView;
}
@Override
public int getWidth(int column) {
return 150;
}
@Override
public int getHeight(int row) {
return 50;
}
@Override
public int getItemViewType(int row, int column) {
if (row < 0) {
return 0;
} else {
return 1;
}
}
@Override
public int getViewTypeCount() {
return 2 ;
}
private int getLayout(int row, int column) {
final int layoutResource;
switch (getItemViewType(row, column)) {
case 0:
layoutResource = R.layout.item_table1_header;
break;
case 1:
layoutResource = R.layout.item_table1;
break;
default:
throw new RuntimeException("wtf?");
}
return layoutResource;
}
}
}
这里只是单纯的自定义 ViewGroup 的开发,不做具体讲解。到这里,TableView 的显示已经处理好了,还缺少一个滑动时候进行 View 的复用。先看一下现在的效果图:
五、ListView 的 复用
通过上面分析可以知道,ListView 的复用主要是体现在滑动的时候,当旧的 item 滑动出去界面的时候,被复用为新的 item 重新添加到 ListView 里面。很明显,滑动的逻辑处理是在 onTouchEvent 方法里面。
在 ListView 中没有搜索到 onTouchEvent 方法,ListView 的 onTouchEvent 方法是在他的父类 AbsListView 中实现的。这边主要分析一下 move 事件时候的处理,可以看见,当 move 的时候调用了
onTouchMove 这个方法。
AbsListView 的 onTouchEvent:
public boolean onTouchEvent(MotionEvent ev) {
...
switch (actionMasked) {
...
case MotionEvent.ACTION_MOVE: {
onTouchMove(ev, vtev);
break;
}
...
}
...
}
AbsListView 的 onTouchMove:
//move 的 时候,mTouchMode 默认情况下是 TOUCH_MODE_SCROLL
private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
...
//mTouchMode 为 TOUCH_MODE_SCROLL
switch (mTouchMode) {
...
case TOUCH_MODE_SCROLL:
case TOUCH_MODE_OVERSCROLL:
scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
break;
}
}
在 onTouchMove 中,mTouchMode 默认情况下是 TOUCH_MODE_SCROLL,调用了 scrollIfNeeded 方法,这个方法是最终处理滑动的方法。
AbsListView 的 scrollIfNeeded:
private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
...
final int deltaY = rawDeltaY;
//incrementalDeltaY 为 Y 方向上的增量,带正负号。
//y - mLastY 即当前点 Y 坐标 - 上一个点 Y 坐标。
//incrementalDeltaY 为正则是往下滑,incrementalDeltaY 为负则是往上滑动。
int incrementalDeltaY =
mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
int lastYCorrection = 0;
//考虑的是 move 情况,这时候 mTouchMode 的值为 TOUCH_MODE_SCROLL
if (mTouchMode == TOUCH_MODE_SCROLL) {
...
if (y != mLastY) {
...
// No need to do all this work if we re not going to move anyway
boolean atEdge = false;
//滑动距离不等 0 的时候,调用 trackMotionScroll 方法
if (incrementalDeltaY != 0) {
atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
}
...
}
...
}
}
trackMotionScroll 由注释就可以知道,是触发一个滑动事件。
AbsListView 的 trackMotionScroll:
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
...
//incrementalDeltaY 大于 0,down 为 false,往下
//incrementalDeltaY 小于 0,down 为 true,往上
final boolean down = incrementalDeltaY < 0;
final boolean inTouchMode = isInTouchMode();
if (inTouchMode) {
hideSelector();
}
final int headerViewsCount = getHeaderViewsCount();
final int footerViewsStart = mItemCount - getFooterViewsCount();
int start = 0;
int count = 0;
if (down) {
//incrementalDeltaY 小于 0,往上
//top 表示 ListView 未滑动前,处于 top 位置的将被滑到最上方,在 top 上面的将被滑出去
int top = -incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
top += listPadding.top;
}
//循环遍历当前页的所有 View
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getBottom() >= top) {
//view 不会被滑出当前页,直接 break 是后面的 View 在更下方,肯定不会被滑出去
break;
} else {
//view 被滑出当前页
//count 统计被滑出当前页的个数
count++;
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();
//添加到回收池
mRecycler.addScrapView(child, position);
}
}
}
} else {
//incrementalDeltaY 大于 0,往下
//与往上滑类似,不再分析
int bottom = getHeight() - incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
bottom -= listPadding.bottom;
}
//倒序循环,在 if 条件成立时候,减少循环次数
for (int i = childCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getTop() <= bottom) {
break;
} else {
start = i;
count++;
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();
mRecycler.addScrapView(child, position);
}
}
}
}
...
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
//该方法一定会被调用
//fillGap 为抽象方法,在 Listview 中实现
fillGap(down);
}
...
return false;
}
ListView 的 fillGap:
void fillGap(boolean down) {
final int count = getChildCount();
if (down) {
//往上滑
int paddingTop = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
paddingTop = getListPaddingTop();
}
final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight : paddingTop;
//往上滑,填充的 View 是在底部
fillDown(mFirstPosition + count, startOffset);
correctTooHigh(getChildCount());
} else {
//往下滑
int paddingBottom = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
paddingBottom = getListPaddingBottom();
}
final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight : getHeight() - paddingBottom;
//往下滑,填充的 View 是在顶部
fillUp(mFirstPosition - 1, startOffset);
correctTooLow(getChildCount());
}
}
ListView 的 fillDown:
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
while (nextTop < end && pos < mItemCount) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
//makeAndAddView 在开头就提到过,会调用 obtainView 方法。obtainView 方法会去回收池获取 View
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
六、TableView
TableView 的实现主要就是模仿 ListView 的复用思想进行开发,功能代码比较简单,但是细节要处理的比较多,在这边就不具体写出来。找了个大神写的 TableView 自定义控件,可直接使用,个人添加了详细的注释。
链接:http://download.csdn.net/download/qq_18983205/9993347
建议按 onMeasure --》onLayout --》onInterceptTouchEvent --》onTouchEvent 顺序进行查看 TableView 源码