先看看效果:






用極少的代碼實現了 動態詳情 及 二級評論 的 數據獲取與處理 和 UI顯示與交互,并且高解耦、高復用、高靈活。
動態列表界面MomentListFragment支持 下拉刷新與上拉加載 和 模糊搜索,反復快速滑動仍然非常流暢。
緩存機制使得數據可在啟動界面后瞬間加載完成。

動態詳情界面MomentActivity支持 (取消)點贊、(刪除)評論、點擊姓名跳到個人詳情 等。
只有1張圖片時圖片放大顯示,超過1張則按九宮格顯示。

用到的CommentContainerView和MomentView都是獨立的組件,既可單獨使用,也可用于ListView或添加至其它ViewGroup等。
CommentContainerView復用


CommentContainerView.java
setOnCommentClickListener : 設置點擊評論監聽createView : 創建ViewbindView : 綁定數據并顯示ViewsetMaxShowCount : 設置最多顯示數量,超過則折疊setComment : 設置評論addCommentView : 添加評論View
package apijson.demo.client.view;
import android.annotation.SuppressLint;import android.app.Activity;import android.content.res.Resources;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.View.OnLongClickListener;import android.view.ViewGroup;import java.util.ArrayList;import java.util.List;import apijson.demo.client.R;import apijson.demo.client.model.CommentItem;import apijson.demo.client.view.CommentView.OnCommentClickListener;import zuo.biao.library.base.BaseView;import zuo.biao.library.util.Log;import zuo.biao.library.util.StringUtil;/**評論容器 * @author Lemon * @useCommentContainerView commentContainerView = new CommentContainerView(context, inflater);adapter中使用convertView = commentContainerView.getView();//[具體見.DemoAdapter] 或 其它類中使用containerView.addView(commentContainerView.getConvertView());commentContainerView.bindView(data);commentContainerView.setOnClickPictureListener(onClickPictureListener);//非必需commentContainerView.setOnDataChangedListener(onDataChangedListener);data = commentContainerView.getData();//非必需commentContainerView.setOnClickListener(onClickListener);//非必需... */public class CommentContainerView extends BaseView<List<CommentItem>> { private static final String TAG = "CommentContainerView"; private OnCommentClickListener onCommentClickListener; /**設置點擊評論監聽 * @param onCommentClickListener */ public void setOnCommentClickListener(OnCommentClickListener onCommentClickListener) { this.onCommentClickListener = onCommentClickListener; } public CommentContainerView(Activity context, Resources resources) { super(context, resources); } private LayoutInflater inflater; public ViewGroup llCommentContainerViewContainer; public View tvCommentContainerViewMore; @SuppressLint("InflateParams") @Override public View createView(LayoutInflater inflater) { this.inflater = inflater; convertView = inflater.inflate(R.layout.comment_container_view, null); llCommentContainerViewContainer = findViewById(R.id.llCommentContainerViewContainer); tvCommentContainerViewMore = findViewById(R.id.tvCommentContainerViewMore); return convertView; } @Override public void bindView(List<CommentItem> list){ llCommentContainerViewContainer.setVisibility(list == null || list.isEmpty() ? View.GONE : View.VISIBLE); if (list == null) { Log.w(TAG, "bindView data_ == null >> data_ = new List<CommentItem>();"); list = new ArrayList<CommentItem>(); } this.data = list; // 評論 setComment(list); } private int maxShowCount = 3; /**設置最多顯示數量,超過則折疊 * @param maxShowCount <= 0 ? 顯示全部 : 超過則折疊 */ public void setMaxShowCount(int maxShowCount) { this.maxShowCount = maxShowCount; } /**設置評論 * @param list */ public void setComment(List<CommentItem> list) { int count = list == null ? 0 : list.size(); boolean showMore = maxShowCount > 0 && count > maxShowCount; tvCommentContainerViewMore.setVisibility(showMore ? View.VISIBLE : View.GONE); llCommentContainerViewContainer.removeAllViews(); llCommentContainerViewContainer.setVisibility(count <= 0 ? View.GONE : View.VISIBLE); if (count > 0) { if (showMore) { list = list.subList(0, maxShowCount); } for (int i = 0; i < list.size(); i++) { addCommentView(i, list.get(i)); } } } /**添加評論 * @param index * @param comment */ @SuppressLint("InflateParams") private void addCommentView(final int index, final CommentItem comment) { if (comment == null) { Log.e(TAG, "addCommentView comment == null >> return; "); return; } String content = StringUtil.getTrimedString(comment.getComment().getContent()); if (StringUtil.isNotEmpty(content, true) == false) { Log.e(TAG, "addCommentView StringUtil.isNotEmpty(content, true) == false >> return; "); return; } CommentTextView commentView = (CommentTextView) inflater.inflate(R.layout.comment_item, null); commentView.setView(comment); if (onCommentClickListener != null) { commentView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { onCommentClickListener.onCommentClick(comment, position, index, false); } }); commentView.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { onCommentClickListener.onCommentClick(comment, position, index, true); return true; } }); } llCommentContainerViewContainer.addView(commentView); }}comment_container_view.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" style="@style/ll_vertical_match_wrap" > <LinearLayout android:id="@+id/llCommentContainerViewContainer" style="@style/ll_vertical_match_wrap" > </LinearLayout> <TextView android:id="@+id/tvCommentContainerViewMore" style="@style/text_small_blue" android:layout_width="match_parent" android:background="@drawable/bg_item_to_alpha" android:gravity="left|center_vertical" android:paddingBottom="4dp" android:paddingTop="4dp" android:text="查看全部" /></LinearLayout>
MomentView復用


MomentView.java
setOnPictureClickListener : 設置點擊圖片監聽createView : 創建ViewbindView : 綁定數據并顯示ViewsetPraise : 設置點贊setShowComment : 設置是否顯示評論getShowComment : 獲取是否顯示評論的設置setComment : 設置評論setPicture : 設置九宮格圖片toComment : 跳轉到所有評論界面getData : 獲取動態綁定的數據isLoggedIn : 判斷是否已登錄,未登錄則跳到登錄界面praise : (取消)點贊onDialogButtonClick : 處理對話框返回結果,比如刪除動態onHttpResponse : 處理Http請求的返回結果,比如點贊onClick : 處理點擊事件,比如點擊內容跳到動態詳情界面onItemClick : 處理點擊圖片的事件,默認是查看大圖,可setOnPictureClickListener接管處理
package apijson.demo.client.view;
import android.annotation.SuppressLint;import android.app.Activity;import android.content.res.Resources;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.GridView;import android.widget.ImageView;import android.widget.LinearLayout.LayoutParams;import android.widget.TextView;import java.util.ArrayList;import java.util.List;import apijson.demo.client.R;import apijson.demo.client.activity_fragment.LoginActivity;import apijson.demo.client.activity_fragment.MomentActivity;import apijson.demo.client.activity_fragment.UserActivity;import apijson.demo.client.activity_fragment.UserListActivity;import apijson.demo.client.application.APIJSONApplication;import apijson.demo.client.model.CommentItem;import apijson.demo.client.model.Moment;import apijson.demo.client.model.MomentItem;import apijson.demo.client.model.User;import apijson.demo.client.util.HttpRequest;import apijson.demo.client.view.CommentView.OnCommentClickListener;import zuo.biao.apijson.JSONResponse;import zuo.biao.library.base.BaseView;import zuo.biao.library.manager.CacheManager;import zuo.biao.library.manager.HttpManager.OnHttpResponseListener;import zuo.biao.library.model.Entry;import zuo.biao.library.ui.AlertDialog;import zuo.biao.library.ui.AlertDialog.OnDialogButtonClickListener;import zuo.biao.library.ui.GridAdapter;import zuo.biao.library.ui.WebViewActivity;import zuo.biao.library.util.ImageLoaderUtil;import zuo.biao.library.util.Log;import zuo.biao.library.util.ScreenUtil;import zuo.biao.library.util.StringUtil;import zuo.biao.library.util.TimeUtil;/**動態 * @author Lemon * @useMomentView momentView = new MomentView(context, inflater);adapter中使用convertView = momentView.getView();//[具體見.DemoAdapter] 或 其它類中使用containerView.addView(momentView.getConvertView());momentView.bindView(data);momentView.setOnPictureClickListener(onPictureClickListener);//非必需momentView.setOnDataChangedListener(onDataChangedListener);data = momentView.getData();//非必需momentView.setOnClickListener(onClickListener);//非必需... */public class MomentView extends BaseView<MomentItem> implements OnClickListener, OnHttpResponseListener, OnDialogButtonClickListener, OnItemClickListener { private static final String TAG = "MomentView"; public interface OnPictureClickListener { void onClickPicture(int momentPosition, MomentView momentView, int pictureIndex); } private OnPictureClickListener onPictureClickListener; /**設置點擊圖片監聽 * @param onPictureClickListener */ public void setOnPictureClickListener(OnPictureClickListener onPictureClickListener) { this.onPictureClickListener = onPictureClickListener; } public MomentView(Activity context, Resources resources) { super(context, resources); } //UI顯示區(操作UI,但不存在數據獲取或處理代碼,也不存在事件監聽代碼)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< private LayoutInflater inflater; public View llMomentViewContainer; public ImageView ivMomentViewHead; public TextView tvMomentViewName; public TextView tvMomentViewStatus; public TextView tvMomentViewContent; public GridView gvMomentView; public TextView tvMomentViewDate; public ImageView ivMomentViewPraise; public ImageView ivMomentViewComment; public ViewGroup llMomentViewPraise; public PraiseTextView tvMomentViewPraise; public View vMomentViewDivider; public ViewGroup llMomentViewCommentContainer; @SuppressLint("InflateParams") @Override public View createView(LayoutInflater inflater) { this.inflater = inflater; convertView = inflater.inflate(R.layout.moment_view, null); llMomentViewContainer = findViewById(R.id.llMomentViewContainer); ivMomentViewHead = findViewById(R.id.ivMomentViewHead, this); tvMomentViewName = findViewById(R.id.tvMomentViewName, this); tvMomentViewStatus = findViewById(R.id.tvMomentViewStatus, this); tvMomentViewContent = findViewById(R.id.tvMomentViewContent, this); gvMomentView = findViewById(R.id.gvMomentView); tvMomentViewDate = findViewById(R.id.tvMomentViewDate); ivMomentViewPraise = findViewById(R.id.ivMomentViewPraise, this); ivMomentViewComment = findViewById(R.id.ivMomentViewComment, this); llMomentViewPraise = findViewById(R.id.llMomentViewPraise, this); tvMomentViewPraise = findViewById(R.id.tvMomentViewPraise, this); vMomentViewDivider = findViewById(R.id.vMomentViewDivider); llMomentViewCommentContainer = findViewById(R.id.llMomentViewCommentContainer); return convertView; } private User user; private Moment moment; private long momentId; private long userId; private boolean isCurrentUser; private int status; public int getStatus() { return status; } @Override public void bindView(MomentItem data_){ this.data = data_; llMomentViewContainer.setVisibility(data == null ? View.GONE : View.VISIBLE); if (data == null) { Log.w(TAG, "bindView data == null >> return;"); return; } this.user = data.getUser(); this.moment = data.getMoment(); this.momentId = moment.getId(); this.userId = moment.getUserId(); this.isCurrentUser = APIJSONApplication.getInstance().isCurrentUser(moment.getUserId()); this.status = data.getMyStatus(); ImageLoaderUtil.loadImage(ivMomentViewHead, user.getHead()); tvMomentViewName.setText(StringUtil.getTrimedString(user.getName())); tvMomentViewStatus.setText(StringUtil.getTrimedString(data.getStatusString())); tvMomentViewStatus.setVisibility(isCurrentUser ? View.VISIBLE : View.GONE); tvMomentViewContent.setVisibility(StringUtil.isNotEmpty(moment.getContent(), true) ? View.VISIBLE : View.GONE); tvMomentViewContent.setText(StringUtil.getTrimedString(moment.getContent())); tvMomentViewDate.setText(TimeUtil.getSmartDate(moment.getDate())); // 圖片 setPicture(moment.getPictureList()); // 點贊 setPraise(data.getIsPraised(), data.getUserList()); // 評論 setComment(data.getCommentItemList()); vMomentViewDivider.setVisibility(llMomentViewPraise.getVisibility() == View.VISIBLE && llMomentViewCommentContainer.getVisibility() == View.VISIBLE ? View.VISIBLE : View.GONE); } /**設置點贊 * @param joined * @param list */ private void setPraise(boolean joined, List<User> list) { ivMomentViewPraise.setImageResource(joined ? R.drawable.praised : R.drawable.praise); llMomentViewPraise.setVisibility(list == null || list.isEmpty() ? View.GONE : View.VISIBLE); if (llMomentViewPraise.getVisibility() == View.VISIBLE) { tvMomentViewPraise.setView(list); } } private boolean showComment = true; public void setShowComment(boolean showComment) { this.showComment = showComment; } public boolean getShowComment() { return showComment; } public CommentContainerView commentContainerView; /**設置評論 * @param list */ public void setComment(List<CommentItem> list) { llMomentViewCommentContainer.setVisibility(showComment == false || list == null || list.isEmpty() ? View.GONE : View.VISIBLE); if (llMomentViewCommentContainer.getVisibility() != View.VISIBLE) { Log.i(TAG, "setComment llMomentViewCommentContainer.getVisibility() != View.VISIBLE >> return;"); return; } if (commentContainerView == null) { commentContainerView = new CommentContainerView(context, resources); llMomentViewCommentContainer.removeAllViews(); llMomentViewCommentContainer.addView(commentContainerView.createView(inflater)); commentContainerView.setOnCommentClickListener(new OnCommentClickListener() { @Override public void onCommentClick(CommentItem item, int position, int index, boolean isLong) { toComment(item, true); } }); commentContainerView.tvCommentContainerViewMore.setOnClickListener(this); commentContainerView.setMaxShowCount(5); } commentContainerView.bindView(list); } private GridAdapter adapter; /**設置圖片 * @param pictureList */ private void setPicture(List<String> pictureList) { List<Entry<String, String>> keyValueList = new ArrayList<Entry<String, String>>(); if (pictureList != null) { for (String picture : pictureList) { keyValueList.add(new Entry<String, String>(picture, null)); } } int pictureNum = keyValueList.size(); gvMomentView.setVisibility(pictureNum <= 0 ? View.GONE : View.VISIBLE); if (pictureNum <= 0) { Log.i(TAG, "setList pictureNum <= 0 >> lvModel.setAdapter(null); return;"); adapter = null; gvMomentView.setAdapter(null); return; } gvMomentView.setNumColumns(pictureNum <= 1 ? 1 : 3); if (adapter == null) { adapter = new GridAdapter(context).setHasName(false); gvMomentView.setAdapter(adapter); } adapter.refresh(keyValueList); gvMomentView.setOnItemClickListener(this); final int gridViewHeight = (int) (ScreenUtil.getScreenSize(context)[0] - convertView.getPaddingLeft() - convertView.getPaddingRight() - getDimension(R.dimen.moment_view_head_width)); try { if (pictureNum >= 7) { gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, gridViewHeight)); } else if (pictureNum >= 4) { gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, (gridViewHeight*2)/3)); } else if (pictureNum >= 2) { gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, gridViewHeight / 3)); } else { gvMomentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); } } catch (Exception e) { Log.e(TAG, " setPictureGrid try int gridViewHeight;...>> catch" + e.getMessage()); } } /**跳轉到所有評論界面 * @param isToComment */ private void toComment(boolean isToComment) { toComment(null, isToComment); } /**跳轉到所有評論界面 * @param commentItem * @param isToComment comment有效時為true */ private void toComment(CommentItem commentItem, boolean isToComment) { if (commentItem == null) { commentItem = new CommentItem(); } toActivity(MomentActivity.createIntent(context, momentId, isToComment , commentItem.getId(), commentItem.getUser().getId(), commentItem.getUser().getName())); } //UI顯示區(操作UI,但不存在數據獲取或處理代碼,也不存在事件監聽代碼)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //Data數據區(存在數據獲取或處理代碼,但不存在事件監聽代碼)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @Override public MomentItem getData() {//bindView(null)不會使data == null return llMomentViewContainer.getVisibility() == View.VISIBLE ? data : null; } /**判斷是否已登錄,如果未登錄則彈出登錄界面 * @return */ private boolean isLoggedIn() { boolean isLoggedIn = APIJSONApplication.getInstance().isLoggedIn(); if (isLoggedIn == false) { context.startActivity(LoginActivity.createIntent(context)); context.overridePendingTransition(R.anim.bottom_push_in, R.anim.hold); } return isLoggedIn; } /**點贊 * @param toPraise */ public void praise(boolean toPraise) { if (data == null || toPraise == data.getIsPraised()) { Log.e(TAG, "praiseWork toPraise == moment.getIsPraise() >> return;"); return; } // setPraise(toPraise, data.getPraiseCount() + (toPraise ? 1 : -1)); HttpRequest.praiseMoment(momentId, toPraise, toPraise ? HTTP_PRAISE : HTTP_CANCEL_PRAISE, this); } //Data數據區(存在數據獲取或處理代碼,但不存在事件監聽代碼)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //Event事件監聽區(只要存在事件監聽代碼就是)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @Override public void onDialogButtonClick(int requestCode, boolean isPositive) { if (isPositive && data != null) { data.setMyStatus(MomentItem.STATUS_DELETING); bindView(data); HttpRequest.deleteMoment(moment.getId(), HTTP_DELETE, this); } } public static final int HTTP_PRAISE = 1; public static final int HTTP_CANCEL_PRAISE = 2; public static final int HTTP_DELETE = 3; @Override public void onHttpResponse(int requestCode, String result, Exception e) { if (data == null) { Log.e(TAG, "onHttpResponse data == null >> return;"); return; } JSONResponse response = new JSONResponse(result); JSONResponse response2 = response.getJSONResponse(Moment.class.getSimpleName()); boolean isSucceed = JSONResponse.isSucceed(response2); switch (requestCode) { case HTTP_PRAISE: case HTTP_CANCEL_PRAISE: if (isSucceed) { data.setIsPraised(requestCode == HTTP_PRAISE); bindView(data); } else { showShortToast((requestCode == HTTP_PRAISE ? "點贊" : "取消點贊") + "失敗,請檢查網絡后重試"); } break; case HTTP_DELETE: showShortToast(isSucceed ? R.string.delete_succeed : R.string.delete_failed); //只對adapter.getCount()有影響。目前是隱藏的,不需要通知,也不需要刷新adapter,用戶手動刷新后自然就更新了。 if (isSucceed) { bindView(null); status = MomentItem.STATUS_DELETED; if (onDataChangedListener != null) { onDataChangedListener.onDataChanged(); } CacheManager.getInstance().remove(MomentItem.class, "" + momentId); } else { data.setMyStatus(MomentItem.STATUS_NORMAL); bindView(data); } break; } } @Override public void onClick(View v) { if (data == null) { return; } if (status == MomentItem.STATUS_PUBLISHING) { showShortToast(R.string.publishing); return; } switch (v.getId()) { case R.id.ivMomentViewHead: case R.id.tvMomentViewName: toActivity(UserActivity.createIntent(context, userId)); break; case R.id.tvMomentViewStatus: if (status == MomentItem.STATUS_NORMAL) { new AlertDialog(context, "", "刪除動態", true, 0, this).show(); } break; case R.id.tvMomentViewContent: case R.id.tvCommentContainerViewMore: toComment(false); break; case R.id.tvMomentViewPraise: case R.id.llMomentViewPraise: toActivity(UserListActivity.createIntent(context, data.getPraiseUserIdList()) .putExtra(UserListActivity.INTENT_TITLE, "點贊的人")); break; default: if (isLoggedIn() == false) { return; } switch (v.getId()) { case R.id.ivMomentViewPraise: praise(! data.getIsPraised()); break; case R.id.ivMomentViewComment: toComment(true); break; default: break; } break; } } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (status == MomentItem.STATUS_PUBLISHING) { showShortToast(R.string.publishing); return; } if (onPictureClickListener != null) { onPictureClickListener.onClickPicture(this.position, this, position); } else { toActivity(WebViewActivity.createIntent(context, null , adapter == null ? null : adapter.getItem(position).getKey())); } } //Event事件監聽區(只要存在事件監聽代碼就是)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}moment_view.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" style="@style/match_wrap" android:descendantFocusability="blocksDescendants" > <LinearLayout android:id="@+id/llMomentViewContainer" style="@style/ll_horizontal_match_wrap" android:background="@color/white" android:gravity="top" android:padding="10dp" > <RelativeLayout android:id="@+id/rlMomentViewItemHead" android:layout_width="@dimen/moment_view_head_width" android:layout_height="@dimen/moment_view_head_height" android:paddingRight="@dimen/moment_view_head_padding_right" > <ImageView android:background="@color/alpha_3" android:id="@+id/ivMomentViewHead" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" /> </RelativeLayout> <LinearLayout style="@style/ll_vertical_match_wrap" android:layout_below="@+id/rlMomentViewItemHead" android:layout_toRightOf="@+id/rlMomentViewItemHead" android:gravity="left" > <LinearLayout style="@style/ll_horizontal_match_wrap" android:layout_height="match_parent" > <TextView android:id="@+id/tvMomentViewName" style="@style/text_small_blue" android:layout_width="match_parent" android:layout_weight="1" android:background="@drawable/bg_item_to_alpha" android:gravity="left" android:text="Name" /> <TextView android:id="@+id/tvMomentViewStatus" style="@style/text_small_blue" android:background="@drawable/bg_item_to_alpha" android:text="發布中" /> </LinearLayout> <TextView android:id="@+id/tvMomentViewContent" style="@style/text_small_black" android:layout_width="match_parent" android:layout_marginTop="5dp" android:background="@drawable/bg_item_to_alpha" android:gravity="left|top" android:maxLines="8" android:paddingBottom="5dp" android:text="This is a content..." /> <apijson.demo.client.view.EmptyEventGridView android:id="@+id/gvMomentView" style="@style/wrap_wrap" android:focusable="false" android:horizontalSpacing="4dp" android:listSelector="@drawable/bg_item_to_alpha" android:numColumns="3" android:paddingTop="4dp" android:scrollbars="none" android:stretchMode="columnWidth" android:verticalSpacing="4dp" /> <LinearLayout style="@style/ll_horizontal_match_wrap" android:layout_height="wrap_content" android:layout_marginTop="5dp" > <TextView android:id="@+id/tvMomentViewDate" style="@style/text_small_black" android:layout_width="match_parent" android:layout_weight="1" android:gravity="left" android:text="2015年12月" /> <ImageView android:id="@+id/ivMomentViewPraise" style="@style/img_btn" android:layout_marginRight="18dp" android:background="@drawable/bg_item_to_alpha" android:src="@drawable/praise" /> <ImageView android:id="@+id/ivMomentViewComment" style="@style/img_btn" android:background="@drawable/bg_item_to_alpha" android:src="@drawable/comment" /> </LinearLayout> <LinearLayout style="@style/ll_vertical_match_wrap" android:layout_marginTop="5dp" android:background="@color/alpha_1" android:paddingLeft="8dp" android:paddingRight="8dp" > <LinearLayout android:id="@+id/llMomentViewPraise" style="@style/ll_horizontal_match_wrap" android:layout_height="wrap_content" android:layout_marginBottom="4dp" android:layout_marginTop="4dp" android:background="@drawable/bg_item_to_alpha" android:gravity="top" > <ImageView android:layout_width="20dp" android:layout_height="20dp" android:scaleType="fitXY" android:src="@drawable/praise" /> <apijson.demo.client.view.PraiseTextView android:id="@+id/tvMomentViewPraise" style="@style/text_small_blue" android:background="@drawable/bg_item_to_alpha" android:gravity="left|top" android:lineSpacingExtra="4dp" android:text="等覺得很贊" /> </LinearLayout> <View android:id="@+id/vMomentViewDivider" style="@style/divider_horizontal_1px" /> <LinearLayout android:id="@+id/llMomentViewCommentContainer" style="@style/ll_vertical_match_wrap" android:paddingBottom="4dp" android:paddingTop="4dp" > </LinearLayout> </LinearLayout> </LinearLayout> </LinearLayout></RelativeLayout>
由于這個項目使用了ZBLibrary快速開發框架,所以實現仿QQ空間和微信朋友圈的這種復雜界面只用了極少的代碼,并且高解耦、高復用、高靈活。
服務端是用APIJSON(Server)工程快速搭建的,客戶端App和服務端通過APIJSON-JSON傳輸結構協議通信,非常方便靈活,省去了大量的接口和文檔!
今年RxJava特別火,在北京市場幾乎是必備技能,所以我還把這個項目做了個RxJava版本,歡迎交流和指教。
實現UI的Java類:
MomentListFragment 395行 動態列表的獲取和顯示MomentActivity 616行 動態和評論列表的獲取、顯示和交互(評論和刪除評論等)MomentAdapter 67行 動態列表的顯示CommentAdapter 82行 評論列表的顯示MomentView 495行 動態的顯示和交互(各種跳轉、點贊、刪除等)EmptyEventGridView 56行 動態里圖片的顯示和交互(觸摸空白處傳遞觸摸事件到內層View)PraiseTextView 129行 動態里點贊用戶的顯示和交互(點擊姓名跳到個人詳情,點擊整體跳到點贊的用戶列表界面)CommentView 153行 一級評論(頭像、姓名、內容)的顯示和交互(回復、刪除等),添加二級評論列表CommentContainerView 172行 二級評論列表的顯示和交互(查看全部等)CommentTextView 122行 二級評論(姓名、內容)的顯示和交互(回復、刪除等)
實現UI的XML布局:
moment_activity 47行 動態和評論列表的顯示moment_view 148行 動態的顯示comment_view 87行 一級評論(頭像、姓名、內容)的顯示comment_container_view 20行 二級評論列表的顯示comment_item 10行 二級評論(姓名、內容)的顯示
為什么沒有實現MomentListFragment對應的XML布局?
因為MomentListFragment繼承BaseHttpListFragment,內部用XListView作為缺省列表View,所以可以不用自己實現了。
實現數據獲取、提交和處理的Java類:
HttpRequest +175行 數據的獲取和提交(getMoment,...,deleteComment)CommentUtil 140行 單層評論和和二級評論的處理Comment 56行 評論數據CommentItem 99行 評論的顯示和交互數據Moment 43行 動態數據MomentItem 272行 動態的顯示和交互數據User 103行 用戶數據
(注:未列出的代碼文件要么和動態無關,要么APIJSON或ZBLibrary已提供。server.model里的類由服務端提供)
仿QQ空間和微信朋友圈,高解耦高復用高靈活

下載試用(測試服務器地址:139.196.140.118:8080)
APIJSONClientApp.apk
源碼及文檔(記得給個Star哦)
https://github.com/TommyLemon/APIJSON
以上所述是小編給大家介紹的Android仿QQ空間動態界面分享功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對VEVB武林網網站的支持!
新聞熱點
疑難解答