項目地址:https://github.com/razerdp/FriendCircle
一起擼個朋友圈吧這是本文所處文集,所有更新都會在這個文集里面哦靠柑,歡迎關(guān)注
上篇鏈接:http://www.reibang.com/p/a2cdf81359fc
下篇鏈接:http://www.reibang.com/p/ff9788581fb0
如您所見底扳,在公司項目app提測后,在下終于閑下來繼續(xù)去擼app了步清』福花了一天時間,抱著服務(wù)器大哥的大腿狂問软舌,終于初步弄出了一個服務(wù)器出來才漆。
之前欠下的評論控件也得以展示了。
ps:在下非常歡迎PR佛点,如果您有更好的想法醇滥,可以PR到dev分支哦-V-
預(yù)覽圖如下:(內(nèi)容頁還沒開始,所以目前只有共有控件的拼裝)
嗯超营。鸳玩。。因為部署在本機演闭,所以網(wǎng)速比較快←_←不跟,而且目前在下只弄了4條數(shù)據(jù),同時內(nèi)容區(qū)(可變部分)還沒開干米碰,所以畫面看起來怪怪的躬拢。
先不管那么多
本篇主要講解評論控件的實現(xiàn):
評論控件采取的依然是繼承TextView,做法跟點贊列表控件差不多见间,但在ListView里面聊闯,我們的評論區(qū)實現(xiàn)方案大概有兩種:
- ListView的item嵌套ListView?(不可让姿摺)
- ListView的item嵌套LinearLayout菱蔬,動態(tài)添加我們的自定義控件。
很明顯史侣,我們采取方案二拴泌。
首先實現(xiàn)我們的控件,因為評論控件比較簡單惊橱,所以我們就不需要attrs了蚪腐。
/**
* Created by 大燈泡 on 2016/2/23.
* 評論控件
*/
public class CommentWidget extends TextView {
private static final String TAG = "CommentWidget";
//用戶名顏色
private int textColor = 0xff517fae;
private static final int textSize = 14;
private int key;
private SpannableStringBuilderAllVer mSpannableStringBuilderAllVer;
...構(gòu)造器
public void setCommentText(CommentInfo info) {
if (info == null) return;
boolean hasContent = false;
//根據(jù)hashCode判斷內(nèi)容是否一致
if (key == 0) {
key = info.hashCode();
}
else {
hasContent = (key == info.hashCode());
}
if (!hasContent) {
key = info.hashCode();
setText("");
setTag(info);
createCommentStringBuilder(info);
}
else {
try {
setText(mSpannableStringBuilderAllVer);
} catch (NullPointerException e) {
e.printStackTrace();
Log.e(TAG, "雖然在下覺得不可能會有這個情況,但還是捕捉下吧税朴,萬一被打臉呢回季。。正林。");
}
}
}
private void createCommentStringBuilder(@NonNull CommentInfo info) {
String content = ": " + info.content + "\0";
if (mSpannableStringBuilderAllVer == null) {
mSpannableStringBuilderAllVer = new SpannableStringBuilderAllVer();
boolean isApply = (info.userB == null);
// 用戶B為空泡一,證明是一條原創(chuàng)評論
if (info.userA != null && isApply) {
CommentClick userA = new CommentClick.Builder(getContext(), info.userA).setTextSize(textSize).build();
mSpannableStringBuilderAllVer.append(info.userA.nick, userA, 0);
mSpannableStringBuilderAllVer.append(content);
}
else if (info.userA != null && !isApply) {
//用戶A,B不空觅廓,證明是回復(fù)評論
CommentClick userA = new CommentClick.Builder(getContext(), info.userA).setTextSize(textSize).build();
mSpannableStringBuilderAllVer.append(info.userA.nick, userA, 0);
mSpannableStringBuilderAllVer.append("回復(fù)");
CommentClick userB = new CommentClick.Builder(getContext(), info.userB).setTextSize(textSize).build();
mSpannableStringBuilderAllVer.append(info.userB.nick, userB, 0);
mSpannableStringBuilderAllVer.append(content);
}
}
setText(mSpannableStringBuilderAllVer);
}
public CommentInfo getData() throws ClassCastException {
return (CommentInfo) getTag();
}
代碼不多鼻忠,而且在下也寫得比較清晰(命名二逼明了,咱們不故作深沉)杈绸,我們主要觀察createCommentStringBuilder()方法帖蔓,這個方法我們主要判斷userB矮瘟,也就是被回復(fù)的用戶是否為空,如果是空塑娇,則證明這是一條原創(chuàng)評論芥永,也就是針對朋友的評論,不空則是回復(fù)別人钝吮。
然后CommentClick跟我們的點贊控件一樣的ClickableSpan實現(xiàn)埋涧,這個沒啥好說的。
另外需要注意的是記得在回復(fù)的內(nèi)容后加上'\0'奇瘦,詳情見點贊列表那篇棘催。
評論控件大概就這樣,但本篇文章重頭戲在于控件的組裝
還記得我們的一起擼個朋友圈吧(step3) - ListAdapter篇嗎耳标,我們的BaseItemDelegate留下了公共部分的初始化醇坝,這次我們就順便的弄回。
我們公共的部分有如下幾個(公共部分即無論哪種類型的朋友圈次坡,都會存在的控件):
- 頭像/昵稱/用戶心情文字呼猪,組成item_header
- 發(fā)布時間/評論按鈕/點贊列表/評論區(qū),組成item_bottom
這幾個是無論如何都會存在的砸琅,所以我們的布局文件可以單獨抽出來復(fù)用(xml就不貼了宋距,又長又無聊):
值得注意的是,item_bootm里面有個地方嵌套布局比較多症脂,原因如下:
我們的點贊&評論控件處于同一個LinearLayout里面谚赎,因為這兩者之間存在著一條分割線,所以采用LinearLayout诱篷,其次壶唤,我們的評論列表則是單獨使用LinearLayout動態(tài)添加控件的,所以這里嵌套了兩層棕所,這無法避免闸盔。。琳省。(當然迎吵,也可以將點贊和評論控件封裝在同一個LinearLayout里面,但個人覺得沒必要)
布局弄好后岛啸,我們完善我們的baseitem代碼
public abstract class BaseItemDelegate
implements BaseItemView<MomentsInfo>, View.OnClickListener, View.OnLongClickListener {
protected Activity context;
//頂部
protected SuperImageView avatar;
protected TextView nick;
protected ClickShowMoreLayout textField;
//底部
protected TextView createTime;
protected ImageView commentImage;
protected FrameLayout commentButton;
protected LinearLayout commentAndPraiseLayout;
protected PraiseWidget praiseWidget;
protected View line;
protected LinearLayout commentLayout;
//中間內(nèi)容層
protected RelativeLayout contentLayout;
常量大概就是這些钓觉,內(nèi)容層里面也許是gridview(圖片)茴肥,也許是viewGroup(網(wǎng)頁分享)坚踩,這個是不可變甚至可能沒有的,所以我們這里僅負責調(diào)整間距瓤狐,不負責其可視性瞬铸,其余的控件都是共有的批幌。
關(guān)于SuperImageView,這個東東是繼承ImageView封裝了Glide的方法嗓节,關(guān)于這個在我的畢業(yè)論文備忘錄中的Day4 - ImageView封裝Glide方法有記載荧缘,這里就不詳述了
在我們的item里面,所有view的操作都是在onBindData進行的拦宣,我們父類進行初始化共有控件主要以下幾個方法:
@Override
public void onBindData(int position, @NonNull View v, @NonNull MomentsInfo data, final int dynamicType) {
mInfo = data;
//初始化共用部分
bindView(v);
bindShareData(data);
bindData(position, v, data, dynamicType);
}
- bindView(v)截粗,這里進行共有控件的findViewById,此處不展示
- bindShareData(data)鸵隧,這里進行共有控件的數(shù)據(jù)展示
- bindData(position, v, data, dynamicType)绸罗,這個是抽象方法,交由子類實現(xiàn)豆瘫,確保子類執(zhí)行到這里的時候父類的共有控件初始完成珊蟀。
本篇我們關(guān)注bindShareData(data)方法。
這個方法內(nèi)容如下:
/** 共有數(shù)據(jù)綁定 */
private void bindShareData(MomentsInfo data) {
avatar.loadImageDefault(data.userInfo.avatar);
nick.setText(data.userInfo.nick);
textField.setText(data.textField);
if (TextUtils.isEmpty(data.textField) && contentLayout != null) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) contentLayout.getLayoutParams();
params.topMargin = -UIHelper.dipToPx(context, 8);
contentLayout.setLayoutParams(params);
}
else {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) contentLayout.getLayoutParams();
params.topMargin = 0;
contentLayout.setLayoutParams(params);
}
createTime.setText(TimeUtil.getTimeString(data.dynamicInfo.createTime));
setCommentPraiseLayoutVisibility(data);
//點贊
praiseWidget.setDatas(data.praiseList);
//評論
addCommentWidget(data.commentList);
}
我們重點關(guān)注addCommentWidget外驱,在前面說過育灸,我們的評論列表使用LinearLayout進行addView。
但這會導致一個問題:由于我們是一個listview昵宇,而我們的baseitem本質(zhì)上是一個viewholder磅崭,這也就意味著假如我們劃出屏幕,再滑回來瓦哎,就會出現(xiàn)在原有的view基礎(chǔ)上又重復(fù)add了一次绽诚。
也許有人說,那我們每次removeAllViews后再add不就可以了么杭煎,這的確可行恩够,但假如量一大,比如連續(xù)10條朋友圈都包含著20~50條評論羡铲,也就意味著滑出去再滑回來就需要new 50個commentwidget蜂桶,這造成的就是視覺上的卡頓,體驗十分不好也切。
而我的解決方法目前想到兩個:
- 維持一個池扑媚,從池里拿出可用的進行復(fù)用(期望,暫未實現(xiàn))
- 動態(tài)添加/減去差額雷恃,多出remove疆股,少了則new(目前采用,實際上這個方法跟上面的池結(jié)合最為妥善)
我目前采用方法2倒槐,具體操作如下:
獲取當前評論區(qū)控件數(shù)量旬痹,記為childCount
chidCount與bean的評論數(shù)(n)比較
childCount>n,則remove掉childCount-n個view(期望維護一個池,將remove掉的放到復(fù)用池)
childCount<n,則new出n-childCount個view
childCount=n,則進行步驟3
所有view進行數(shù)據(jù)綁定(數(shù)據(jù)更新)
這樣做的好處就是減少了new對象的操作,起碼滑起來順暢好多两残。
具體代碼如下:
private void addCommentWidget(List<CommentInfo> commentList) {
if (commentList == null || commentList.size() == 0) return;
/**
* 優(yōu)化方案:
* 因為是在listview里面永毅,那么復(fù)用肯定有,意味著滑動的時候必須要removeView或者addView
* 但為了性能提高人弓,不可以直接removeAllViews
* 于是采取以下方案:
* 根據(jù)現(xiàn)有的view進行remove/add差額
* 然后統(tǒng)一設(shè)置
* */
final int childCount = commentLayout.getChildCount();
if (childCount < commentList.size()) {
//當前的view少于list的長度沼死,則補充相差的view
int subCount = commentList.size() - childCount;
for (int i = 0; i < subCount; i++) {
CommentWidget commentWidget = new CommentWidget(context);
LinearLayout.LayoutParams params=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.topMargin=1;
params.bottomMargin=1;
commentWidget.setLayoutParams(params);
commentWidget.setLineSpacing(4,1);
commentWidget.setOnClickListener(this);
commentWidget.setOnLongClickListener(this);
commentLayout.addView(commentWidget);
}
}
else if (childCount > commentList.size()) {
//當前的view的數(shù)目比list的長度大,則減去對應(yīng)的view
commentLayout.removeViews(commentList.size(), childCount - commentList.size());
}
//綁定數(shù)據(jù)
for (int n = 0; n < commentList.size(); n++) {
CommentWidget commentWidget = (CommentWidget) commentLayout.getChildAt(n);
if (commentWidget != null) commentWidget.setCommentText(commentList.get(n));
}
}
最后是評論控件和點贊列表控件的分割線判定與layout可視性判定:
/** 是否有點贊或者評論 */
private void setCommentPraiseLayoutVisibility(MomentsInfo data) {
if ((data.commentList == null || data.commentList.size() == 0) &&
(data.praiseList == null || data.praiseList.size() == 0)) {
//全空崔赌,取消顯示
commentAndPraiseLayout.setVisibility(View.GONE);
}
else {
//某項不空意蛀,則展示layout
commentAndPraiseLayout.setVisibility(View.VISIBLE);
//點贊或者評論某個為空,分割線不展示
if (data.commentList == null || data.commentList.size() == 0 ||
data.praiseList == null || data.praiseList.size() == 0) {
line.setVisibility(View.GONE);
}
else {
line.setVisibility(View.VISIBLE);
}
//點贊為空健芭,取消點贊控件的可見性
if (data.praiseList == null || data.praiseList.size() == 0) {
praiseWidget.setVisibility(View.GONE);
}
else {
praiseWidget.setVisibility(View.VISIBLE);
}
//評論
if (data.commentList == null || data.commentList.size() == 0) {
commentLayout.setVisibility(View.GONE);
}
else {
commentLayout.setVisibility(View.VISIBLE);
}
}
}
其余的相關(guān)代碼浸间,如bean實體,volley初始化等請看源碼吟榴,這里就不寫出來了魁蒜。
下一篇將會進行內(nèi)容頁的定制以及默默地精神分裂構(gòu)造朋友圈虛擬數(shù)據(jù)。
【附:】本篇JSON數(shù)據(jù):
{
data: {
hostInfo: {
hostid: 1001,
hostAvatar: "http://upload.jianshu.io/users/upload_avatars/684042/bd1b2f796e3a.jpg",
hostNick: "羽翼君",
hostWallPic: "http://www.pp3.cn/uploads/allimg/111118/10562Cb5-13.jpg"
},
moments: [
{
userInfo: {
nick: "羽翼君",
avatar: "http://upload.jianshu.io/users/upload_avatars/684042/bd1b2f796e3a.jpg",
userId: 1001
},
dynamicInfo: {
dynamicId: 10001,
createUserId: 1001,
dynamicType: 10,
praiseState: 1,
createTime: 1456296202,
candelete: 1
},
textField: "這是第一條朋友圈哦",
praiseList: [
{
nick: "羽翼君",
avatar: "http://upload.jianshu.io/users/upload_avatars/684042/bd1b2f796e3a.jpg",
userId: 1001
},
{
nick: "涵菱",
avatar: "http://img7.3wmm.cc/pic/c/f/d/cfd2c2291ba75df42efedfe4bc62ee39.jpg",
userId: 1004
},
{
nick: "短發(fā)美比我在這i",
avatar: "http://img0w.pconline.com.cn/pconline/1310/29/3719457_13667094527.jpg",
userId: 1044
},
{
nick: "丑化小丑不丑吩翻。",
avatar: "http://img1.touxiang.cn/uploads/20141128/28-021805_451.jpg",
userId: 1054
}
],
commentList: [
{
userA: {
nick: " 振然",
avatar: "http://cdn.duitang.com/uploads/item/201408/30/20140830175648_js4hP.png",
userId: 1014
},
commentId: 1,
content: "新年好",
candelete: 1,
createTime: 1454397315
},
{
userA: {
nick: "羽翼君",
avatar: "http://upload.jianshu.io/users/upload_avatars/684042/bd1b2f796e3a.jpg",
userId: 1001
},
commentId: 1,
content: "hello~",
candelete: 1,
createTime: 1454483655
},
{
userA: {
nick: "詩雁",
avatar: "http://img4.duitang.com/uploads/item/201601/11/20160111175420_ZmTzU.jpeg",
userId: 1006
},
userB: {
nick: "羽翼君",
avatar: "http://upload.jianshu.io/users/upload_avatars/684042/bd1b2f796e3a.jpg",
userId: 1001
},
commentId: 1,
content: "哇兜看,好巧-V-",
candelete: 1,
createTime: 1454483715
}
]
},
{
userInfo: {
nick: "傲露",
avatar: "http://img1.hao661.com/uploads/allimg/c141030/141463I01W940-5IH0.jpg",
userId: 1010
},
dynamicInfo: {
dynamicId: 10003,
createUserId: 1010,
dynamicType: 11,
praiseState: 1,
createTime: 1454743095,
candelete: 0
},
textField: "咳咳。狭瞎。细移。。測試一下",
praiseList: [
{
nick: "羽翼君",
avatar: "http://upload.jianshu.io/users/upload_avatars/684042/bd1b2f796e3a.jpg",
userId: 1001
},
{
nick: "凌之",
avatar: "http://img5.imgtn.bdimg.com/it/u=3341777813,2293496692&fm=11&gp=0.jpg",
userId: 1003
},
{
nick: "白雪",
avatar: "http://img5.duitang.com/uploads/item/201406/26/20140626190424_TCXuP.jpeg",
userId: 1009
},
{
nick: "柔胤",
avatar: "http://img5.imgtn.bdimg.com/it/u=660454163,590477124&fm=11&gp=0.jpg",
userId: 1011
},
{
nick: "琪家",
avatar: "http://img5.duitang.com/uploads/item/201502/01/20150201174019_A5LYU.png",
userId: 1015
},
{
nick: "暮色伊人熊锭。",
avatar: "http://b.hiphotos.baidu.com/zhidao/wh%3D600%2C800/sign=6a5d1183d358ccbf1be9bd3c29e89006/9213b07eca806538d5541c2295dda144ad348241.jpg",
userId: 1041
},
{
nick: "短發(fā)美比我在這i",
avatar: "http://img0w.pconline.com.cn/pconline/1310/29/3719457_13667094527.jpg",
userId: 1044
},
{
nick: "~花舞う街で~",
avatar: "http://c.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=fa3f854c8618367aaddc77d91b43a7e2/bba1cd11728b4710f37cb5a9c3cec3fdfc032307.jpg",
userId: 1045
},
{
nick: "妖視覺〃",
avatar: "http://img1.touxiang.cn/uploads/20141128/28-021817_497.jpg",
userId: 1063
},
{
nick: "墨煙三色傾人城弧轧。",
avatar: "http://img1.touxiang.cn/uploads/20140815/15-072749_540.jpg",
userId: 1079
},
{
nick: "別嘲笑胖女孩!",
avatar: "http://img1.touxiang.cn/uploads/20140812/12-072839_61.jpg",
userId: 1087
},
{
nick: "默 ’_哀碗殷、",
avatar: "http://img1.touxiang.cn/uploads/20140812/12-072932_837.jpg",
userId: 1094
}
],
commentList: [
{
userA: {
nick: "透過骨z1里的傲 つ",
avatar: "http://img1.touxiang.cn/uploads/20141128/28-021810_437.jpg",
userId: 1058
},
commentId: 4,
content: "這是啥",
candelete: 0,
createTime: 1454746695
},
{
userA: {
nick: "傲露",
avatar: "http://img1.hao661.com/uploads/allimg/c141030/141463I01W940-5IH0.jpg",
userId: 1010
},
userB: {
nick: "透過骨z1里的傲 つ",
avatar: "http://img1.touxiang.cn/uploads/20141128/28-021810_437.jpg",
userId: 1058
},
commentId: 4,
content: "have a test",
candelete: 0,
createTime: 1454746698
},
{
userA: {
nick: "透過骨z1里的傲 つ",
avatar: "http://img1.touxiang.cn/uploads/20141128/28-021810_437.jpg",
userId: 1058
},
userB: {
nick: "傲露",
avatar: "http://img1.hao661.com/uploads/allimg/c141030/141463I01W940-5IH0.jpg",
userId: 1010
},
commentId: 4,
content: "噢~so ga",
candelete: 0,
createTime: 1454750298
}
],
content: {
imgurl: [ ],
dynamicid: 0
}
},
{
userInfo: {
nick: "~花舞う街で~",
avatar: "http://c.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=fa3f854c8618367aaddc77d91b43a7e2/bba1cd11728b4710f37cb5a9c3cec3fdfc032307.jpg",
userId: 1045
},
dynamicInfo: {
dynamicId: 10002,
createUserId: 1045,
dynamicType: 11,
createTime: 1454656515,
candelete: 0
},
praiseList: [ ],
commentList: [
{
userA: {
nick: "皓博",
avatar: "http://t2.du114.com/uploads/160105/18-16010511202M47.jpg",
userId: 1012
},
commentId: 3,
content: "~",
candelete: 0,
createTime: 1454742975
}
],
content: {
imgurl: [ ],
dynamicid: 0
}
},
{
userInfo: {
nick: "白雪",
avatar: "http://img5.duitang.com/uploads/item/201406/26/20140626190424_TCXuP.jpeg",
userId: 1009
},
dynamicInfo: {
dynamicId: 10004,
createUserId: 1009,
dynamicType: 11,
praiseState: 1,
createTime: 1454483715,
candelete: 0
},
textField: "我發(fā)發(fā)圖精绎,我不說話。",
praiseList: [
{
nick: "羽翼君",
avatar: "http://upload.jianshu.io/users/upload_avatars/684042/bd1b2f796e3a.jpg",
userId: 1001
}
],
commentList: [
{
userA: {
nick: "~花舞う街で~",
avatar: "http://c.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=fa3f854c8618367aaddc77d91b43a7e2/bba1cd11728b4710f37cb5a9c3cec3fdfc032307.jpg",
userId: 1045
},
commentId: 2,
content: "路過評論锌妻。代乃。。",
candelete: 0,
createTime: 1454742855
}
],
content: {
imgurl: [
"http://img5.duitang.com/uploads/item/201206/06/20120606175201_WZ2F3.thumb.700_0.jpeg",
"http://img5.duitang.com/uploads/item/201206/06/20120606175201_WZ2F3.thumb.700_0.jpeg",
"http://img5.duitang.com/uploads/item/201206/06/20120606175201_WZ2F3.thumb.700_0.jpeg"
],
dynamicid: 10004
}
}
]
},
stateCode: 200,
requestTime: 1456418501691,
start: 4,
loadMore: 0
}