先看看效果:
用极少的代码实现了 动态详情 及 二级评论 的 数据获取与处理 和 UI显示与交互,并且高解耦、高复用、高灵活。
动态列表界面MomentListFragment支持 下拉刷新与上拉加载 和 模糊搜索,反复快速滑动仍然非常流畅。
缓存机制使得数据可在启动界面后瞬间加载完成。
动态详情界面MomentActivity支持 (取消)点赞、(删除)评论、点击姓名跳到个人详情 等。
只有1张图片时图片放大显示,超过1张则按九宫格显示。
用到的CommentContainerView和MomentView都是独立的组件,既可单独使用,也可用于ListView或添加至其它ViewGroup等。
CommentContainerView复用
CommentContainerView.java
setonCommentClickListener : 设置点击评论监听 createView : 创建View bindView : 绑定数据并显示View setMaxShowCount : 设置最多显示数量,超过则折叠 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; public class CommentContainerView extends baseView> { private static final String TAG = "CommentContainerView"; private onCommentClickListener 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
list){ llCommentContainerViewContainer.setVisibility(list == null || list.isEmpty() ? View.GONE : View.VISIBLE); if (list == null) { Log.w(TAG, "bindView data_ == null >> data_ = new List ();"); list = new ArrayList (); } this.data = list; // 评论 setComment(list); } private int maxShowCount = 3; public void setMaxShowCount(int maxShowCount) { this.maxShowCount = maxShowCount; } public void setComment(List 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)); } } } @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
MomentView复用
MomentView.java
setonPictureClickListener : 设置点击图片监听 createView : 创建View bindView : 绑定数据并显示View setPraise : 设置点赞 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; public class MomentView extends baseViewimplements onClickListener , OnHttpResponseListener, OnDialogButtonClickListener, onItemClickListener { private static final String TAG = "MomentView"; public interface onPictureClickListener { void onClickPicture(int momentPosition, MomentView momentView, int pictureIndex); } private onPictureClickListener 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); } private void setPraise(boolean joined, List 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; public void setComment(List 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; private void setPicture(List pictureList) { List > keyValueList = new ArrayList >(); if (pictureList != null) { for (String picture : pictureList) { keyValueList.add(new Entry (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()); } } private void toComment(boolean isToComment) { toComment(null, isToComment); } 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;
}
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;
}
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
由于这个项目使用了ZBLibrary快速开发框架,所以实现仿QQ空间和微信朋友圈的这种复杂界面只用了极少的代码,并且高解耦、高复用、高灵活。
服务端是用APIJSON(Server)工程快速搭建的,客户端App和服务端通过APIJSON-JSON传输结构协议通信,非常方便灵活,省去了大量的接口和文档!
今年RxJava特别火,在北京市场几乎是必备技能,所以我还把这个项目做了个RxJava版本,欢迎交流和指教。
实现UI的Java类:
MomentListFragment395行动态列表的获取和显示 MomentActivity 616行动态和评论列表的获取、显示和交互(评论和删除评论等) MomentAdapter 67行 动态列表的显示 CommentAdapter 82行 评论列表的显示 MomentView 495行动态的显示和交互(各种跳转、点赞、删除等) EmptyEventGridView56行 动态里图片的显示和交互(触摸空白处传递触摸事件到内层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行动态的显示和交互数据 User103行用户数据
(注:未列出的代码文件要么和动态无关,要么APIJSON或ZBLibrary已提供。server.model里的类由服务端提供)
仿QQ空间和微信朋友圈,高解耦高复用高灵活
下载试用(测试服务器地址:139.196.140.118:8080)
APIJSONClientApp.apk
源码及文档(记得给个Star哦)
https://github.com/TommyLemon/APIJSON
以上所述是小编给大家介绍的Android仿QQ空间动态界面分享功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对考高分网网站的支持!



