<p>先看看效果:</p>
<p></p>
<p></p>
<p> </p>
<p>用极少的代码实现了 <strong>动态详情</strong> 及 <strong>二级评论</strong> 的 数据获取与处理 和 UI显示与交互,并且<strong>高解耦、高复用、高灵活</strong>。</p>
<p> </p>
<p>动态列表界面MomentListFragment支持 <strong>下拉刷新与上拉加载</strong> 和 <strong>模糊搜索</strong>,反复快速滑动仍然非常流畅。</p>
<p><strong>缓存机制</strong>使得数据可在启动界面后瞬间加载完成。</p>
<p></p>
<p> </p>
<p>动态详情界面MomentActivity支持 <strong>(取消)点赞</strong>、<strong>(删除)评论</strong>、<strong>点击姓名跳到个人详情</strong> 等。</p>
<p>只有1张图片时图片放大显示,超过1张则按<strong>九宫格</strong>显示。</p>
<p></p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p>用到的CommentContainerView和MomentView都是独立的组件,既可单独使用,也可用于ListView或添加至其它ViewGroup等。</p>
<p> </p>
<p><strong>CommentContainerView复用</strong></p>
<p></p>
<p> </p>
<p>CommentContainerView.java </p>
setOnCommentClickListener : 设置点击评论监听
createView : 创建View
bindView : 绑定数据并显示View
setMaxShowCount : 设置最多显示数量,超过则折叠
setComment : 设置评论
addCommentView : 添加评论View
1 package apijson.demo.client.view;
2
3 import android.annotation.SuppressLint;
4 import android.app.Activity;
5 import android.content.res.Resources;
6 import android.view.LayoutInflater;
7 import android.view.View;
8 import android.view.View.OnClickListener;
9 import android.view.View.OnLongClickListener;
10 import android.view.ViewGroup;
11
12 import java.util.ArrayList;
13 import java.util.List;
14
15 import apijson.demo.client.R;
16 import apijson.demo.client.model.CommentItem;
17 import apijson.demo.client.view.CommentView.OnCommentClickListener;
18 import zuo.biao.library.base.BaseView;
19 import zuo.biao.library.util.Log;
20 import zuo.biao.library.util.StringUtil;
21
22 /**评论容器
23 * @author Lemon
24 * @use
25 CommentContainerView commentContainerView = new CommentContainerView(context, inflater);
26 adapter中使用convertView = commentContainerView.getView();//[具体见.DemoAdapter] 或 其它类中使用
27 containerView.addView(commentContainerView.getConvertView());
28 commentContainerView.bindView(data);
29 commentContainerView.setOnClickPictureListener(onClickPictureListener);//非必需
30 commentContainerView.setOnDataChangedListener(onDataChangedListener);data = commentContainerView.getData();//非必需
31 commentContainerView.setOnClickListener(onClickListener);//非必需
32 ...
33 */
34 public class CommentContainerView extends BaseView<List<CommentItem>> {
35 private static final String TAG = "CommentContainerView";
36
37 private OnCommentClickListener onCommentClickListener;
38 /**设置点击评论监听
39 * @param onCommentClickListener
40 */
41 public void setOnCommentClickListener(OnCommentClickListener onCommentClickListener) {
42 this.onCommentClickListener = onCommentClickListener;
43 }
44
45
46 public CommentContainerView(Activity context, Resources resources) {
47 super(context, resources);
48 }
49
50
51
52 private LayoutInflater inflater;
53
54 public ViewGroup llCommentContainerViewContainer;
55 public View tvCommentContainerViewMore;
56
57 @SuppressLint("InflateParams")
58 @Override
59 public View createView(LayoutInflater inflater) {
60 this.inflater = inflater;
61 convertView = inflater.inflate(R.layout.comment_container_view, null);
62
63 llCommentContainerViewContainer = findViewById(R.id.llCommentContainerViewContainer);
64
65 tvCommentContainerViewMore = findViewById(R.id.tvCommentContainerViewMore);
66
67 return convertView;
68 }
69
70
71 @Override
72 public void bindView(List<CommentItem> list){
73 llCommentContainerViewContainer.setVisibility(list == null || list.isEmpty() ? View.GONE : View.VISIBLE);
74 if (list == null) {
75 Log.w(TAG, "bindView data_ == null >> data_ = new List<CommentItem>();");
76 list = new ArrayList<CommentItem>();
77 }
78 this.data = list;
79
80 // 评论
81 setComment(list);
82 }
83
84
85 private int maxShowCount = 3;
86 /**设置最多显示数量,超过则折叠
87 * @param maxShowCount <= 0 ? 显示全部 : 超过则折叠
88 */
89 public void setMaxShowCount(int maxShowCount) {
90 this.maxShowCount = maxShowCount;
91 }
92
93
94 /**设置评论
95 * @param list
96 */
97 public void setComment(List<CommentItem> list) {
98 int count = list == null ? 0 : list.size();
99 boolean showMore = maxShowCount > 0 && count > maxShowCount;
100
101 tvCommentContainerViewMore.setVisibility(showMore ? View.VISIBLE : View.GONE);
102
103 llCommentContainerViewContainer.removeAllViews();
104 llCommentContainerViewContainer.setVisibility(count <= 0 ? View.GONE : View.VISIBLE);
105
106 if (count > 0) {
107 if (showMore) {
108 list = list.subList(0, maxShowCount);
109 }
110 for (int i = 0; i < list.size(); i++) {
111 addCommentView(i, list.get(i));
112 }
113 }
114
115 }
116
117
118 /**添加评论
119 * @param index
120 * @param comment
121 */
122 @SuppressLint("InflateParams")
123 private void addCommentView(final int index, final CommentItem comment) {
124 if (comment == null) {
125 Log.e(TAG, "addCommentView comment == null >> return; ");
126 return;
127 }
128 String content = StringUtil.getTrimedString(comment.getComment().getContent());
129 if (StringUtil.isNotEmpty(content, true) == false) {
130 Log.e(TAG, "addCommentView StringUtil.isNotEmpty(content, true) == false >> return; ");
131 return;
132 }
133
134 CommentTextView commentView = (CommentTextView) inflater.inflate(R.layout.comment_item, null);
135 commentView.setView(comment);
136
137 if (onCommentClickListener != null) {
138 commentView.setOnClickListener(new OnClickListener() {
139
140 @Override
141 public void onClick(View v) {
142 onCommentClickListener.onCommentClick(comment, position, index, false);
143 }
144 });
145 commentView.setOnLongClickListener(new OnLongClickListener() {
146
147 @Override
148 public boolean onLongClick(View v) {
149 onCommentClickListener.onCommentClick(comment, position, index, true);
150 return true;
151 }
152 });
153 }
154
155 llCommentContainerViewContainer.addView(commentView);
156 }
157
158 }
<p> </p>
<p> </p>
<p>comment_container_view.xml</p>
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 style="@style/ll_vertical_match_wrap" >
4
5 <LinearLayout
6 android:id="@+id/llCommentContainerViewContainer"
7 style="@style/ll_vertical_match_wrap" >
8 </LinearLayout>
9
10 <TextView
11 android:id="@+id/tvCommentContainerViewMore"
12 style="@style/text_small_blue"
13 android:layout_width="match_parent"
14 android:background="@drawable/bg_item_to_alpha"
15 android:gravity="left|center_vertical"
16 android:paddingBottom="4dp"
17 android:paddingTop="4dp"
18 android:text="查看全部" />
19
20 </LinearLayout>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p><strong> MomentView复用</strong></p>
<p></p>
<p> </p>
<p>MomentView.java</p>
setOnPictureClickListener : 设置点击图片监听
createView : 创建View
bindView : 绑定数据并显示View
setPraise : 设置点赞
setShowComment : 设置是否显示评论
getShowComment : 获取是否显示评论的设置
setComment : 设置评论
setPicture : 设置九宫格图片
toComment : 跳转到所有评论界面
getData : 获取动态绑定的数据
isLoggedIn : 判断是否已登录,未登录则跳到登录界面
praise : (取消)点赞
onDialogButtonClick : 处理对话框返回结果,比如删除动态
onHttpResponse : 处理Http请求的返回结果,比如点赞
onClick : 处理点击事件,比如点击内容跳到动态详情界面
onItemClick : 处理点击图片的事件,默认是查看大图,可setOnPictureClickListener接管处理
1 package apijson.demo.client.view;
2
3 import android.annotation.SuppressLint;
4 import android.app.Activity;
5 import android.content.res.Resources;
6 import android.view.LayoutInflater;
7 import android.view.View;
8 import android.view.View.OnClickListener;
9 import android.view.ViewGroup;
10 import android.widget.AdapterView;
11 import android.widget.AdapterView.OnItemClickListener;
12 import android.widget.GridView;
13 import android.widget.ImageView;
14 import android.widget.LinearLayout.LayoutParams;
15 import android.widget.TextView;
16
17 import java.util.ArrayList;
18 import java.util.List;
19
20 import apijson.demo.client.R;
21 import apijson.demo.client.activity_fragment.LoginActivity;
22 import apijson.demo.client.activity_fragment.MomentActivity;
23 import apijson.demo.client.activity_fragment.UserActivity;
24 import apijson.demo.client.activity_fragment.UserListActivity;
25 import apijson.demo.client.application.APIJSONApplication;
26 import apijson.demo.client.model.CommentItem;
27 import apijson.demo.client.model.Moment;
28 import apijson.demo.client.model.MomentItem;
29 import apijson.demo.client.model.User;
30 import apijson.demo.client.util.HttpRequest;
31 import apijson.demo.client.view.CommentView.OnCommentClickListener;
32 import zuo.biao.apijson.JSONResponse;
33 import zuo.biao.library.base.BaseView;
34 import zuo.biao.library.manager.CacheManager;
35 import zuo.biao.library.manager.HttpManager.OnHttpResponseListener;
36 import zuo.biao.library.model.Entry;
37 import zuo.biao.library.ui.AlertDialog;
38 import zuo.biao.library.ui.AlertDialog.OnDialogButtonClickListener;
39 import zuo.biao.library.ui.GridAdapter;
40 import zuo.biao.library.ui.WebViewActivity;
41 import zuo.biao.library.util.ImageLoaderUtil;
42 import zuo.biao.library.util.Log;
43 import zuo.biao.library.util.ScreenUtil;
44 import zuo.biao.library.util.StringUtil;
45 import zuo.biao.library.util.TimeUtil;
46
47 /**动态
48 * @author Lemon
49 * @use
50 MomentView momentView = new MomentView(context, inflater);
51 adapter中使用convertView = momentView.getView();//[具体见.DemoAdapter] 或 其它类中使用
52 containerView.addView(momentView.getConvertView());
53 momentView.bindView(data);
54 momentView.setOnPictureClickListener(onPictureClickListener);//非必需
55 momentView.setOnDataChangedListener(onDataChangedListener);data = momentView.getData();//非必需
56 momentView.setOnClickListener(onClickListener);//非必需
57 ...
58 */
59 public class MomentView extends BaseView<MomentItem> implements OnClickListener
60 , OnHttpResponseListener, OnDialogButtonClickListener, OnItemClickListener {
61 private static final String TAG = "MomentView";
62
63 public interface OnPictureClickListener {
64 void onClickPicture(int momentPosition, MomentView momentView, int pictureIndex);
65 }
66
67 private OnPictureClickListener onPictureClickListener;
68 /**设置点击图片监听
69 * @param onPictureClickListener
70 */
71 public void setOnPictureClickListener(OnPictureClickListener onPictureClickListener) {
72 this.onPictureClickListener = onPictureClickListener;
73 }
74
75 public MomentView(Activity context, Resources resources) {
76 super(context, resources);
77 }
78
79
80 //UI显示区(操作UI,但不存在数据获取或处理代码,也不存在事件监听代码)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
81
82 private LayoutInflater inflater;
83
84
85 public View llMomentViewContainer;
86
87 public ImageView ivMomentViewHead;
88
89 public TextView tvMomentViewName;
90 public TextView tvMomentViewStatus;
91
92 public TextView tvMomentViewContent;
93
94 public GridView gvMomentView;
95
96 public TextView tvMomentViewDate;
97 public ImageView ivMomentViewPraise;
98 public ImageView ivMomentViewComment;
99
100 public ViewGroup llMomentViewPraise;
101 public PraiseTextView tvMomentViewPraise;
102
103 public View vMomentViewDivider;
104
105 public ViewGroup llMomentViewCommentContainer;
106 @SuppressLint("InflateParams")
107 @Override
108 public View createView(LayoutInflater inflater) {
109 this.inflater = inflater;
110 convertView = inflater.inflate(R.layout.moment_view, null);
111
112 llMomentViewContainer = findViewById(R.id.llMomentViewContainer);
113
114 ivMomentViewHead = findViewById(R.id.ivMomentViewHead, this);
115
116 tvMomentViewName = findViewById(R.id.tvMomentViewName, this);
117 tvMomentViewStatus = findViewById(R.id.tvMomentViewStatus, this);
118
119 tvMomentViewContent = findViewById(R.id.tvMomentViewContent, this);
120
121 gvMomentView = findViewById(R.id.gvMomentView);
122
123 tvMomentViewDate = findViewById(R.id.tvMomentViewDate);
124 ivMomentViewPraise = findViewById(R.id.ivMomentViewPraise, this);
125 ivMomentViewComment = findViewById(R.id.ivMomentViewComment, this);
126
127 llMomentViewPraise = findViewById(R.id.llMomentViewPraise, this);
128 tvMomentViewPraise = findViewById(R.id.tvMomentViewPraise, this);
129
130 vMomentViewDivider = findViewById(R.id.vMomentViewDivider);
131
132 llMomentViewCommentContainer = findViewById(R.id.llMomentViewCommentContainer);
133
134 return convertView;
135 }
136
137
138 private User user;
139 private Moment moment;
140 private long momentId;
141 private long userId;
142
143 private boolean isCurrentUser;
144 private int status;
145 public int getStatus() {
146 return status;
147 }
148 @Override
149 public void bindView(MomentItem data_){
150 this.data = data_;
151 llMomentViewContainer.setVisibility(data == null ? View.GONE : View.VISIBLE);
152 if (data == null) {
153 Log.w(TAG, "bindView data == null >> return;");
154 return;
155 }
156 this.user = data.getUser();
157 this.moment = data.getMoment();
158 this.momentId = moment.getId();
159 this.userId = moment.getUserId();
160 this.isCurrentUser = APIJSONApplication.getInstance().isCurrentUser(moment.getUserId());
161 this.status = data.getMyStatus();
162
163 ImageLoaderUtil.loadImage(ivMomentViewHead, user.getHead());
164
165 tvMomentViewName.setText(StringUtil.getTrimedString(user.getName()));
166 tvMomentViewStatus.setText(StringUtil.getTrimedString(data.getStatusString()));
167 tvMomentViewStatus.setVisibility(isCurrentUser ? View.VISIBLE : View.GONE);
168
169 tvMomentViewContent.setVisibility(StringUtil.isNotEmpty(moment.getContent(), true) ? View.VISIBLE : View.GONE);
170 tvMomentViewContent.setText(StringUtil.getTrimedString(moment.getContent()));
171
172 tvMomentViewDate.setText(TimeUtil.getSmartDate(moment.getDate()));
173
174 // 图片
175 setPicture(moment.getPictureList());
176 // 点赞
177 setPraise(data.getIsPraised(), data.getUserList());
178 // 评论
179 setComment(data.getCommentItemList());
180
181 vMomentViewDivider.setVisibility(llMomentViewPraise.getVisibility() == View.VISIBLE
182 && llMomentViewCommentContainer.getVisibility() == View.VISIBLE ? View.VISIBLE : View.GONE);
183
184 }
185
186
187 /**设置点赞
188 * @param joined
189 * @param list
190 */
191 private void setPraise(boolean joined, List<User> list) {
192 ivMomentViewPraise.setImageResource(joined ? R.drawable.praised : R.drawable.praise);
193 llMomentViewPraise.setVisibility(list == null || list.isEmpty() ? View.GONE : View.VISIBLE);
194 if (llMomentViewPraise.getVisibility() == View.VISIBLE) {
195 tvMomentViewPraise.setView(list);
196 }
197 }
198
199 private boolean showComment = true;
200 public void setShowComment(boolean showComment) {
201 this.showComment = showComment;
202 }
203 public boolean getShowComment() {
204 return showComment;
205 }
206
207
208 public CommentContainerView commentContainerView;
209 /**设置评论
210 * @param list
211 */
212 public void setComment(List<CommentItem> list) {
213 llMomentViewCommentContainer.setVisibility(showComment == false || list == null || list.isEmpty()
214 ? View.GONE : View.VISIBLE);
215
216 if (llMomentViewCommentContainer.getVisibility() != View.VISIBLE) {
217 Log.i(TAG, "setComment llMomentViewCommentContainer.getVisibility() != View.VISIBLE >> return;");
218 return;
219 }
220
221 if (commentContainerView == null) {
222 commentContainerView = new CommentContainerView(context, resources);
223 llMomentViewCommentContainer.removeAllViews();
224 llMomentViewCommentContainer.addView(commentContainerView.createView(inflater));
225
226 commentContainerView.setOnCommentClickListener(new OnCommentClickListener() {
227
228 @Override
229 public void onCommentClick(CommentItem item, int position, int index, boolean isLong) {
230 toComment(item, true);
231 }
232 });
233 commentContainerView.tvCommentContainerViewMore.setOnClickListener(this);
234
235 commentContainerView.setMaxShowCount(5);
236 }
237
238 commentContainerView.bindView(list);
239 }
240
241 private GridAdapter adapter;
242 /**设置图片
243 * @param pictureList
244 */
245 private void setPicture(List<String> pictureList) {
246 List<Entry<String, String>> keyValueList = new ArrayList<Entry<String, String>>();
247 if (pictureList != null) {
248 for (String picture : pictureList) {
249 keyValueList.add(new Entry<String, String>(picture, null));
250 }
251 }
252 int pictureNum = keyValueList.size();
253 gvMomentView.setVisibility(pictureNum <= 0 ? View.GONE : View.VISIBLE);
254 if (pictureNum <= 0) {
255 Log.i(TAG, "setList pictureNum <= 0 >> lvModel.setAdapter(null); return;");
256 adapter = null;
257 gvMomentView.setAdapter(null);
258 return;
259 }
260
261 gvMomentView.setNumColumns(pictureNum <= 1 ? 1 : 3);
262 if (adapter == null) {
263 adapter = new GridAdapter(context).setHasName(false);
264 gvMomentView.setAdapter(adapter);
265 }
266 adapter.refresh(keyValueList);
267 gvMomentView.setOnItemClickListener(this);
268
269 final int gridViewHeight = (int) (ScreenUtil.getScreenSize(context)[0]
270 - convertView.getPaddingLeft() - convertView.getPaddingRight()
271 - getDimension(R.dimen.moment_view_head_width));
272 try {
273 if (pictureNum >= 7) {
274 gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, gridViewHeight));
275 } else if (pictureNum >= 4) {
276 gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, (gridViewHeight*2)/3));
277 } else if (pictureNum >= 2) {
278 gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, gridViewHeight / 3));
279 } else {
280 gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
281 }
282 } catch (Exception e) {
283 Log.e(TAG, " setPictureGrid try int gridViewHeight;...>> catch" + e.getMessage());
284 }
285 }
286
287
288
289 /**跳转到所有评论界面
290 * @param isToComment
291 */
292 private void toComment(boolean isToComment) {
293 toComment(null, isToComment);
294 }
295 /**跳转到所有评论界面
296 * @param commentItem
297 * @param isToComment comment有效时为true
298 */
299 private void toComment(CommentItem commentItem, boolean isToComment) {
300 if (commentItem == null) {
301 commentItem = new CommentItem();
302 }
303 toActivity(MomentActivity.createIntent(context, momentId, isToComment
304 , commentItem.getId(), commentItem.getUser().getId(), commentItem.getUser().getName()));
305 }
306
307 //UI显示区(操作UI,但不存在数据获取或处理代码,也不存在事件监听代码)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
308
309
310
311
312
313
314
315
316
317
318 //Data数据区(存在数据获取或处理代码,但不存在事件监听代码)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
319
320
321 @Override
322 public MomentItem getData() {//bindView(null)不会使data == null
323 return llMomentViewContainer.getVisibility() == View.VISIBLE ? data : null;
324 }
325
326
327 /**判断是否已登录,如果未登录则弹出登录界面
328 * @return
329 */
330 private boolean isLoggedIn() {
331 boolean isLoggedIn = APIJSONApplication.getInstance().isLoggedIn();
332 if (isLoggedIn == false) {
333 context.startActivity(LoginActivity.createIntent(context));
334 context.overridePendingTransition(R.anim.bottom_push_in, R.anim.hold);
335 }
336 return isLoggedIn;
337 }
338
339
340 /**点赞
341 * @param toPraise
342 */
343 public void praise(boolean toPraise) {
344 if (data == null || toPraise == data.getIsPraised()) {
345 Log.e(TAG, "praiseWork toPraise == moment.getIsPraise() >> return;");
346 return;
347 }
348 // setPraise(toPraise, data.getPraiseCount() + (toPraise ? 1 : -1));
349 HttpRequest.praiseMoment(momentId, toPraise, toPraise ? HTTP_PRAISE : HTTP_CANCEL_PRAISE, this);
350 }
351
352 //Data数据区(存在数据获取或处理代码,但不存在事件监听代码)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
353
354
355
356
357
358
359
360
361 //Event事件监听区(只要存在事件监听代码就是)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
362
363
364 @Override
365 public void onDialogButtonClick(int requestCode, boolean isPositive) {
366 if (isPositive && data != null) {
367 data.setMyStatus(MomentItem.STATUS_DELETING);
368 bindView(data);
369 HttpRequest.deleteMoment(moment.getId(), HTTP_DELETE, this);
370 }
371 }
372
373
374
375 public static final int HTTP_PRAISE = 1;
376 public static final int HTTP_CANCEL_PRAISE = 2;
377 public static final int HTTP_DELETE = 3;
378 @Override
379 public void onHttpResponse(int requestCode, String result, Exception e) {
380 if (data == null) {
381 Log.e(TAG, "onHttpResponse data == null >> return;");
382 return;
383 }
384 JSONResponse response = new JSONResponse(result);
385 JSONResponse response2 = response.getJSONResponse(Moment.class.getSimpleName());
386 boolean isSucceed = JSONResponse.isSucceed(response2);
387 switch (requestCode) {
388 case HTTP_PRAISE:
389 case HTTP_CANCEL_PRAISE:
390 if (isSucceed) {
391 data.setIsPraised(requestCode == HTTP_PRAISE);
392 bindView(data);
393 } else {
394 showShortToast((requestCode == HTTP_PRAISE ? "点赞" : "取消点赞") + "失败,请检查网络后重试");
395 }
396 break;
397 case HTTP_DELETE:
398 showShortToast(isSucceed ? R.string.delete_succeed : R.string.delete_failed);
399 //只对adapter.getCount()有影响。目前是隐藏的,不需要通知,也不需要刷新adapter,用户手动刷新后自然就更新了。
400 if (isSucceed) {
401 bindView(null);
402 status = MomentItem.STATUS_DELETED;
403 if (onDataChangedListener != null) {
404 onDataChangedListener.onDataChanged();
405 }
406 CacheManager.getInstance().remove(MomentItem.class, "" + momentId);
407 } else {
408 data.setMyStatus(MomentItem.STATUS_NORMAL);
409 bindView(data);
410 }
411 break;
412 }
413 }
414
415
416 @Override
417 public void onClick(View v) {
418 if (data == null) {
419 return;
420 }
421 if (status == MomentItem.STATUS_PUBLISHING) {
422 showShortToast(R.string.publishing);
423 return;
424 }
425 switch (v.getId()) {
426 case R.id.ivMomentViewHead:
427 case R.id.tvMomentViewName:
428 toActivity(UserActivity.createIntent(context, userId));
429 break;
430 case R.id.tvMomentViewStatus:
431 if (status == MomentItem.STATUS_NORMAL) {
432 new AlertDialog(context, "", "删除动态", true, 0, this).show();
433 }
434 break;
435 case R.id.tvMomentViewContent:
436 case R.id.tvCommentContainerViewMore:
437 toComment(false);
438 break;
439 case R.id.tvMomentViewPraise:
440 case R.id.llMomentViewPraise:
441 toActivity(UserListActivity.createIntent(context, data.getPraiseUserIdList())
442 .putExtra(UserListActivity.INTENT_TITLE, "点赞的人"));
443 break;
444 default:
445 if (isLoggedIn() == false) {
446 return;
447 }
448 switch (v.getId()) {
449 case R.id.ivMomentViewPraise:
450 praise(! data.getIsPraised());
451 break;
452 case R.id.ivMomentViewComment:
453 toComment(true);
454 break;
455 default:
456 break;
457 }
458 break;
459 }
460 }
461
462 @Override
463 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
464 if (status == MomentItem.STATUS_PUBLISHING) {
465 showShortToast(R.string.publishing);
466 return;
467 }
468 if (onPictureClickListener != null) {
469 onPictureClickListener.onClickPicture(this.position, this, position);
470 } else {
471 toActivity(WebViewActivity.createIntent(context, null
472 , adapter == null ? null : adapter.getItem(position).getKey()));
473 }
474 }
475
476 //Event事件监听区(只要存在事件监听代码就是)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
477
478 }
<p> </p>
<p> </p>
<p> </p>
<p>moment_view.xml</p>
1 <?xml version="1.0" encoding="utf-8"?>
2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 style="@style/match_wrap"
4 android:descendantFocusability="blocksDescendants" >
5
6 <LinearLayout
7 android:id="@+id/llMomentViewContainer"
8 style="@style/ll_horizontal_match_wrap"
9 android:background="@color/white"
10 android:gravity="top"
11 android:padding="10dp" >
12
13 <RelativeLayout
14 android:id="@+id/rlMomentViewItemHead"
15 android:layout_width="@dimen/moment_view_head_width"
16 android:layout_height="@dimen/moment_view_head_height"
17 android:paddingRight="@dimen/moment_view_head_padding_right" >
18
19 <ImageView
20 android:background="@color/alpha_3"
21 android:id="@+id/ivMomentViewHead"
22 android:layout_width="match_parent"
23 android:layout_height="match_parent"
24 android:scaleType="centerCrop" />
25 </RelativeLayout>
26
27 <LinearLayout
28 style="@style/ll_vertical_match_wrap"
29 android:layout_below="@+id/rlMomentViewItemHead"
30 android:layout_toRightOf="@+id/rlMomentViewItemHead"
31 android:gravity="left" >
32
33 <LinearLayout
34 style="@style/ll_horizontal_match_wrap"
35 android:layout_height="match_parent" >
36
37 <TextView
38 android:id="@+id/tvMomentViewName"
39 style="@style/text_small_blue"
40 android:layout_width="match_parent"
41 android:layout_weight="1"
42 android:background="@drawable/bg_item_to_alpha"
43 android:gravity="left"
44 android:text="Name" />
45
46 <TextView
47 android:id="@+id/tvMomentViewStatus"
48 style="@style/text_small_blue"
49 android:background="@drawable/bg_item_to_alpha"
50 android:text="发布中" />
51 </LinearLayout>
52
53 <TextView
54 android:id="@+id/tvMomentViewContent"
55 style="@style/text_small_black"
56 android:layout_width="match_parent"
57 android:layout_marginTop="5dp"
58 android:background="@drawable/bg_item_to_alpha"
59 android:gravity="left|top"
60 android:maxLines="8"
61 android:paddingBottom="5dp"
62 android:text="This is a content..." />
63
64 <apijson.demo.client.view.EmptyEventGridView
65 android:id="@+id/gvMomentView"
66 style="@style/wrap_wrap"
67 android:focusable="false"
68 android:horizontalSpacing="4dp"
69 android:listSelector="@drawable/bg_item_to_alpha"
70 android:numColumns="3"
71 android:paddingTop="4dp"
72 android:scrollbars="none"
73 android:stretchMode="columnWidth"
74 android:verticalSpacing="4dp" />
75
76 <LinearLayout
77 style="@style/ll_horizontal_match_wrap"
78 android:layout_height="wrap_content"
79 android:layout_marginTop="5dp" >
80
81 <TextView
82 android:id="@+id/tvMomentViewDate"
83 style="@style/text_small_black"
84 android:layout_width="match_parent"
85 android:layout_weight="1"
86 android:gravity="left"
87 android:text="2015年12月" />
88
89 <ImageView
90 android:id="@+id/ivMomentViewPraise"
91 style="@style/img_btn"
92 android:layout_marginRight="18dp"
93 android:background="@drawable/bg_item_to_alpha"
94 android:src="@drawable/praise" />
95
96 <ImageView
97 android:id="@+id/ivMomentViewComment"
98 style="@style/img_btn"
99 android:background="@drawable/bg_item_to_alpha"
100 android:src="@drawable/comment" />
101 </LinearLayout>
102
103 <LinearLayout
104 style="@style/ll_vertical_match_wrap"
105 android:layout_marginTop="5dp"
106 android:background="@color/alpha_1"
107 android:paddingLeft="8dp"
108 android:paddingRight="8dp" >
109
110 <LinearLayout
111 android:id="@+id/llMomentViewPraise"
112 style="@style/ll_horizontal_match_wrap"
113 android:layout_height="wrap_content"
114 android:layout_marginBottom="4dp"
115 android:layout_marginTop="4dp"
116 android:background="@drawable/bg_item_to_alpha"
117 android:gravity="top" >
118
119 <ImageView
120 android:layout_width="20dp"
121 android:layout_height="20dp"
122 android:scaleType="fitXY"
123 android:src="@drawable/praise" />
124
125 <apijson.demo.client.view.PraiseTextView
126 android:id="@+id/tvMomentViewPraise"
127 style="@style/text_small_blue"
128 android:background="@drawable/bg_item_to_alpha"
129 android:gravity="left|top"
130 android:lineSpacingExtra="4dp"
131 android:text="等觉得很赞" />
132 </LinearLayout>
133
134 <View
135 android:id="@+id/vMomentViewDivider"
136 style="@style/divider_horizontal_1px" />
137
138 <LinearLayout
139 android:id="@+id/llMomentViewCommentContainer"
140 style="@style/ll_vertical_match_wrap"
141 android:paddingBottom="4dp"
142 android:paddingTop="4dp" >
143 </LinearLayout>
144 </LinearLayout>
145 </LinearLayout>
146 </LinearLayout>
147
148 </RelativeLayout>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p>由于这个项目使用了<a href="https://github.com/TommyLemon/Android-ZBLibrary">ZBLibrary快速开发框架</a>,所以实现仿<strong>QQ空间</strong>和<strong>微信朋友圈</strong>的这种复杂界面只用了<strong>极少的代码</strong>,并且<strong>高解耦、高复用、高灵活。</strong></p>
<p>服务端是用<a href="https://github.com/TommyLemon/APIJSON" >APIJSON(Server)工程</a>快速搭建的,客户端App和服务端通过<a href="https://github.com/TommyLemon/APIJSON" >APIJSON-JSON传输结构协议</a>通信,非常方便灵活,省去了大量的接口和文档!</p>
<p>今年RxJava特别火,在北京市场几乎是必备技能,所以我还把这个项目做了个<a href="https://github.com/TommyLemon/APIJSON-Android-RxJava" >RxJava版本</a>,欢迎交流和指教。</p>
<p> </p>
<p><strong>实现UI的Java类:</strong></p>
MomentListFragment 395行 动态列表的获取和显示
MomentActivity 616行 动态和评论列表的获取、显示和交互(评论和删除评论等)
MomentAdapter 67行 动态列表的显示
CommentAdapter 82行 评论列表的显示
MomentView 495行 动态的显示和交互(各种跳转、点赞、删除等)
EmptyEventGridView 56行 动态里图片的显示和交互(触摸空白处传递触摸事件到内层View)
PraiseTextView 129行 动态里点赞用户的显示和交互(点击姓名跳到个人详情,点击整体跳到点赞的用户列表界面)
CommentView 153行 一级评论(头像、姓名、内容)的显示和交互(回复、删除等),添加二级评论列表
CommentContainerView 172行 二级评论列表的显示和交互(查看全部等)
CommentTextView 122行 二级评论(姓名、内容)的显示和交互(回复、删除等)
<p> </p>
<p><strong>实现UI的XML布局:</strong></p>
moment_activity 47行 动态和评论列表的显示
moment_view 148行 动态的显示
comment_view 87行 一级评论(头像、姓名、内容)的显示
comment_container_view 20行 二级评论列表的显示
comment_item 10行 二级评论(姓名、内容)的显示
<p>为什么没有实现MomentListFragment对应的XML布局?</p>
<p>因为MomentListFragment继承BaseHttpListFragment,内部用XListView作为缺省列表View,所以可以不用自己实现了。</p>
<p> </p>
<p><strong>实现数据获取、提交和处理的Java类:</strong></p>
HttpRequest +175行 数据的获取和提交(getMoment,...,deleteComment)
CommentUtil 140行 单层评论和和二级评论的处理
Comment 56行 评论数据
CommentItem 99行 评论的显示和交互数据
Moment 43行 动态数据
MomentItem 272行 动态的显示和交互数据
User 103行 用户数据
<p> </p>
<p>(注:未列出的代码文件要么和动态无关,要么APIJSON或ZBLibrary已提供。server.model里的类由服务端提供)</p>
<p> </p>
<p> </p>
<p> </p>
<p> <strong>仿QQ空间和微信朋友圈,高解耦高复用高灵活</strong></p>
<p><strong></strong></p>
<p> </p>
<p><strong>下载试用(测试服务器地址:</strong>139.196.140.118:8080<strong>)</strong>
<a href="http://files.cnblogs.com/files/tommylemon/APIJSONApp.apk" >APIJSONClientApp.apk</a></p>
<p><strong>源码及文档(客户端+服务端的源码和数据,记得给个<strong>Star哦</strong>)</strong>
<a href="https://github.com/TommyLemon/APIJSON">https://github.com/TommyLemon/APIJSON</a></p>
<p> </p>