(十九)ListView 复用思想 —— TableView 的实现

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

本文最后附一 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 源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值