[Android学习笔记三] Support v7提供交错式网格布局开发示例
本文主要介绍Android Support v7提供的RecycleView和交错式布局(通常成为瀑布流布局)的使用和事件监听处理。
1. 涉及到开源库有:
Fresco :
Facebook开源的不是一般强大的图片加载组件库
Android View和事件绑定库,通过注解完成,在编译时APT处理注解文档。
2. 模块配置
Android Studio模块,build.gradle配置:
|
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
|
apply plugin:
'com.android.application'
android {
compileSdkVersion
21
buildToolsVersion
'21.1.2'
defaultConfig {
applicationId
'secondriver.sdk'
minSdkVersion
16
targetSdkVersion
21
versionCode
1
versionName
'1.0'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile(
'proguard-android.txt'
),
'proguard-rules.pro'
}
}
packagingOptions {
exclude
'META-INF/services/javax.annotation.processing.Processor'
}
lintOptions {
disable
'InvalidPackage'
}
}
dependencies {
compile fileTree(dir:
'libs'
, include: [
'*.jar'
])
compile
'com.android.support:appcompat-v7:21.0.3'
compile
'com.android.support:recyclerview-v7:21.0.3@aar'
compile
'com.android.support:cardview-v7:21.0.3@aar'
compile
'com.facebook.fresco:fresco:0.8.0'
compile
'com.jakewharton:butterknife:7.0.1'
testCompile
'junit:junit:4.12'
}
|
3. 布局文件
主布局文件:activity_recycleview.xml 包含一个RecycleView,将作为主屏幕组件。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
RelativeLayout
android:id
=
"@+id/swipe_refresh_layout"
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
>
<
android.support.v7.widget.RecyclerView
android:id
=
"@+id/recycler_view"
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:layout_alignParentTop
=
"true"
android:scrollbars
=
"vertical"
/>
</
RelativeLayout
>
|
RecycleView 通过设置不同的布局管理器对象来实现不同的布局显示,如:
android.support.v7.widget.LinearLayoutManager 可以实现ListView的布局效果
android.support.v7.widget.GridLayoutManager 可以实现GridView的布局效果
android.support.v7.widget.StaggeredGridLayoutManager 可以实现交错式网格布局效果
次布局文件(RecycleView中显示的每一项视图的布局):item_recycelview.xml 通过com.android.support:CardView包提供的CardView帧式容器布局包含一个ImageView和TextView作为RecycleView的每一项视图的布局。
|
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
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
xmlns:card_view
=
"http://schemas.android.com/apk/res-auto"
xmlns:fresco
=
"http://schemas.android.com/tools"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:gravity
=
"center"
>
<
android.support.v7.widget.CardView
android:id
=
"@+id/card_view"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_gravity
=
"center"
card_view:cardCornerRadius
=
"4dp"
card_view:cardUseCompatPadding
=
"true"
>
<
RelativeLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:orientation
=
"vertical"
>
<
com.facebook.drawee.view.SimpleDraweeView
android:id
=
"@+id/info_image"
android:layout_width
=
"300dp"
android:layout_height
=
"420dp"
fresco:actualImageScaleType
=
"centerCrop"
fresco:placeholderImage
=
"@mipmap/ic_launcher"
fresco:roundAsCircle
=
"false"
fresco:roundBottomLeft
=
"true"
fresco:roundBottomRight
=
"true"
fresco:roundTopLeft
=
"true"
fresco:roundTopRight
=
"true"
fresco:roundedCornerRadius
=
"1dp"
fresco:roundingBorderWidth
=
"2dp"
/>
<
TextView
android:id
=
"@+id/info_text"
android:layout_width
=
"200dp"
android:layout_height
=
"80dp"
android:textSize
=
"20sp"
android:textColor
=
"@android:color/holo_blue_dark"
android:textAppearance
=
"?android:attr/textAppearanceMedium"
android:layout_alignLeft
=
"@id/info_image"
android:layout_alignRight
=
"@id/info_image"
android:layout_below
=
"@id/info_image"
android:gravity
=
"left|center_vertical"
/>
</
RelativeLayout
>
</
android.support.v7.widget.CardView
>
</
LinearLayout
>
|
ImageView组件使用的是Fresco库提供的视图组件。
4. Activity实现和数据填充
4.1 RecycleView中的每一项视图的数据组成
文本信息+ 图片地址(url,file,resId) 本示例中采用Url网络图片
|
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* View项的数据对象
*/
static
class
Pair {
public
String text;
public
String url;
public
Pair(String text, String url) {
this
.text = text;
this
.url = url;
}
}
|
4.2 RecycleView中国的每一项视图的数据填充,即适配器
自定义适配器需要实现RecycleView.Adapter类。
|
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
|
static
class
RecycleViewAdapter
extends
RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
* RecycleView的View项单击事件监听
*/
public
interface
OnRecycleViewItemClickListener {
void
onRecycleViewItemClick(View view,
int
position);
}
private
ArrayList<Pair> items =
new
ArrayList<>();
private
OnRecycleViewItemClickListener mOnRecycleViewItemClickListener;
public
RecycleViewAdapter(ArrayList<Pair> items) {
this
.items = items;
}
@Override
public
RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
int
viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyclerview, parent,
false
);
return
new
RecycleViewItemHolder(view, mOnRecycleViewItemClickListener);
}
@Override
public
void
onBindViewHolder(RecyclerView.ViewHolder holder,
int
position) {
Pair pair = items.get(position);
((RecycleViewItemHolder) holder).setContent(pair);
}
@Override
public
int
getItemCount() {
return
items.size();
}
public
void
setOnRecycleViewItemClickListener(OnRecycleViewItemClickListener onItemClickListener) {
if
(
null
!= onItemClickListener) {
this
.mOnRecycleViewItemClickListener = onItemClickListener;
}
}
}
|
主要实现onCreateViewHolder和onBindViewHolder方法。由于RecycleView并未提供为视图项添加监听事件,这里自定义个一个事件监听接口,在实例化适配器的时候可以选择设置事件监听。
4.3 创建ViewHolder需要通过自定义ViewHolder类继承RecycleView.ViewHolder。
|
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
|
static
class
RecycleViewItemHolder
extends
RecyclerView.ViewHolder
implements
View.OnClickListener {
@Bind
(R.id.info_text)
public
TextView infoTextView;
@Bind
(R.id.info_image)
public
SimpleDraweeView draweeView;
private
RecycleViewAdapter.OnRecycleViewItemClickListener onItemClickListener;
public
RecycleViewItemHolder(View itemView, RecycleViewAdapter.OnRecycleViewItemClickListener onItemClickListener) {
super
(itemView);
ButterKnife.bind(
this
, itemView);
this
.onItemClickListener = onItemClickListener;
itemView.setOnClickListener(
this
);
}
public
void
setContent(Pair pair) {
infoTextView.setText(pair.text);
draweeView.setImageURI(Uri.parse(pair.url));
}
@Override
public
void
onClick(View v) {
if
(
null
!= onItemClickListener) {
onItemClickListener.onRecycleViewItemClick(v, getPosition());
}
}
}
|
在构造方法中的传入OnRecycleViewItemClickListener对象,并且自定义ViewHolder实现OnClickListener接口,通过为itemView对象设置OnClickListener监听事件,在onClick方法中将点击事件的处理交由OnRecycleViewItemClickListener对象处理,从而达到为RecycleView中的itemView注册点击事件。
如果不采用这种方式添加事件监听,就不需要自定义监听接口和自定义ViewHolder实现事件监听接口以及事件处理。另外可以通过获得的itemView之后,通过组件I的获取组件来添加事件监听。还可以通过获得的自定义ViewHolder,来访问每个组件。
4.4 Activity的具体实现
|
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
package
secondriver.sdk.activity;
import
android.app.Activity;
import
android.net.Uri;
import
android.os.Bundle;
import
android.support.v7.widget.DefaultItemAnimator;
import
android.support.v7.widget.RecyclerView;
import
android.support.v7.widget.StaggeredGridLayoutManager;
import
android.util.Log;
import
android.view.LayoutInflater;
import
android.view.View;
import
android.view.ViewGroup;
import
android.widget.TextView;
import
android.widget.Toast;
import
com.facebook.drawee.view.SimpleDraweeView;
import
java.util.ArrayList;
import
java.util.Arrays;
import
java.util.Random;
import
butterknife.Bind;
import
butterknife.ButterKnife;
import
secondriver.sdk.R;
/**
* Author : secondriver
* Created : 2015/11/18
*/
public
class
RecycleViewActivity
extends
Activity {
private
static
final
String TAG = RecyclerView.
class
.getName();
private
static
final
String[] RES_URL =
new
String[]{
"http://p1.wmpic.me/article/2015/11/16/1447644849_hySANEEF.jpg"
,
//减少篇幅,此处省去14个图片Url
};
@Bind
(R.id.recycler_view)
public
RecyclerView mRecycleView;
private
final
int
PRE_SCREEN_NUMBER =
6
;
private
final
int
SPAN_COUNT =
2
;
private
int
previousLastIndex =
0
;
private
boolean
isSlidingToLast =
false
;
private
RecycleViewAdapter mAdapter;
private
ArrayList<Pair> mItem =
new
ArrayList<>();
//交错式网格布局管理对象,即通常称的瀑布流布局
private
StaggeredGridLayoutManager mLayoutManager;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_recyclerview);
ButterKnife.bind(
this
);
Fresco.initialize(
this
);
//重要,Fresco做一系列初始化工作
initRecycleView();
}
private
void
initRecycleView() {
mLayoutManager =
new
StaggeredGridLayoutManager(SPAN_COUNT, StaggeredGridLayoutManager.VERTICAL);
mRecycleView.setLayoutManager(mLayoutManager);
mAdapter =
new
RecycleViewAdapter(mItem);
loadData(
false
);
mRecycleView.setAdapter(mAdapter);
mRecycleView.setItemAnimator(
new
DefaultItemAnimator());
//RecycleView的View项单击事件监听
mAdapter.setOnRecycleViewItemClickListener(
new
RecycleViewAdapter.OnRecycleViewItemClickListener() {
@Override
public
void
onRecycleViewItemClick(View view,
int
position) {
long
id = mRecycleView.getChildItemId(view);
Log.d(TAG,
"View项的根视图:"
+ view.getClass().getName() +
",position="
+ position +
" ViewHolder_Id="
+ id);
//通过findViewById查找View项中的元素
SimpleDraweeView draweeView = (SimpleDraweeView) view.findViewById(R.id.info_image);
if
(
null
!= draweeView) {
draweeView.setImageURI(Uri.parse(RES_URL[
0
]));
Toast.makeText(RecycleViewActivity.
this
,
"通过findViewById查找View项中的元素"
, Toast.LENGTH_LONG).show();
}
RecycleViewItemHolder recycleViewItemHolder = (RecycleViewItemHolder) mRecycleView.findViewHolderForPosition(position);
if
(
null
!= recycleViewItemHolder) {
recycleViewItemHolder.infoTextView.setText(
"通过ViewHolder找到View项中的元素"
);
}
}
});
//下拉刷新,追加内容
mRecycleView.setOnScrollListener(
new
RecyclerView.OnScrollListener() {
@Override
public
void
onScrollStateChanged(RecyclerView recyclerView,
int
newState) {
super
.onScrollStateChanged(recyclerView, newState);
if
(newState == RecyclerView.SCROLL_STATE_IDLE) {
if
(isPullToBottom() && isSlidingToLast) {
if
(mItem.size() >
36
) {
//最大数据量
Toast.makeText(RecycleViewActivity.
this
,
"没有数据了"
, Toast.LENGTH_LONG).show();
return
;
}
else
{
loadData(
false
);
Log.d(TAG,
"notifyItemRangeInserted startPosition="
+ previousLastIndex);
mAdapter.notifyItemRangeInserted(previousLastIndex, PRE_SCREEN_NUMBER);
}
}
}
if
(newState == RecyclerView.SCROLL_STATE_SETTLING) {
Log.d(TAG,
"settling"
);
}
}
@Override
public
void
onScrolled(RecyclerView recyclerView,
int
dx,
int
dy) {
super
.onScrolled(recyclerView, dx, dy);
isSlidingToLast = dy >
0
;
//上拉,下滑
Log.d(TAG,
"dx = "
+ dx +
" dy="
+ dy +
" isSlidingToLast="
+ isSlidingToLast);
}
}
);
}
private
boolean
isPullToBottom() {
int
[] lastIndexs = mLayoutManager.findLastCompletelyVisibleItemPositions(
null
);
Log.d(TAG,
"last item ="
+ Arrays.toString(lastIndexs) +
", have item="
+ mAdapter.getItemCount());
int
maxIndex = mAdapter.getItemCount() -
1
;
for
(
int
i : lastIndexs) {
if
(i == maxIndex) {
return
true
;
}
}
return
false
;
}
private
void
loadData(
boolean
isClear) {
if
(isClear) {
mItem.clear();
}
previousLastIndex = mItem.size();
Random r =
new
Random();
for
(
int
index =
0
; index < PRE_SCREEN_NUMBER && index < RES_URL.length; index++) {
mItem.add(
new
Pair(
"Card "
+ (previousLastIndex + index), RES_URL[r.nextInt(RES_URL.length)]));
}
Log.d(TAG,
"mItem count ="
+ mItem.size());
}
}
|
说明:
RecycleView的ItemView的数据由Pair对象管理存储
Fresco库默认配置下的使用之前需要初始化调用Fresco.initialize(Context)
上述实现了2个事件监听,一个OnScrollerListener由RecycleView提供,实现下拉刷新;一个OnRecycleViewItemClickListener由自定义的RecycleViewAdpater提供,实现单击itemView来更换图片(SimpleDraweeView)和文字(TextView)。
Butterknife和Fresco库的具体使用参见其Github上的文档。
5. 效果图
比较遗憾ScreenRecord不支持模拟器和Android4.4一下的系统,只能附上截图。



