• RecyclerView使用技巧(item动画及嵌套高度适配解决方案)


    原文地址 · Frank-Zhu  http://frank-zhu.github.io/android/2015/02/26/android-recyclerview-part-3/?utm_source=tuicool&utm_medium=referral

    在上一篇(RecyclerView使用详解(二))文章中介绍了RecyclerView的多Item布局实现,接下来要来讲讲RecyclerView的Cursor实现,相较于之前的实现,Cursor有更多的使用场景,也更加的常用,特别是配合LoaderManager和CursorLoader进行数据的缓存及加载显示,基于此我们来重点看看RecyclerView的CursorAdapter具体要怎么实现。

    一、CursorAdapter实现(配合LoaderManager和CursorLoader)

    如果之前你用过ListView实现过此功能(CursorAdapter),那么你一定对下面这两个方法并不陌生

    1     @Override
    2     public View newView(Context context, Cursor cursor, ViewGroup parent) {
    3         return null;
    4     }
    5 
    6     @Override
    7     public void bindView(View view, Context context, Cursor cursor) {
    8 
    9     }

    其中newView方法是用来创建Item布局的,bindView 方法是用来绑定当前View数据的,就相当于之前的getView方法拆成了两个方法实现。

    如果你用RecyclerView,你会发现CursorAdapter这个类没有了,既然没有了,那我们就自己仿照着ListView的CursorAdapter类来实现,具体的代码没什么大的出入,无非就是注册两个观察者去监听数据库数据的变化,但是有两个地方需要注意一下,一个就是hasStableIds() 这个方法RecyclerView.Adapter中不能复写父类的方法,需要在初始化的时候调用setHasStableIds(true); 来完成相同功能,第二个就是notifyDataSetInvalidated() 这个方法没有,统一修改成notifyDataSetChanged() 方法即可。

     1 void init(Context context, Cursor c, int flags) {
     2         boolean cursorPresent = c != null;
     3         mCursor = c;
     4         mDataValid = cursorPresent;
     5         mContext = context;
     6         mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
     7         if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
     8             mChangeObserver = new ChangeObserver();
     9             mDataSetObserver = new MyDataSetObserver();
    10         } else {
    11             mChangeObserver = null;
    12             mDataSetObserver = null;
    13         }
    14 
    15         if (cursorPresent) {
    16             if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
    17             if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
    18         }
    19 
    20         setHasStableIds(true);//这个地方要注意一下,需要将表关联ID设置为true
    21     }
    22 
    23     //************//
    24     public Cursor swapCursor(Cursor newCursor) {
    25         if (newCursor == mCursor) {
    26             return null;
    27         }
    28         Cursor oldCursor = mCursor;
    29         if (oldCursor != null) {
    30             if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
    31             if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
    32         }
    33         mCursor = newCursor;
    34         if (newCursor != null) {
    35             if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
    36             if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
    37             mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
    38             mDataValid = true;
    39             // notify the observers about the new cursor
    40             notifyDataSetChanged();
    41         } else {
    42             mRowIDColumn = -1;
    43             mDataValid = false;
    44             // notify the observers about the lack of a data set
    45             //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
    46             notifyDataSetChanged();//注意此处
    47         }
    48         return oldCursor;
    49     }
    50     //************//
    51     private class MyDataSetObserver extends DataSetObserver {
    52         @Override
    53         public void onChanged() {
    54             mDataValid = true;
    55             notifyDataSetChanged();
    56         }
    57 
    58         @Override
    59         public void onInvalidated() {
    60             mDataValid = false;
    61             //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
    62             notifyDataSetChanged();//注意此处
    63         }
    64     }

    怎么样,是不是很简单,没错,就是这么简单,这里是完整的BaseAbstractRecycleCursorAdapter代码,用法和ListView的CursorAdapter用法一致,具体的可以看看我的Recyclerview LoaderManager Provider

    二、Item的动画实现 RecyclerView本身就已经实现了ITEM的动画,只需要调用以下几个函数来增删Item即可出现默认动画。

    1 notifyItemChanged(int)
    2 notifyItemInserted(int)
    3 notifyItemRemoved(int)
    4 notifyItemRangeChanged(int, int)
    5 notifyItemRangeInserted(int, int)
    6 notifyItemRangeRemoved(int, int)

    怎么样,是不是很轻松,如果你不满足系统默认动画,那么你可以自定义实现RecyclerView.ItemAnimator的接口方法,实现代码可以参考DefaultItemAnimator.当然,如果你不想自己实现,那么也没关系,这里有人已经写了开源库,你可以去看看recyclerview-animators,这里给出默认动画实现方式代码AnimFragment

    三、嵌套RecycleView

    一般是不推荐使用嵌套RecycleView的,和ListView是类似的,遇到这种需要嵌套的View一般都是想别的办法来规避,比如动态AddView,或者通过RecycleView的MultipleItemAdapter来实现,通过设置不同的ItemType布局不同的View,但是有时候会闲麻烦,想直接就用嵌套的方式来做,那么和ListView实现方式不同的是,ListView的实现一般都是继承ListView然后复写onMeasure方法,如下所示:

    1     @Override
    2     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    3         int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
    4         super.onMeasure(widthMeasureSpec, expandSpec);
    5     }

      但是需要特别注意的一点,RecycleView的实现方式不再是继承RecycleView来做,而是通过修改LayoutManager的方式,即通过继承LinearLayoutManager GridLayoutManagerStaggeredGridLayoutManager来修改子控件的测量,下面给出主要代码:

    FullyLinearLayoutManager

     1 private int[] mMeasuredDimension = new int[2];
     2 
     3     @Override
     4     public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
     5                           int widthSpec, int heightSpec) {
     6 
     7         final int widthMode = View.MeasureSpec.getMode(widthSpec);
     8         final int heightMode = View.MeasureSpec.getMode(heightSpec);
     9         final int widthSize = View.MeasureSpec.getSize(widthSpec);
    10         final int heightSize = View.MeasureSpec.getSize(heightSpec);
    11 
    12         Log.i(TAG, "onMeasure called. 
    widthMode " + widthMode
    13                 + " 
    heightMode " + heightSpec
    14                 + " 
    widthSize " + widthSize
    15                 + " 
    heightSize " + heightSize
    16                 + " 
    getItemCount() " + getItemCount());
    17 
    18         int width = 0;
    19         int height = 0;
    20         for (int i = 0; i < getItemCount(); i++) {
    21             measureScrapChild(recycler, i,
    22                     View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
    23                     View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
    24                     mMeasuredDimension);
    25 
    26             if (getOrientation() == HORIZONTAL) {
    27                 width = width + mMeasuredDimension[0];
    28                 if (i == 0) {
    29                     height = mMeasuredDimension[1];
    30                 }
    31             } else {
    32                 height = height + mMeasuredDimension[1];
    33                 if (i == 0) {
    34                     width = mMeasuredDimension[0];
    35                 }
    36             }
    37         }
    38         switch (widthMode) {
    39             case View.MeasureSpec.EXACTLY:
    40                 width = widthSize;
    41             case View.MeasureSpec.AT_MOST:
    42             case View.MeasureSpec.UNSPECIFIED:
    43         }
    44 
    45         switch (heightMode) {
    46             case View.MeasureSpec.EXACTLY:
    47                 height = heightSize;
    48             case View.MeasureSpec.AT_MOST:
    49             case View.MeasureSpec.UNSPECIFIED:
    50         }
    51 
    52         setMeasuredDimension(width, height);
    53     }
    54 
    55     private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
    56                                    int heightSpec, int[] measuredDimension) {
    57         try {
    58             View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsException
    59 
    60             if (view != null) {
    61                 RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
    62 
    63                 int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
    64                         getPaddingLeft() + getPaddingRight(), p.width);
    65 
    66                 int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
    67                         getPaddingTop() + getPaddingBottom(), p.height);
    68 
    69                 view.measure(childWidthSpec, childHeightSpec);
    70                 measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
    71                 measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
    72                 recycler.recycleView(view);
    73             }
    74         } catch (Exception e) {
    75             e.printStackTrace();
    76         } finally {
    77         }
    78     }

    FullyGridLayoutManager

     1 private int[] mMeasuredDimension = new int[2];
     2 
     3     @Override
     4     public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
     5         final int widthMode = View.MeasureSpec.getMode(widthSpec);
     6         final int heightMode = View.MeasureSpec.getMode(heightSpec);
     7         final int widthSize = View.MeasureSpec.getSize(widthSpec);
     8         final int heightSize = View.MeasureSpec.getSize(heightSpec);
     9 
    10         int width = 0;
    11         int height = 0;
    12         int count = getItemCount();
    13         int span = getSpanCount();
    14         for (int i = 0; i < count; i++) {
    15             measureScrapChild(recycler, i,
    16                     View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
    17                     View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
    18                     mMeasuredDimension);
    19 
    20             if (getOrientation() == HORIZONTAL) {
    21                 if (i % span == 0) {
    22                     width = width + mMeasuredDimension[0];
    23                 }
    24                 if (i == 0) {
    25                     height = mMeasuredDimension[1];
    26                 }
    27             } else {
    28                 if (i % span == 0) {
    29                     height = height + mMeasuredDimension[1];
    30                 }
    31                 if (i == 0) {
    32                     width = mMeasuredDimension[0];
    33                 }
    34             }
    35         }
    36 
    37         switch (widthMode) {
    38             case View.MeasureSpec.EXACTLY:
    39                 width = widthSize;
    40             case View.MeasureSpec.AT_MOST:
    41             case View.MeasureSpec.UNSPECIFIED:
    42         }
    43 
    44         switch (heightMode) {
    45             case View.MeasureSpec.EXACTLY:
    46                 height = heightSize;
    47             case View.MeasureSpec.AT_MOST:
    48             case View.MeasureSpec.UNSPECIFIED:
    49         }
    50 
    51         setMeasuredDimension(width, height);
    52     }
    53 
    54     private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
    55                                    int heightSpec, int[] measuredDimension) {
    56         if (position < getItemCount()) {
    57             try {
    58                 View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsException
    59                 if (view != null) {
    60                     RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
    61                     int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
    62                             getPaddingLeft() + getPaddingRight(), p.width);
    63                     int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
    64                             getPaddingTop() + getPaddingBottom(), p.height);
    65                     view.measure(childWidthSpec, childHeightSpec);
    66                     measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
    67                     measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
    68                     recycler.recycleView(view);
    69                 }
    70             } catch (Exception e) {
    71                 e.printStackTrace();
    72             }
    73         }
    74     }

    ##四、效果图如下:

    Item默认动画效果

    嵌套ScrollView效果

  • 相关阅读:
    红黑树-插入篇
    并查集
    Rabin-Karp【转载】
    KMP
    怎样花两月时间去应聘互联网公司 [转载]
    c++ 智能指针【转载】
    java序列化
    Web页面导出Excel表格
    基于jquery-UI的日期选择器
    Doc命令
  • 原文地址:https://www.cnblogs.com/BobGo/p/5626691.html
Copyright © 2020-2023  润新知