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