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

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

上篇鏈接:http://www.itdecent.cn/p/15a9fe8f917f
下篇鏈接:http://www.itdecent.cn/p/58894dfb3f09

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


再開(kāi)始之前,羽翼君想說(shuō)一件事情,目前我還是一個(gè)學(xué)生,沒(méi)什么能力買(mǎi)一個(gè)牛逼的服務(wù)器,僅僅是學(xué)生價(jià)租的阿里云,但是,因?yàn)闉榱朔奖?,我把服?wù)器的IP都寫(xiě)在了我們的項(xiàng)目里面,結(jié)果他喵的昨晚被攻擊了!

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

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

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


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

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

preview

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


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

如下圖:

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

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

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

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

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

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

那么這個(gè)中間類(lèi)是干什么的呢?直觀(guān)的說(shuō),就是如下圖這樣的結(jié)構(gòu):

中間層

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

假如故事發(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

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

在前期,這樣做問(wèn)題不大,OK,這個(gè)初創(chuàng)公司順利的拿下A輪投資,接下來(lái)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變來(lái)變?nèi)サ男枨?,在自己?fù)責(zé)的區(qū)域應(yīng)對(duì)著一波又一波的需求忙得焦頭爛額。(viewholder與activity耦合度過(guò)高)

于是,他們決定請(qǐng)人。經(jīng)過(guò)簡(jiǎn)歷篩選,筆試,面試后,他們找到了合適的人選,于是接下來(lái)的分工就變成了這樣:

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

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

說(shuō)了那么多,其實(shí)就是一句話(huà):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,但總的來(lái)說(shuō),有點(diǎn)形似而神不似吧。

首先既然要做解耦,那就必須涉及到抽象,而抽象,就我經(jīng)驗(yàn)來(lái)說(shuō),接口化應(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è)類(lèi)下多個(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è)類(lèi)型。

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

/**
 * Created by 大燈泡 on 2016/3/8.
 * 事件控制器
 * 本控制器用于BaseItemDelegate的事件處理
 * 事件處理完成通過(guò)callback回調(diào)給activity,避免BaseItem與activity耦合度過(guò)高
 */
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ì)象置空。

接下來(lái)改造一下我們的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)書(shū)都有記錄,所以這里就略過(guò)了,如果您看的云里霧里,可以在這篇文章看到所有的解析。

在viewholder和controller完成后,我們最后需要在activity將controller給new出來(lái),然后添加到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的代碼略過(guò),詳情可以看GitHub。

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

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


首先我們思考一下,如何更新我們的界面是最好的。目前來(lái)說(shuō)我想到有以下方法:

  • 當(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í)體類(lèi),解析服務(wù)器返回信息后進(jìn)行更新操作。
  • 當(dāng)點(diǎn)贊/取消點(diǎn)贊請(qǐng)求成功后,本地進(jìn)行插入/刪除。

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

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

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

綜上所述,目前采取第二個(gè)方案。(ps:第二個(gè)方案目前我的實(shí)現(xiàn)并不好,但暫時(shí)沒(méi)有想到更好的方案,如果您有好的建議,在下衷心希望可以留下您的評(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)求類(lèi)型,用于單activity多個(gè)請(qǐng)求的區(qū)分
    private int requestType;
    //請(qǐng)求回來(lái)的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ì)象來(lái)存放,但如果這樣做,就會(huì)導(dǎo)致以后這個(gè)類(lèi)也許會(huì)越來(lái)越臃腫。而這并不是我所希望看到的。

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

/**
 * Created by 大燈泡 on 2016/3/10.
 * 控制器實(shí)體類(lèi)
 */
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的事件處理
 * 事件處理完成通過(guò)callback回調(diào)給activity,避免BaseItem與activity耦合度過(guò)高
 */
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í)候必定是成功后的事件。

接下來(lái)在我們的請(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是我為了方便寫(xiě)url而寫(xiě)的一個(gè)工具類(lèi),這里就不展示了。

最后,我們補(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)然,我覺(jué)得這樣實(shí)現(xiàn)并不太好,原因如下:

  • 在CallBack中,我們把MomentsInfo給暴露了,如果出現(xiàn)誤操作,導(dǎo)致的就是朋友圈內(nèi)容顯示的問(wèn)題
  • 中間層依賴(lài)Request,原因在于MomentsInfo的傳值方法,通過(guò)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)論

也就是說(shuō)有兩個(gè)按鈕,但我們不應(yīng)該把事件都放到popup類(lèi)里面完成,這會(huì)導(dǎo)致耦合度問(wèn)題(事件處理必定需要viewholder里面的數(shù)據(jù),如果在popup里面完成,意味著需要跟viewholder相互依賴(lài)),因此,我們采取接口,將點(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)接口(見(jiàn)Step 3最后)

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

要實(shí)現(xiàn)這個(gè)效果可以說(shuō)很簡(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)單的說(shuō),就是改變動(dòng)畫(huà)的不同時(shí)間的值,從而改變動(dòng)畫(huà)的變化率。

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

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

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

如果可視化

效果如下圖:

interpolator

不好意思,因?yàn)樘猛媪?,所以多玩了一?huì)。。。。

在代碼上,我們只需要繼承LinearInterpolator然后重寫(xiě)getInterpolation就可以了。

    static class SpringInterPolator extends LinearInterpolator {

        public SpringInterPolator() {
        }

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

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

剩下的代碼也就不貼了。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容