Recycleview的实例及于listview的强弱分析

什么是recycleview


Recycleview是一种新的视图组,目标是为任何基于适配器的视图提供相似的渲染方式。它被作为Listview和GridView控件的继承者,

Recycleview架构,提供了一种插拔式的体验,高度的解耦,异常灵活,设置不同的layoutmanager,itemdecoration,itemAnimator实现酷炫的效果

Listview和gridview能做的,recycleview都能实现,而且能实现瀑布流效果
原理

RecyclerView与ListView原理是类似的:都是仅仅维护少量的View并且可以展示大量的数据集。RecyclerView用以下两种方式简化了数据的展示和处理:

· 使用LayoutManager来确定每一个item的排列方式。

· 为增加和删除项目提供默认的动画效果。

你也可以定义你自己的LayoutManager和添加删除动画,RecyclerView项目结构如下:

Adapter:使用RecyclerView之前,你需要一个继承自RecyclerView.Adapter的适配器,作用是将数据与每一个item的界面进行绑定。

LayoutManager:用来确定每一个item如何进行排列摆放,何时展示和隐藏。回收或重用一个View的时候,LayoutManager会向适配器请求新的数据来替换旧的数据,这种机制避免了创建过多的View和频繁的调用findViewById方法(与ListView原理类似)。

目前SDK中提供了三种自带的LayoutManager:

LinearLayoutManager

GridLayoutManager

StaggeredGridLayoutManager

应用

间隔样式

自定义间隔样式需要继承RecyclerView.ItemDecoration类,该类是个抽象类,主要有三个方法。

· onDraw(Canvas c, RecyclerView parent, State state),在Item绘制之前被调用,该方法主要用于绘制间隔样式

· onDrawOver(Canvas c, RecyclerView parent, State state),在Item绘制之前被调用,该方法主要用于绘制间隔样式

· getItemOffsets(Rect outRect, View view, RecyclerView parent, State state),设置item的偏移量,偏移的部分用于填充间隔样式,在RecyclerView的onMesure()中会调用该方法

onDraw()和onDrawOver()这两个方法都是用于绘制间隔样式,我们只需要复写其中一个方法即可。直接来看一下自定义的间隔样式的实现吧。

public class MyDividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };
    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
    /**
     * 用于绘制间隔样式
     */
    private Drawable mDivider;
    /**
     * 列表的方向,水平/竖直
     */
    private int mOrientation;


    public MyDividerItemDecoration(Context context, int orientation) {
        // 获取默认主题的属性
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(orientation);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        // 绘制间隔
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }

    private void setOrientation(int orientation) {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    /**
     * 绘制间隔
     */
    private void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin +
                    Math.round(ViewCompat.getTranslationY(child));
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    /**
     * 绘制间隔
     */
    private void drawHorizontal(Canvas c, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getHeight() - parent.getPaddingBottom();
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int left = child.getRight() + params.rightMargin +
                    Math.round(ViewCompat.getTranslationX(child));
            final int right = left + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }
}

然后在代码中设置RecyclerView的间隔样式

mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL));

效果图

RecyclerView支持水平列表,简单改一下属性,看看水平列表的显示效果。

修改Item的布view_rv_item.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:orientation="vertical"
              android:layout_width="@dimen/md_common_view_width"
              android:layout_height="match_parent">
    <TextView
        android:id="@+id/item_tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"

        tools:text="item"/>
  < inearLayout>

修改ayoutManager的初始化和间隔样式初始化

mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.HORIZONTAL));

效果图

动画设置

RecyclerView可以设置列表中Item删除和添加的动画,在v7包中给我们提供了一种默认的Item删除和添加的动画,如果没有特殊的需求,默认使用这个动画即可。

// 设置Item添加和移除的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());

添加删除和添加Item的动作。在Adapter里面添加方法。

public void addNewItem() {
    if(mData == null) {
        mData = new ArrayList<>();
    }
    mData.add(0, "new Item");
    notifyItemInserted(0);
}

public void deleteItem() {
    if(mData == null || mData.isEmpty()) {
        return;
    }
    mData.remove(0);
    notifyItemRemoved(0); }

添加事件的处理

public void onClick(View v) {
    int id = v.getId();
    if(id == R.id.rv_add_item_btn) {
        mAdapter.addNewItem();
        // 由于Adapter内部是直接在首个Item位置做增加操作,增加完毕后列表移动到首个Item位置
        mLayoutManager.scrollToPosition(0);
    } else if(id == R.id.rv_del_item_btn){
        mAdapter.deleteItem();
        // 由于Adapter内部是直接在首个Item位置做删除操作,删除完毕后列表移动到首个Item位置
        mLayoutManager.scrollToPosition(0);

运行的效果(网络图片,类似这样的效果,有动画的)

自定义RecyclerView实现滚动时内容联动

小案例

布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  

   <FrameLayout  
       android:layout_width="fill_parent"  
        android:layout_height="0dp"  
        android:layout_weight="1" >  

       <ImageView  
            android:id="@+id/id_content"  
            android:layout_width="fill_parent"  
           android:layout_height="fill_parent"  
            android:layout_gravity="center"  
            android:layout_margin="10dp"  
           android:scaleType="centerCrop"  
            android:src="@drawable/ic_launcher" />  
    </FrameLayout>  

    <com.example.zhy_horizontalscrollview03.MyRecyclerView  
      android:id="@+id/id_recyclerview_horizontal"  
       android:layout_width="match_parent"      
    android:layout_height="120dp"  
     android:layout_gravity="bottom"  
      android:background="#FF0000"  
      android:scrollbars="none" />  

</LinearLayout>  

添加一个显示大图的区域,把RecyclerView改为自己定义的。

然后看我们自定义RecyclerView的代码:

import android.content.Context;  
import android.support.v7.widget.RecyclerView;  
import android.util.AttributeSet;  
import android.view.MotionEvent;  
import android.view.View;  

public class CopyOfMyRecyclerView extends RecyclerView  
{  

    public CopyOfMyRecyclerView(Context context, AttributeSet attrs)  
    {  
        super(context, attrs);  
    }  

    private View mCurrentView;  

    /** 
     * 滚动时回调的接口 
     */  
    private OnItemScrollChangeListener mItemScrollChangeListener;  

    public void setOnItemScrollChangeListener(  
            OnItemScrollChangeListener mItemScrollChangeListener)  
    {  
        this.mItemScrollChangeListener = mItemScrollChangeListener;  
    }  

    public interface OnItemScrollChangeListener  
    {  
        void onChange(View view, int position);  
    }  

    @Override  
    protected void onLayout(boolean changed, int l, int t, int r, int b)  
    {  
        super.onLayout(changed, l, t, r, b);  

        mCurrentView = getChildAt(0);  

        if (mItemScrollChangeListener != null)  
        {  
            mItemScrollChangeListener.onChange(mCurrentView,  
                    getChildPosition(mCurrentView));  
        }  
    }  

    @Override  
    public boolean onTouchEvent(MotionEvent e)  
    {  

        if (e.getAction() == MotionEvent.ACTION_MOVE)  
        {  
            mCurrentView = getChildAt(0);  
            // Log.e("TAG", getChildPosition(getChildAt(0)) + "");  
            if (mItemScrollChangeListener != null)  
            {  
                mItemScrollChangeListener.onChange(mCurrentView,  
                        getChildPosition(mCurrentView));  

            }  

        }  

        return super.onTouchEvent(e);  
    }  

}  

定义了一个滚动时回调的接口,然后在onTouchEvent中,监听ACTION_MOVE,用户手指滑动时,不断把当前第一个View回调回去~

效果图(网络):

实现即使手指离开,下面还在滑动,上面也会联动 。直接在ACTION_MOVE中回调,触发的频率太高了,理论上一张图片只会触发一次~~

优化:

既然希望手指离开还能联动,那么不仅需要ACTION_MOVE需要监听,还得监听一个加速度,速度到达一定值,然后继续移动

import android.content.Context;  
import android.support.v7.widget.RecyclerView;  
import android.support.v7.widget.RecyclerView.OnScrollListener;  
import android.util.AttributeSet;  
import android.view.View;  

public class MyRecyclerView extends RecyclerView implements OnScrollListener  
{  

    /** 
     * 记录当前第一个View 
     */  
    private View mCurrentView;  

    private OnItemScrollChangeListener mItemScrollChangeListener;  

    public void setOnItemScrollChangeListener(  
            OnItemScrollChangeListener mItemScrollChangeListener)  
    {  
        this.mItemScrollChangeListener = mItemScrollChangeListener;  
    }  

    public interface OnItemScrollChangeListener  
    {  
        void onChange(View view, int position);  
    }  

    public MyRecyclerView(Context context, AttributeSet attrs)  
    {  
        super(context, attrs);  
        // TODO Auto-generated constructor stub  
        this.setOnScrollListener(this);  
    }  

    @Override  
    protected void onLayout(boolean changed, int l, int t, int r, int b)  
    {  
        super.onLayout(changed, l, t, r, b);  

        mCurrentView = getChildAt(0);  

        if (mItemScrollChangeListener != null)  
        {  
            mItemScrollChangeListener.onChange(mCurrentView,  
                    getChildPosition(mCurrentView));  
        }  
    }  


    @Override  
    public void onScrollStateChanged(int arg0)  
    {  
    }  

    /** 
     *  
     * 滚动时,判断当前第一个View是否发生变化,发生才回调 
     */  
    @Override  
    public void onScrolled(int arg0, int arg1)  
    {  
        View newView = getChildAt(0);  

        if (mItemScrollChangeListener != null)  
        {  
            if (newView != null && newView != mCurrentView)  
            {  
                mCurrentView = newView ;  
                mItemScrollChangeListener.onChange(mCurrentView,  
                        getChildPosition(mCurrentView));  

            }  
        }  

    }  

}  

放弃了重写onTouchEvent方法,让这个类实现RecyclerView.OnScrollListener接口,然后设置监听,在onScrolled里面进行判断。

使用了一个成员变化存储当前第一个View,只有第一个View发生变化时才回调

MainActivity:

import java.util.ArrayList;  
import java.util.Arrays;  
import java.util.List;  

import android.app.Activity;  
import android.os.Bundle;  
import android.support.v7.widget.LinearLayoutManager;  
import android.support.v7.widget.RecyclerView;  
import android.view.View;  
import android.view.Window;  
import android.widget.ImageView;  
import android.widget.Toast;  


public class MainActivity extends Activity  
{  

    private MyRecyclerView mRecyclerView;  
    private GalleryAdapter mAdapter;  
    private List<Integer> mDatas;  
    private ImageView mImg ;   


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

        mImg = (ImageView) findViewById(R.id.id_content);  

        mDatas = new ArrayList<Integer>(Arrays.asList(R.drawable.a,  
                R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e,  
                R.drawable.f, R.drawable.g, R.drawable.h, R.drawable.l));  

        mRecyclerView = (MyRecyclerView) findViewById(R.id.id_recyclerview_horizontal);  
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);  
        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);  

        mRecyclerView.setLayoutManager(linearLayoutManager);  
        mAdapter = new GalleryAdapter(this, mDatas);  
        mRecyclerView.setAdapter(mAdapter);  

        mRecyclerView.setOnItemScrollChangeListener(new OnItemScrollChangeListener()  
        {  
            @Override  
            public void onChange(View view, int position)  
            {  
                mImg.setImageResource(mDatas.get(position));  
            };  
        });  

        mAdapter.setOnItemClickLitener(new OnItemClickLitener()  
        {  
            @Override  
            public void onItemClick(View view, int position)  
            {  
//              Toast.makeText(getApplicationContext(), position + "", Toast.LENGTH_SHORT)  
//                      .show();  
                mImg.setImageResource(mDatas.get(position));  
            }  
        });  

    }  

}  

效果图:

listview && recycleview 比较

收回机制比较

首先先看ListView的,想要搞清楚回收机制,我们当然要去看它的onTouchEvent中对应的ACTION_MOVE方法。

ListView继承自AbsListView,所以其滑动逻辑会在AbsListView中处理,最终会走到trackMotionScroll这个方法。

boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
    final int childCount = getChildCount();
    if (childCount == 0) {
        return true;
    }

    final int firstTop = getChildAt(0).getTop();
    final int lastBottom = getChildAt(childCount - 1).getBottom();

    final Rect listPadding = mListPadding;

    // "effective padding" In this case is the amount of padding that affects
    // how much space should not be filled by items. If we don't clip to padding
    // there is no effective padding.
    int effectivePaddingTop = 0;
    int effectivePaddingBottom = 0;
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        effectivePaddingTop = listPadding.top;
        effectivePaddingBottom = listPadding.bottom;
    }

     // FIXME account for grid vertical spacing too?
    final int spaceAbove = effectivePaddingTop - firstTop;
    final int end = getHeight() - effectivePaddingBottom;
    final int spaceBelow = lastBottom - end;

    final int height = getHeight() - mPaddingBottom - mPaddingTop;
    if (deltaY < 0) {
        deltaY = Math.max(-(height - 1), deltaY);
    } else {
        deltaY = Math.min(height - 1, deltaY);
    }

    if (incrementalDeltaY < 0) {
        incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
    } else {
        incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
    }

    final int firstPosition = mFirstPosition;

    // Update our guesses for where the first and last views are
    if (firstPosition == 0) {
        mFirstPositionDistanceGuess = firstTop - listPadding.top;
    } else {
        mFirstPositionDistanceGuess += incrementalDeltaY;
    }
    if (firstPosition + childCount == mItemCount) {
        mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
    } else {
        mLastPositionDistanceGuess += incrementalDeltaY;
    }

    final boolean cannotScrollDown = (firstPosition == 0 &&
            firstTop >= listPadding.top && incrementalDeltaY >= 0);
    final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
            lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);

    if (cannotScrollDown || cannotScrollUp) {
        return incrementalDeltaY != 0;
    }

    final boolean down = incrementalDeltaY < 0;

    final boolean inTouchMode = isInTouchMode();
    if (inTouchMode) {
        hideSelector();
    }

    final int headerViewsCount = getHeaderViewsCount();
    final int footerViewsStart = mItemCount - getFooterViewsCount();

    int start = 0;
    int count = 0;

    if (down) {
        int top = -incrementalDeltaY;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            top += listPadding.top;
        }
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (child.getBottom() >= top) {
                break;
            } else {
                count++;
                int position = firstPosition + i;
                if (position >= headerViewsCount && position < footerViewsStart) {
                    // The view will be rebound to new data, clear any
                    // system-managed transient state.
                    child.clearAccessibilityFocus();
                    mRecycler.addScrapView(child, position);
                }
            }
        }
    } else {
        int bottom = getHeight() - incrementalDeltaY;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            bottom -= listPadding.bottom;
        }
        for (int i = childCount - 1; i >= 0; i--) {
            final View child = getChildAt(i);
            if (child.getTop() <= bottom) {
                break;
            } else {
                start = i;
                count++;
                int position = firstPosition + i;
                if (position >= headerViewsCount && position < footerViewsStart) {
                    // The view will be rebound to new data, clear any
                    // system-managed transient state.
                    child.clearAccessibilityFocus();
                    mRecycler.addScrapView(child, position);
                }
            }
        }
    }

    mMotionViewNewTop = mMotionViewOriginalTop + deltaY;

    mBlockLayoutRequests = true;

    if (count > 0) {
        detachViewsFromParent(start, count);
        mRecycler.removeSkippedScrap();
    }

    // invalidate before moving the children to avoid unnecessary invalidate
    // calls to bubble up from the children all the way to the top
    if (!awakenScrollBars()) {
       invalidate();
    }

    offsetChildrenTopAndBottom(incrementalDeltaY);

    if (down) {
        mFirstPosition += count;
    }

    final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
    if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
        fillGap(down);
    }

    if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
        final int childIndex = mSelectedPosition - mFirstPosition;
        if (childIndex >= 0 && childIndex < getChildCount()) {
            positionSelector(mSelectedPosition, getChildAt(childIndex));
        }
    } else if (mSelectorPosition != INVALID_POSITION) {
        final int childIndex = mSelectorPosition - mFirstPosition;
        if (childIndex >= 0 && childIndex < getChildCount()) {
            positionSelector(INVALID_POSITION, getChildAt(childIndex));
        }
    } else {
        mSelectorRect.setEmpty();
    }

    mBlockLayoutRequests = false;

    invokeOnItemScrollListener();

    return false;
}

这个方法的两个入参scrollY表示从开始滑动的时候到当前滑动的y轴的距离,incrementalDeltaY表示滑动时y轴的增量值,这个值可以通过判断正负来确定是向上滑还是向下滑。这里我们以一个方向为准,如果整个滑动是向下滑的,也就是局部变量down为true,那么在这种情况下,如果child.getBottom() < top,也就是说bottom值小于top值,那么说明在向下滑动这种场景下,这个child已经滑出屏幕不在显示范围内了,于是,AbsListView就调用了mRecycler.addScrapView(child, position);这个函数将其加入到了mRecycler中。这里有一个很重要的点,就是mRecycler,它也是AbsListView和它的子类(ListView GridView等等)能正常运作最主要原因。

final RecycleBin mRecycler = new RecycleBin();
可以看到它其实是一个RecycleBin对象。

class RecycleBin {

    ........
    private ArrayList<View> mCurrentScrap;
    private ArrayList<View>[] mScrapViews;
    ........

    void addScrapView(View scrap, int position) {
            final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
            if (lp == null) {
                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)) {
                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<View>();
                    }
                    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<View>();
                    }
                    mTransientStateViews.put(position, scrap);
                } else {
                    // Otherwise, we'll have to remove the view and start over.
                    if (mSkippedScrap == null) {
                        mSkippedScrap = new ArrayList<View>();
                    }
                    mSkippedScrap.add(scrap);
                }
            } else {
                if (mViewTypeCount == 1) {
                    mCurrentScrap.add(scrap);
                } else {
                    mScrapViews[viewType].add(scrap);
                }

                if (mRecyclerListener != null) {
                    mRecyclerListener.onMovedToScrapHeap(scrap);
                }
            }
        }
}

可以看到刚刚说起的那个addScrapView方法,其中有一段方法:

if (mViewTypeCount == 1) {
    mCurrentScrap.add(scrap);
} else {
    mScrapViews[viewType].add(scrap);
}

根据ViewTypeCount去添加已经废弃的View。从这个方法就可以说明,ListView的回收机制是和ViewType有关的,每一个ViewType对应一个对应的废弃list。

现在我们可以知道,在AsbListView中,如果一个View已经滑出了屏幕,那么说明它已经被废弃(scrap)了,就会被添加到RecycleBin中(transient的View除外,这个忽略)。那么我们什么时候去取出这里面存储的View呢?让我们回到onTouchEvent函数。

我们可以看到在trackMotionScroll函数中有一个fillGap方法。

abstract void fillGap(boolean down);
这是一个抽象方法,这是合理的,因为AbsListView的子类显示情况都不一样,ListView有ListView的显示,GridView有GridView的显示,所以需要它们自己去判断。

void fillGap(boolean down) {
    final int count = getChildCount();
    if (down) {
        int paddingTop = 0;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            paddingTop = getListPaddingTop();
        }
        final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
                paddingTop;
        fillDown(mFirstPosition + count, startOffset);
        correctTooHigh(getChildCount());
    } else {
        int paddingBottom = 0;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            paddingBottom = getListPaddingBottom();
        }
        final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
                getHeight() - paddingBottom;
        fillUp(mFirstPosition - 1, startOffset);
        correctTooLow(getChildCount());
    }
}

在这个函数中,通过down这个参数判断滑动方向,如果向下滑,则调用fillDown方法,否则调用fillUp方法。而在这两个方法中,都会去调用obtainView方法从对应的RecycleBin中取出scrapView填充到AbsListView中。

View obtainView(int position, boolean[] isScrap) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

    isScrap[0] = false;

    // Check whether we have a transient state view. Attempt to re-bind the
    // data and discard the view if we fail.
    final View transientView = mRecycler.getTransientStateView(position);
    if (transientView != null) {
        final LayoutParams params = (LayoutParams) transientView.getLayoutParams();

        // If the view type hasn't changed, attempt to re-bind the data.
        if (params.viewType == mAdapter.getItemViewType(position)) {
            final View updatedView = mAdapter.getView(position, transientView, this);

            // If we failed to re-bind the data, scrap the obtained view.
            if (updatedView != transientView) {
                setItemViewLayoutParams(updatedView, position);
                mRecycler.addScrapView(updatedView, position);
            }
        }

        // Scrap view implies temporary detachment.
        isScrap[0] = true;
        return transientView;
    }

    final View scrapView = mRecycler.getScrapView(position);
    final View child = mAdapter.getView(position, scrapView, this);
    if (scrapView != null) {
        if (child != scrapView) {
            // Failed to re-bind the data, return scrap to the heap.
            mRecycler.addScrapView(scrapView, position);
        } else {
            isScrap[0] = true;

            child.dispatchFinishTemporaryDetach();
        }
    }

    if (mCacheColorHint != 0) {
        child.setDrawingCacheBackgroundColor(mCacheColorHint);
    }

    if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
        child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
    }

    setItemViewLayoutParams(child, position);

    if (AccessibilityManager.getInstance(mContext).isEnabled()) {
        if (mAccessibilityDelegate == null) {
            mAccessibilityDelegate = new ListItemAccessibilityDelegate();
        }
        if (child.getAccessibilityDelegate() == null) {
            child.setAccessibilityDelegate(mAccessibilityDelegate);
        }
    }

    Trace.traceEnd(Trace.TRACE_TAG_VIEW);

    return child;
}

这下我们就清楚了,原来通过RecycleBin这样一个东西,就算有再多的item,AbsListView中永远只会存在那么几个,滑出屏幕的child回收,然后再重用,于是就尽可能的避免了oom的发生。

接下去,让我们看看RecyclerView是怎么做的,在RecyclerView的ACTION_MOVE方法中,实际会调用其内部的LayoutManager的scrollBy方法去完成滑动,这里我们看LinearLayoutManager的实现。

int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getChildCount() == 0 || dy == 0) {
        return 0;
    }
    mLayoutState.mRecycle = true;
    ensureLayoutState();
    final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
    final int absDy = Math.abs(dy);
    updateLayoutState(layoutDirection, absDy, true, state);
    final int freeScroll = mLayoutState.mScrollingOffset;
    final int consumed = freeScroll + fill(recycler, mLayoutState, state, false);
    if (consumed < 0) {
        if (DEBUG) {
            Log.d(TAG, "Don't have any more elements to scroll");
        }
        return 0;
    }
    final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
    mOrientationHelper.offsetChildren(-scrolled);
    if (DEBUG) {
        Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
    }
    mLayoutState.mLastScrollDelta = scrolled;
    return scrolled;
}

其中的fill方法最终会调用到layoutChunk方法,而在这个方法中,会去recycler中取废弃的View进行填充。说到这儿大家可以发现,其实RecyclerView和AbsListView的回收机制在大致框架上是一致的,就是通过一个类似Recycler的回收器去存储和获取废弃的View,下面让我们看看RecyclerView的回收器。

public final class Recycler {

    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
    private ArrayList<ViewHolder> mChangedScrap = null;
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    private final List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    private int mViewCacheMax = DEFAULT_CACHE_SIZE;
    private RecycledViewPool mRecyclerPool;
    private ViewCacheExtension mViewCacheExtension;
    private static final int DEFAULT_CACHE_SIZE = 2;

可以看到在Recycler中存这么多的List,而在获取View的过程中,Recycler会先从scrap的List中获取,如果没有获取到就从cache中获取。另外还有ViewCacheExtension和RecycledViewPool这两个额外的选项需要开发者手动去设置。
RecyclerView提供了比AbsListView更加完善的回收机制,配以细节的优化和postOnAnimation方法所保证的”Android 16ms”机制,RecyclerView在滑动性能上确实会比AbsListView更出色。
但是虽然经过了优化,但是就像我前面说的RecyclerView并不是万能,对于一些非常复杂的item布局,一旦处理不好,RecyclerView所表现出来的性能也ListView是相差无几的,所以[使用RecyclerView代替ListView]并不仅仅应该体现在这里。
为什么要使用RecycleView

为什么在Google推出了RecyclerView并且经过了这么多个版本迭代之后,ListView没有被标明为deprecated呢?在ViewPager和HorizontalScrollView推出以后,Gallery就惨遭deprecated的命运,RecyclerView虽然被标上了加强版AbsListView的标记,但是它们其实是两个不同的控件。AbsListView的子类们有着完善的功能,如果你的滑动组件只想简单的使用滑动显示这个功能,并且想轻松的使用divider,header,footer或者点击事件这些功能,那么使用AbsListView是完全没有问题的。
RecyclerView的重点应该放在[flexible]上,灵活是它[最大]的特点,由于AbsListView的功能完善,所以你想要定制它其实是很困难的,换句话说,AbsLisView已经强耦合了很多和[滑动,回收]无关的功能。这个时候RecyclerView的强大之处就显示出来了,LayoutManager,Adapter,ItemAnimator,ItemDecoration等等各司其职,这使得RecyclerView能够实现深度的定制化。系统提供的三种LayoutManager可以无缝衔接ListView和GridView,瀑布流的实现也变得无分简单。滑动删除和长按交换只需要添加几个类就可以实现。除此之外,RecyclerView的动画配以局部刷新也是它比较出色的地方,在AbsListView时代,只有一个notifyDatasetChanged方法,想要做局部刷新需要自己去实现,动画更是难做,但是在RecyclerView中,有很多适配局部刷新的api,还有ItemAnimator这样的神器去支持动画。至于点击事件,Google将RecyclerView取名叫这个名字的原因就是想让这个组件只关注[Recycle],关于点击事件,在ViewHolder中添加是轻而易举的事,封装起来也不难,而且如果把这个逻辑写在组件内部,它的position和动画将会比较难处理,AbsListView里就花了比较多的精力去处理这一方面的逻辑。此外,在我们使用ListView的过程中,如果item中有可点击组件,例如button,那么点击事件的冲突也是一个让开发者很烦恼的事情,但是RecyclerView的好处就是把点击事件的控制权完全的交给开发者,避免了这样的痛苦。最后,RecyclerView天生支持嵌套滑动,可以很好的配合NestedScrollView或者CoordinatorLayout,而AbsListView则是需要在一定的版本上才支持这个机制,这也算是RecycleView的一个优势吧。

参考:http://blog.csdn.net/lmj623565791/article/details/38173061/

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值