Android仿QQ空間動(dòng)態(tài)界面分享功能
先看看效果:






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

動(dòng)態(tài)詳情界面MomentActivity支持 (取消)點(diǎn)贊、(刪除)評論、點(diǎn)擊姓名跳到個(gè)人詳情 等。
只有1張圖片時(shí)圖片放大顯示,超過1張則按九宮格顯示。

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


CommentContainerView.java
setOnCommentClickListener : 設(shè)置點(diǎn)擊評論監(jiān)聽 createView : 創(chuàng)建View bindView : 綁定數(shù)據(jù)并顯示View setMaxShowCount : 設(shè)置最多顯示數(shù)量,超過則折疊 setComment : 設(shè)置評論 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
* @use
CommentContainerView 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;
/**設(shè)置點(diǎn)擊評論監(jiān)聽
* @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;
/**設(shè)置最多顯示數(shù)量,超過則折疊
* @param maxShowCount <= 0 ? 顯示全部 : 超過則折疊
*/
public void setMaxShowCount(int maxShowCount) {
this.maxShowCount = maxShowCount;
}
/**設(shè)置評論
* @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復(fù)用


MomentView.java
setOnPictureClickListener : 設(shè)置點(diǎn)擊圖片監(jiān)聽 createView : 創(chuàng)建View bindView : 綁定數(shù)據(jù)并顯示View setPraise : 設(shè)置點(diǎn)贊 setShowComment : 設(shè)置是否顯示評論 getShowComment : 獲取是否顯示評論的設(shè)置 setComment : 設(shè)置評論 setPicture : 設(shè)置九宮格圖片 toComment : 跳轉(zhuǎn)到所有評論界面 getData : 獲取動(dòng)態(tài)綁定的數(shù)據(jù) isLoggedIn : 判斷是否已登錄,未登錄則跳到登錄界面 praise : (取消)點(diǎn)贊 onDialogButtonClick : 處理對話框返回結(jié)果,比如刪除動(dòng)態(tài) onHttpResponse : 處理Http請求的返回結(jié)果,比如點(diǎn)贊 onClick : 處理點(diǎn)擊事件,比如點(diǎn)擊內(nèi)容跳到動(dòng)態(tài)詳情界面 onItemClick : 處理點(diǎn)擊圖片的事件,默認(rèn)是查看大圖,可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;
/**動(dòng)態(tài)
* @author Lemon
* @use
MomentView 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;
/**設(shè)置點(diǎn)擊圖片監(jiān)聽
* @param onPictureClickListener
*/
public void setOnPictureClickListener(OnPictureClickListener onPictureClickListener) {
this.onPictureClickListener = onPictureClickListener;
}
public MomentView(Activity context, Resources resources) {
super(context, resources);
}
//UI顯示區(qū)(操作UI,但不存在數(shù)據(jù)獲取或處理代碼,也不存在事件監(jiān)聽代碼)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
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());
// 點(diǎn)贊
setPraise(data.getIsPraised(), data.getUserList());
// 評論
setComment(data.getCommentItemList());
vMomentViewDivider.setVisibility(llMomentViewPraise.getVisibility() == View.VISIBLE
&& llMomentViewCommentContainer.getVisibility() == View.VISIBLE ? View.VISIBLE : View.GONE);
}
/**設(shè)置點(diǎn)贊
* @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;
/**設(shè)置評論
* @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;
/**設(shè)置圖片
* @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());
}
}
/**跳轉(zhuǎn)到所有評論界面
* @param isToComment
*/
private void toComment(boolean isToComment) {
toComment(null, isToComment);
}
/**跳轉(zhuǎn)到所有評論界面
* @param commentItem
* @param isToComment comment有效時(shí)為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顯示區(qū)(操作UI,但不存在數(shù)據(jù)獲取或處理代碼,也不存在事件監(jiān)聽代碼)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//Data數(shù)據(jù)區(qū)(存在數(shù)據(jù)獲取或處理代碼,但不存在事件監(jiān)聽代碼)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
@Override
public MomentItem getData() {//bindView(null)不會(huì)使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;
}
/**點(diǎn)贊
* @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數(shù)據(jù)區(qū)(存在數(shù)據(jù)獲取或處理代碼,但不存在事件監(jiān)聽代碼)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//Event事件監(jiān)聽區(qū)(只要存在事件監(jiān)聽代碼就是)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
@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 ? "點(diǎn)贊" : "取消點(diǎn)贊") + "失敗,請檢查網(wǎng)絡(luò)后重試");
}
break;
case HTTP_DELETE:
showShortToast(isSucceed ? R.string.delete_succeed : R.string.delete_failed);
//只對adapter.getCount()有影響。目前是隱藏的,不需要通知,也不需要刷新adapter,用戶手動(dòng)刷新后自然就更新了。
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, "", "刪除動(dòng)態(tài)", 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, "點(diǎn)贊的人"));
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事件監(jiān)聽區(qū)(只要存在事件監(jiān)聽代碼就是)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
}
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="發(fā)布中" />
</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>
由于這個(gè)項(xiàng)目使用了ZBLibrary快速開發(fā)框架,所以實(shí)現(xiàn)仿QQ空間和微信朋友圈的這種復(fù)雜界面只用了極少的代碼,并且高解耦、高復(fù)用、高靈活。
服務(wù)端是用APIJSON(Server)工程快速搭建的,客戶端App和服務(wù)端通過APIJSON-JSON傳輸結(jié)構(gòu)協(xié)議通信,非常方便靈活,省去了大量的接口和文檔!
今年RxJava特別火,在北京市場幾乎是必備技能,所以我還把這個(gè)項(xiàng)目做了個(gè)RxJava版本,歡迎交流和指教。
實(shí)現(xiàn)UI的Java類:
MomentListFragment 395行 動(dòng)態(tài)列表的獲取和顯示 MomentActivity 616行 動(dòng)態(tài)和評論列表的獲取、顯示和交互(評論和刪除評論等) MomentAdapter 67行 動(dòng)態(tài)列表的顯示 CommentAdapter 82行 評論列表的顯示 MomentView 495行 動(dòng)態(tài)的顯示和交互(各種跳轉(zhuǎn)、點(diǎn)贊、刪除等) EmptyEventGridView 56行 動(dòng)態(tài)里圖片的顯示和交互(觸摸空白處傳遞觸摸事件到內(nèi)層View) PraiseTextView 129行 動(dòng)態(tài)里點(diǎn)贊用戶的顯示和交互(點(diǎn)擊姓名跳到個(gè)人詳情,點(diǎn)擊整體跳到點(diǎn)贊的用戶列表界面) CommentView 153行 一級評論(頭像、姓名、內(nèi)容)的顯示和交互(回復(fù)、刪除等),添加二級評論列表 CommentContainerView 172行 二級評論列表的顯示和交互(查看全部等) CommentTextView 122行 二級評論(姓名、內(nèi)容)的顯示和交互(回復(fù)、刪除等)
實(shí)現(xiàn)UI的XML布局:
moment_activity 47行 動(dòng)態(tài)和評論列表的顯示 moment_view 148行 動(dòng)態(tài)的顯示 comment_view 87行 一級評論(頭像、姓名、內(nèi)容)的顯示 comment_container_view 20行 二級評論列表的顯示 comment_item 10行 二級評論(姓名、內(nèi)容)的顯示
為什么沒有實(shí)現(xiàn)MomentListFragment對應(yīng)的XML布局?
因?yàn)镸omentListFragment繼承BaseHttpListFragment,內(nèi)部用XListView作為缺省列表View,所以可以不用自己實(shí)現(xiàn)了。
實(shí)現(xiàn)數(shù)據(jù)獲取、提交和處理的Java類:
HttpRequest +175行 數(shù)據(jù)的獲取和提交(getMoment,...,deleteComment) CommentUtil 140行 單層評論和和二級評論的處理 Comment 56行 評論數(shù)據(jù) CommentItem 99行 評論的顯示和交互數(shù)據(jù) Moment 43行 動(dòng)態(tài)數(shù)據(jù) MomentItem 272行 動(dòng)態(tài)的顯示和交互數(shù)據(jù) User 103行 用戶數(shù)據(jù)
(注:未列出的代碼文件要么和動(dòng)態(tài)無關(guān),要么APIJSON或ZBLibrary已提供。server.model里的類由服務(wù)端提供)
仿QQ空間和微信朋友圈,高解耦高復(fù)用高靈活

下載試用(測試服務(wù)器地址:139.196.140.118:8080)
源碼及文檔(記得給個(gè)Star哦)
https://github.com/TommyLemon/APIJSON
以上所述是小編給大家介紹的Android仿QQ空間動(dòng)態(tài)界面分享功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- android 通過向viewpage中添加listview來完成滑動(dòng)效果(類似于qq滑動(dòng)界面)
- Android QQ登錄界面繪制代碼
- Android使用ViewDragHelper實(shí)現(xiàn)仿QQ6.0側(cè)滑界面(一)
- Android仿QQ、微信聊天界面長按提示框效果
- Android使用ViewDragHelper實(shí)現(xiàn)QQ6.X最新版本側(cè)滑界面效果實(shí)例代碼
- Android應(yīng)用中使用ViewPager實(shí)現(xiàn)類似QQ的界面切換效果
- Android實(shí)現(xiàn)QQ登錄界面遇到問題及解決方法
- Android QQ新用戶注冊界面繪制
- Android ListView自定義Adapter實(shí)現(xiàn)仿QQ界面
- Android小程序?qū)崿F(xiàn)簡易QQ界面
相關(guān)文章
Android中手機(jī)錄屏并轉(zhuǎn)換GIF的兩種方式
本文主要介紹了android中手機(jī)錄屏并轉(zhuǎn)換GIF的兩種方式,具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-01-01
詳解Android通過修改配置文件設(shè)置wifi密碼
這篇文章主要介紹了詳解Android通過修改配置文件設(shè)置wifi密碼的相關(guān)資料,需要的朋友可以參考下2017-07-07
淺談AnDroidDraw+DroidDraw實(shí)現(xiàn)Android程序UI設(shè)計(jì)的分析說明
本篇文章是對AnDroidDraw+DroidDraw實(shí)現(xiàn)Android程序UI設(shè)計(jì)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Android毛玻璃背景效果簡單實(shí)現(xiàn)代碼
這篇文章主要介紹了Android毛玻璃背景效果簡單實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-08-08
Android轉(zhuǎn)場效果實(shí)現(xiàn)示例淺析
這篇文章主要為大家介紹了Android轉(zhuǎn)場效果實(shí)現(xiàn)示例淺析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
Android實(shí)現(xiàn)隱藏手機(jī)底部虛擬按鍵
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)隱藏手機(jī)底部虛擬按鍵,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08

