闲谈Android ListView设计

ListView 是安卓里面常用的列表控件,具有列表item复用等特点,今天我们通过源码来了解他的实现方式。

我们先从它的祖宗说起。如果我们是google的设计师,需要设计符合现在功能的ListView,我们该如何入手。首先是数据与View的分离,他们中间可以通过一个桥(适配器)建立连接。所以先定义一个Adapter接口

public interface Adapter {
}

Adapter中添加一对回调监听数据的变动,这样数据发生改变了,可以通知view重新绘制。

   
    void registerDataSetObserver(DataSetObserver observer);

    void unregisterDataSetObserver(DataSetObserver observer);

Adapter中有多少数据,根据索引取数据,指定数据位置的id。id是否稳定指向固定的数据。

    int getCount();   
   
    Object getItem(int position);
 
    long getItemId(int position);
    
    boolean hasStableIds();
    

Adapter的子类是要用户实现的。指定位置的数据怎么展现,诸如采用什么布局,如何用View自带的复用机制,以及View的引用,给一个回调让用户操作。

    View getView(int position, View convertView, ViewGroup parent);

加个常量,有些数据让view忽略。


    static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;

一个列表控件可能有多种样式在一个列表的需求,我们搞个type区分。大部分实现都是getViewTypeCount返回1.

  
    int getItemViewType(int position);
   
    int getViewTypeCount();
    
    static final int NO_SELECTION = Integer.MIN_VALUE;

数据是否为空,我们的列表控件未来会有头和尾,所以这个实现要特别注意。

     boolean isEmpty();

}

Adapter完成了,加个扩展接口,去指定item是否能反馈输入事件,比如点击啥的。

public interface ListAdapter extends Adapter {


    public boolean areAllItemsEnabled();

    boolean isEnabled(int position);
}

下面开始着手view的编写,Android里面自定义view,继承View或者ViewGroup.前者是单个View,后者是View的容器。我们要实现的是一个列表控件。所以声明一个ViewGroup抽象子类,里面声明一些abstract方法。
用一个泛型声明,可以让各种AdapterView子类关联到各种Adapter.

public abstract class AdapterView<T extends Adapter> extends ViewGroup {

定义三种item的点击事件成员变量,选择,点击,长按。使我们的AdapterView可以设置监听,并且触发时回调用户的实现。


    OnItemSelectedListener mOnItemSelectedListener;
    OnItemClickListener mOnItemClickListener;
    OnItemLongClickListener mOnItemLongClickListener;

OnItemClickListener的接口定义,以及get,set方法,回调几个参数给用户,AdapterView自身,item-view,位置,id。
performItemClick方法提供一个程序主动调用的方式。

    public interface OnItemClickListener {
        void onItemClick(AdapterView<?> parent, View view, int position, long id);
    }

    public void setOnItemClickListener(@Nullable OnItemClickListener listener) {
        mOnItemClickListener = listener;
    }
    @Nullable
    public final OnItemClickListener getOnItemClickListener() {
        return mOnItemClickListener;
    }

 
    public boolean performItemClick(View view, int position, long id) {
        final boolean result;
        if (mOnItemClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnItemClickListener.onItemClick(this, view, position, id);
            result = true;
        } else {
            result = false;
        }

        if (view != null) {
            view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        }
        return result;
    }

OnItemLongClickListener的接口定义

    public interface OnItemLongClickListener {
 
        boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
    }

    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        mOnItemLongClickListener = listener;
    }
    public final OnItemLongClickListener getOnItemLongClickListener() {
        return mOnItemLongClickListener;
    }

OnItemSelectedListener的接口定义,应该是有些场景是需要先选中才用触发操作。比如TV,要先用遥控器选中。


    public interface OnItemSelectedListener {
   
        void onItemSelected(AdapterView<?> parent, View view, int position, long id);

   
        void onNothingSelected(AdapterView<?> parent);
    }

    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
        mOnItemSelectedListener = listener;
    }

    @Nullable
    public final OnItemSelectedListener getOnItemSelectedListener() {
        return mOnItemSelectedListener;
    }

与AdapterView 关联的Adapter的get,set方法。setAdapter方法中要执行更多的处理。

    public abstract T getAdapter();

    public abstract void setAdapter(T adapter);

父类ViewGroup的一些增加删除view不再适应,抛出异常。


    @Override
    public void addView(View child) {
        throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
    }
    @Override
    public void addView(View child, int index) {
        throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
    }
    @Override
    public void addView(View child, LayoutParams params) {
        throw new UnsupportedOperationException("addView(View, LayoutParams) "
                + "is not supported in AdapterView");
    }
    @Override
    public void addView(View child, int index, LayoutParams params) {
        throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
                + "is not supported in AdapterView");
    }

    
    @Override
    public void removeView(View child) {
        throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
    }

   
    @Override
    public void removeViewAt(int index) {
        throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
    }

  
    @Override
    public void removeAllViews() {
        throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
    }
 @Override
    public void setOnClickListener(OnClickListener l) {
        throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
                + "You probably want setOnItemClickListener instead");
    }

当列表没有数据时,展示一个提示View.编写get,set方法,set的时候,判断是否需要展示。更新展示状态。


    private View mEmptyView;
       /**
     * Sets the view to show if the adapter is empty
     */
    @android.view.RemotableViewMethod
    public void setEmptyView(View emptyView) {
        mEmptyView = emptyView;

   
        if (emptyView != null
                && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        }

        final T adapter = getAdapter();
        final boolean empty = ((adapter == null) || adapter.isEmpty());
        updateEmptyStatus(empty);
    }

    public View getEmptyView() {
        return mEmptyView;
    }

根据empty入参,判断并设置AdapterView与emptyView的展示。filter mod默认返回false。判断用户是否在输入键盘。过滤选择数据。

    private void updateEmptyStatus(boolean empty) {
        if (isInFilterMode()) {
            empty = false;
        }

        if (empty) {
            if (mEmptyView != null) {
                mEmptyView.setVisibility(View.VISIBLE);
                setVisibility(View.GONE);
            } else {
                // If the caller just removed our empty view, make sure the list view is visible
                setVisibility(View.VISIBLE);
            }

            // We are now GONE, so pending layouts will not be dispatched.
            // Force one here to make sure that the state of the list matches
            // the state of the adapter.
            if (mDataChanged) {           
                this.onLayout(false, mLeft, mTop, mRight, mBottom); 
            }
        } else {
            if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
            setVisibility(View.VISIBLE);
        }
    }
   
    boolean isInFilterMode() {
        return false;
    }

AdapterView 委托Adapter按索引返回数据与id.

    
    public Object getItemAtPosition(int position) {
        T adapter = getAdapter();
        return (adapter == null || position < 0) ? null : adapter.getItem(position);
    }

    public long getItemIdAtPosition(int position) {
        T adapter = getAdapter();
        return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
    }

获取ApapterView内item数量。

    @ViewDebug.ExportedProperty(category = "list")
    int mItemCount;
   
    @ViewDebug.CapturedViewProperty
    public int getCount() {
        return mItemCount;
    }

根据view 来获取位置,view可以是ApapterView的item ,也可以是item里面包含的view.首先找到item.再遍历ApapterView的子类,匹配,返回位置

    public int getPositionForView(View view) {
        View listItem = view;
        try {
            View v;
            while ((v = (View) listItem.getParent()) != null && !v.equals(this)) {
                listItem = v;
            }
        } catch (ClassCastException e) {
            // We made it up to the window without find this list view
            return INVALID_POSITION;
        }

        if (listItem != null) {
            // Search the children for the list item
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                if (getChildAt(i).equals(listItem)) {
                    return mFirstPosition + i;
                }
            }
        }

        // Child not found!
        return INVALID_POSITION;
    }

mFirstPosition是第一个在屏幕中展示的item的位置. getFirstVisiblePosition()方法返回的就是它。getLastVisiblePosition 返回的是屏幕中最后一个可见的位置,getChildCount是viewGroup的方法,返回子View的个数。 可见AdapterView中子View个数就是屏幕中显示的那么多。


    @ViewDebug.ExportedProperty(category = "scrolling")
    int mFirstPosition = 0;
    public int getFirstVisiblePosition() {
        return mFirstPosition;
    }

  
    public int getLastVisiblePosition() {
        return mFirstPosition + getChildCount() - 1;
    }

 

记录当前选中的和下一个被选中item 的,位置和id。


    @ViewDebug.ExportedProperty(category = "list")
    int mNextSelectedPosition = INVALID_POSITION;

    long mNextSelectedRowId = INVALID_ROW_ID;

 
    @ViewDebug.ExportedProperty(category = "list")
    int mSelectedPosition = INVALID_POSITION;

 
    long mSelectedRowId = INVALID_ROW_ID;

数据变动监听者的实现,实现onChanged。标记数据改变,记录item数量,记录与恢复mInstanceState,requestLayout申请界面重新布局,绘制。onInvalidated恢复初始。

public abstract class DataSetObserver {
   
    public void onChanged() {
        // Do nothing
    }
    public void onInvalidated() {
        // Do nothing
    }
}
  class AdapterDataSetObserver extends DataSetObserver {

        private Parcelable mInstanceState = null;

        @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();
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            mDataChanged = true;

            if (AdapterView.this.getAdapter().hasStableIds()) {
                // Remember the current state for the case where our hosting activity is being
                // stopped and later restarted
                mInstanceState = AdapterView.this.onSaveInstanceState();
            }

            // Data is invalid so we should reset our state
            mOldItemCount = mItemCount;
            mItemCount = 0;
            mSelectedPosition = INVALID_POSITION;
            mSelectedRowId = INVALID_ROW_ID;
            mNextSelectedPosition = INVALID_POSITION;
            mNextSelectedRowId = INVALID_ROW_ID;
            mNeedSync = false;

            checkFocus();
            requestLayout();
        }

        public void clearSavedState() {
            mInstanceState = null;
        }
    }

AbsListView继承AdapterView,他的子类ListView,GridView等负责各种样式具体实现。缓存机制就在这里实现。

public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
        ViewTreeObserver.OnTouchModeChangeListener,
        RemoteViewsAdapter.RemoteAdapterConnectionCallback {

内部类RecycleBin负责缓存复用itemview的实现。

    final RecycleBin mRecycler = new RecycleBin();

RecycleBin中分mActiveViews与mScrapViews,mActiveViews(激活)就是显示在屏幕上的itemview。mScrapViews(废弃)是曾经可见,后来被回收,随着滑动被复用的itemview。RecycleBin包含了对激活对象与废弃对象集合数组的一些操作。


    class RecycleBin {
        private RecyclerListener mRecyclerListener;
        private int mFirstActivePosition;
        private View[] mActiveViews = new View[0];
        private ArrayList<View>[] mScrapViews;

        private int mViewTypeCount;

        private ArrayList<View> mCurrentScrap;

        private ArrayList<View> mSkippedScrap;
        private SparseArray<View> mTransientStateViews;
        private LongSparseArray<View> mTransientStateViewsById;

当一个激活view变成废弃view到的一个回调通知

    public static interface RecyclerListener {
   
        void onMovedToScrapHeap(View view);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值