Android仿微博之微博数据解析展示

接着上一篇,我们在 OAuthActivity 中获得到了 access_token ,我们将这个重要的值,传递给 MainActivity (关于Activity之间的传值, Fragment 与 Activity 的传值方式详见下文),在 MainActivity 中,我们要展示主界面,这里我使用了 BottomNavigationView + Fragment 。官方的这个 BottomNavigationView ,写的很简洁,也很好懂,但是还是有些 bug 不太好使。网上有很红的第三方库,却感觉没有官方这么整洁有序。接下来我们看看这个控件的使用方法。首先布局文件里添加

1
2
3
4
5
6
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:menu="@menu/navigation"/>

在创建该控件时候,会自动生成一个 menu.xml 文件,就是我们的底部菜单子项了。完善 menu 相关信息以后,在 Acitivity 中绑定控件,运行后我们可以看到,会有相应个按钮,但是会有动画,微博这里使用了 5 个,显然这个动画不太好看。多了显乱。于是这里,我们就需要去掉动画。但是理所想当然的我们会觉得官方应该给了我们一个去掉动画的属性直接设置,but ,没有。。

这里我们参考使用大神的 BottomNavigationViewHelper ,来做相关取消设置。

接下来我们可以设置默认选择第一个子项,使用代码

1
bottomNavigationView.getMenu().getItem(0).setChecked(true);

并且我们在这里要做到点击相应子项,显示相关 Fragment 。这里我写了 changeFragment 这个方法

1
2
3
4
5
6
7
8
9
10
11
private void changeFragment(Fragment fragment) {
String access_token = getIntent().getStringExtra("access_token");
String uid=getIntent().getStringExtra("uid");
Bundle args = new Bundle();
args.putString("access_token", access_token);
args.putString("uid",uid);
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(R.id.tb, fragment)
.commit();
}

我们需要获取 access_token ,以及其他 Fragment 会使用到的 uid 。当然,这两个参数我们完全可以写成全局变量。
接下来,我们首先了解一下如何创建一个 Fragment 。一个 Fragment 创建,首先要有一个 FragmentManager ,其次,创建需要一个 Transaction 。在创建完成以后,一定要commit,才能成功创建。这里我们需要一个容器,来盛放创建好的 Fragment ,所以,我们在 MainActivity 的布局文件中,使用一个 LinerLayout 作为盛放容器。我们需要切换 Fragment ,故我们使用 replace 来代替之前已经创建好的Fragment。首先,我们需要先初始化一个 Fragment ,不妨直接使用该方法,写在选择切换操作之前来初始化。接下来,我们只需使用 bottomNavigationView 的监听器监听点击事件,使用以上方法来创建相应的 Fragment 即可。然后我们会在每个 Fragment 中显示不同的内容,但是我们知道,他都要用到 access_token ,所以这时候,我们就需要把这个参数传递给各个 Fragment 。
在 Activity 之间传递参数,我们只需要用 Intent 就可以方便的进行传递:

1
2
3
4
5
6
7
8
//第一个Activity
Intent intent=new Intent();
intent.setClass(OAuthActivity.this, MainActivity.class);
intent.putExtra("access_token",access_token);
intent.putExtra("uid",uid);
startActivity(intent);
//第二个Activity中
String uid=getIntent().getStringExtra("uid");

而Activity向Fragment中传递参数:

1
2
3
4
Bundle args = new Bundle();
args.putString("access_token", access_token);
args.putString("uid",uid);
fragment.setArguments(args);

在Fragment中取参数,onCreate方法中有相应的可以说是“模板”的操作了,照猫画虎总是会的

1
2
3
4
5
6
7
8
9
10
11
12
//Fragment中
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
access_token = getArguments().getString("access_token");
}
}

在Fragment接收参数以后,我们就可以开始进行 Fragment 的处理了。首先,我们在第一个 Fragment 中显示授权用户所关注好友的微博。很显然,我们需要一个 RecycleView 来放置所有的微博信息。 RecycleView 在《第一行代码》中就讲的十分详细了,这里我使用了 RecycleView + CardView 来显示每一块微博信息。这里的数据处理操作,和之前一样,我们使用 okhttp 进行请求,将返回的数据使用 Gson 封装处理。然后通过该对象的 get 方法来获得值。这里需要注意的就是,当我们在对盛放内容的容器进行操作时(如给 TextView 赋值),一定要在线程中进行。之前我们处理线程操作,会 new 一个子线程,但是我们在这里有更方便的操作。 runOnUiThread 方法。在 Activity 中我们要进行改变布局的事情,就可以直接使用这个方法,而在 Fragment 中,我们只需先 get 盛放它的 activity ,在使用这个方法即可。 getActivity().runOnUiThread 。使用 RecycleView ,首先要有布局管理器,
LayoutManager ,什么类型的 Manager 主要取决于 recycleView 使用的布局。其次是 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
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
public class WeiboAdapter extends RecyclerView.Adapter<WeiboAdapter.ViewHolder> {
private Context context;
private List<Statuses> statuses;
private List<List<Image>> imagesList;
private static final String AT = "@[\u4e00-\u9fa5\\w]+";// @人
private static final String TOPIC = "#[\u4e00-\u9fa5\\w]+#";// ##话题
private static final String EMOJI = "\\[[\u4e00-\u9fa5\\w]+\\]";// 表情
private static final String URL = "http://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";// url
private static final String REGEX="("+AT+")|"+"("+TOPIC+")|"+"("+EMOJI+")|"+"("+URL+")";
private static String access_token;
private static String id;
static class ViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
ImageView user_icon;
TextView user_name;
TextView weibo_time;
TextView weibo_content;
Button forward;
Button comment;
Button like;
ImageButton more;
NineGridlayout pic;
View commentView;
public ViewHolder(View itemView) {
super(itemView);
commentView=itemView;
cardView = (CardView) itemView;
user_icon = (ImageView) itemView.findViewById(R.id.user_image);
user_name = (TextView) itemView.findViewById(R.id.user_name);
weibo_time = (TextView) itemView.findViewById(R.id.time_weibo);
weibo_content = (TextView) itemView.findViewById(R.id.weibo_content);
forward = (Button) itemView.findViewById(R.id.resend);
comment = (Button) itemView.findViewById(R.id.comment);
like = (Button) itemView.findViewById(R.id.like);
more = (ImageButton) itemView.findViewById(R.id.more_user);
pic = (NineGridlayout) itemView.findViewById(R.id.iv_ngrid_layout);
}
}
public WeiboAdapter(List<Statuses> mstatuses, List<List<Image>> datalist,String token) {
// 构造函数,传入所需值,并赋值给全局变量,后续的操作都将在这几个数据源上进行
access_token=token;
statuses = mstatuses;
imagesList = datalist;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//用于创建ViewHolder实例,我们在这个方法中将item的布局加载出来,然后创建一个ViewHolder实例,并把加载出来的布局传入到构造函数当中,最后将ViewHolder实例返回。
if (context == null) {
context = parent.getContext();
}
View view = LayoutInflater.from(context).inflate(R.layout.weibo_item, parent, false);
final ViewHolder holder=new ViewHolder(view);
//RecycleView并没有提供和ListVIew一样的setOnItemClickListener()方法,点击事件都要在onCreateViewHolder中注册,可对任意子项进行点击事件注册
holder.comment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position=holder.getAdapterPosition();
Statuses statuses2=statuses.get(position);
Intent intentsend=new Intent(v.getContext(), CommentActivity.class);
intentsend.putExtra("access_token",access_token);
intentsend.putExtra("id",id);
System.out.println("就是这个id啦"+id);
v.getContext().startActivity(intentsend);
Toast.makeText(v.getContext(),"点击!"+position,Toast.LENGTH_SHORT).show();
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
//用于对RecycleView子项的数据进行赋值,会在每个子项被屏幕滚动到的时候执行,通过position参数得到当前项的微博状态Statues实例。然后再将数据设置到ViewHolder的各个控件中。
Statuses statuses1 = statuses.get(position);
Glide.with(context).load(statuses1.getUser().getAvatar_hd()).into(holder.user_icon);
//处理微博内容,@,##,emoji等
SpannableString contents= SpannableString.valueOf(statuses1.getText().toString());
contents=getWeiBoContent(context,contents,holder.weibo_content);
holder.weibo_content.setText(contents);
id= String.valueOf(statuses1.getId());
User user = statuses1.getUser();
holder.user_name.setText(user.getScreen_name());
holder.weibo_time.setText(statuses1.getCreated_at());
holder.forward.setText(statuses1.getReposts_count());
holder.comment.setText(statuses1.getComments_count());
holder.like.setText(statuses1.getAttitudes_count());
List<Pic_urls> piclist = statuses1.getPic_urls();
System.out.println("Pic list:" + piclist.size());
List<Image> templist = new ArrayList<>();
for (int j = 0; j < piclist.size(); j++) {
Image image = new Image(piclist.get(j).getThumbnail_pic(), 45, 45);
templist.add(image);
}
if (templist.isEmpty()) {
holder.pic.setVisibility(View.GONE);
} else {
holder.pic.setVisibility(View.VISIBLE);
holder.pic.setAdapter(new Adapter(context, templist));
}
}
@Override
public int getItemCount() {
//告诉RecycleView一共有多少个子项,返回数据源长度
return statuses.size();
}
//九宫图适配器
class Adapter extends NineGridAdapter {
public Adapter(Context context, List<Image> list) {
super(context, list);
}
@Override
public int getCount() {
return (list == null) ? 0 : list.size();
}
@Override
public String getUrl(int position) {
return getItem(position) == null ? null : ((Image) getItem(position)).getUrl();
}
@Override
public Object getItem(int position) {
return (list == null) ? null : list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int i, View view) {
ImageView iv = null;
if (view != null && view instanceof ImageView) {
iv = (ImageView) view;
} else {
iv = new ImageView(context);
}
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
iv.setBackgroundColor(context.getResources().getColor((android.R.color.transparent)));
String url = getUrl(i);
Picasso.with(context).load(getUrl(i)).placeholder(new ColorDrawable(Color.parseColor("#f5f5f5"))).into(iv);
if (!TextUtils.isEmpty(url)) {
iv.setTag(url);
}
return iv;
}
}
/**
* 继承ClickableSpan复写updateDrawState方法,自定义所需样式
* @author Rabbit_Lee
*
*/
public static class MyClickableSpan extends ClickableSpan {
@Override
public void onClick(View widget) {
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(Color.BLUE);
ds.setUnderlineText(false);
}
}
/**
* 设置微博内容样式
* @param context
* @param source
* @param textView
* @return
*/
public static SpannableString getWeiBoContent(final Context context, SpannableString source, TextView textView) {
SpannableString spannableString = new SpannableString(source);
//设置正则
Pattern pattern = Pattern.compile(REGEX);
//设置待测字符串
Matcher matcher = pattern.matcher(spannableString);
if (matcher.find()) {
// 要实现文字的点击效果,这里需要做特殊处理
textView.setMovementMethod(LinkMovementMethod.getInstance());
// 重置正则位置
matcher.reset();
}
while (matcher.find()) {
// 根据group的括号索引,可得出具体匹配哪个正则(0代表全部,1代表第一个括号)
final String at = matcher.group(1);
final String topic = matcher.group(2);
String emoji = matcher.group(3);
final String url = matcher.group(4);
// 处理@符号
if (at != null) {
//获取匹配位置
int start = matcher.start(1);
int end = start + at.length();
MyClickableSpan clickableSpan = new MyClickableSpan() {
@Override
public void onClick(View widget) {
//这里需要做跳转用户的实现,先用一个Toast代替
Toast.makeText(context, "点击了用户:" + at, Toast.LENGTH_LONG).show();
}
};
clickableSpan.updateDrawState(textView.getPaint());
spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
// 处理话题##符号
if (topic != null) {
int start = matcher.start(2);
int end = start + topic.length();
MyClickableSpan clickableSpan = new MyClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(context, "点击了话题:" + topic, Toast.LENGTH_LONG).show();
}
};
spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (emoji != null) {
int start = matcher.start(3);
int end = start + emoji.length();
EmotionUtils emotionUtils=new EmotionUtils();
int ResId = emotionUtils.getImgByName(emoji);
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResId);
if (bitmap != null) {
// 获取字符的大小
int size = (int) textView.getTextSize();
// 压缩Bitmap
bitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);
// 设置表情
ImageSpan imageSpan = new ImageSpan(context, bitmap);
spannableString.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// 处理url地址
if (url != null) {
int start = matcher.start(4);
int end = start + url.length();
MyClickableSpan clickableSpan = new MyClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(context, "点击了网址:" + url, Toast.LENGTH_LONG).show();
}
};
spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return spannableString;
}
}

在Fragment中,我们就要去使用该 RecyleView ,在 Activity 中使用 RecycleView 时候,布局管理器等内容我们要写在 Activity 的 onCreate() 方法中,那么在 Fragment 中,我们就需要写在 onActivityCreated() 这个方法中,这里我们需要传递参数给适配器,所以我们直接在网络操作获取到参数后将它进行传递,故我们只需要在 onActivityCreated() 中调用包含网络操作的 getWeibo() 这个自定义方法即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public void onResponse(Call call, final Response response) throws IOException {
getActivity().runOnUiThread(new Runnable() {
final String responesText = response.body().string();
Gson gson = new Gson();
Weibo weibo = gson.fromJson(responesText, Weibo.class);
@Override
public void run() {
Log.w(TAG, "run: response is " + responesText);
//LayoutManager用于指定Recycle View的布局方式,这里使用的LinearLayoutManager就是线性布局的意思
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
//接下来我们创建了 WeiboAdapter的实例,并将微博数据传递到 WeiboAdapter的构造函数中,最后调用recyclerView的setAdapter()方法来完成适配器的设置
adapter = new WeiboAdapter(weibo.getStatuses(), imagesList,access_token);
recyclerView.setAdapter(adapter);
Log.w(TAG, "run: " + imagesList);
}
});
}

获取微博每部分的内容不是什么难事。难在了,我们如何把它理想的显示。

我们需要做的显示文本信息并不难,可是单纯显示出来就够了吗?显然,微博信息需要经过很多处理,比如@,##和URL的区别显示。还有九宫格图片的显示,表情的显示。每一块都需要我们单独处理,在下一篇中我们将着重介绍正则表达式处理文本信息。

在处理实体类中有一些坑详见最后一篇微博发布的末尾解释。