一起擼個(gè)朋友圈吧(step5) - 控件篇(評(píng)論popup下+交互事件結(jié)構(gòu))

項(xiàng)目地址:https://github.com/razerdp/FriendCircle
一起擼個(gè)朋友圈吧這是本文所處文集,所有更新都會(huì)在這個(gè)文集里面哦舶吗,歡迎關(guān)注

上篇鏈接:http://www.reibang.com/p/15a9fe8f917f
下篇鏈接:http://www.reibang.com/p/58894dfb3f09

本篇圖文較多征冷,流量黨請(qǐng)慎重


再開始之前,羽翼君想說一件事情誓琼,目前我還是一個(gè)學(xué)生检激,沒什么能力買一個(gè)牛逼的服務(wù)器,僅僅是學(xué)生價(jià)租的阿里云腹侣,但是叔收,因?yàn)闉榱朔奖悖野逊?wù)器的IP都寫在了我們的項(xiàng)目里面傲隶,結(jié)果他喵的昨晚被攻擊了饺律!

這個(gè)服務(wù)器根本沒有什么利用價(jià)值,即使是用來做肉雞跺株,根本沒法塞牙縫好么复濒。雖然花的錢不多脖卖,也僅僅是為了這個(gè)項(xiàng)目和我的畢業(yè)設(shè)計(jì)方便而租的服務(wù)器。

當(dāng)然巧颈,這也是我的錯(cuò)畦木,我不應(yīng)該直接把服務(wù)器地址貼上的,這也是我的鍋砸泛,所以在push的時(shí)候我把地址換成了我的吐槽十籍。。晾嘶。

如果您需要測(cè)試數(shù)據(jù)妓雾,您可以簡(jiǎn)信我或者加我的QQ來拿到地址,非常抱歉我這么做垒迂。(我很害怕到畢業(yè)設(shè)計(jì)答辯那天來個(gè)攻擊靶狄觥)
【END】


在上篇,我們初步完成了評(píng)論popup的展示机断,這一次我們需要補(bǔ)全剩下的交互代碼楷拳。

首先上預(yù)覽圖吧(為了方便,以后統(tǒng)一在電腦模擬器上錄制):

preview

<h1 id="step1">Step 1:困境</h1>


在實(shí)現(xiàn)之前吏奸,不妨看看我們現(xiàn)在遇到的問題:

如下圖:

結(jié)構(gòu)圖

從圖中我們可以看到欢揖,我們現(xiàn)在整一個(gè)朋友圈的實(shí)現(xiàn)方案如下:

  • Activity作為一個(gè)controller,它現(xiàn)在僅僅負(fù)責(zé)的是拉取數(shù)據(jù)奋蔚,并沒有其他的工作她混。
  • Adapter,在我們將viewholder抽象出來后泊碑,adapter看起來僅僅就是將類型跟對(duì)應(yīng)的viewholder匹配起來坤按,并渲染出來。
  • 而ViewHolder馒过,則是負(fù)責(zé)將數(shù)據(jù)展示臭脓,同時(shí)一切的數(shù)據(jù)/操作都是在ViewHolder實(shí)現(xiàn)的。

那么問題來了腹忽,我們的工程進(jìn)行到這里来累,我們其實(shí)沒有做任何的點(diǎn)擊/請(qǐng)求(朋友圈列表拉取除外)而如今,我們需要增加交互等方法窘奏,按照?qǐng)D中的結(jié)構(gòu)嘹锁,我們可以有如下的方法(目前我所想到的):

  • 因?yàn)関iewholder持有activity的context,我們可以通過activity提供公用方法着裹,然后使用(if context instance of xxx){ (Activity)context.xxxx}來調(diào)用activity的方法
  • EventBus事件通知
  • 中間類领猾,使用中間類來處理activity與viewholder之間的交互。

顯然,方法一過于笨重不便于擴(kuò)展瘤运,方法二雖然挺方便的,但是在onEventMainThread方法里我們需要很多的判斷匠题,所以我們使用方法三拯坟。

那么這個(gè)中間類是干什么的呢?直觀的說韭山,就是如下圖這樣的結(jié)構(gòu):

中間層

可能看圖還是有點(diǎn)不太明白郁季,那我們通過一個(gè)例子來解釋一下吧:

假如故事發(fā)生在一個(gè)初創(chuàng)公司,這個(gè)公司目前的分層如下:

  • BOSS(對(duì)應(yīng)Activity
  • 技術(shù)總監(jiān)CTO(對(duì)應(yīng)Adapter
  • 具體各個(gè)技術(shù)小組的leader(對(duì)應(yīng)各個(gè)ViewHolder

在開始階段因?yàn)榧毙枰龀霎a(chǎn)品給投資人看效果钱磅,所以并沒有招到很多人梦裂,因此一直都是Boss下發(fā)需求給CTO,然后CTO評(píng)估后再下發(fā)給技術(shù)小組的leader盖淡,然后leader完成需求年柠。
類比于我們擼朋友圈目前進(jìn)度:先完成界面展示,而不管任何交互)*

在前期褪迟,這樣做問題不大冗恨,OK,這個(gè)初創(chuàng)公司順利的拿下A輪投資味赃,接下來B輪就需要打造產(chǎn)品特點(diǎn)和細(xì)節(jié)研磨掀抹,這時(shí)候就會(huì)發(fā)現(xiàn),如果還是按照之前的做法(boss->cto->leader->產(chǎn)品生產(chǎn))心俗,效率大大的降低傲武,同時(shí)因?yàn)閘eader忙著忙那,同時(shí)應(yīng)對(duì)著boss變來變?nèi)サ男枨蟪情唬谧约贺?fù)責(zé)的區(qū)域應(yīng)對(duì)著一波又一波的需求忙得焦頭爛額揪利。(viewholder與activity耦合度過高)

于是,他們決定請(qǐng)人吠谢。經(jīng)過簡(jiǎn)歷篩選土童,筆試,面試后工坊,他們找到了合適的人選献汗,于是接下來的分工就變成了這樣:

  • boss下發(fā)需求(此時(shí)其實(shí)應(yīng)該是boss跟產(chǎn)品評(píng)估,但為了篇幅王污,先略過產(chǎn)品)(Activity通知adapter更新)
  • CTO開評(píng)估會(huì)議并細(xì)分/下發(fā)任務(wù)(Adapter將數(shù)據(jù)分發(fā)到各個(gè)viewholder并渲染)
  • leader收到任務(wù)罢吃,開內(nèi)部會(huì)議進(jìn)行分工,而leader則是負(fù)責(zé)項(xiàng)目結(jié)構(gòu)昭齐,項(xiàng)目基礎(chǔ)框架的優(yōu)化等(viewholder綁定數(shù)據(jù)并展示)
    • 各個(gè)小組成員收到任務(wù)尿招,開始投入生產(chǎn)(碼代碼)(controll處理各種交互,請(qǐng)求等事件)

然后當(dāng)各個(gè)小組成員完成任務(wù),交由給leader就谜,leader進(jìn)行review后交由測(cè)試怪蔑,測(cè)試通過后可以通知可以準(zhǔn)備發(fā)版。在各個(gè)高層使用過初步滿意后正式發(fā)版丧荐。(在我們的代碼里缆瓣,省略那么多步驟,直接通知activity進(jìn)行更新)

說了那么多虹统,其實(shí)就是一句話:controller承擔(dān)了最繁瑣的步驟弓坞,做好后通知activity去更新數(shù)據(jù)。

<h1 id="step2">Step 2:controller的實(shí)現(xiàn)</h1>


在一大篇無(wú)聊的敘述后车荔,我們就談?wù)勅绾螌?shí)現(xiàn)controller渡冻。談起controller,就不得不想到MVC忧便,進(jìn)而想到MVP族吻。我們這里并非實(shí)現(xiàn)MVP,但總的來說茬腿,有點(diǎn)形似而神不似吧呼奢。

首先既然要做解耦,那就必須涉及到抽象切平,而抽象握础,就我經(jīng)驗(yàn)來說,接口化應(yīng)該是最好的悴品。

所以我們先抽象出一個(gè)BaseController(注:此接口不遵循單一職責(zé)原則):

/**
 * Created by 大燈泡 on 2016/3/9.
 * 控制器接口化
 */
public interface BaseDynamicController {
    // 點(diǎn)贊
    void addPraise(long userid, long dynamicid, MomentsInfo info, @RequestType.DynamicRequestType int requesttype);
    // 取消點(diǎn)贊
    void cancelPraise(long userid, long dynamicid, MomentsInfo info, @RequestType.DynamicRequestType int requesttype);

}

我們需要的操作都將會(huì)在接口里限定早芭。
關(guān)于@RequestType.DynamicRequestType畦韭,一般而言,在需要傳入一定范圍內(nèi)的值時(shí),我們應(yīng)該使用注解限定啥容,這樣可以降低誤操作涵妥。另外RequestType用于區(qū)分同一個(gè)類下多個(gè)請(qǐng)求促脉。

所以這里限定傳入的值必須是DynamicRequestType所支持的值:

public class RequestType {

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ADD_PRAISE,CANCEL_PRAISE})
    public @interface DynamicRequestType{}
    // 點(diǎn)贊
    public static final int ADD_PRAISE=0x10;
    public static final int CANCEL_PRAISE=0x11;
}

目前我們只做了點(diǎn)贊和取消點(diǎn)贊悦污,所以暫時(shí)只需要這兩個(gè)類型。

接口寫完后退子,我們接下來需要實(shí)現(xiàn)這個(gè)接口岖妄,定義一個(gè)類DynamicController并實(shí)現(xiàn)請(qǐng)求回調(diào)接口以及剛剛我們定義的controller接口:

/**
 * Created by 大燈泡 on 2016/3/8.
 * 事件控制器
 * 本控制器用于BaseItemDelegate的事件處理
 * 事件處理完成通過callback回調(diào)給activity,避免BaseItem與activity耦合度過高
 */
public class DynamicController implements BaseResponseListener, BaseDynamicController {
    private static final String TAG = "DynamicController";
    private CallBack mCallBack;
    private Activity mContext;
    //=============================================================request
    private DynamicAddPraiseRequest mDynamicAddPraiseRequest;
    private DynamicCancelPraiseRequest mDynamicCancelPraiseRequest;

    public DynamicController(Activity context, @NonNull CallBack callBack) {
        mContext = context;
        mCallBack = callBack;
    }

    //=============================================================request callback
    @Override
    public void onStart(BaseResponse response) {

    }

    @Override
    public void onStop(BaseResponse response) {

    }

    @Override
    public void onFailure(BaseResponse response) {

    }

    @Override
    public void onSuccess(BaseResponse response) {
    
    }

    //=============================================================controller methods
    @Override
    public void addPraise(long userid, long dynamicid, MomentsInfo info,
                          @RequestType.DynamicRequestType int requesttype) {

    }

    @Override
    public void cancelPraise(long userid, long dynamicid, MomentsInfo info,
                             @RequestType.DynamicRequestType int requesttype) {

    }
    //=============================================================destroy
    public void destroyController() {
        
    }
    public interface CallBack {
        void onResultCallBack(BaseResponse response);
    }
}

在處理完成后寂祥,我們需要通知activity進(jìn)行數(shù)據(jù)更新荐虐,所以我們需要定一個(gè)CallBack,讓activity實(shí)現(xiàn)這個(gè)接口丸凭。同時(shí)為了緊張的內(nèi)存福扬,我們還需要定義一個(gè)destroy方法腕铸,及時(shí)的進(jìn)行對(duì)象置空。

接下來改造一下我們的BaseItemDelegate铛碑,因?yàn)槲覀儺?dāng)初設(shè)計(jì)的時(shí)候是采取接口的形式狠裹,所以我們實(shí)質(zhì)上是改造BaseItemView

public interface BaseItemView<T> {
...
    void setController(BaseDynamicController controller);
    BaseDynamicController getController();
}

我們?cè)贐aseItemView添加controller的setter/getter,然后在BaseItemDelegate進(jìn)行賦值汽烦,最后就到我們的Adapter進(jìn)行設(shè)置:
CircleBaseAdapter.java:

    //因?yàn)槲覀儺?dāng)初設(shè)計(jì)的時(shí)候采用的builder模式酪耳,所以我們僅僅需要到builder添加一個(gè)參數(shù)就好了,這里就不貼代碼了刹缝。
    public CircleBaseAdapter(Activity context, Builder<T> mBuilder) {
      ...
        mDynamicController=mBuilder.mDynamicController;
    }
...
      @Override
    public View getView(int position, View convertView, ViewGroup parent) {
       ...
        view.setActivityContext(context);
        view.onFindView(convertView);
        view.onBindData(position, convertView, getItem(position), dynamicType);
        if (view.getController()==null)view.setController(mDynamicController);

        return convertView;
    }

因?yàn)橹貜?fù)的代碼在之前的簡(jiǎn)書都有記錄,所以這里就略過了颈将,如果您看的云里霧里梢夯,可以在這篇文章看到所有的解析。

在viewholder和controller完成后晴圾,我們最后需要在activity將controller給new出來颂砸,然后添加到builder里面,使adapter,activity共同持有一個(gè)對(duì)象死姚。

public class FriendCircleDemoActivity extends FriendCircleBaseActivity implements DynamicController.CallBack {
    private FriendCircleRequest mCircleRequest;

    private DynamicController mDynamicController;

    // 方案二人乓,預(yù)留
 /*   @Override
    protected void onEventMainThread(Events events) {
        if (events == null || events.getEvent() == null) return;
        if (events.getEvent() instanceof Events.CallToRefresh) {
            if (((Events.CallToRefresh) events.getEvent()).needRefresh) mCircleRequest.execute();
        }
    }*/

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     ...
        bindListView(R.id.listview, header,
                FriendCircleAdapterUtil.getAdapter(this, mMomentsInfos, mDynamicController));
        initReq();
        //mListView.manualRefresh();
    }
...

    @Override
    public void onResultCallBack(BaseResponse response) {
        
    }
...
    }

其中FriendCircleAdapterUtil的代碼略過,詳情可以看GitHub都毒。

到這里為止色罚,我們的結(jié)構(gòu)大致完成,接下來就是將剩下的代碼補(bǔ)全账劲。

<h1 id="step3">Step 3:代碼補(bǔ)全</h1>


首先我們思考一下戳护,如何更新我們的界面是最好的。目前來說我想到有以下方法:

  • 當(dāng)點(diǎn)贊/取消點(diǎn)贊請(qǐng)求成功后瀑焦,我們調(diào)用activity的列表請(qǐng)求將整個(gè)朋友圈數(shù)據(jù)都重新拉一遍腌且。
  • 當(dāng)點(diǎn)贊/取消點(diǎn)贊請(qǐng)求成功后,服務(wù)器返回當(dāng)前動(dòng)態(tài)的點(diǎn)贊列表信息榛瓮,我們獲取當(dāng)前動(dòng)態(tài)的實(shí)體類铺董,解析服務(wù)器返回信息后進(jìn)行更新操作。
  • 當(dāng)點(diǎn)贊/取消點(diǎn)贊請(qǐng)求成功后禀晓,本地進(jìn)行插入/刪除精续。

上面幾個(gè)方案中
第一個(gè)方案很明顯是不適合的,因?yàn)槊看吸c(diǎn)贊都需要將整個(gè)列表拉一次匆绣,解析耗時(shí)不說驻右,就流量消耗也是很可觀的。遂放棄崎淳。

第二個(gè)方案目前采用堪夭,但也有不完善的地方,這個(gè)下文再說。

第三個(gè)方案看似不錯(cuò)森爽,但如果遇到下面這種情況就不適應(yīng)了:你點(diǎn)贊的同時(shí)你好友也點(diǎn)贊恨豁,但因?yàn)闆]有數(shù)據(jù)返回,所以你好友的點(diǎn)贊并沒有刷出來爬迟。

綜上所述橘蜜,目前采取第二個(gè)方案。(ps:第二個(gè)方案目前我的實(shí)現(xiàn)并不好付呕,但暫時(shí)沒有想到更好的方案计福,如果您有好的建議,在下衷心希望可以留下您的評(píng)論

首先回到我們的controller中徽职,在CallBack里我們傳遞的參數(shù)是BaseResponse象颖,但BaseResponse當(dāng)初我們?cè)O(shè)計(jì)的時(shí)候,其接受的數(shù)據(jù)如下:

public class BaseResponse {
    //請(qǐng)求碼
    private int status;
    //錯(cuò)誤碼
    private int errorCode;
    //請(qǐng)求類型姆钉,用于單activity多個(gè)請(qǐng)求的區(qū)分
    private int requestType;
    //請(qǐng)求回來的JSON字符串
    private String jsonStr;
    //錯(cuò)誤信息
    private String errorMsg;
    //待用说订,可以存放解析后的JSON Array
    private ArrayList<Object> datas=new ArrayList<>();
    //存放解析后的數(shù)據(jù)
    private Object data;
    //是否展示dialog
    private boolean showDialog;

    private int start;
    private boolean hasMore;
...
}

可以看到,我們存放數(shù)據(jù)的地方僅僅只有一個(gè)Object潮瓶,但我們需要拿到一個(gè)動(dòng)態(tài)實(shí)體和點(diǎn)贊后服務(wù)器返回的數(shù)據(jù)解析陶冷。當(dāng)然,我們可以選擇在BaseResponse再添加一個(gè)對(duì)象來存放毯辅,但如果這樣做埂伦,就會(huì)導(dǎo)致以后這個(gè)類也許會(huì)越來越臃腫。而這并不是我所希望看到的思恐。

所以赤屋,我們需要開辟一個(gè)新的用于controller的實(shí)體:

/**
 * Created by 大燈泡 on 2016/3/10.
 * 控制器實(shí)體類
 */
public class DynamicControllerEntity<T> {
    private MomentsInfo mMomentsInfo;
    private T data;

    public MomentsInfo getMomentsInfo() {
        return mMomentsInfo;
    }
    public void setMomentsInfo(MomentsInfo momentsInfo) {
        mMomentsInfo = momentsInfo;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
}

做好這個(gè)之后,我們補(bǔ)全controller的內(nèi)容:

/**
 * Created by 大燈泡 on 2016/3/8.
 * 事件控制器
 * 本控制器用于BaseItemDelegate的事件處理
 * 事件處理完成通過callback回調(diào)給activity壁袄,避免BaseItem與activity耦合度過高
 */
public class DynamicController implements BaseResponseListener, BaseDynamicController {
    private static final String TAG = "DynamicController";
    private CallBack mCallBack;
    private Activity mContext;
    //=============================================================request
    private DynamicAddPraiseRequest mDynamicAddPraiseRequest;
    private DynamicCancelPraiseRequest mDynamicCancelPraiseRequest;

    public DynamicController(Activity context, @NonNull CallBack callBack) {
        mContext = context;
        mCallBack = callBack;
    }

    //=============================================================request callback
...

    @Override
    public void onSuccess(BaseResponse response) {
        if (response.getStatus() == 200) {
            if (mCallBack != null) mCallBack.onResultCallBack(response);
        }
        else {
            ToastUtils.ToastMessage(mContext, response.getErrorMsg());
        }
    }

    //=============================================================controller methods
    @Override
    public void addPraise(long userid, long dynamicid, MomentsInfo info,
                          @RequestType.DynamicRequestType int requesttype) {
        if (mDynamicAddPraiseRequest == null) {
            mDynamicAddPraiseRequest = new DynamicAddPraiseRequest(info);
            mDynamicAddPraiseRequest.setOnResponseListener(this);
            mDynamicAddPraiseRequest.setRequestType(requesttype);
        }
        mDynamicAddPraiseRequest.setInfo(info);
        mDynamicAddPraiseRequest.userid = userid;
        mDynamicAddPraiseRequest.dynamicid = dynamicid;
        mDynamicAddPraiseRequest.execute();
    }

    @Override
    public void cancelPraise(long userid, long dynamicid, MomentsInfo info,
                             @RequestType.DynamicRequestType int requesttype) {
        if (mDynamicCancelPraiseRequest == null) {
            mDynamicCancelPraiseRequest = new DynamicCancelPraiseRequest(info);
            mDynamicCancelPraiseRequest.setOnResponseListener(this);
            mDynamicCancelPraiseRequest.setRequestType(requesttype);
        }
        mDynamicCancelPraiseRequest.setInfo(info);
        mDynamicCancelPraiseRequest.userid = userid;
        mDynamicCancelPraiseRequest.dynamicid = dynamicid;
        mDynamicCancelPraiseRequest.execute();
    }
    //=============================================================destroy
    public void destroyController() {
        mDynamicAddPraiseRequest = null;
        mCallBack = null;
    }
    public interface CallBack {
        void onResultCallBack(BaseResponse response);
    }
}

當(dāng)我們請(qǐng)求成功后类早,才調(diào)用的activity回調(diào)方法,所以我們?cè)赼ctivity處理的時(shí)候必定是成功后的事件嗜逻。

接下來在我們的請(qǐng)求里做對(duì)應(yīng)的操作:

public class DynamicAddPraiseRequest extends BaseHttpRequestClient {

    public long userid;
    public long dynamicid;
    private MomentsInfo mInfo;

    public DynamicAddPraiseRequest(MomentsInfo info) {
        mInfo = info;
    }

    public MomentsInfo getInfo() {
        return mInfo;
    }

    public void setInfo(MomentsInfo info) {
        mInfo = info;
    }

    @Override
    public String setUrl() {
        return new RequestUrlUtils.Builder().setHost(FriendCircleApp.getRootUrl())
                                            .setPath("/dynamic/addpraise/")
                                            .addParam("userid", userid)
                                            .addParam("dynamicid", dynamicid)
                                            .build();
    }

    @Override
    public void parseResponse(BaseResponse response, JSONObject json, int start, boolean hasMore) throws JSONException {
        if (response.getStatus()==200){
            DynamicControllerEntity<List<UserInfo>> entity=new DynamicControllerEntity();
            entity.setMomentsInfo(mInfo);
            List<UserInfo> praiseList= JSONUtil.toList(json.optString("data"),new TypeToken<ArrayList<UserInfo>>(){}
                    .getType
                    ());
            entity.setData(praiseList);
            response.setData(entity);
        }

    }
}

其中RequestUrlUtils是我為了方便寫url而寫的一個(gè)工具類涩僻,這里就不展示了。

最后栈顷,我們補(bǔ)全activity的回調(diào)以及BaseItemDelegate的點(diǎn)擊事件處理:

activity:

 @Override
    public void onResultCallBack(BaseResponse response) {
        // 通知更新
        switch (response.getRequestType()) {
            case RequestType.ADD_PRAISE:
                DynamicControllerEntity<List<UserInfo>> entity
                        = (DynamicControllerEntity<List<UserInfo>>) response.getData();
                MomentsInfo info = entity.getMomentsInfo();
                info.dynamicInfo.praiseState=CommonValue.HAS_PRAISE;
                if (info != null) {
                    if (info.praiseList != null) {
                        info.praiseList.clear();
                        info.praiseList.addAll(entity.getData());
                    }else {
                        info.praiseList=entity.getData();
                    }
                }
                mAdapter.notifyDataSetChanged();
                break;
            case RequestType.CANCEL_PRAISE:
                DynamicControllerEntity<List<UserInfo>> cancelEntity
                        = (DynamicControllerEntity<List<UserInfo>>) response.getData();
                MomentsInfo mInfo = cancelEntity.getMomentsInfo();
                mInfo.dynamicInfo.praiseState=CommonValue.NOT_PRAISE;
                if (mInfo != null) {
                    if (mInfo.praiseList != null) {
                        mInfo.praiseList.clear();
                        mInfo.praiseList.addAll(cancelEntity.getData());
                    }else {
                        mInfo.praiseList=cancelEntity.getData();
                    }
                }
                mAdapter.notifyDataSetChanged();
                break;
        }
    }

BaseItemDelegate:

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            // 評(píng)論按鈕
            case R.id.comment_button:
                if (mInfo == null) return;
                mCommentPopup.setDynamicInfo(mInfo.dynamicInfo);
                mCommentPopup.setOnCommentPopupClickListener(new CommentPopup.OnCommentPopupClickListener() {
                    @Override
                    public void onLikeClick(View v, DynamicInfo info) {
                        if (mDynamicController != null) {
                            switch (info.praiseState) {
                                case CommonValue.NOT_PRAISE:
                                    mDynamicController.addPraise(LocalHostInfo.INSTANCE.getHostId(), info.dynamicId,
                                            mInfo, RequestType.ADD_PRAISE);
                                    break;
                                case CommonValue.HAS_PRAISE:
                                    mDynamicController.cancelPraise(LocalHostInfo.INSTANCE.getHostId(), info.dynamicId,
                                            mInfo, RequestType.CANCEL_PRAISE);
                                    break;
                                default:
                                    break;
                            }
                        }
                    }

                    @Override
                    public void onCommentClick(View v, DynamicInfo info) {

                    }
                });
                mCommentPopup.showPopupWindow(commentImage);
                break;
            default:
                break;
        }
    }

至此逆日,我們的controller中間層實(shí)現(xiàn)完成,當(dāng)然萄凤,我覺得這樣實(shí)現(xiàn)并不太好室抽,原因如下:

  • 在CallBack中,我們把MomentsInfo給暴露了靡努,如果出現(xiàn)誤操作坪圾,導(dǎo)致的就是朋友圈內(nèi)容顯示的問題
  • 中間層依賴Request晓折,原因在于MomentsInfo的傳值方法,通過request傳兽泄,這并不太好漓概,因?yàn)閞equest理論上應(yīng)該僅僅負(fù)責(zé)請(qǐng)求和解析,不應(yīng)該作為信使病梢。

以上兩點(diǎn)在以后我希望等我的水平提高后可以解決甚至重構(gòu)胃珍。如果您有好的建議,希望能在評(píng)論區(qū)留下腳印或者GitHub提交PR蜓陌。

<h1 id="step4">Step 4:Popup的補(bǔ)充</h1>


popup在上一篇文章中已經(jīng)是初步實(shí)現(xiàn)了觅彰,本篇僅僅針對(duì)一些內(nèi)容進(jìn)行補(bǔ)充:

我們的評(píng)論popup有兩個(gè)功能:

  • 點(diǎn)贊
  • 評(píng)論

也就是說有兩個(gè)按鈕,但我們不應(yīng)該把事件都放到popup類里面完成钮热,這會(huì)導(dǎo)致耦合度問題(事件處理必定需要viewholder里面的數(shù)據(jù)缔莲,如果在popup里面完成,意味著需要跟viewholder相互依賴)霉旗,因此,我們采取接口蛀骇,將點(diǎn)擊動(dòng)作拋出去給viewholder自己處理厌秒。

首先我們定義一個(gè)接口:

    public interface OnCommentPopupClickListener {
        void onLikeClick(View v, DynamicInfo info);

        void onCommentClick(View v, DynamicInfo info);
    }

因?yàn)辄c(diǎn)贊和評(píng)論都涉及到數(shù)據(jù)庫(kù)對(duì)動(dòng)態(tài)id的CRUD操作,所以我們直接傳入DynamicInfo(DynamicInfo和接口的setter/getter略)擅憔。

然后我們?cè)趐opup里面實(shí)現(xiàn)onClickListener:

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.item_like:
                if (mOnCommentPopupClickListener != null) {
                    mOnCommentPopupClickListener.onLikeClick(v, mDynamicInfo);
                    mLikeView.clearAnimation();
                    mLikeView.startAnimation(mScaleAnimation);
                }
                break;
            case R.id.item_comment:
                if (mOnCommentPopupClickListener != null) {
                    mOnCommentPopupClickListener.onCommentClick(v, mDynamicInfo);
                    dismiss();
                }
                break;
        }
    }

最后外部viewholder實(shí)現(xiàn)接口(見Step 3最后)

事件處理解決后鸵闪,第二個(gè)問題,我們可以看到朋友圈點(diǎn)贊的心心是有一個(gè)動(dòng)畫效果的暑诸,簡(jiǎn)單的描述就是:心心放大蚌讼,然后縮小。

要實(shí)現(xiàn)這個(gè)效果可以說很簡(jiǎn)單:給兩個(gè)Animation个榕,在第一個(gè)結(jié)束的onAnimationEnd調(diào)用第二個(gè)Animation不就行了么篡石?

是的,這樣是非常簡(jiǎn)單西采,也十分明了凰萨。但,這樣做就需要兩個(gè)Animation對(duì)象械馆,對(duì)于內(nèi)存十分看緊的我胖眷,決定使用一個(gè)對(duì)象完成。

那么霹崎,要使用一個(gè)Animation完成放大后縮小的效果珊搀,就不得不提到插值器這個(gè)東東了。

插值器簡(jiǎn)單的說尾菇,就是改變動(dòng)畫的不同時(shí)間的值境析,從而改變動(dòng)畫的變化率囚枪。

這里推薦一個(gè)網(wǎng)站,這個(gè)網(wǎng)站可以將公式可視化為插值器曲線:
http://inloop.github.io/interpolator/

那么要實(shí)現(xiàn)先放大后縮小簿晓,我們的插值器曲線必定是先上升后下降眶拉,這時(shí)候很容易想到一個(gè)初中學(xué)過的東西:三角函數(shù)

sin函數(shù)在一個(gè)周期內(nèi)有兩個(gè)峰值,±1憔儿,而我們?nèi)“雮€(gè)周期就可以得到一條先升后降的曲線了忆植。

如果可視化

效果如下圖:

interpolator

不好意思,因?yàn)樘猛媪粟司剩远嗤媪艘粫?huì)朝刊。。蜈缤。拾氓。

在代碼上,我們只需要繼承LinearInterpolator然后重寫getInterpolation就可以了底哥。

    static class SpringInterPolator extends LinearInterpolator {

        public SpringInterPolator() {
        }

        @Override
        public float getInterpolation(float input) {
            return (float) Math.sin(input*Math.PI);
        }
    }

在動(dòng)畫setInterpolator的時(shí)候使用SpringInterPolator即可咙鞍。

剩下的代碼也就不貼了。

下一篇我們完成評(píng)論區(qū)的事件交互趾徽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末续滋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子孵奶,更是在濱河造成了極大的恐慌疲酌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件了袁,死亡現(xiàn)場(chǎng)離奇詭異朗恳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)载绿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門粥诫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人崭庸,你說我怎么就攤上這事臀脏。” “怎么了冀自?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵揉稚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我熬粗,道長(zhǎng)搀玖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任驻呐,我火速辦了婚禮灌诅,結(jié)果婚禮上芳来,老公的妹妹穿的比我還像新娘。我一直安慰自己猜拾,他們只是感情好即舌,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挎袜,像睡著了一般顽聂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盯仪,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天紊搪,我揣著相機(jī)與錄音,去河邊找鬼全景。 笑死耀石,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爸黄。 我是一名探鬼主播滞伟,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼炕贵!你這毒婦竟也來了梆奈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鲁驶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后舞骆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钥弯,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年督禽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脆霎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狈惫,死狀恐怖睛蛛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胧谈,我是刑警寧澤忆肾,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站菱肖,受9級(jí)特大地震影響客冈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稳强,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一场仲、第九天 我趴在偏房一處隱蔽的房頂上張望和悦。 院中可真熱鬧,春花似錦渠缕、人聲如沸鸽素。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)馍忽。三九已至,卻和暖如春蚜迅,著一層夾襖步出監(jiān)牢的瞬間舵匾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工谁不, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坐梯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓刹帕,卻偏偏與公主長(zhǎng)得像吵血,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子偷溺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容