一起擼個朋友圈吧(step5) - 控件篇【控件組裝&評論控件】

項目地址:https://github.com/razerdp/FriendCircle
一起擼個朋友圈吧這是本文所處文集,所有更新都會在這個文集里面哦,歡迎關注

上篇鏈接:http://www.lxweimin.com/p/a2cdf81359fc
下篇鏈接:http://www.lxweimin.com/p/ff9788581fb0

如您所見,在公司項目app提測后,在下終于閑下來繼續去擼app了?;艘惶鞎r間,抱著服務器大哥的大腿狂問,終于初步弄出了一個服務器出來。

之前欠下的評論控件也得以展示了。

ps:在下非常歡迎PR,如果您有更好的想法,可以PR到dev分支哦-V-


預覽圖如下:(內容頁還沒開始,所以目前只有共有控件的拼裝)

預覽

嗯。。。因為部署在本機,所以網速比較快←_←,而且目前在下只弄了4條數據,同時內容區(可變部分)還沒開干,所以畫面看起來怪怪的。

先不管那么多

本篇主要講解評論控件的實現:

評論控件采取的依然是繼承TextView,做法跟點贊列表控件差不多,但在ListView里面,我們的評論區實現方案大概有兩種:

  • ListView的item嵌套ListView?(不可?。?/li>
  • ListView的item嵌套LinearLayout,動態添加我們的自定義控件。
    很明顯,我們采取方案二。

首先實現我們的控件,因為評論控件比較簡單,所以我們就不需要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;

...構造器

 public void setCommentText(CommentInfo info) {
        if (info == null) return;
        boolean hasContent = false;
        //根據hashCode判斷內容是否一致
        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為空,證明是一條原創評論
            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不空,證明是回復評論
                CommentClick userA = new CommentClick.Builder(getContext(), info.userA).setTextSize(textSize).build();
                mSpannableStringBuilderAllVer.append(info.userA.nick, userA, 0);
                mSpannableStringBuilderAllVer.append("回復");
                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,也就是被回復的用戶是否為空,如果是空,則證明這是一條原創評論,也就是針對朋友的評論,不空則是回復別人。

然后CommentClick跟我們的點贊控件一樣的ClickableSpan實現,這個沒啥好說的。

另外需要注意的是記得在回復的內容后加上'\0',詳情見點贊列表那篇。

評論控件大概就這樣,但本篇文章重頭戲在于控件的組裝

還記得我們的一起擼個朋友圈吧(step3) - ListAdapter篇嗎,我們的BaseItemDelegate留下了公共部分的初始化,這次我們就順便的弄回。

我們公共的部分有如下幾個(公共部分即無論哪種類型的朋友圈,都會存在的控件):

  • 頭像/昵稱/用戶心情文字,組成item_header
  • 發布時間/評論按鈕/點贊列表/評論區,組成item_bottom

這幾個是無論如何都會存在的,所以我們的布局文件可以單獨抽出來復用(xml就不貼了,又長又無聊):

item_header
item_bottom

值得注意的是,item_bootm里面有個地方嵌套布局比較多,原因如下:
我們的點贊&評論控件處于同一個LinearLayout里面,因為這兩者之間存在著一條分割線,所以采用LinearLayout,其次,我們的評論列表則是單獨使用LinearLayout動態添加控件的,所以這里嵌套了兩層,這無法避免。。。(當然,也可以將點贊和評論控件封裝在同一個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;

    //中間內容層
    protected RelativeLayout contentLayout;

常量大概就是這些,內容層里面也許是gridview(圖片),也許是viewGroup(網頁分享),這個是不可變甚至可能沒有的,所以我們這里僅負責調整間距,不負責其可視性,其余的控件都是共有的。

關于SuperImageView,這個東東是繼承ImageView封裝了Glide的方法,關于這個在我的畢業論文備忘錄中的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),這里進行共有控件的數據展示
  • bindData(position, v, data, dynamicType),這個是抽象方法,交由子類實現,確保子類執行到這里的時候父類的共有控件初始完成。

本篇我們關注bindShareData(data)方法。

這個方法內容如下:

/** 共有數據綁定 */
    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);
    }

我們重點關注addCommentWidget,在前面說過,我們的評論列表使用LinearLayout進行addView。

但這會導致一個問題:由于我們是一個listview,而我們的baseitem本質上是一個viewholder,這也就意味著假如我們劃出屏幕,再滑回來,就會出現在原有的view基礎上又重復add了一次。

也許有人說,那我們每次removeAllViews后再add不就可以了么,這的確可行,但假如量一大,比如連續10條朋友圈都包含著20~50條評論,也就意味著滑出去再滑回來就需要new 50個commentwidget,這造成的就是視覺上的卡頓,體驗十分不好。

而我的解決方法目前想到兩個:

  • 維持一個池,從池里拿出可用的進行復用(期望,暫未實現)
  • 動態添加/減去差額,多出remove,少了則new(目前采用,實際上這個方法跟上面的池結合最為妥善)

我目前采用方法2,具體操作如下:

  1. 獲取當前評論區控件數量,記為childCount

  2. chidCount與bean的評論數(n)比較

  3. childCount>n,則remove掉childCount-n個view(期望維護一個池,將remove掉的放到復用池)

  4. childCount<n,則new出n-childCount個view

  5. childCount=n,則進行步驟3

  6. 所有view進行數據綁定(數據更新)

這樣做的好處就是減少了new對象的操作,起碼滑起來順暢好多。

具體代碼如下:

 private void addCommentWidget(List<CommentInfo> commentList) {
        if (commentList == null || commentList.size() == 0) return;
        /**
         * 優化方案:
         * 因為是在listview里面,那么復用肯定有,意味著滑動的時候必須要removeView或者addView
         * 但為了性能提高,不可以直接removeAllViews
         * 于是采取以下方案:
         *    根據現有的view進行remove/add差額
         *    然后統一設置
         * */
        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的數目比list的長度大,則減去對應的view
            commentLayout.removeViews(commentList.size(), childCount - commentList.size());
        }
        //綁定數據
        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);
            }
        }
    }

其余的相關代碼,如bean實體,volley初始化等請看源碼,這里就不寫出來了。

下一篇將會進行內容頁的定制以及默默地精神分裂構造朋友圈虛擬數據。

【附:】本篇JSON數據:

JSON
{
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: "短發美比我在這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: "短發美比我在這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: "我發發圖,我不說話。",
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
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容