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

  ...

}


androidstudio缓存区间 android listview缓存机制_androidstudio缓存区间

数据改变时,调用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);
        }
    }
 }


androidstudio缓存区间 android listview缓存机制_ListView缓存_02

此方法在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;
}


androidstudio缓存区间 android listview缓存机制_getView_03

 

 

 

4.缓存时机

 

<1> 缓存时机1 setAdapter

使用ListView或者GridView时,首先需要setAdapter操作


mAdapter = new ListViewAdapter(ListViewActivity.this, mList);

mListView.setAdapter(mAdapter);


androidstudio缓存区间 android listview缓存机制_getView_04

 

源码 

ListView的setAdapter()方法部分源码


@Override
public void setAdapter(ListAdapter adapter) {


   //1.首先需要清除缓存
   mRecycler.clear();


   super.setAdapter(adapter);


   //2.设置类型个数
   mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());


}


androidstudio缓存区间 android listview缓存机制_ListView缓存_05

 

 

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


}


androidstudio缓存区间 android listview缓存机制_RecycleBin缓存_06

 

 

<3> 缓存时机3  onMeasure方法


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


  if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
           ((LayoutParams) child.getLayoutParams()).viewType)) {
       mRecycler.addScrapView(child, 0);
  }


}


androidstudio缓存区间 android listview缓存机制_RecycleBin缓存_07

 

 

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




}


androidstudio缓存区间 android listview缓存机制_Glide 设置缓存_08

 

 

 

 

 

 

 

四.总结

我们在使用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;
}


androidstudio缓存区间 android listview缓存机制_androidstudio缓存区间_09

 

<1> 当View convertView==null时,即此时需要初始化屏幕中显示的View。所以此时需要findViewById。然后设置View convertView的Tag。

<2> 当View convertView!=null时,即此时屏幕中已经初始化了View。调用时机是,首个可见的Item滚出屏幕,然后屏幕中的底部第一个不可见的Item显示在屏幕的最下面。