RecyclerView使用 及 滑动时加载图片优化方案

来源:互联网 发布:积分购物源码 编辑:程序博客网 时间:2024/06/06 08:50

RecyclerView使用 及 滑动时加载图片优化方案



简述

本篇博文主要给大家分享关于RecyclerView控件的使用及通过继承RecyclerView来实现滑动时加载图片的优化方案,也同样能解决防止图片乱序的问题,之前有在网上有看到大神对Android中ListView异步加载图片乱序问题进行过分析,并深入剖析原理后分别给出了3种对应的解决方案:一 、使用findViewWithTag。二、使用弱引用关联。三、使用Volley框架提供的NetworkImageView。

看了之后思索了很久,后来才想到,哦,原来自己也一直这么在用。也算是一种解决方案吧,虽然不是从问题的根本进行处理,但根据实际业务需要,也同样能合理的解决。如以下两种方案:

  • 1、控制线程数量 + 数据分页加载
  • 2、重写onScrollStateChanged方法

这个我们后面再谈,下面先来看看RecyclerView控件的使用及我们为什么选择使用它。


RecyclerView的使用

RecyclerView 位于package android.support.v7.widget; 包下,直接继承了android.view.ViewGroup,是Android中新添加的一个用来取代ListView的滑动控件,其灵活性与可替代性比ListView更优秀,运行原理与ListView类似,都是通过维护少量的View可展示大量的数据集。

总结其优点:

  • 一、标准化了ViewHolder,使用Adapter适配器时,面向ViewHolder而不是单纯的View,直接把ViewHolder的实现封装起来,用户只要实现自己的ViewHolder就可以了,该组件会自动帮你回收并复用每一个item。不但变得更精简,也变得更加容易使用,而且更容易组合设计出自己需要的滑动布局。
  • 二、将Layout抽象成了一个LayoutManager,RecyclerView不负责子View的布局,而是通过使用LayoutManager来实现不同的布局效果,如使用LinearLayoutManager来指定方向,其默认是垂直,也可以设置为水平,当然你也可以自己来定义。


我们来看看官方给出的示例:

1.MyActivity.java

[java] view plain copy
 print?
  1.   public class MyActivity extends Activity {    
  2.     private RecyclerView mRecyclerView;    
  3.     private RecyclerView.Adapter mAdapter;    
  4.     private RecyclerView.LayoutManager mLayoutManager;    
  5.     
  6.     @Override    
  7.     protected void onCreate(Bundle savedInstanceState) {    
  8.         super.onCreate(savedInstanceState);    
  9.         setContentView(R.layout.my_activity);    
  10.         mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);    
  11.     
  12.         // improve performance if you know that changes in content do not change the size of the RecyclerView    
  13.        //如果确定每个item的内容不会改变RecyclerView的大小,设置这个选项可以提高性能  
  14.         mRecyclerView.setHasFixedSize(true);    
  15.     
  16.         // use a linear layout manager    
  17.        <span style="white-space:pre">//创建默认的线性LayoutManager</span>  
  18.         mLayoutManager = new LinearLayoutManager(this);    
  19.         mRecyclerView.setLayoutManager(mLayoutManager);    
  20.     
  21.         // specify an adapter (see also next example)    
  22.        //设置Adapter  
  23.         mAdapter = new MyAdapter(myDataset);    
  24.         mRecyclerView.setAdapter(mAdapter);    
  25.     }    
  26.     ...    
  27. }  

LayoutManager:用来确定每一个item如何进行排列摆放,何时展示和隐藏。提供默认的动画效果,你也可以定义你自己的LayoutManager和添加删除动画。在回收或重用一个View时,LayoutManager会向适配器请求新的数据来替换旧的数据,这种机制避免了创建过多的View和频繁的调用findViewById方法。


2.MyAdapter
[java] view plain copy
 print?
  1.   public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {    
  2.     private String[] mDataset;    
  3.     
  4.     // Provide a reference to the type of views that you are using (custom viewholder)    
  5.     //自定义的ViewHolder,持有每个Item的的所有界面元素  
  6.     public static class ViewHolder extends RecyclerView.ViewHolder {    
  7.         public TextView mTextView;    
  8.         public ViewHolder(TextView v) {    
  9.             super(v);    
  10.             mTextView = v;    
  11.         }    
  12.     }    
  13.     
  14.     // Provide a suitable constructor (depends on the kind of dataset)    
  15.     public MyAdapter(String[] myDataset) {    
  16.         mDataset = myDataset;    
  17.     }    
  18.     
  19.     // Create new views (invoked by the layout manager)    
  20.    //创建新View,被LayoutManager调用  
  21.     @Override    
  22.     public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,    
  23.                                                    int viewType) {    
  24.         // create a new view    
  25.         View v = LayoutInflater.from(parent.getContext())    
  26.                                .inflate(R.layout.my_text_view, parent, false);    
  27.         // set the view's size, margins, paddings and layout parameters    
  28.         ...    
  29.         ViewHolder vh = new ViewHolder(v);    
  30.         return vh;    
  31.     }    
  32.     
  33.     // Replace the contents of a view (invoked by the layout manager)    
  34.    //将数据与界面进行绑定  
  35.     @Override    
  36.     public void onBindViewHolder(ViewHolder holder, int position) {    
  37.         // - get element from your dataset at this position    
  38.         // - replace the contents of the view with that element    
  39.         holder.mTextView.setText(mDataset[position]);    
  40.     
  41.     }    
  42.     
  43.     // Return the size of your dataset (invoked by the layout manager)    
  44.     //这个就不解释了  
  45.     @Override    
  46.     public int getItemCount() {    
  47.         return mDataset.length;    
  48.     }    
  49. }  
Adapter:在使用RecyclerView之前,你需要一个继承自RecyclerView.Adapter的适配器,作用是将数据与每一个item的界面进行绑定。


3.XML布局

[html] view plain copy
 print?
  1. <!-- A RecyclerView with some commonly used attributes -->    
  2. <android.support.v7.widget.RecyclerView    
  3.     android:id="@+id/my_recycler_view"    
  4.     android:scrollbars="vertical"    
  5.     android:layout_width="match_parent"    
  6.     android:layout_height="match_parent"/>  

注:recyclerview No adapter attached; skipping layout 若出现该错误,是由于跳过了布局,没有Adapter与之对接的原因。


附:

①设置为横向的List:

[java] view plain copy
 print?
  1. mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);  
②设置Grid布局列表,声明LayoutManager为GridLayoutManager即可:
[java] view plain copy
 print?
  1. mLayoutManager = new GridLayoutManager(context,columNum);  
  2. mRecyclerView.setLayoutManager(mLayoutManager);  
③瀑布流布局
[java] view plain copy
 print?
  1. //使用StaggeredGridLayoutManager  

从上述例子可以看出,RecyclerView的用法并不复杂,反而更灵活好用,它将数据、排列方式、数据的展示方式都分割开来,自定义的形式也非常多,非常灵活。下方共享一个开源示例,且解决了ScrollView嵌套RecyclerView无法显示的问题:

        


     


下载链接:http://download.csdn.net/detail/gao_chun/9124221



滑动时图片优化方案描述

1、控制线程数量 + 数据分页加载

我们在使用滑动控件呈现图片数据时,显然都会在getView方法里创建新的线程去异步加载图片,不可能有一百条或上千条数据一口气全部塞过来吧(当然你要这么干也是可以的),那么根据项目需求必然会进行分页加载,咱一页显示的item条数也别太夸张就好。而且,当我们点击屏幕快速向下滑动时,每个Item都会调用getView一次,必然会创建出很多线程去加载图片的URL资源,控制好线程的数量,加个线程池就非常有必要了。为了避免OOM导致FC,注意图片需要缓存,因为从内存中读取图片资源是非常快的。

2、重写onScrollStateChanged方法

这种方案用的也很普遍,相信只要细心观察,就会发现类似微博、Facebook、或者一些图片壁纸类的APP,在滑动时未加载的图片是不会立刻加载呈现的,只有当滑动停止后才会加载,这里需要注意一点的是,只加载当前屏幕内的图片。这么一说可能有童鞋就明白了。我们可以通过继承RecyclerView去自定义一个滑动控件,通过继承OnScrollListener后重写其 onScrolled方法 和 onScrollStateChanged 等方法来做相应处理。

例如:

[java] view plain copy
 print?
  1. private class AutoLoadScrollListener extends OnScrollListener {     
  2.        //......  
  3.        public void onScrollStateChanged(RecyclerView recyclerView, int newState){  
  4.        }  
  5.     }  
我们通过 extends OnScrollListener 并且 @Override 其 onScrollStateChanged 方法,通过判断state来处理,此处对其滚动的状态newState做一个说明,方面大家了解学习,分别有3个状态,即 0 - 1 - 2:

状态为0时当前屏幕停止滚动;

状态为1时:屏幕在滚动 且 用户仍在触碰或手指还在屏幕上;

状态为2时:随用户的操作,屏幕上产生的惯性滑动;


实现

我们就不自己去写那些异步加载图片,缓存啥的代码块了,简单明了,直接使用ImageLoader就可以。下面通过实例讲解该功能的实现,老规矩,先来效果图:

              


先来瞄瞄activity_main.xml布局文件:

[html] view plain copy
 print?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:fitsSystemWindows="true"  
  6.     android:orientation="vertical">  
  7.   
  8.     <RelativeLayout  
  9.         android:id="@+id/layout_titlebar"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="48dp"  
  12.         android:background="#00F1A0">  
  13.   
  14.         <TextView  
  15.             android:textSize="16dp"  
  16.             android:id="@+id/text_title"  
  17.             android:layout_width="match_parent"  
  18.             android:layout_height="match_parent"  
  19.             android:gravity="center"  
  20.             android:text="好慌~"  
  21.             android:textColor="@android:color/white" />  
  22.   
  23.     </RelativeLayout>  
  24.   
  25.   
  26.     <FrameLayout  
  27.         android:id="@+id/frame_container"  
  28.         android:layout_width="match_parent"  
  29.         android:layout_height="match_parent" />  
  30.   
  31. </LinearLayout>  

很简单,用了一个Fragment而已,再来看看主Fragment的布局:

[html] view plain copy
 print?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:background="@android:color/white">  
  6.   
  7.     <android.support.v4.widget.SwipeRefreshLayout  
  8.         android:id="@+id/swipeRefreshLayout"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent">  
  11.   
  12.         <org.gaochun.view.AutoLoadRecyclerView  
  13.             android:id="@+id/recycler_view"  
  14.             android:layout_width="match_parent"  
  15.             android:layout_height="match_parent"  
  16.             android:scrollbars="vertical" />  
  17.   
  18.     </android.support.v4.widget.SwipeRefreshLayout>  
  19.   
  20. </FrameLayout>  

我们使用了 SwipeRefreshLayout 中包裹了自定义的 AutoLoadRecyclerView,关于SwipeRefreshLayout 控件的使用,大家可以自行百度,这里就不多说了。有盆友可能会说,挖槽,好慌~ ,这测试图哪来的,貌似还挺清晰的哇,好吧,只能帮你到这了:

[java] view plain copy
 print?
  1. package org.gaochun.myapplication;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  * Created by gao_chun on 2015/9/18. 
  8.  */  
  9. public class ImageUrl {  
  10.   
  11.     public static List<String> imageList(){  
  12.   
  13.         List<String> mUrls = new ArrayList<String>();  
  14.         mUrls.add("http://e.hiphotos.baidu.com/image/pic/item/a1ec08fa513d2697e542494057fbb2fb4316d81e.jpg");  
  15.         mUrls.add("http://c.hiphotos.baidu.com/image/pic/item/30adcbef76094b36de8a2fe5a1cc7cd98d109d99.jpg");  
  16.         mUrls.add("http://h.hiphotos.baidu.com/image/pic/item/7c1ed21b0ef41bd5f2c2a9e953da81cb39db3d1d.jpg");  
  17.         mUrls.add("http://g.hiphotos.baidu.com/image/pic/item/55e736d12f2eb938d5277fd5d0628535e5dd6f4a.jpg");  
  18.         mUrls.add("http://e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg");  
  19.         mUrls.add("http://b.hiphotos.baidu.com/image/pic/item/9d82d158ccbf6c81b94575cfb93eb13533fa40a2.jpg");  
  20.         mUrls.add("http://e.hiphotos.baidu.com/image/pic/item/4bed2e738bd4b31c1badd5a685d6277f9e2ff81e.jpg");  
  21.         mUrls.add("http://www.huabian.com/uploadfile/2014/1202/20141202025659854.jpg");  
  22.         mUrls.add("http://www.huabian.com/uploadfile/2014/1202/20141202025700989.jpg");  
  23.         mUrls.add("http://g.hiphotos.baidu.com/image/pic/item/0d338744ebf81a4c87a3add4d52a6059252da61e.jpg");  
  24.         mUrls.add("http://a.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee5080c8142ff5e0fe99257e19.jpg");  
  25.         mUrls.add("http://f.hiphotos.baidu.com/image/pic/item/4034970a304e251f503521f5a586c9177e3e53f9.jpg");  
  26.         mUrls.add("http://b.hiphotos.baidu.com/image/pic/item/279759ee3d6d55fbb3586c0168224f4a20a4dd7e.jpg");  
  27.         mUrls.add("http://img2.xkhouse.com/bbs/hfhouse/data/attachment/forum/corebbs/2009-11/2009113011534566298.jpg");  
  28.         mUrls.add("http://a.hiphotos.baidu.com/image/pic/item/e824b899a9014c087eb617650e7b02087af4f464.jpg");  
  29.         mUrls.add("http://c.hiphotos.baidu.com/image/pic/item/9c16fdfaaf51f3de1e296fa390eef01f3b29795a.jpg");  
  30.         mUrls.add("http://d.hiphotos.baidu.com/image/pic/item/b58f8c5494eef01f119945cbe2fe9925bc317d2a.jpg");  
  31.         mUrls.add("http://h.hiphotos.baidu.com/image/pic/item/902397dda144ad340668b847d4a20cf430ad851e.jpg");  
  32.         mUrls.add("http://b.hiphotos.baidu.com/image/pic/item/359b033b5bb5c9ea5c0e3c23d139b6003bf3b374.jpg");  
  33.         mUrls.add("http://a.hiphotos.baidu.com/image/pic/item/8d5494eef01f3a292d2472199d25bc315d607c7c.jpg");  
  34.         mUrls.add("http://b.hiphotos.baidu.com/image/pic/item/e824b899a9014c08878b2c4c0e7b02087af4f4a3.jpg");  
  35.         mUrls.add("http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg");  
  36.         return mUrls;  
  37.     }  
  38. }  

下面来看看主要的类AutoLoadRecyclerView,其实这个类也很简单:

[java] view plain copy
 print?
  1. public class AutoLoadRecyclerView extends RecyclerView implements LoadFinishCallBack {  
  2.   
  3.     private onLoadMoreListener loadMoreListener;    //加载更多回调  
  4.     private boolean isLoadingMore;                  //是否加载更多  
  5.   
  6.     public AutoLoadRecyclerView(Context context) {  
  7.         this(context, null);  
  8.     }  
  9.   
  10.     public AutoLoadRecyclerView(Context context, AttributeSet attrs) {  
  11.         this(context, attrs, 0);  
  12.     }  
  13.   
  14.     public AutoLoadRecyclerView(Context context, AttributeSet attrs, int defStyle) {  
  15.         super(context, attrs, defStyle);  
  16.   
  17.         isLoadingMore = false;  //默认无需加载更多  
  18.         setOnScrollListener(new AutoLoadScrollListener(nulltruetrue));  
  19.     }  
  20.   
  21.     /** 
  22.      * 配置显示图片,需要设置这几个参数,快速滑动时,暂停图片加载 
  23.      * 
  24.      * @param imageLoader   ImageLoader实例对象 
  25.      * @param pauseOnScroll 
  26.      * @param pauseOnFling 
  27.      */  
  28.     public void setOnPauseListenerParams(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling) {  
  29.   
  30.         setOnScrollListener(new AutoLoadScrollListener(imageLoader, pauseOnScroll, pauseOnFling));  
  31.   
  32.     }  
  33.   
  34.     public void setLoadMoreListener(onLoadMoreListener loadMoreListener) {  
  35.         this.loadMoreListener = loadMoreListener;  
  36.     }  
  37.   
  38.     @Override  
  39.     public void loadFinish(Object obj) {  
  40.         isLoadingMore = false;  
  41.     }  
  42.   
  43.   
  44.     //加载更多的回调接口  
  45.     public interface onLoadMoreListener {  
  46.         void loadMore();  
  47.     }  
  48.   
  49.   
  50.     /** 
  51.      * 滑动自动加载监听器 
  52.      */  
  53.     private class AutoLoadScrollListener extends OnScrollListener {  
  54.   
  55.         private ImageLoader imageLoader;  
  56.         private final boolean pauseOnScroll;  
  57.         private final boolean pauseOnFling;  
  58.   
  59.         public AutoLoadScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling) {  
  60.             super();  
  61.             this.pauseOnScroll = pauseOnScroll;  
  62.             this.pauseOnFling = pauseOnFling;  
  63.             this.imageLoader = imageLoader;  
  64.         }  
  65.   
  66.         @Override  
  67.         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {  
  68.             super.onScrolled(recyclerView, dx, dy);  
  69.   
  70.             //由于GridLayoutManager是LinearLayoutManager子类,所以也适用  
  71.             if (getLayoutManager() instanceof LinearLayoutManager) {  
  72.                 int lastVisibleItem = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();  
  73.                 int totalItemCount = AutoLoadRecyclerView.this.getAdapter().getItemCount();  
  74.   
  75.                 //有回调接口,且不是加载状态,且计算后剩下2个item,且处于向下滑动,则自动加载  
  76.                 if (loadMoreListener != null && !isLoadingMore && lastVisibleItem >= totalItemCount -  
  77.                         2 && dy > 0) {  
  78.                     loadMoreListener.loadMore();  
  79.                     isLoadingMore = true;  
  80.                 }  
  81.             }  
  82.         }  
  83.   
  84.         //当屏幕停止滚动时为0;当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1;由于用户的操作,屏幕产生惯性滑动时为2  
  85.         @Override  
  86.         public void onScrollStateChanged(RecyclerView recyclerView, int newState) {  
  87.   
  88.             //根据newState状态做处理  
  89.             if (imageLoader != null) {  
  90.                 switch (newState) {  
  91.                     case 0:  
  92.                         imageLoader.resume();  
  93.                         break;  
  94.   
  95.                     case 1:  
  96.                         if (pauseOnScroll) {  
  97.                             imageLoader.pause();  
  98.                         } else {  
  99.                             imageLoader.resume();  
  100.                         }  
  101.                         break;  
  102.   
  103.                     case 2:  
  104.                         if (pauseOnFling) {  
  105.                             imageLoader.pause();  
  106.                         } else {  
  107.                             imageLoader.resume();  
  108.                         }  
  109.                         break;  
  110.                 }  
  111.             }  
  112.         }  
  113.     }  

也就是说,我们通过定义了一个 setOnPauseListenerParams 方法去设置滑动监听事件setOnScrollListener,并通过定义内部类AutoLoadScrollListener去@Override相关方法并做相应的处理。


下载链接:http://download.csdn.net/detail/gao_chun/9124659



【转载注明gao_chun的Blog:http://blog.csdn.net/gao_chun/article/details/48550117】

0 1
原创粉丝点击