Android 源码的适配器模式

Android 源码的适配器模式

适配器模式介绍

适配器模式在我们的开发中使用率极高,从代码中随处可见的 Adapter 就可以判断出来。从最早的 ListView、GridView 到现在最新的 RecyclerView 都需要使用 Adapter。

适配器模式的定义

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够一起工作。

Android 源码中的适配器模式

在开发过程中,ListView 的 Adapter 是我们最常见的类型之一。

ListView 中并没有 Adapter 相关的成员变量,其实 Adapter 在 ListView 的父类 AbsListView 中,AbsListView 是一个列表控件的抽象。

public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
        ViewTreeObserver.OnTouchModeChangeListener,
        RemoteViewsAdapter.RemoteAdapterConnectionCallback {
    ...
    ListAdapter mAdapter;
    ...
    // 关联到 Window 时调用,获取调用 Adapter 中的 getCount 方法等
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        final ViewTreeObserver treeObserver = getViewTreeObserver();
        treeObserver.addOnTouchModeChangeListener(this);
        if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
            treeObserver.addOnGlobalLayoutListener(this);
        }
        // 给适配器注册一个观察者
        if (mAdapter != null && mDataSetObserver == null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            // Data may have changed while we were detached. Refresh.
            mDataChanged = true;
            mOldItemCount = mItemCount;
            // 获取 Item 的数量,调用的是 mAdapter 的 getCount 方法
            mItemCount = mAdapter.getCount();
        }
    }

    ...

    // 子类需要覆写 layoutChildren 函数来布局 child view,也就是 item view
    @Override
    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.markChildrenDirty();
        }

        // 布局 Child View
        layoutChildren();

        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;

        // TODO: Move somewhere sane. This doesn't belong in onLayout().
        if (mFastScroll != null) {
            mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
        }
        mInLayout = false;
    }
    ...
}

ListView 实现了 layoutChildren 方法,代码如下:

    @Override
    protected void layoutChildren() {
        ...

        try {
            super.layoutChildren();

            invalidate();

            ...
            // 根据布局模式来布局 item view
            switch (mLayoutMode) {
            ...
            case LAYOUT_FORCE_TOP:
            case LAYOUT_FORCE_BOTTOM:
            case LAYOUT_SPECIFIC:
            case LAYOUT_SYNC:
                break;
            case LAYOUT_MOVE_SELECTION:
            default:
                ...
            }
        ...
    }

ListView 覆写了 AbsListView 中的 layoutChildren 函数,在该函数中根据布局模式来布局 item view,例如,默认情况是从上到下开始布局,但是,也有从下到上开始布局的,例如 QQ 聊天窗口的气泡布局,最新的消息就会布局到窗口的最底部。

fillDown 从上往下填充 item view。

    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 获取 item 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;
    }

fillUp 从下往上填充布局。

    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;
        }

        while (nextBottom > end && pos >= 0) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            // 通过 makeAndAddView 获取 item view
            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;
    }

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;
            }
        }

        // 获取一个 item view
        // Make a new view for this position, or convert an unused view if
        // possible.
        final View child = obtainView(position, mIsScrap);

        // 将 item view 设置到对应的地方
        // This needs to be positioned and measured.
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

makeAndAddView 分为两个步骤,第一个是根据 position 获取一个 item view,然后将这个 view 布局到特定的位置。获取一个 item view 调用的是 obtainView 方法。这个方法在 AbsListView 中。

obtainView 方法如下:

    View obtainView(int position, boolean[] outMetadata) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        outMetadata[0] = false;

        // Check whether we have a transient state view. Attempt to re-bind the
        // data and discard the view if we fail.
        final View transientView = mRecycler.getTransientStateView(position);
        if (transientView != null) {
            final LayoutParams params = (LayoutParams) transientView.getLayoutParams();

            // If the view type hasn't changed, attempt to re-bind the data.
            if (params.viewType == mAdapter.getItemViewType(position)) {
                final View updatedView = mAdapter.getView(position, transientView, this);

                // If we failed to re-bind the data, scrap the obtained view.
                if (updatedView != transientView) {
                    setItemViewLayoutParams(updatedView, position);
                    mRecycler.addScrapView(updatedView, position);
                }
            }

            outMetadata[0] = true;

            // Finish the temporary detach started in addScrapView().
            transientView.dispatchFinishTemporaryDetach();
            return transientView;
        }
        // 1. 从缓存的 item view 中获取,ListView 的复用机制就在这里
        final View scrapView = mRecycler.getScrapView(position);
        // 2. 注意,这里将 scrapView 设置给了 Adapter 的 getView 函数
        final View child = mAdapter.getView(position, scrapView, this);
        if (scrapView != null) {
            if (child != scrapView) {
                // Failed to re-bind the data, return scrap to the heap.
                mRecycler.addScrapView(scrapView, position);
            } else if (child.isTemporarilyDetached()) {
                outMetadata[0] = true;

                // Finish the temporary detach started in addScrapView().
                child.dispatchFinishTemporaryDetach();
            }
        }
        ...
        return child;
    }

obtainView 方法定义了列表控件的 item view 的复用逻辑,首先会从 RecyclerBin 中获取一个缓存的 View。如果有缓存则将这个缓存的 View 传递到 Adapter 的 getView 的第二个参数中,这也就是我们对 Adapter 的最常见的优化方式,即判断 getView 的 convertView 是否为空。如果为空则从 xml 中创建视图,否则使用缓存的 View。这样避免了每次都从 xml 加载布局的消耗,能够显著提升 ListView 的效率。

在 ListView 的适配器模式中,target 角色就是 View,Adapter 就是将 item view 输出为 view 抽象的角色,adaptee 就是需要被处理的 item view。通过增加 adapter 一层来将 item view 的操作抽象起来,listView 等集合视图通过 adapter 对象获得 item 的个数、数据、item view 等,从而达到适配各种数据、各种 item 视图的效果。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值