仿QQ空间和微信朋友圈,高解耦高复用高灵活

<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>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,142评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,952评论 4 60
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,514评论 18 399
  • 爱上文字的孩子,都是不快乐的。 第一次看到这话,心就被猛的击中,似是含了一口极贵的苦咖啡,咽不下,亦不愿吐。 细细...
    凉木汐阅读 1,215评论 2 12
  • 今天我有感觉,同事说那句话确实是生气了。 “哎呀,是周大师的速度快,一个顶两个嘛。” 话很酸,听着心里很不是滋味儿...
    风十二郎阅读 588评论 0 1