Android之RecyclerView 实现真正的Gallery效果

举报
chenyu 发表于 2021/07/27 01:04:02 2021/07/27
【摘要】 简介: RecyclerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,但是直接把viewholder的实现封装起来,用户只要实现自己的viewholder就可以了,该组件会自动帮你回收复用每一个item。 它不但变得更精简,也变得更加容易使用,而且更容易组合设计出自己需要的滑动...

简介:

RecyclerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,但是直接把viewholder的实现封装起来,用户只要实现自己的viewholder就可以了,该组件会自动帮你回收复用每一个item。

它不但变得更精简,也变得更加容易使用,而且更容易组合设计出自己需要的滑动布局。

要使用RecyclerView,请参考 ,其实你也可以只下载一个jar包,添加到自己的libs里就能使用它了,Recycler.jar

使用它的理由:

RecyclerView  is a more advanced and flexible version of  ListView . This widget is a container for large sets of views that can be recycled and scrolled very efficiently. Use the  RecyclerView  widget when you have lists with elements that change dynamically.
简单说,它是ListView的进化,为了当你需要动态展示一组数据的时候就会需要它。

当然,如果只是动态展示数据,listview也可以做到,用它替代listview的原因有几个:
·简介中提到的它封装了viewholder的回收复用。
·RecyclerView使用布局管理器管理子view的位置(目前尚只提供了LinearLayoutManager),也就是说你再不用拘泥于ListView的线性展示方式,如果之后提供其他custom LayoutManager的支持,你能够使用复杂的布局来展示一个动态组件。
·自带了ItemAnimation,可以设置加载和移除时的动画,方便做出各种动态浏览的效果。

Google官方最近新增加的RecyclerView,据说是ListView的升级版本,本篇博客,首先介绍RecyclerView的用法,然后经行一定的分析;最后自定义一下RecyclerView实现我们需要的相册效果。

1、RecyclerView的基本用法

首先主Activity的布局文件:

   
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent" >
  5. <android.support.v7.widget.RecyclerView
  6. android:id="@+id/id_recyclerview_horizontal"
  7. android:layout_width="match_parent"
  8. android:layout_height="120dp"
  9. android:layout_centerVertical="true"
  10. android:background="#FF0000"
  11. android:scrollbars="none" />
  12. </RelativeLayout>
Item的布局文件:

   
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="120dp"
  4. android:layout_height="120dp"
  5. android:background="@drawable/item_bg02" >
  6. <ImageView
  7. android:id="@+id/id_index_gallery_item_image"
  8. android:layout_width="80dp"
  9. android:layout_height="80dp"
  10. android:layout_alignParentTop="true"
  11. android:layout_centerHorizontal="true"
  12. android:layout_margin="5dp"
  13. android:scaleType="centerCrop" />
  14. <TextView
  15. android:id="@+id/id_index_gallery_item_text"
  16. android:layout_width="wrap_content"
  17. android:layout_height="wrap_content"
  18. android:layout_below="@id/id_index_gallery_item_image"
  19. android:layout_centerHorizontal="true"
  20. android:layout_marginBottom="5dp"
  21. android:layout_marginTop="5dp"
  22. android:textColor="#ff0000"
  23. android:text="some info"
  24. android:textSize="12dp" />
  25. </RelativeLayout>
数据适配器:

   
  1. package com.example.zhy_horizontalscrollview03;
  2. import java.util.List;
  3. import android.content.Context;
  4. import android.support.v7.widget.RecyclerView;
  5. import android.view.LayoutInflater;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. import android.widget.ImageView;
  9. import android.widget.TextView;
  10. public class GalleryAdapter extends
  11. RecyclerView.Adapter<GalleryAdapter.ViewHolder>
  12. {
  13. private LayoutInflater mInflater;
  14. private List<Integer> mDatas;
  15. public GalleryAdapter(Context context, List<Integer> datats)
  16. {
  17. mInflater = LayoutInflater.from(context);
  18. mDatas = datats;
  19. }
  20. public static class ViewHolder extends RecyclerView.ViewHolder
  21. {
  22. public ViewHolder(View arg0)
  23. {
  24. super(arg0);
  25. }
  26. ImageView mImg;
  27. TextView mTxt;
  28. }
  29. @Override
  30. public int getItemCount()
  31. {
  32. return mDatas.size();
  33. }
  34. /**
  35. * 创建ViewHolder
  36. */
  37. @Override
  38. public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i)
  39. {
  40. View view = mInflater.inflate(R.layout.activity_index_gallery_item,
  41. viewGroup, false);
  42. ViewHolder viewHolder = new ViewHolder(view);
  43. viewHolder.mImg = (ImageView) view
  44. .findViewById(R.id.id_index_gallery_item_image);
  45. return viewHolder;
  46. }
  47. /**
  48. * 设置值
  49. */
  50. @Override
  51. public void onBindViewHolder(final ViewHolder viewHolder, final int i)
  52. {
  53. viewHolder.mImg.setImageResource(mDatas.get(i));
  54. }
  55. }
可以看到数据适配器与BaseAdapter比较发生了相当大的变化,主要有3个方法:

getItemCount 这个不用说,获取总的条目数

onCreateViewHolder 创建ViewHolder

onBindViewHolder 将数据绑定至ViewHolder

可见,RecyclerView对ViewHolder也进行了一定的封装,但是如果你仔细观察,你会发出一个疑问,ListView里面有个getView返回View为Item的布局,那么这个Item的样子在哪控制?

其实是这样的,我们创建的ViewHolder必须继承RecyclerView.ViewHolder,这个RecyclerView.ViewHolder的构造时必须传入一个View,这个View相当于我们ListView getView中的convertView (即:我们需要inflate的item布局需要传入)。

还有一点,ListView中convertView是复用的,在RecyclerView中,是把ViewHolder作为缓存的单位了,然后convertView作为ViewHolder的成员变量保持在ViewHolder中,也就是说,假设没有屏幕显示10个条目,则会创建10个ViewHolder缓存起来,每次复用的是ViewHolder,所以他把getView这个方法变为了onCreateViewHolder。有兴趣的自己打印下Log,测试下。

最后在Activity中使用:


   
  1. package com.example.zhy_horizontalscrollview03;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import android.app.Activity;
  6. import android.os.Bundle;
  7. import android.support.v7.widget.LinearLayoutManager;
  8. import android.support.v7.widget.RecyclerView;
  9. import android.view.Window;
  10. public class MainActivity extends Activity
  11. {
  12. private RecyclerView mRecyclerView;
  13. private GalleryAdapter mAdapter;
  14. private List<Integer> mDatas;
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState)
  17. {
  18. super.onCreate(savedInstanceState);
  19. requestWindowFeature(Window.FEATURE_NO_TITLE);
  20. setContentView(R.layout.activity_main);
  21. initDatas();
  22. //得到控件
  23. mRecyclerView = (RecyclerView) findViewById(R.id.id_recyclerview_horizontal);
  24. //设置布局管理器
  25. LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
  26. linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
  27. mRecyclerView.setLayoutManager(linearLayoutManager);
  28. //设置适配器
  29. mAdapter = new GalleryAdapter(this, mDatas);
  30. mRecyclerView.setAdapter(mAdapter);
  31. }
  32. private void initDatas()
  33. {
  34. mDatas = new ArrayList<Integer>(Arrays.asList(R.drawable.a,
  35. R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e,
  36. R.drawable.f, R.drawable.g, R.drawable.h, R.drawable.l));
  37. }
  38. }

使用起来也很方便,唯一的区别就是要设置LayoutManager,目前只有一个实现类,就是LinearLayoutManager,可以设置为水平或者垂直。

最后效果图





效果很不错,这就是RecyclerView的基本用法了,但是你会发现一个坑爹的地方,竟然没有提供setOnItemClickListener这个回调,要不要这么坑爹。。。

2、为RecyclerView添加OnItemClickListener回调

虽然它没有提供,但是添加个OnItemClickListener对我们来说还不是小菜一碟~

我决定在Adapter中添加这个回调接口:



  
  1. package com.example.zhy_horizontalscrollview03;
  2. import java.util.List;
  3. import android.content.Context;
  4. import android.support.v7.widget.RecyclerView;
  5. import android.view.LayoutInflater;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.view.ViewGroup;
  9. import android.widget.ImageView;
  10. import android.widget.TextView;
  11. public class GalleryAdapter extends
  12. RecyclerView.Adapter<GalleryAdapter.ViewHolder>
  13. {
  14. /**
  15. * ItemClick的回调接口
  16. * @author zhy
  17. *
  18. */
  19. public interface OnItemClickLitener
  20. {
  21. void onItemClick(View view, int position);
  22. }
  23. private OnItemClickLitener mOnItemClickLitener;
  24. public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener)
  25. {
  26. this.mOnItemClickLitener = mOnItemClickLitener;
  27. }
  28. private LayoutInflater mInflater;
  29. private List<Integer> mDatas;
  30. public GalleryAdapter(Context context, List<Integer> datats)
  31. {
  32. mInflater = LayoutInflater.from(context);
  33. mDatas = datats;
  34. }
  35. public static class ViewHolder extends RecyclerView.ViewHolder
  36. {
  37. public ViewHolder(View arg0)
  38. {
  39. super(arg0);
  40. }
  41. ImageView mImg;
  42. TextView mTxt;
  43. }
  44. @Override
  45. public int getItemCount()
  46. {
  47. return mDatas.size();
  48. }
  49. @Override
  50. public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i)
  51. {
  52. View view = mInflater.inflate(R.layout.activity_index_gallery_item,
  53. viewGroup, false);
  54. ViewHolder viewHolder = new ViewHolder(view);
  55. viewHolder.mImg = (ImageView) view
  56. .findViewById(R.id.id_index_gallery_item_image);
  57. return viewHolder;
  58. }
  59. @Override
  60. public void onBindViewHolder(final ViewHolder viewHolder, final int i)
  61. {
  62. viewHolder.mImg.setImageResource(mDatas.get(i));
  63. //如果设置了回调,则设置点击事件
  64. if (mOnItemClickLitener != null)
  65. {
  66. viewHolder.itemView.setOnClickListener(new OnClickListener()
  67. {
  68. @Override
  69. public void onClick(View v)
  70. {
  71. mOnItemClickLitener.onItemClick(viewHolder.itemView, i);
  72. }
  73. });
  74. }
  75. }
  76. }

很简单,创建一个接口,提供一个设置入口,然后在onBindViewHolder中判断即可。

最后在主Activity中设置监听:


  
  1. mAdapter = new GalleryAdapter(this, mDatas);
  2. mAdapter.setOnItemClickLitener(new OnItemClickLitener()
  3. {
  4. @Override
  5. public void onItemClick(View view, int position)
  6. {
  7. Toast.makeText(MainActivity.this, position+"", Toast.LENGTH_SHORT)
  8. .show();
  9. }
  10. });
  11. mRecyclerView.setAdapter(mAdapter);
好了,这样就行了,看效果图:

效果还是不错的,接下来我想改成相册效果,即上面显示一张大图,下面的RecyclerView做为图片切换的指示器。

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

首先修改下布局:

布局文件:


  
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical" >
  6. <FrameLayout
  7. android:layout_width="fill_parent"
  8. android:layout_height="0dp"
  9. android:layout_weight="1" >
  10. <ImageView
  11. android:id="@+id/id_content"
  12. android:layout_width="fill_parent"
  13. android:layout_height="fill_parent"
  14. android:layout_gravity="center"
  15. android:layout_margin="10dp"
  16. android:scaleType="centerCrop"
  17. android:src="@drawable/ic_launcher" />
  18. </FrameLayout>
  19. <com.example.zhy_horizontalscrollview03.MyRecyclerView
  20. android:id="@+id/id_recyclerview_horizontal"
  21. android:layout_width="match_parent"
  22. android:layout_height="120dp"
  23. android:layout_gravity="bottom"
  24. android:background="#FF0000"
  25. android:scrollbars="none" />
  26. </LinearLayout>

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

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


  
  1. package com.example.zhy_horizontalscrollview03;
  2. import android.content.Context;
  3. import android.support.v7.widget.RecyclerView;
  4. import android.util.AttributeSet;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. public class CopyOfMyRecyclerView extends RecyclerView
  8. {
  9. public CopyOfMyRecyclerView(Context context, AttributeSet attrs)
  10. {
  11. super(context, attrs);
  12. }
  13. private View mCurrentView;
  14. /**
  15. * 滚动时回调的接口
  16. */
  17. private OnItemScrollChangeListener mItemScrollChangeListener;
  18. public void setOnItemScrollChangeListener(
  19. OnItemScrollChangeListener mItemScrollChangeListener)
  20. {
  21. this.mItemScrollChangeListener = mItemScrollChangeListener;
  22. }
  23. public interface OnItemScrollChangeListener
  24. {
  25. void onChange(View view, int position);
  26. }
  27. @Override
  28. protected void onLayout(boolean changed, int l, int t, int r, int b)
  29. {
  30. super.onLayout(changed, l, t, r, b);
  31. mCurrentView = getChildAt(0);
  32. if (mItemScrollChangeListener != null)
  33. {
  34. mItemScrollChangeListener.onChange(mCurrentView,
  35. getChildPosition(mCurrentView));
  36. }
  37. }
  38. @Override
  39. public boolean onTouchEvent(MotionEvent e)
  40. {
  41. if (e.getAction() == MotionEvent.ACTION_MOVE)
  42. {
  43. mCurrentView = getChildAt(0);
  44. // Log.e("TAG", getChildPosition(getChildAt(0)) + "");
  45. if (mItemScrollChangeListener != null)
  46. {
  47. mItemScrollChangeListener.onChange(mCurrentView,
  48. getChildPosition(mCurrentView));
  49. }
  50. }
  51. return super.onTouchEvent(e);
  52. }
  53. }

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

关于我咋知道getChildAt(0)和getChildPosition()可用,起初我以为有getFirstVisibleItem这个方法,后来发现么有;但是发现了getRecycledViewPool()看名字我觉得是Viewholder那个缓存队列,我想那么直接取这个队列的第一个不就是我要的View么,后来没有成功。我就观察它内部的View,最后发现,第一个显示的始终是它第一个child,至于getChildPosition这个看方法就看出来了。

现在的效果:

和我之前那个例子的效果是一模一样的,不过,我还想做一些改变,我觉得Gallery或者说相册的指示器,下面可能1000来张图片,我不仅喜欢手指在屏幕上滑动时,图片会自动切换。我还希望,如果我给指示器一个加速度,即使手指离开,下面还在滑动,上面也会联动 。而且我还想做些优化,直接在ACTION_MOVE中回调,触发的频率太高了,理论上一张图片只会触发一次~~

4、优化与打造真正的Gallery效果

既然希望手指离开还能联动,那么不仅需要ACTION_MOVE需要监听,还得监听一个加速度,速度到达一定值,然后继续移动~~再理一理,需要这么麻烦么,不是能滚动么,那么应该有OnScrollListener啊,小看一把,果然有,哈哈哈~天助我也,下面看修改后的代码:


  
  1. package com.example.zhy_horizontalscrollview03;
  2. import android.content.Context;
  3. import android.support.v7.widget.RecyclerView;
  4. import android.support.v7.widget.RecyclerView.OnScrollListener;
  5. import android.util.AttributeSet;
  6. import android.view.View;
  7. public class MyRecyclerView extends RecyclerView implements OnScrollListener
  8. {
  9. /**
  10. * 记录当前第一个View
  11. */
  12. private View mCurrentView;
  13. private OnItemScrollChangeListener mItemScrollChangeListener;
  14. public void setOnItemScrollChangeListener(
  15. OnItemScrollChangeListener mItemScrollChangeListener)
  16. {
  17. this.mItemScrollChangeListener = mItemScrollChangeListener;
  18. }
  19. public interface OnItemScrollChangeListener
  20. {
  21. void onChange(View view, int position);
  22. }
  23. public MyRecyclerView(Context context, AttributeSet attrs)
  24. {
  25. super(context, attrs);
  26. // TODO Auto-generated constructor stub
  27. this.setOnScrollListener(this);
  28. }
  29. @Override
  30. protected void onLayout(boolean changed, int l, int t, int r, int b)
  31. {
  32. super.onLayout(changed, l, t, r, b);
  33. mCurrentView = getChildAt(0);
  34. if (mItemScrollChangeListener != null)
  35. {
  36. mItemScrollChangeListener.onChange(mCurrentView,
  37. getChildPosition(mCurrentView));
  38. }
  39. }
  40. @Override
  41. public void onScrollStateChanged(int arg0)
  42. {
  43. }
  44. /**
  45. *
  46. * 滚动时,判断当前第一个View是否发生变化,发生才回调
  47. */
  48. @Override
  49. public void onScrolled(int arg0, int arg1)
  50. {
  51. View newView = getChildAt(0);
  52. if (mItemScrollChangeListener != null)
  53. {
  54. if (newView != null && newView != mCurrentView)
  55. {
  56. mCurrentView = newView ;
  57. mItemScrollChangeListener.onChange(mCurrentView,
  58. getChildPosition(mCurrentView));
  59. }
  60. }
  61. }
  62. }

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

至于优化:我使用了一个成员变化存储当前第一个View,只有第一个View发生变化时才回调~~太完美了~

看MainActivity:


  
  1. package com.example.zhy_horizontalscrollview03;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import android.app.Activity;
  6. import android.os.Bundle;
  7. import android.support.v7.widget.LinearLayoutManager;
  8. import android.support.v7.widget.RecyclerView;
  9. import android.view.View;
  10. import android.view.Window;
  11. import android.widget.ImageView;
  12. import android.widget.Toast;
  13. import com.example.zhy_horizontalscrollview03.GalleryAdapter.OnItemClickLitener;
  14. import com.example.zhy_horizontalscrollview03.MyRecyclerView.OnItemScrollChangeListener;
  15. public class MainActivity extends Activity
  16. {
  17. private MyRecyclerView mRecyclerView;
  18. private GalleryAdapter mAdapter;
  19. private List<Integer> mDatas;
  20. private ImageView mImg ;
  21. @Override
  22. protected void onCreate(Bundle savedInstanceState)
  23. {
  24. super.onCreate(savedInstanceState);
  25. requestWindowFeature(Window.FEATURE_NO_TITLE);
  26. setContentView(R.layout.activity_main);
  27. mImg = (ImageView) findViewById(R.id.id_content);
  28. mDatas = new ArrayList<Integer>(Arrays.asList(R.drawable.a,
  29. R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e,
  30. R.drawable.f, R.drawable.g, R.drawable.h, R.drawable.l));
  31. mRecyclerView = (MyRecyclerView) findViewById(R.id.id_recyclerview_horizontal);
  32. LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
  33. linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
  34. mRecyclerView.setLayoutManager(linearLayoutManager);
  35. mAdapter = new GalleryAdapter(this, mDatas);
  36. mRecyclerView.setAdapter(mAdapter);
  37. mRecyclerView.setOnItemScrollChangeListener(new OnItemScrollChangeListener()
  38. {
  39. @Override
  40. public void onChange(View view, int position)
  41. {
  42. mImg.setImageResource(mDatas.get(position));
  43. };
  44. });
  45. mAdapter.setOnItemClickLitener(new OnItemClickLitener()
  46. {
  47. @Override
  48. public void onItemClick(View view, int position)
  49. {
  50. // Toast.makeText(getApplicationContext(), position + "", Toast.LENGTH_SHORT)
  51. // .show();
  52. mImg.setImageResource(mDatas.get(position));
  53. }
  54. });
  55. }
  56. }

代码没什么变化~多了个设置回调~

效果图:



可以看到不仅支持手机在上面移动时的变化,如果我给了一个加速度,下面持续滚动,上面也会持续变化~~大赞~每张图片回调一次,效率也相当不错。


好了,看完这边博客,相信大家对于RecyclerView有了一定的认识,甚至对于如何改造一个控件也多了一份了解~~






文章来源: chenyu.blog.csdn.net,作者:chen.yu,版权归原作者所有,如需转载,请联系作者。

原文链接:chenyu.blog.csdn.net/article/details/48519899

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。