# 仿9GAG制作过程(四)
# 有话要说
这次主要讲述主页面下拉刷新和上拉加载功能的实现。
主要是使用了SwipeRefreshLayout
的布局方式,并在此基础上通过RecyclerView
的特性增加了上拉加载的功能。
# 成果
# 实现方式
# 页面布局
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipeRefreshView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:background="#f0f0f0"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
通过SwipeRefreshLayout
来实现下拉刷新功能。
# 下拉刷新
SwipeRefreshLayout swipeRefreshLayout;
// 下拉刷新控件
swipeRefreshLayout = getView().findViewById(R.id.swipeRefreshView);
// 设置下拉控件背景色
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(Color.WHITE);
// 设置下来控件主色
swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent);
// 设置下拉刷新事件
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String url = baseUrl + (++currentPage);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
try {
Response response = client.newCall(request).execute();
String json = response.body().string();
if (json != null) {
Gson gson = new Gson();
List<NewsBean> newDatas = gson.fromJson(json, new TypeToken<List<NewsBean>>(){}.getType());
if (newsBeans != null && newsBeans.size() > 0) {
newsBeans.addAll(0, newDatas);
}
}
Message message = new Message();
message.what = UPDATE_NEWS;
handler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
});
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
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
通过setProgressBackgroundColorSchemeColor
来设置下拉控件的背景色,也就是圈圈的主体颜色。
通过setColorSchemeResources
来设置下拉控件中间的线条颜色。
通过setOnRefreshListener
来定义下拉刷新事件。
然后通过currentPage
来实现下拉刷新之后获取的数据是下一页的数据,再添加到集合开头。
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case QUERY_NEWS:
recyclerView.getAdapter().notifyDataSetChanged();
break;
case UPDATE_NEWS:
recyclerView.getAdapter().notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
break;
case LOAD_MORE:
((NewsAdapter)recyclerView.getAdapter()).changeStatus(NewsAdapter.UNLOADING);
currentState = NewsAdapter.UNLOADING;
break;
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
刷新完成之后,通过notifyDataSetChanged
告诉RecyclerView
数据改变了,进而更改页面显示。通过setRefreshing
来控制下拉刷新控件的显示。
由此,完成了下拉刷新的实现。
# 上拉加载
由于SwipeRefreshLayout
并不提供上拉加载的功能,于是准备利用RecyclerView
灵活的特性来实现上拉加载功能。
package com.example.lanxingren.imitating9gag.adapter;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.lanxingren.imitating9gag.R;
import com.example.lanxingren.imitating9gag.bean.NewsBean;
import com.example.lanxingren.imitating9gag.util.GlideApp;
import java.util.List;
public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<NewsBean> myNewsList;
private Context myContext;
// 是否加载
public static final int LOADING = 1;
public static final int UNLOADING = 2;
private int mStatus = UNLOADING;// 当前加载状态
// item的viewType
private final int ITEM = 1;
private final int FOOTER = 2;
static class NewsHolder extends RecyclerView.ViewHolder {
CardView cardView;
TextView titleView;
ImageView imageView;
TextView pointView;
ImageView likeImageView;
ImageView unlikeImageView;
private NewsHolder (View view) {
super(view);
cardView = (CardView) view;
titleView = view.findViewById(R.id.item_title);
imageView = view.findViewById(R.id.item_image);
pointView = view.findViewById(R.id.item_point);
likeImageView = view.findViewById(R.id.item_like);
unlikeImageView = view.findViewById(R.id.item_unlike);
}
}
static class FooterHolder extends RecyclerView.ViewHolder {
CardView cardView;
private FooterHolder (View view) {
super(view);
cardView = view.findViewById(R.id.cardView_footer);
}
}
public NewsAdapter (List<NewsBean> newsList) {
this.myNewsList = newsList;
}
@Override
public int getItemCount() {
int count = 0;
if (myNewsList != null) {
count = myNewsList.size() + 1;
}
return count;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (myContext == null) {
myContext = parent.getContext();
}
if (viewType == ITEM) {
View view = LayoutInflater.from(myContext).inflate(R.layout.item_news, parent, false);
return new NewsHolder(view);
} else {
View view = LayoutInflater.from(myContext).inflate(R.layout.item_footer, parent, false);
return new FooterHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof NewsHolder) {
final NewsHolder newsHolder = (NewsHolder)holder;
final NewsBean newsBean = myNewsList.get(position);
// 设置标题
String title = "9GAG#" + newsBean.getId();
if (newsBean.getTitle() != null && newsBean.getTitle().length() > 0) {
title = newsBean.getTitle();
}
newsHolder.titleView.setText(title);
// 屏幕宽度
int screenWidth = myContext.getResources()
.getDisplayMetrics()
.widthPixels;
// 屏幕高度
int screenHeight = myContext.getResources()
.getDisplayMetrics()
.heightPixels;
// 设置图片,不知道为什么override这样设置就可以让图片正正好显示,有时间研究一下?
if (newsBean.getUrls() != null && newsBean.getUrls().size() > 0) {
GlideApp.with(myContext)
.load(newsBean.getUrls().get(0))
.override(screenWidth, screenHeight)
.into(newsHolder.imageView);
}
// 设置点赞数
String point = Integer.toString(newsBean.getLike() - newsBean.getUnlike());
newsHolder.pointView.setText(point);
// 设置点赞图标显示
int accentColor = myContext.getResources().getColor(R.color.colorAccent);
int defaultColor = myContext.getResources().getColor(R.color.defaultColor);
switch (newsBean.getIsLiked()) {
case -1:
newsHolder.likeImageView.setColorFilter(defaultColor);
newsHolder.unlikeImageView.setColorFilter(accentColor);
break;
case 0:
newsHolder.likeImageView.setColorFilter(defaultColor);
newsHolder.unlikeImageView.setColorFilter(defaultColor);
break;
case 1:
newsHolder.likeImageView.setColorFilter(accentColor);
newsHolder.unlikeImageView.setColorFilter(defaultColor);
break;
}
// 初始化监听事件
newsHolder.likeImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
newsBean.setIsLiked(1);
notifyDataSetChanged();
}
});
newsHolder.unlikeImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
newsBean.setIsLiked(-1);
notifyDataSetChanged();
}
});
} else if (holder instanceof FooterHolder) {
switch (mStatus) {
case UNLOADING:
((FooterHolder) holder).cardView.setVisibility(View.GONE);
break;
case LOADING:
((FooterHolder) holder).cardView.setVisibility(View.VISIBLE);
break;
}
}
}
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return FOOTER;
} else {
return ITEM;
}
}
public void changeStatus(int status) {
this.mStatus = status;
notifyDataSetChanged();
}
}
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
在适配器中定义了两个布局,一个是普通布局,一个是尾布局(footer
)。
下面是footer
的具体布局:
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_marginTop="10dp"
android:layout_marginBottom="0dp"
app:cardCornerRadius="0dp"
android:elevation="0dp"
android:id="@+id/cardView_footer">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_gravity="center_horizontal"
/>
</android.support.v7.widget.CardView>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 注意点
- 通过
mStatus
来判断尾布局是否展示 ITEM
代表的是普通布局,即段子的布局;FOOTER
代表的是尾布局- 由于增加了一个
item
,故getItemCount
得在原先的基础上加上一 - 在
onCreateViewHolder
中通过viewType
来创建不同的viewHolder
- 实现
getItemViewType
方法,根据position
的值来确定viewType
- 在
onBindViewHolder
中先判断viewHolder
的类型,如果是尾布局(footer
)的话,再根据mStatus
来判断是否展示 changeStatus
主要是给外面用的,通过该方法可以控制footer
的显示
# 使用上拉加载
private void initLoadMoreListener() {
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(final RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
// 获取当前可见的item位置
int lastVisiblePosition = 0;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
lastVisiblePosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
}
// 当前加载状态是UNLOADING && 当前可见的item位置是最后一条时
if (currentState == NewsAdapter.UNLOADING
&& lastVisiblePosition + 1 == recyclerView.getAdapter().getItemCount()) {
// 改变footer的可见性
((NewsAdapter)recyclerView.getAdapter()).changeStatus(NewsAdapter.LOADING);
currentState = NewsAdapter.LOADING;
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String url = baseUrl + (++currentPage);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
try {
Response response = client.newCall(request).execute();
String json = response.body().string();
if (json != null) {
Gson gson = new Gson();
List<NewsBean> newDatas = gson.fromJson(json, new TypeToken<List<NewsBean>>(){}.getType());
if (newsBeans != null && newsBeans.size() > 0) {
newsBeans.addAll(newsBeans.size(), newDatas);
}
}
Message message = new Message();
message.what = LOAD_MORE;
handler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
});
}
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
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
通过给recyclerView
加上滚动事件来实现下拉加载功能,具体逻辑如下:
- 获取页面可见最下面的
item
位置 - 判断当前尾布局的加载状态
- 如果尾布局现在的状态是
UNLOADING && item
位置为最后一个,则开线程加载数据 - 将尾布局展示,即开始转圈动画
- 请求网络,获取数据,放入数据集
- 根据
handler
的处理,告诉RecyclerView
数据改变,然后将footer
隐藏
通过以上的过程就可以实现上拉加载的效果。
# 参考
- SwipeRefreshLayout详解和自定义上拉加载更多 (opens new window)
- wipeRefreshLayout + RecyclerView 实现 上拉刷新 和 下拉刷新 (opens new window)
# 结束语
本次学习了Android
的下拉刷新以及上拉加载的实现,对RecyclerView
有了进一步的了解。
接下来准备实现点赞功能以及GIF
的暂停功能。