本篇博文主要给大家分享关于RecyclerView控件的使用及通过继承RecyclerView来实现滑动时加载图片的优化方案,也同样能解决防止图片乱序的问题,之前有在网上有看到大神对Android中ListView异步加载图片乱序问题进行过分析,并深入剖析原理后分别给出了3种对应的解决方案:一 、使用findViewWithTag。二、使用弱引用关联。三、使用Volley框架提供的NetworkImageView。
看了之后思索了很久,后来才想到,哦,原来自己也一直这么在用。也算是一种解决方案吧,虽然不是从问题的根本进行处理,但根据实际业务需要,也同样能合理的解决。如以下两种方案:
1、控制线程数量 + 数据分页加载2、重写onScrollStateChanged方法
这个我们后面再谈,下面先来看看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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public
class
MyActivity
extends
Activity {
private
RecyclerView mRecyclerView;
private
RecyclerView.Adapter mAdapter;
private
RecyclerView.LayoutManager mLayoutManager;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// improve performance if you know that changes in content do not change the size of the RecyclerView
//如果确定每个item的内容不会改变RecyclerView的大小,设置这个选项可以提高性能
mRecyclerView.setHasFixedSize(
true
);
// use a linear layout manager
//创建默认的线性LayoutManager
mLayoutManager =
new
LinearLayoutManager(
this
);
mRecyclerView.setLayoutManager(mLayoutManager);
// specify an adapter (see also next example)
//设置Adapter
mAdapter =
new
MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
}
...
}
|
LayoutManager:用来确定每一个item如何进行排列摆放,何时展示和隐藏。提供默认的动画效果,你也可以定义你自己的LayoutManager和添加删除动画。在回收或重用一个View时,LayoutManager会向适配器请求新的数据来替换旧的数据,这种机制避免了创建过多的View和频繁的调用findViewById方法。
2.MyAdapter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public
class
MyAdapter
extends
RecyclerView.Adapter<myadapter.viewholder> {
private
String[] mDataset;
// Provide a reference to the type of views that you are using (custom viewholder)
//自定义的ViewHolder,持有每个Item的的所有界面元素
public
static
class
ViewHolder
extends
RecyclerView.ViewHolder {
public
TextView mTextView;
public
ViewHolder(TextView v) {
super
(v);
mTextView = v;
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public
MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
//创建新View,被LayoutManager调用
@Override
public
MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int
viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent,
false
);
// set the view's size, margins, paddings and layout parameters
...
ViewHolder vh =
new
ViewHolder(v);
return
vh;
}
// Replace the contents of a view (invoked by the layout manager)
//将数据与界面进行绑定
@Override
public
void
onBindViewHolder(ViewHolder holder,
int
position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.mTextView.setText(mDataset[position]);
}
// Return the size of your dataset (invoked by the layout manager)
//这个就不解释了
@Override
public
int
getItemCount() {
return
mDataset.length;
}
}</myadapter.viewholder>
|
3.XML布局
1
2
|
<!-- A RecyclerView with some commonly used attributes -->
</android.support.v7.widget.recyclerview>
|
附:
①设置为横向的List:
1
|
mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
|
1
2
|
mLayoutManager =
new
GridLayoutManager(context,columNum);
mRecyclerView.setLayoutManager(mLayoutManager);
|
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 等方法来做相应处理。
例如:
1
2
3
4
5
|
private
class
AutoLoadScrollListener
extends
OnScrollListener {
//......
public
void
onScrollStateChanged(RecyclerView recyclerView,
int
newState){
}
}
|
状态为0时:当前屏幕停止滚动;
状态为1时:屏幕在滚动 且 用户仍在触碰或手指还在屏幕上;
状态为2时:随用户的操作,屏幕上产生的惯性滑动;
我们就不自己去写那些异步加载图片,缓存啥的代码块了,简单明了,直接使用ImageLoader就可以。下面通过实例讲解该功能的实现,老规矩,先来效果图:
先来瞄瞄activity_main.xml布局文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!--?xml version=
1.0
encoding=utf-
8
?-->
<linearlayout android:fitssystemwindows=
"true"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
android:orientation=
"vertical"
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<relativelayout android:background=
"#00F1A0"
android:id=
"@+id/layout_titlebar"
android:layout_height=
"48dp"
android:layout_width=
"match_parent"
>
<textview android:gravity=
"center"
android:id=
"@+id/text_title"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
android:text=
"好慌~"
android:textcolor=
"@android:color/white"
android:textsize=
"16dp"
>
</textview></relativelayout>
<framelayout android:id=
"@+id/frame_container"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
>
</framelayout></linearlayout>
|
1
2
3
4
5
6
7
8
9
10
|
<!--?xml version=
1.0
encoding=utf-
8
?-->
<framelayout android:background=
"@android:color/white"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<org.gaochun.view.autoloadrecyclerview android:id=
"@+id/recycler_view"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
android:scrollbars=
"vertical"
>
</org.gaochun.view.autoloadrecyclerview></android.support.v4.widget.swiperefreshlayout>
</framelayout>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package
org.gaochun.myapplication;
import
java.util.ArrayList;
import
java.util.List;
/**
* Created by gao_chun on 2015/9/18.
*/
public
class
ImageUrl {
public
static
List<string> imageList(){
List<string> mUrls =
new
ArrayList<string>();
mUrls.add(http:
//e.hiphotos.baidu.com/image/pic/item/a1ec08fa513d2697e542494057fbb2fb4316d81e.jpg);
mUrls.add(http:
//c.hiphotos.baidu.com/image/pic/item/30adcbef76094b36de8a2fe5a1cc7cd98d109d99.jpg);
mUrls.add(http:
//h.hiphotos.baidu.com/image/pic/item/7c1ed21b0ef41bd5f2c2a9e953da81cb39db3d1d.jpg);
mUrls.add(http:
//g.hiphotos.baidu.com/image/pic/item/55e736d12f2eb938d5277fd5d0628535e5dd6f4a.jpg);
mUrls.add(http:
//e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg);
mUrls.add(http:
//b.hiphotos.baidu.com/image/pic/item/9d82d158ccbf6c81b94575cfb93eb13533fa40a2.jpg);
mUrls.add(http:
//e.hiphotos.baidu.com/image/pic/item/4bed2e738bd4b31c1badd5a685d6277f9e2ff81e.jpg);
mUrls.add(http:
//www.huabian.com/uploadfile/2014/1202/20141202025659854.jpg);
mUrls.add(http:
//www.huabian.com/uploadfile/2014/1202/20141202025700989.jpg);
mUrls.add(http:
//g.hiphotos.baidu.com/image/pic/item/0d338744ebf81a4c87a3add4d52a6059252da61e.jpg);
mUrls.add(http:
//a.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee5080c8142ff5e0fe99257e19.jpg);
mUrls.add(http:
//f.hiphotos.baidu.com/image/pic/item/4034970a304e251f503521f5a586c9177e3e53f9.jpg);
mUrls.add(http:
//b.hiphotos.baidu.com/image/pic/item/279759ee3d6d55fbb3586c0168224f4a20a4dd7e.jpg);
mUrls.add(http:
//img2.xkhouse.com/bbs/hfhouse/data/attachment/forum/corebbs/2009-11/2009113011534566298.jpg);
mUrls.add(http:
//a.hiphotos.baidu.com/image/pic/item/e824b899a9014c087eb617650e7b02087af4f464.jpg);
mUrls.add(http:
//c.hiphotos.baidu.com/image/pic/item/9c16fdfaaf51f3de1e296fa390eef01f3b29795a.jpg);
mUrls.add(http:
//d.hiphotos.baidu.com/image/pic/item/b58f8c5494eef01f119945cbe2fe9925bc317d2a.jpg);
mUrls.add(http:
//h.hiphotos.baidu.com/image/pic/item/902397dda144ad340668b847d4a20cf430ad851e.jpg);
mUrls.add(http:
//b.hiphotos.baidu.com/image/pic/item/359b033b5bb5c9ea5c0e3c23d139b6003bf3b374.jpg);
mUrls.add(http:
//a.hiphotos.baidu.com/image/pic/item/8d5494eef01f3a292d2472199d25bc315d607c7c.jpg);
mUrls.add(http:
//b.hiphotos.baidu.com/image/pic/item/e824b899a9014c08878b2c4c0e7b02087af4f4a3.jpg);
mUrls.add(http:
//g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg);
return
mUrls;
}
}
</string></string></string>
|
下面来看看主要的类AutoLoadRecyclerView,其实这个类也很简单:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
public
class
AutoLoadRecyclerView
extends
RecyclerView
implements
LoadFinishCallBack {
private
onLoadMoreListener loadMoreListener;
//加载更多回调
private
boolean
isLoadingMore;
//是否加载更多
public
AutoLoadRecyclerView(Context context) {
this
(context,
null
);
}
public
AutoLoadRecyclerView(Context context, AttributeSet attrs) {
this
(context, attrs,
0
);
}
public
AutoLoadRecyclerView(Context context, AttributeSet attrs,
int
defStyle) {
super
(context, attrs, defStyle);
isLoadingMore =
false
;
//默认无需加载更多
setOnScrollListener(
new
AutoLoadScrollListener(
null
,
true
,
true
));
}
/**
* 配置显示图片,需要设置这几个参数,快速滑动时,暂停图片加载
*
* @param imageLoader ImageLoader实例对象
* @param pauseOnScroll
* @param pauseOnFling
*/
public
void
setOnPauseListenerParams(ImageLoader imageLoader,
boolean
pauseOnScroll,
boolean
pauseOnFling) {
setOnScrollListener(
new
AutoLoadScrollListener(imageLoader, pauseOnScroll, pauseOnFling));
}
public
void
setLoadMoreListener(onLoadMoreListener loadMoreListener) {
this
.loadMoreListener = loadMoreListener;
}
@Override
public
void
loadFinish(Object obj) {
isLoadingMore =
false
;
}
//加载更多的回调接口
public
interface
onLoadMoreListener {
void
loadMore();
}
/**
* 滑动自动加载监听器
*/
private
class
AutoLoadScrollListener
extends
OnScrollListener {
private
ImageLoader imageLoader;
private
final
boolean
pauseOnScroll;
private
final
boolean
pauseOnFling;
public
AutoLoadScrollListener(ImageLoader imageLoader,
boolean
pauseOnScroll,
boolean
pauseOnFling) {
super
();
this
.pauseOnScroll = pauseOnScroll;
this
.pauseOnFling = pauseOnFling;
this
.imageLoader = imageLoader;
}
@Override
public
void
onScrolled(RecyclerView recyclerView,
int
dx,
int
dy) {
super
.onScrolled(recyclerView, dx, dy);
//由于GridLayoutManager是LinearLayoutManager子类,所以也适用
if
(getLayoutManager()
instanceof
LinearLayoutManager) {
int
lastVisibleItem = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
int
totalItemCount = AutoLoadRecyclerView.
this
.getAdapter().getItemCount();
//有回调接口,且不是加载状态,且计算后剩下2个item,且处于向下滑动,则自动加载
if
(loadMoreListener !=
null
&& !isLoadingMore && lastVisibleItem >= totalItemCount -
2
&& dy >
0
) {
loadMoreListener.loadMore();
isLoadingMore =
true
;
}
}
}
//当屏幕停止滚动时为0;当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1;由于用户的操作,屏幕产生惯性滑动时为2
@Override
public
void
onScrollStateChanged(RecyclerView recyclerView,
int
newState) {
//根据newState状态做处理
if
(imageLoader !=
null
) {
switch
(newState) {
case
0
:
imageLoader.resume();
break
;
case
1
:
if
(pauseOnScroll) {
imageLoader.pause();
}
else
{
imageLoader.resume();
}
break
;
case
2
:
if
(pauseOnFling) {
imageLoader.pause();
}
else
{
imageLoader.resume();
}
break
;
}
}
}
}
|