AbsListView 浅析

来源:互联网 发布:谷歌拼音输入法linux 编辑:程序博客网 时间:2024/05/29 21:29
 Base class that can be used to implement virtualized lists of items. A list does
 not have a spatial definition here. For instance, subclases of this class can
display the content of the list in a grid, in a carousel, as stack, etc.

这个是AbsListView 的类注释,大体上是说 abs是列表类的基础,这个列表在这里没有特别的制定。
可以是 列表样式的,网格样式的等。。。

listview 是一个熟悉的不能在熟悉的控件,典型的列表控件,但是我觉得最经典的应该是listview 的复用机制,和他的是怎么滚动的,怎么在滚动过程中将item的复用给应用在里面去的。

setAdapter看一下

public void setAdapter(ListAdapter adapter) {
    //如果有设置adapter 就清空,并且把上一个数据观察者解除注册。
    if (mAdapter != null && mDataSetObserver != null) {
        mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }

    //重置数据状态
    resetList();
    mRecycler.clear();

    //设置数据,如果有header信息,或者 fooder信息 就需要构造 HeaderViewListAdapter Adapter
    if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
        mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
    } else {
        mAdapter = adapter;
    }

    //重置选择位置和条目
    mOldSelectedPosition = INVALID_POSITION;
    mOldSelectedRowId = INVALID_ROW_ID;

    // AbsListView#setAdapter will update choice mode states.
    //清除 absListView 状态
    super.setAdapter(adapter);

    //如果有数据,开始初始化数据信息
    if (mAdapter != null) {
        //一般继承baseAdapter都是固定值 true
        mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
        //之前的数据条目数
        mOldItemCount = mItemCount;
        //新的数据条目数
        mItemCount = mAdapter.getCount();
        checkFocus();

        //new 一个新的数据观察者对象  用来做数据更新用 例如 adapter调用 notifyDataSetChanged
        mDataSetObserver = new AdapterDataSetObserver();
        mAdapter.registerDataSetObserver(mDataSetObserver);

        //设置类型 数据回收的类型
        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();
    }

    //请求刷新界面
    requestLayout();
}

listview 重置了 mRecycler,并且调用mRecycler.setViewTypeCount 设置回收的数据类型。
这个类在 AbsListview中是这样定义的
/**
 * The data set used to store unused views that should be reused during the next layout
 * to avoid creating new ones
 */
final RecycleBin mRecycler = new RecycleBin();

他是被用来存储数据和重用用来避免多次创建view。

The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the start of a layout. By construction, they are displaying current information. At the end of layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that could potentially be used by the adapter to avoid allocating views unnecessarily.

class RecycleBin {}

大意是说 RecycleBin 的主要功能是为了更方便的复用view,他有两个层级,分别是ActiveViews和ScrapViews。ActiveViews是当前展示在屏幕上的 layout,但是最终所有的views 都将降级为ScrapViews。 ScrapViews  是被回收掉的不在屏幕展示的view, 他为了避免adapter 重新创建view。

/*Views that were on screen at the start of layout. This array is populated        *at the start of
 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
 * Views in mActiveViews represent a contiguous range of Views, with position of the first
 * view store in mFirstActivePosition.
 */
private View[] mActiveViews = new View[0];

/**
 * Unsorted views that can be used by the adapter as a convert view.
 */
private ArrayList<View>[] mScrapViews;

这是RecycleBin 中复用的两个关键行变量,mActiveViews就是当前展示的view,mScrapViews是已经被降级的view的集合,但是为什么一个是数组,另外一个是list数组,这点比较奇怪,在看一下listView中 setAdapter中调用了一个
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
设置回收数据的类型

public void setViewTypeCount(int viewTypeCount) {
    if (viewTypeCount < 1) {
        throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
    }
    //noinspection unchecked
    ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
    for (int i = 0; i < viewTypeCount; i++) {
        scrapViews[i] = new ArrayList<View>();
    }
    mViewTypeCount = viewTypeCount;
    mCurrentScrap = scrapViews[0];
    mScrapViews = scrapViews;
}

这个方法初始化 scrapViews 的数量,还是以list view 为例,外面有几种item样式,这里也就要能初始化出来几种类型,listview 的adapter 一般都是继承baseAdapter看一下他getViewTypeCount 这个方法
public int getViewTypeCount() {
    return 1;
}
这里是baseadapter的实现,这里默认item只有一种类型,这个需要默认是1 也就是如果需要多种类型的需要自己来实现。

/**
 * Get the view corresponding to the specified position. The view will be removed from
 * mActiveViews if it is found.
 *
 * @param position The position to look up in mActiveViews
 * @return The view if it is found, null otherwise
 */
View getActiveView(int position) {
    int index = position - mFirstActivePosition;
    final View[] activeViews = mActiveViews;
    if (index >=0 && index < activeViews.length) {
        final View match = activeViews[index];
        activeViews[index] = null;
        return match;
    }
    return null;
}

获取当前界面展示的view。

/**
 * @return A view from the ScrapViews collection. These are unordered.
 */
View getScrapView(int position) {
    if (mViewTypeCount == 1) {
        //只有一个复用类型的
        return retrieveFromScrap(mCurrentScrap, position);
    } else {
        //多个复用类型的
        int whichScrap = mAdapter.getItemViewType(position);
        if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
            return retrieveFromScrap(mScrapViews[whichScrap], position);
        }
    }
    return null;
}
从scrapView中获取相应类型的view。

void addScrapView(View scrap, int position) {
    AbsListView.LayoutParams lp = (AbsListView.LayoutParams)      scrap.getLayoutParams();
    if (lp == null) {
        return;
    }

   。。。。

    scrap.dispatchStartTemporaryDetach();
    if (mViewTypeCount == 1) {
        mCurrentScrap.add(scrap);
    } else {
        mScrapViews[viewType].add(scrap);
    }

    scrap.setAccessibilityDelegate(null);
    if (mRecyclerListener != null) {
        mRecyclerListener.onMovedToScrapHeap(scrap);
    }
}

将view放入相对应的回收类型中。

在看AbsListView  onTouchEven方法 中的 move动作处理
case MotionEvent.ACTION_MOVE: {
    int pointerIndex = ev.findPointerIndex(mActivePointerId);
    if (pointerIndex == -1) {
        pointerIndex = 0;
        mActivePointerId = ev.getPointerId(pointerIndex);
    }
    final int y = (int) ev.getY(pointerIndex);

    if (mDataChanged) {
        // Re-sync everything if data has been changed
        // since the scroll operation can query the adapter.
        //如果数据改变就更新条目
        layoutChildren();
    }

    switch (mTouchMode) {
    case TOUCH_MODE_DOWN:
    case TOUCH_MODE_TAP:
    case TOUCH_MODE_DONE_WAITING:
        // Check if we have moved far enough that it looks more like a
        // scroll than a tap
        startScrollIfNeeded(y);
        break;
    case TOUCH_MODE_SCROLL:
    case TOUCH_MODE_OVERSCROLL:
        //如果是需要滚动 就调用这个方法
        scrollIfNeeded(y);
        break;
    }
    break;
}

srcollIfNeeded

private void scrollIfNeeded(int y) {
    final int rawDeltaY = y - mMotionY;
    final int deltaY = rawDeltaY - mMotionCorrection;
    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;

    if (mTouchMode == TOUCH_MODE_SCROLL) {
        if (PROFILE_SCROLLING) {
            if (!mScrollProfilingStarted) {
                Debug.startMethodTracing("AbsListViewScroll");
                mScrollProfilingStarted = true;
            }
        }

        if (mScrollStrictSpan == null) {
            // If it's non-null, we're already in a scroll.
            mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
        }

        if (y != mLastY) {
            // We may be here after stopping a fling and continuing to scroll.
            // If so, we haven't disallowed intercepting touch events yet.
            // Make sure that we do so in case we're in a parent that can intercept.
            //去滚动,设置不会被父控件打断
            if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
                    Math.abs(rawDeltaY) > mTouchSlop) {
                final ViewParent parent = getParent();
                if (parent != null) {
                    parent.requestDisallowInterceptTouchEvent(true);
                }
            }

            final int motionIndex;
            if (mMotionPosition >= 0) {
                motionIndex = mMotionPosition - mFirstPosition;
            } else {
                // If we don't have a motion position that we can reliably track,
                // pick something in the middle to make a best guess at things below.
                motionIndex = getChildCount() / 2;
            }

            int motionViewPrevTop = 0;
            View motionView = this.getChildAt(motionIndex);
            if (motionView != null) {
                motionViewPrevTop = motionView.getTop();
            }

            // No need to do all this work if we're not going to move anyway
            boolean atEdge = false;
            if (incrementalDeltaY != 0) {
                //这个方法中将不可见的view 放入添加到 回收列表
                atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
            }

            // Check to see if we have bumped into the scroll limit
            motionView = this.getChildAt(motionIndex);
            if (motionView != null) {
                // Check if the top of the motion view is where it is
                // supposed to be
                final int motionViewRealTop = motionView.getTop();
                if (atEdge) {
                    // Apply overscroll

                    //做滚动操作
                    int overscroll = -incrementalDeltaY -
                            (motionViewRealTop - motionViewPrevTop);
                    overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
                            0, mOverscrollDistance, true);
                    if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
                        // Don't allow overfling if we're at the edge.
                        if (mVelocityTracker != null) {
                            mVelocityTracker.clear();
                        }
                    }

                    final int overscrollMode = getOverScrollMode();
                    if (overscrollMode == OVER_SCROLL_ALWAYS ||
                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
                                    !contentFits())) {
                        mDirection = 0; // Reset when entering overscroll.
                        mTouchMode = TOUCH_MODE_OVERSCROLL;
                        if (rawDeltaY > 0) {
                            mEdgeGlowTop.onPull((float) overscroll / getHeight());
                            if (!mEdgeGlowBottom.isFinished()) {
                                mEdgeGlowBottom.onRelease();
                            }
                            invalidate(mEdgeGlowTop.getBounds(false));
                        } else if (rawDeltaY < 0) {
                            mEdgeGlowBottom.onPull((float) overscroll / getHeight());
                            if (!mEdgeGlowTop.isFinished()) {
                                mEdgeGlowTop.onRelease();
                            }
                            invalidate(mEdgeGlowBottom.getBounds(true));
                        }
                    }
                }
                mMotionY = y;
            }
            mLastY = y;
        }
    } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
        if (y != mLastY) {
            final int oldScroll = mScrollY;
            final int newScroll = oldScroll - incrementalDeltaY;
            int newDirection = y > mLastY ? 1 : -1;

            if (mDirection == 0) {
                mDirection = newDirection;
            }

            int overScrollDistance = -incrementalDeltaY;
            if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
                overScrollDistance = -oldScroll;
                incrementalDeltaY += overScrollDistance;
            } else {
                incrementalDeltaY = 0;
            }

            if (overScrollDistance != 0) {
                overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
                        0, mOverscrollDistance, true);
                final int overscrollMode = getOverScrollMode();
                if (overscrollMode == OVER_SCROLL_ALWAYS ||
                        (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
                                !contentFits())) {
                    if (rawDeltaY > 0) {
                        mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
                        if (!mEdgeGlowBottom.isFinished()) {
                            mEdgeGlowBottom.onRelease();
                        }
                        invalidate(mEdgeGlowTop.getBounds(false));
                    } else if (rawDeltaY < 0) {
                        mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
                        if (!mEdgeGlowTop.isFinished()) {
                            mEdgeGlowTop.onRelease();
                        }
                        invalidate(mEdgeGlowBottom.getBounds(true));
                    }
                }
            }

            if (incrementalDeltaY != 0) {
                // Coming back to 'real' list scrolling
                if (mScrollY != 0) {
                    mScrollY = 0;
                    invalidateParentIfNeeded();
                }

                trackMotionScroll(incrementalDeltaY, incrementalDeltaY);

                mTouchMode = TOUCH_MODE_SCROLL;

                // We did not scroll the full amount. Treat this essentially like the
                // start of a new touch scroll
                final int motionPosition = findClosestMotionRow(y);

                mMotionCorrection = 0;
                View motionView = getChildAt(motionPosition - mFirstPosition);
                mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
                mMotionY = y;
                mMotionPosition = motionPosition;
            }
            mLastY = y;
            mDirection = newDirection;
        }
    }
}

做滚动处理 调用 overScrollBy

 */
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {

     。。。。。。。。
    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);
            if (child.getBottom() >= top) {
                break;
            } else {
                count++;
                int position = firstPosition + i;
                if (position >= headerViewsCount && position < footerViewsStart) {
                    mRecycler.addScrapView(child, position);
                }
            }
        }
    } else {
        int bottom = getHeight() - incrementalDeltaY;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            bottom -= listPadding.bottom;
        }
        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) {
                    mRecycler.addScrapView(child, position);
                }
            }
        }
    }

    。。。。

    return false;
}

这个方法将不可见的view 放入不可见的列表中去 看看何时将它取出来了,滚动之后,调用了重新绘制界面的方法,看一下。

获取已经被放到scrapView的view。
View obtainView(int position, boolean[] isScrap) {
    isScrap[0] = false;
    View scrapView;

        。。。。。。。

    //从已经回收的view中取出相对应的来
    scrapView = mRecycler.getScrapView(position);

    View child;
    if (scrapView != null) {
        //将复用的view 给Apdater重新持用
        child = mAdapter.getView(position, scrapView, this);

        if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        }

        //将检测 如果重新使用的view 不是从回收中取出来的  已然放入回收站中
        if (child != scrapView) {
            mRecycler.addScrapView(scrapView, position);
            if (mCacheColorHint != 0) {
                child.setDrawingCacheBackgroundColor(mCacheColorHint);
            }
        } else {
            isScrap[0] = true;
            child.dispatchFinishTemporaryDetach();
        }
    } else {
        child = mAdapter.getView(position, null, this);

         
    }


    。。。。。。。


    return child;
0 0
原创粉丝点击