項(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)一在電腦模擬器上錄制):
<h1 id="step1">Step 1:困境</h1>
在實(shí)現(xiàn)之前吏奸,不妨看看我們現(xiàn)在遇到的問題:
如下圖:
從圖中我們可以看到欢揖,我們現(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è)周期就可以得到一條先升后降的曲線了忆植。
如果可視化
效果如下圖:
不好意思,因?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ū)的事件交互趾徽。