草庐IT

Android RecyclerView详解及实现瀑布流式布局

liuyvhao 2023-03-28 原文
RecyclerView一个可以代替ListView和GridView的控件,那么RecyclerView到底比他们好在哪里?

RecyclerView架构提供了一种插拔式的体验,所以实现了代码的高度解耦,使用起来也异常的灵活。


我们可以通过设置它的LayoutManager控制其显示的方式,通过ItemDecoration控制Item间的间隔,通过ItemAnimator控制Item的增删动画

RecyclerView.LayoutManager提供了三个实现类其中LinearLayoutManager 现行管理器,支持横向、纵向,GridLayoutManager 网格布局管理器,StaggeredGridLayoutManager 瀑布就式布局管理器


那么先从LinearLayoutManager看起

先在gradle中引用compile 'com.android.support:recyclerview-v7:23.4.0'

Activity布局文件如下:

<?xml version="1.0" encoding="utf-8"?>   <RelativeLayout 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"       tools:context="com.lg.recyclerviewdemo.LinearActivity">          <android.support.v7.widget.RecyclerView           android:id="@+id/linear_recycler"           android:layout_width="match_parent"           android:layout_height="match_parent"/>      </RelativeLayout>item布局文件:

<?xml version="1.0" encoding="utf-8"?>   <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"       android:layout_width="match_parent"       android:layout_height="match_parent">          <LinearLayout           android:layout_width="match_parent"           android:layout_height="match_parent"           android:gravity="center">              <ImageView               android:layout_width="70dp"               android:layout_height="70dp"               android:src="@drawable/android" />              <TextView               android:id="@+id/recycler_item_tv"               android:layout_width="wrap_content"               android:layout_height="100dp"               android:layout_marginLeft="20dp"               android:gravity="center"               android:textColor="@color/colorPrimary"               android:textSize="20sp"               android:textStyle="bold" />       </LinearLayout>   </RelativeLayout>在Acitvity中初始化数据:

mDatas = new ArrayList<String>();   for (int i = 1; i <= 65; i++) {         mDatas.add("item"+i);   }核心代码:

recyclerAdapter = new RecyclerAdapter();   //设置布局管理器   linear_recycler.setLayoutManager(new LinearLayoutManager(this));   //设置adapter   linear_recycler.setAdapter(recyclerAdapter);   //添加分割线   linear_recycler.addItemDecoration(new DividerLinearItemDecoration(this, DividerLinearItemDecoration.VERTICAL_LIST));接下来自制adapter:

public class RecyclerAdapter extends RecyclerView.Adapter<LinearHolder> {       private View view;          @Override       public LinearHolder onCreateViewHolder(ViewGroup parent, int viewType) {           //利用反射将item的布局加载出来           view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, null);           //new一个我们的ViewHolder,findViewById操作都在LinearHolder的构造方法中进行了           return new LinearHolder(view);       }          @Override       public void onBindViewHolder(LinearHolder holder, int position) {           holder.recycler_item.setText(MainActivity.mDatas.get(position));       }          @Override       public int getItemCount() {           return MainActivity.mDatas.size();       }   }      class LinearHolder extends RecyclerView.ViewHolder {       TextView recycler_item;       public LinearHolder(View itemView) {           super(itemView);           recycler_item = (TextView) itemView.findViewById(R.id.recycler_item_tv);       }再绘画它的分割线:

public class DividerLinearItemDecoration 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 DividerLinearItemDecoration(Context context, int orientation) {           final TypedArray a = context.obtainStyledAttributes(ATTRS);           mDivider = a.getDrawable(0);           a.recycle();           setOrientation(orientation);       }          public void setOrientation(int orientation) {           if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {               throw new IllegalArgumentException("invalid orientation");           }           mOrientation = orientation;       }          @Override       public void onDraw(Canvas c, RecyclerView parent) {           if (mOrientation == VERTICAL_LIST) {               drawVertical(c, parent);           } else {               drawHorizontal(c, parent);           }       }             public 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);               RecyclerView v = new RecyclerView(parent.getContext());               final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child                       .getLayoutParams();               final int top = child.getBottom() + params.bottomMargin;               final int bottom = top + mDivider.getIntrinsicHeight();               mDivider.setBounds(left, top, right, bottom);               mDivider.draw(c);           }       }          public 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;               final int right = left + mDivider.getIntrinsicHeight();               mDivider.setBounds(left, top, right, bottom);               mDivider.draw(c);           }       }          @Override       public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {           if (mOrientation == VERTICAL_LIST) {               outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());           } else {               outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);           }       }   }好了,我们来看看效果:

嘛,貌似和ListView没什么区别,还这么麻烦

别急,我们试试GridLayoutManager

很简单,我们只需要改变LayoutManager和ItemDecoration就行了:

grid_recycler.setLayoutManager(new GridLayoutManager(this,2));   grid_recycler.addItemDecoration(new DividerGridItemDecoration(this));DividerGridItemDecoration代码:

public class DividerGridItemDecoration extends RecyclerView.ItemDecoration {       private static final int[] ATTRS = new int[] { android.R.attr.listDivider };       private Drawable mDivider;          public DividerGridItemDecoration(Context context)       {           final TypedArray a = context.obtainStyledAttributes(ATTRS);           mDivider = a.getDrawable(0);           a.recycle();       }          @Override       public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)       {              drawHorizontal(c, parent);           drawVertical(c, parent);          }          private int getSpanCount(RecyclerView parent)       {           // 列数           int spanCount = -1;           RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();           if (layoutManager instanceof GridLayoutManager)           {                  spanCount = ((GridLayoutManager) layoutManager).getSpanCount();           } else if (layoutManager instanceof StaggeredGridLayoutManager)           {               spanCount = ((StaggeredGridLayoutManager) layoutManager)                       .getSpanCount();           }           return spanCount;       }          public void drawHorizontal(Canvas c, RecyclerView parent)       {           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.getLeft() - params.leftMargin;               final int right = child.getRight() + params.rightMargin                       + mDivider.getIntrinsicWidth();               final int top = child.getBottom() + params.bottomMargin;               final int bottom = top + mDivider.getIntrinsicHeight();               mDivider.setBounds(left, top, right, bottom);               mDivider.draw(c);           }       }          public void drawVertical(Canvas c, RecyclerView parent)       {           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.getTop() - params.topMargin;               final int bottom = child.getBottom() + params.bottomMargin;               final int left = child.getRight() + params.rightMargin;               final int right = left + mDivider.getIntrinsicWidth();                  mDivider.setBounds(left, top, right, bottom);               mDivider.draw(c);           }       }          private boolean isLastColum(RecyclerView parent, int pos, int spanCount,                                   int childCount)       {           RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();           if (layoutManager instanceof GridLayoutManager)           {               if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边               {                   return true;               }           } else if (layoutManager instanceof StaggeredGridLayoutManager)           {               int orientation = ((StaggeredGridLayoutManager) layoutManager)                       .getOrientation();               if (orientation == StaggeredGridLayoutManager.VERTICAL)               {                   if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边                   {                       return true;                   }               } else               {                   childCount = childCount - childCount % spanCount;                   if (pos >= childCount)// 如果是最后一列,则不需要绘制右边                       return true;               }           }           return false;       }          private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,                                 int childCount)       {           RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();           if (layoutManager instanceof GridLayoutManager)           {               childCount = childCount - childCount % spanCount;               if (pos >= childCount)// 如果是最后一行,则不需要绘制底部                   return true;           } else if (layoutManager instanceof StaggeredGridLayoutManager)           {               int orientation = ((StaggeredGridLayoutManager) layoutManager)                       .getOrientation();               // StaggeredGridLayoutManager 且纵向滚动               if (orientation == StaggeredGridLayoutManager.VERTICAL)               {                   childCount = childCount - childCount % spanCount;                   // 如果是最后一行,则不需要绘制底部                   if (pos >= childCount)                       return true;               } else               // StaggeredGridLayoutManager 且横向滚动               {                   // 如果是最后一行,则不需要绘制底部                   if ((pos + 1) % spanCount == 0)                   {                       return true;                   }               }           }           return false;       }          @Override       public void getItemOffsets(Rect outRect, int itemPosition,                                  RecyclerView parent)       {           int spanCount = getSpanCount(parent);           int childCount = parent.getAdapter().getItemCount();           if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部           {               outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);           } else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边           {               outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());           } else           {               outRect.set(0, 0, mDivider.getIntrinsicWidth(),                       mDivider.getIntrinsicHeight());           }       }   }看下效果吧:

渍,有点意思,不过也没那么神乎其神啊

别忘了,我们还有个StaggeredGridLayoutManager没用

展示了那么多纵向的,我们来个横向的,同样改变LayoutManager

stag_grid_recycler.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.HORIZONTAL));看下效果:

一个RecyclerView就能实现这么多功能,确实强大啊

不过,你以为这样就完了?下来要放大招了

看标题,瀑布流有木有,你用ListView不会那么简单就实现吧,但是用RecyclerView分分钟

稍微改变item布局,让图片放在字的上面

我们在onBindViewHolder给item设置随机高度:

LayoutParams layoutParams = holder.sg_item.getLayoutParams();   layoutParams.height = heights.get(position);看下大招效果:

我就问你6不6,6的话还不快关注我(嘎嘎)

好吧,可能吓到你了,什么?点击事件?

好吧,很不幸告诉你,要自己写,对,就是要自己写。

前面已经说过了,RecyclerView实现了高度解耦,非常的灵活(你要干什么,自己去写)。那就写吧!

先写个接口:

public interface OnItemClickLitener {       /*点击事件*/       void onItemClick(View view, int position);       /*长按事件*/       void onItemLongClick(View view, int position);   }在adapter中加入代码:

private OnItemClickLitener mOnItemClickLitener;      public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener) {         this.mOnItemClickLitener = mOnItemClickLitener;   }onBindViewHolder方法中加入:

holder.sg_item.setOnClickListener(new View.OnClickListener() {          @Override          public void onClick(View v) {                 int pos = holder.getLayoutPosition();                 mOnItemClickLitener.onItemClick(holder.itemView, pos);             }          });   holder.sg_item.setOnLongClickListener(new View.OnLongClickListener() {         @Override         public boolean onLongClick(View v) {                int pos = holder.getLayoutPosition();                mOnItemClickLitener.onItemLongClick(holder.itemView, pos);                return false;         }   });然后在Activity中调用:

staggeredGridAdapter.setOnItemClickLitener(new OnItemClickLitener() {               @Override               public void onItemClick(View view, int position) {                   staggeredGridAdapter.notifyItemRemoved(position);               }                  @Override               public void onItemLongClick(View view, final int position) {                   android.support.v7.app.AlertDialog.Builder builder = new AlertDialog.Builder(StaggeredGridVActivity.this);                   builder.setTitle("Delete?")                           .setNegativeButton("no", null)                           .setPositiveButton("yes", new DialogInterface.OnClickListener() {                               @Override                               public void onClick(DialogInterface dialog, int which) {                                   staggeredGridAdapter.notifyItemRemoved(position);                                   Toast.makeText(StaggeredGridVActivity.this,MainActivity.mDatas.get(position),Toast.LENGTH_SHORT).show();                               }                           }).show();               }           });效果图:

怎么样?厉害吧。不过你以为这样就完了?

如果我想要将item托拉拽再加上侧滑删除呢?

首先,如果要实现托拉拽功能,那item长按事件还是不要写代码的,避免事件冲突

然后在Activity中加入代码:

ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);   itemTouchHelper.attachToRecyclerView(stag_v_recycler);callback代码:

ItemTouchHelper.Callback callback = new ItemTouchHelper.Callback() {             //这个方法是用来设置我们拖动的方向以及侧滑的方向的        @Override          public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {              //设置拖拽方向为上下左右              final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN |                      ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;              //设置侧滑方向为从左到右和从右到左都可以              final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;              //将方向参数设置进去              return makeMovementFlags(dragFlags, swipeFlags);          }             /**          * @param recyclerView          * @param viewHolder 拖动的ViewHolder          * @param target 目标位置的ViewHolder          * @return          */          @Override          public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {              int fromPosition = viewHolder.getAdapterPosition();//得到拖动ViewHolder的position              int toPosition = target.getAdapterPosition();//得到目标ViewHolder的position              if (fromPosition < toPosition) {                  //分别把中间所有的item的位置重新交换                  for (int i = fromPosition; i < toPosition; i++) {                      Collections.swap(MainActivity.mDatas, i, i + 1);                  }              } else {                  for (int i = fromPosition; i > toPosition; i--) {                      Collections.swap(MainActivity.mDatas, i, i - 1);                  }              }              staggeredGridAdapter.notifyItemMoved(fromPosition, toPosition);              //返回true表示执行拖动              return true;          }             @Override          public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {              int position = viewHolder.getAdapterPosition();              staggeredGridAdapter.notifyItemRemoved(position);          }             @Override          public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {              super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);              if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {                  //滑动时改变Item的透明度                  final float alpha = 1 - Math.abs(dX) / (float) viewHolder.itemView.getWidth();                  viewHolder.itemView.setAlpha(alpha);                  viewHolder.itemView.setTranslationX(dX);              }          }         };OK,我们来看看效果:

如果你喜欢我的文章,那就关注我的博客吧,我会不定期的发些技术贴

源码地址:http://down.51cto.com/data/2222200

有关Android RecyclerView详解及实现瀑布流式布局的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. ruby - nanoc 和多种布局 - 2

    是否可以为特定(或所有)项目使用多个布局?例如,我有几个项目,我想对其应用两种不同的布局。一个是绿色的,一个是蓝色的(但是)。我想将它们编译到我的输出目录中的两个不同文件夹中(例如v1和v2)。我一直在玩弄规则和编译block,但我不知道这是怎么回事。因为,每个项目在编译过程中只编译一次,我不能告诉nanoc第一次用layout1编译,第二次用layout2编译。我试过这样的东西,但它导致输出文件损坏。compile'*'doifitem.binary?#don’tfilterbinaryitemselsefilter:erblayout'layout1'layout'layout2'

  3. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  6. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  7. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  8. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

  9. ruby - 实现k最近邻需要哪些数据? - 2

    我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项

  10. ruby-on-rails - 使用 Ruby 正确处理 Stripe 错误和异常以实现一次性收费 - 2

    我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)

随机推荐