Android_ListView (基本使用 / RecycleBin机制 / 源码解析 / 异步图片错位解决方案)

Android_ListView (基本使用 / RecycleBin机制 / 源码解析 / 异步图片错位解决方案)


本文由 Luzhuo 编写,转发请保留该信息.
原文: http://blog.csdn.net/Rozol/article/details/78161840


  • 把数据用列表的形式,动态滚动的方式,展示给用户.
  • ListView 作为界面展示的容器控件必然会直接或者间接的继承ViewGroup, 现在看看源代码的继承结构

    public class ListView extends AbsListView {
          }
    
    public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback {
          }
    
    public abstract class AdapterView<T extends Adapter> extends ViewGroup {
          }
  • 现在知道ListView确实是继承ViewGroup的,那么就会重写 onMeasure() onLayout() onDraw() 这三个基本的方法, 大家是否注意到继承ViewGroup的后,onMeasure() onLayout()会多次执行的问题(执行了4次onMeasure(), 2次onLayout()),以下是log.

基本使用

public class MainActivity extends AppCompatActivity {
   
    private ListView listView;
    private String[] listImage = Resource.grilImage;
    private BitmapUtils bitmapUtils;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        initData();
    }

    private void initView() {
        bitmapUtils = new BitmapUtils(this);
        listView = (ListView) findViewById(R.id.listview);
    }

    private void initData() {
        ImageAdapter IAdapter = new ImageAdapter(MainActivity.this, listImage, bitmapUtils);
        listView.setAdapter(IAdapter);

        listView.setOnScrollListener(new PauseOnScrollListener(bitmapUtils, false, true));
    }
}

public class ImageAdapter extends BaseAdapter {
   
    private Context context;
    private String[] listImage;
    private LayoutInflater inflater;
    private BitmapUtils bitmapUtils;

    public ImageAdapter(Context context, String[] listImage, BitmapUtils bitmapUtils) {
        this.context = context;
        this.listImage = listImage;
        inflater = LayoutInflater.from(context);
        this.bitmapUtils = bitmapUtils;
    }

    @Override
    public int getCount() {
        return listImage == null ? 0 : listImage.length;
    }

    @Override
    public Object getItem(int position) {
        return listImage[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 = inflater.inflate(R.layout.item_list, null);
            viewHolder = new ViewHolder();

            viewHolder.imageview = (ImageView)convertView.findViewById(R.id.imageview);
            viewHolder.textview = (TextView)convertView.findViewById(R.id.textview);

            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder) convertView.getTag();
        }

        if(listImage.length != 0){
            bitmapUtils.display(viewHolder.imageview, listImage[position]);
            viewHolder.textview.setText("第"+position+"张图片");
        }
        return  convertView;
    }

    class ViewHolder{
        ImageView imageview;
        TextView textview;
    }
}

Adapter适配器模式

  • 从上面的使用代码可以看出,要让ListView正常工作,就要设置Adapter,Adapter就是适配器

    public class MyAdapter extends BaseAdapter {
         
        @Override
        public int getCount() {
            return 0;
        }
    
        @Override
        public Object getItem(int position) {
            return null;
        }
    
        @Override
        public long getItemId(int position) {
            return 0;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return null;
        }
    }
  • 继承Adapter会被要求必须重写上述4个方法. 数据是不尽相同的, ListView只关心交互和展示的工作,不关心你数据是什么样的,从哪来的. 而Adapter的统一接口就解决的数据适配的问题.

  • 示范图:

recycleBin机制

  • 为了简洁说明ListView是怎么工作的,先讲下 AbsListView 类里的内部类 RecycleBin

    public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback {
         
    
        final RecycleBin mRecycler = new RecycleBin();
    
        class RecycleBin {
            private RecyclerListener mRecyclerListener;
    
            /**
             * The position of the first view stored in mActiveViews.
             */
            private int mFirstActivePosition;
    
            /**
             * Views that were on screen at the start of layout. This array is populated at the start of
             * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
             * Views in mActiveViews represent a contiguous range of Views, with position of the first
             * view store in mFirstActivePosition.
             */
            // ↓↓↓
            private View[] mActiveViews = new View[0];
    
            /**
             * Unsorted views that can be used by the adapter as a convert view.
             */
            // ↓↓↓
            private ArrayList<View>[] mScrapViews;
    
            private int mViewTypeCount;
            // ↓↓↓
            private ArrayList<View> mCurrentScrap;
    
            private ArrayList<View> mSkippedScrap;
    
            private SparseArray<View> mTransientStateViews;
            private LongSparseArray<View> mTransientStateViewsById;
    
            // ↓↓↓
            public void setViewTypeCount(int viewTypeCount) {
                if (viewTypeCount < 1) {
                    throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
                }
                //noinspection unchecked
                ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
                for (int i = 0; i < viewTypeCount; i++) {
                    scrapViews[i] = new ArrayList<View>();
                }
                mViewTypeCount = viewTypeCount;
                mCurrentScrap = scrapViews[0];
                mScrapViews = scrapViews;
            }
    
            /**
             * 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;
                    }
                }
            }
    
            /**
             * Get the view corresponding to the specified position. The view will be removed from
             * mActiveViews if it is found.
             *
             * @param position The position to look up in mActiveViews
             * @return The view if it is found, null otherwise
             */
            // ↓↓↓
            View getActiveView(int position) {
                int index = position - mFirstActivePosition;
                final View[] activeViews = mActiveViews;
                if (index >=0 && index < activeViews.length) {
                    final View match = activeViews[index];
                    activeViews[index] = null;
                    return match;
                }
                return null;
            }
    
            /**
             * @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;
            }
    
            /**
             * 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.
                        getSkippedScrap().add(scrap);
                    }
                } else {
                    if (mViewTypeCount == 1) {
                        mCurrentScrap.add(scrap);
                    } else {
                        mScrapViews[viewType].add(scrap);
                    }
    
                    if (mRecyclerListener != null) {
                        mRecyclerListener.onMovedToScrapHeap(scrap);
                    }
                }
            }
        }
    }
    • 以上代码是从源码中拷贝出来,并删掉了一些不重要的方法
    • 成员变量:

      • View[] mActiveViews: 用于存放活动View(也就是在屏幕上展示的View)
      • int mFirstActivePosition: 存放mActiveViews中第一个View的Position(也就是第几个Item)
      • ArrayList<View>[] mScrapViews: 废弃的View,可通过Adapter转为convertView继续使用(看看基本使用Adapter代码,我们就是将这些废弃的view重复使用的)
      • ArrayList<View> mCurrentScrap: ViewTypeCount == 1 的废弃View会被存在这个集合里
      • 方法:
      • public void setViewTypeCount(int viewTypeCount) {}: 该方法会根据传入的类型数量初始化 mScrapViews 和 mCurrentScrap; mScrapViews 存了不同类型View的集合, mCurrentScrap是mScrapViews的第一个集合; 看来ListView是可以传入多个类型的View的
      • void fillActiveViews(int childCount, int firstActivePosition) {}: 主要是将mActiveViews填满子View (保存屏幕上展示的View)
      • View getActiveView(int position) {}: 根据position获取mActiveViews里的View
      • View getScrapView(int position) {}: 源码主要调用了retrieveFromScrap(mCurrentScrap, position)方法,现在看看这个方法是干吗的?

        private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
            final int size = scrapViews.size();
            if (size > 0) {
                // See if we still have a view for this position or ID.
                for (int i = 0; i < size; i++) {
                    final View view = scrapViews.get(i);
                    final AbsListView.LayoutParams params =
                            (AbsListView.LayoutParams) view.getLayoutParams();
        
                    if (mAdapterHasStableIds) {
                        final long id = mAdapter.getItemId(position);
                        if (id == params.itemId) {
                            return scrapViews.remove(i);
                        }
                    } else if (params.scrappedFromPosition == position) {
                        final View scrap = scrapViews.remove(i);
                        clearAccessibilityFromScrap(scrap);
                        return scrap;
                    }
                }
                final View scrap = scrapViews.remove(size - 1);
                clearAccessibilityFromScrap(scrap);
                return scrap;
            } else {
                return null;
            }
        }
        • 看来getScrapView(int position)是获取废弃的View, 如果能获取到就返回View,获取不到就返回null
    • void addScrapView(View scrap, int position) {}: 把废弃的View添加到mCurrentScrap里, 把具有过渡效果的废弃View添加到mTransientStateViews里(带有过渡效果的View这里不做讲解)
    • 可见recycleBin主要工作就是填满和获取展示View,添加和获取缓存View.

ListView的执行逻辑源码

ListView的初始化逻辑

  • ListView作为View容器控件,那么我们就从 onMeasure() onLayout() 这2个基本的被重写方法开始研究
  • onMeasure() 主要是测量ListView的大小; onLayout()用于确定子View的布局, 这才是核心, 该方法并没有在ListView中实现, 而是在抽象父类AbsListView中实现.

    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();
        // ↓↓↓ 1. 如果change == true, ListView的大小和位置发生变化 
        if (changed) {
            for (int i = 0; i < childCount; i++) {
                // ↓↓↓ 2. 那么就把所有子布局强制重绘
                getChildAt(i).forceLayout();
            }
            mRecycler.markChildrenDirty();
        }
    
        // ↓↓↓ 3. 调用子类ListView的layoutChildren()方法
        layoutChildren();
        mInLayout = false;
    
        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
    
        // TODO: Move somewhere sane. This doesn't belong in onLayout().
        if (mFastScroll != null) {
            mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
        }
    }
    • 接着看看layoutChildren()做了什么

      protected void layoutChildren() {
          final boolean blockLayoutRequests = mBlockLayoutRequests;
          if (blockLayoutRequests) {
              return;
          }
      
          mBlockLayoutRequests = true;
      
          try {
              super.layoutChildren();
      
              invalidate();
      
              if (mAdapter == null) {
                  resetList();
                  invokeOnItemScrollListener();
                  return;
              }
      
              final int childrenTop = mListPadding.top;
              final int childrenBottom = mBottom - mTop - mListPadding.bottom;
              // ↓↓↓ 1. ListView中还未填充任务子View, 得到结果为0
              final int childCount = getChildCount();
      
              int index = 0;
              int delta = 0;
      
              View sel;
              View oldSel = null;
              View oldFirst = null;
              View newSel = null;
      
              // Remember stuff we will need down below
              switch (mLayoutMode) {
              <
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值