一.ListView基础使用
Activity代码
public class ListViewActivity extends AppCompatActivity {
private ListView mListView;
private ListViewAdapter mAdapter;
private List<ListViewBean> mList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view);
initData();
}
/**
* 赋值
*/
private void initData() {
mListView = findViewById(R.id.activity_list_view_listView);
mList = new ArrayList<>();
ListViewBean listViewBean1 = new ListViewBean();
listViewBean1.setAva("");
listViewBean1.setTitle("张三");
ListViewBean listViewBean2 = new ListViewBean();
listViewBean2.setAva("");
listViewBean2.setTitle("韦德");
ListViewBean listViewBean3 = new ListViewBean();
listViewBean3.setAva("");
listViewBean3.setTitle("保罗");
for (int i = 0; i < 30; i++) {
mList.add(listViewBean1);
mList.add(listViewBean2);
mList.add(listViewBean2);
}
mAdapter = new ListViewAdapter(ListViewActivity.this, mList);
mListView.setAdapter(mAdapter);
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch(scrollState){
case SCROLL_STATE_FLING://惯性滚动
Log.d("ListViewActivity","ListView onScrollStateChanged 惯性滚动");
break;
case SCROLL_STATE_IDLE://空闲
Log.d("ListViewActivity","ListView onScrollStateChanged 空闲");
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Log.d("ListViewActivity","ListView onScroll方法执行...");
}
});
}
}
Adapter代码
public class ListViewAdapter extends BaseAdapter {
private List<ListViewBean> mList;
private Context mContext;
public ListViewAdapter(Context context, List<ListViewBean> list) {
mContext = context;
mList = list;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = View.inflate(mContext, R.layout.listview_item, null);
viewHolder = new ViewHolder();
viewHolder.imageView = convertView.findViewById(R.id.listview_item_imageview);
viewHolder.textView = convertView.findViewById(R.id.listview_item_textview);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.textView.setText(mList.get(position).getTitle());
return convertView;
}
public static class ViewHolder {
private ImageView imageView;
private TextView textView;
}
}
二.ListView缓存机制讲解
ListView结构
public class ListView extends AbsListView {
...
}
ListView继承AbsListView。AbsListView类中有一个内部类RecycleBin。这个内部类RecycleBin和ListView的缓存息息相关。GridView也是继承AbsListView类,所以GridView的缓存也是一样的。这里就不赘述了。
三.RecycleBin类详解
1.RecycleBin英文解释
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.
2.RecycleBin汉语解释
RecycleBin 促进了视图在布局中的重用。
RecycleBin有两层存储。
ActiveViews(数组 View[]):活动视图 即 屏幕中的ItemView。ActiveViews是那些在布局开始时出现在屏幕上的视图。通过构造,它们显示当前的信息。在布局的最后,所有的视图在ActiveViews降级为ScrapViews。
ScrapViews(集合 ArrayList<View>[]):废料视图 即 划出屏幕外的ItemView。ScrapViews是旧视图,适配器可能会使用它来避免不必要地分配视图。
3.RecycleBin详解
<1> 常用成员变量
class RecycleBin {
private RecyclerListener mRecyclerListener;
//The position of the first view stored in mActiveViews. 第一个可见的View
private int mFirstActivePosition;
//Views that were on screen at the start of layout. 屏幕中View的数组
private View[] mActiveViews = new View[0];
//Unsorted views that can be used by the adapter as a convert view. 屏幕外的View
private ArrayList<View>[] mScrapViews;
//View的类型的数量
private int mViewTypeCount;
private ArrayList<View> mCurrentScrap;
private ArrayList<View> mSkippedScrap;
private SparseArray<View> mTransientStateViews;
private LongSparseArray<View> mTransientStateViewsById;
}
<2> ActiveViews(数组 View[]):活动视图 初始化
RecycleBin内部类中的fillActiveViews方法:存放屏幕上活跃的View。
/**
* Fill ActiveViews with all of the children of the AbsListView.
*
* @param childCount The minimum number of views mActiveViews should hold
* @param firstActivePosition The position of the first view that will be stored in
* mActiveViews
*/
void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;
//noinspection MismatchedReadAndWriteOfArray
final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
// Don't put header or footer views into the scrap heap
if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
// However, we will NOT place them into scrap views.
activeViews[i] = child;
// Remember the position so that setupChild() doesn't reset state.
lp.scrappedFromPosition = firstActivePosition + i;
}
}
}
此方法在ListView源码中的layoutChildren方法调用
@Override
protected void layoutChildren() {
...
boolean dataChanged = mDataChanged;
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
recycleBin.fillActiveViews(childCount, firstPosition);
}
...
}
数据改变时,调用addScrapView()方法。此方法下面讲解。
数据没有改变时,调用fillActiveViews()方法。初始化在布局开始时出现在屏幕上的视图。
<3> ScrapViews(集合 ArrayList<View>[]):废料视图 初始化
RecycleBin内部类中的addScrapView方法:对不在屏幕中的View进行缓存。
/**
* Puts a view into the list of scrap views.
* <p>
* If the list data hasn't changed or the adapter has stable IDs, views
* with transient state will be preserved for later retrieval.
*
* @param scrap The view to add
* @param position The view's position within its parent
*/
void addScrapView(View scrap, int position) {
final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
// Can't recycle, but we don't know anything about the view.
// Ignore it completely.
return;
}
lp.scrappedFromPosition = position;
// Remove but don't scrap header or footer views, or views that
// should otherwise not be recycled.
final int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
// Can't recycle. If it's not a header or footer, which have
// special handling and should be ignored, then skip the scrap
// heap and we'll fully detach the view later.
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
getSkippedScrap().add(scrap);
}
return;
}
scrap.dispatchStartTemporaryDetach();
// The the accessibility state of the view may change while temporary
// detached and we do not allow detached views to fire accessibility
// events. So we are announcing that the subtree changed giving a chance
// to clients holding on to a view in this subtree to refresh it.
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
// Don't scrap views that have transient state.
final boolean scrapHasTransientState = scrap.hasTransientState();
if (scrapHasTransientState) {
if (mAdapter != null && mAdapterHasStableIds) {
// If the adapter has stable IDs, we can reuse the view for
// the same data.
if (mTransientStateViewsById == null) {
mTransientStateViewsById = new LongSparseArray<>();
}
mTransientStateViewsById.put(lp.itemId, scrap);
} else if (!mDataChanged) {
// If the data hasn't changed, we can reuse the views at
// their old positions.
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<>();
}
mTransientStateViews.put(position, scrap);
} else {
// Otherwise, we'll have to remove the view and start over.
clearScrapForRebind(scrap);
getSkippedScrap().add(scrap);
}
} else {
clearScrapForRebind(scrap);
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
}
此方法在ListView源码中的layoutChildren方法调用,如上。数据改变时,调用addScrapView()方法。
<4> getScrapView方法:获取缓存的View
/**
* @return A view from the ScrapViews collection. These are unordered.
*/
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;
}
4.缓存时机
<1> 缓存时机1 setAdapter
使用ListView或者GridView时,首先需要setAdapter操作
mAdapter = new ListViewAdapter(ListViewActivity.this, mList);
mListView.setAdapter(mAdapter);
源码
ListView的setAdapter()方法部分源码
@Override
public void setAdapter(ListAdapter adapter) {
//1.首先需要清除缓存
mRecycler.clear();
super.setAdapter(adapter);
//2.设置类型个数
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
}
<2> 缓存时机2 子Item滚动
private void scrollListItemsBy(int amount) {
while (first.getBottom() < listTop) {
AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
recycleBin.addScrapView(first, mFirstPosition);
}
detachViewFromParent(first);
first = getChildAt(0);
mFirstPosition++;
}
}
<3> 缓存时机3 onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, 0);
}
}
<4> 缓存时机4 measureHeightOfChildren方法
final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
int maxHeight, int disallowPartialChildPosition) {
if (recyle && recycleBin.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
recycleBin.addScrapView(child, -1);
}
}
四.总结
我们在使用ListView或者GridView时,创建Adapter时,重写的getView方法。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = View.inflate(mContext, R.layout.listview_item, null);
viewHolder = new ViewHolder();
viewHolder.imageView = convertView.findViewById(R.id.listview_item_imageview);
viewHolder.textView = convertView.findViewById(R.id.listview_item_textview);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.textView.setText(mList.get(position).getTitle());
return convertView;
}
public static class ViewHolder {
private ImageView imageView;
private TextView textView;
}
<1> 当View convertView==null时,即此时需要初始化屏幕中显示的View。所以此时需要findViewById。然后设置View convertView的Tag。
<2> 当View convertView!=null时,即此时屏幕中已经初始化了View。调用时机是,首个可见的Item滚出屏幕,然后屏幕中的底部第一个不可见的Item显示在屏幕的最下面。