一起擼個(gè)朋友圈吧 - 重構(gòu)

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

上篇鏈接:http://www.reibang.com/p/deda1849a084
下篇鏈接:http://www.reibang.com/p/8d24f9b7a63a

在我們這個(gè)項(xiàng)目開發(fā)進(jìn)行到一半,因?yàn)槟承﹩栴},我決定停下來重構(gòu)一下窄潭。

是的赃绊,作為一枚開發(fā)译红,我覺得重構(gòu)是一件很正常的事情铛铁。還好,因?yàn)槲覀兊腣iewHolder的抽象化崎溃,重構(gòu)工程不大蜻直,也許我們可以看作是一次局部改造吧。

本次重構(gòu)的內(nèi)容如下:

  • 評(píng)論控件的改進(jìn)
  • viewholder和activity之間的關(guān)系推翻重做(采用mvp模式)

本篇文章將要講述的是為何要重構(gòu)以及重構(gòu)的過程袁串。


原因:

原因很簡單概而,因?yàn)榇a寫著寫著發(fā)現(xiàn)我們?cè)绞窍胨神詈蠀s越發(fā)覺不知不覺間緊耦合。

還記得上一篇文章的這張圖嗎囱修?


看起來感覺還行赎瑰,的確,在做評(píng)論之前這個(gè)結(jié)構(gòu)確實(shí)還行破镰,但要做評(píng)論的時(shí)候餐曼,這個(gè)結(jié)構(gòu)就不行了。

原因很簡單鲜漩,因?yàn)槲覀兊腸ontroller是單向的源譬。

viewholder單向操作controller,controller單項(xiàng)通知Activity更新adapter孕似。

在此之前瓶佳,我們的操作幾乎都是單向的,比如點(diǎn)贊鳞青。

點(diǎn)贊的操作就是:贊一個(gè)→請(qǐng)求→請(qǐng)求完成→刷新數(shù)據(jù)→展示→完霸饲。

這沒問題,互不涉及臂拓,互不干擾厚脉,各自完成各自的工作。

但到現(xiàn)在胶惰,我們需要評(píng)論了傻工,這時(shí)候問題來了:

  • 評(píng)論控件不可能在ViewHolder,肯定是在Activity層
  • 評(píng)論等操作的觸發(fā)條件卻是在ViewHolder孵滞,比如點(diǎn)了某個(gè)評(píng)論進(jìn)行回復(fù)等中捆。

那么問題就出在這個(gè)單向聯(lián)系的問題,很明顯我們現(xiàn)在需要的是ViewHolder調(diào)用Activity的某個(gè)方法來控制評(píng)論框坊饶。如果按照現(xiàn)在這種結(jié)構(gòu)做下去泄伪,得到的,只能是這幾個(gè)類的聯(lián)系越來越緊密匿级。

所以蟋滴,我們需要采取一下別的方式了。

于是就有了這次的重構(gòu)痘绎。

重構(gòu):

說到各種模式津函,對(duì)于老大MVC,在越來越重的需求里孤页,發(fā)現(xiàn)Model層的負(fù)擔(dān)太重尔苦,又要處理事件觸發(fā)又要處理數(shù)據(jù)什么的。

于是就有了最近挺火的MVP行施,關(guān)于MVP允坚,網(wǎng)上有很多很多的例子和解析,這里就不多說明了悲龟。

當(dāng)然屋讶,還有MVVM,因?yàn)镈ataBinding而火起來须教,也許以后在java代碼里調(diào)用view皿渗,塞入數(shù)據(jù)這種代碼可能很少。

但目前起碼不會(huì)轻腺,所以為了防止大破壞乐疆,我們采取MVP的模式。


首先贬养,我們需要干掉舊的挤土,拿起手中的砍刀(del鍵),到我們項(xiàng)目的app下把controller這個(gè)包包給砍掉误算。

然后仰美,我們把新歡mvp這個(gè)包包弄進(jìn)來-V-(舊的不去新的不來←_←)

于是我們的結(jié)構(gòu)就變成了這樣(重新整理過):

其他地方改動(dòng)不大迷殿,app包下現(xiàn)在如下:

  • app包主要存放各種基類或配置,接口等
    • adapter:我們的朋友圈adapter基類就在這里安家哦
    • config:各種配置參數(shù)咖杂,比如下拉刷新模式的定義庆寺,是否點(diǎn)贊的狀態(tài)定義什么的。
    • https:volley的封裝诉字,嗯懦尝。。壤圃。咱們只是從network換成了https陵霉,其實(shí)我覺得應(yīng)該換成http,畢竟http和https是兩種東東對(duì)吧
    • interfaces:接口喲
    • mvp:本次的主角

其他包換湯不換藥伍绳,我們主要看mvp踊挠。

mvp包包

按照規(guī)范,咱們弄一個(gè)model/presenter/view三個(gè)包包墨叛,然后m層里面放的bean包和實(shí)現(xiàn)類的包止毕,而根包下放的則是接口。

其余相同


Step 1:View

首先我們定義一個(gè)接口漠趁,叫做DynamicView扁凛,該接口實(shí)現(xiàn)三個(gè)方法:

  • 刷新點(diǎn)贊數(shù)據(jù)
  • 刷新評(píng)論數(shù)據(jù)
  • 展示評(píng)論框
/**
 * Created by 大燈泡 on 2016/3/17.
 * mvp-視圖層
 * 僅用于更新/展示數(shù)據(jù)和部分的用戶交互
 */
public interface DynamicView{

    // 點(diǎn)贊刷新
    void refreshPraiseData(int currentDynamicPos,
                           @CommonValue.PraiseState int praiseState, @NonNull List<UserInfo> praiseList);

    // 評(píng)論刷新
    void refreshCommentData(int currentDynamicPos,
                            @RequestType.CommentRequestType int requestType, @NonNull List<CommentInfo> commentList);

    // 評(píng)論框展示
    void showInputBox(int currentDynamicPos, @CommonValue.CommentType int commentType, CommentInfo commentInfo);
}

在上一篇文章里,我說過暴露了MomentsInfo是個(gè)很不好的方案闯传,所以這次我們只需要把當(dāng)前操作的動(dòng)態(tài)(viewholder)所在的位置拋出去就好了谨朝,外面可以通過adapter得到對(duì)應(yīng)數(shù)據(jù)。

我們view實(shí)現(xiàn)的前兩個(gè)個(gè)方法僅僅用于數(shù)據(jù)刷新甥绿,數(shù)據(jù)來源當(dāng)然是我們親愛的model了字币,第三個(gè)方法則是view層本該承擔(dān)的部分交互。

寫完View共缕,我們接下來寫寫model洗出。


Step 2:Model

對(duì)于Model,我們可以直接選擇一個(gè)類图谷,但對(duì)于功能復(fù)雜點(diǎn)的翩活,我們還是應(yīng)該使用接口來松耦合。

那啥便贵,多組合少繼承菠镇,接口化高抽象。這才是一個(gè)優(yōu)秀項(xiàng)目所應(yīng)該擁有的對(duì)吧(當(dāng)然承璃,我這個(gè)項(xiàng)目并非屬于優(yōu)秀項(xiàng)目-V-利耍,但我們可以盡力做到優(yōu)秀)

廢話不多說,首先創(chuàng)建一個(gè)針對(duì)點(diǎn)贊用的接口:

/**
 * Created by 大燈泡 on 2016/3/17.
 * model - 點(diǎn)贊接口
 */
public interface PraiseModel {

    //點(diǎn)贊
    void addPraise(int currentDynamicPos, long userid, long dynamicid);

    // 取消點(diǎn)贊
    void cancelPraise(int currentDynamicPos, long userid, long dynamicid);
}

點(diǎn)贊接口的參數(shù)跟我們之前的controller并沒有很大的區(qū)別。

然后創(chuàng)建我們的實(shí)現(xiàn)類:DynamicModelImpl

/**
 * Created by 大燈泡 on 2016/3/17.
 * mvp - model
 * 復(fù)雜邏輯的操作/請(qǐng)求/耗時(shí)等隘梨,處理后的數(shù)據(jù)提供
 *
 * 若過于復(fù)雜的操作程癌,可能需要接口來松耦合
 */
public class DynamicModelImpl implements BaseResponseListener, PraiseModel {
    private DynamicResultCallBack callBack;

    public DynamicModelImpl(DynamicResultCallBack callBack) {
        this.callBack = callBack;
    }

    //=============================================================request
    private DynamicAddPraiseRequest mDynamicAddPraiseRequest;
    private DynamicCancelPraiseRequest mDynamicCancelPraiseRequest;

    //=============================================================public methods
    @Override
    public void addPraise(int currentDynamicPos, long userid, long dynamicid) {

    }

    @Override
    public void cancelPraise(int currentDynamicPos, long userid, long dynamicid) {

    }

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

    }

    @Override
    public void onStop(BaseResponse response) {

    }

    @Override
    public void onFailure(BaseResponse response) {

    }

    @Override
    public void onSuccess(BaseResponse response) {

    }
}

我們的實(shí)現(xiàn)類最基本需要實(shí)現(xiàn)一個(gè)接口:BaseResponseListener

因?yàn)檎?qǐng)求都在這里執(zhí)行,所以我們當(dāng)初設(shè)計(jì)volley時(shí)就單獨(dú)抽象出來了出嘹,這樣就可以任意類使用席楚。

同時(shí)實(shí)現(xiàn)我們的model接口,于是就得到了上述代碼的結(jié)構(gòu)了税稼。

最后我們還需要一個(gè)callback,用來回調(diào)通知presenter垮斯,告訴他咱們工作完成了郎仆,請(qǐng)上報(bào)總司令進(jìn)行下一階段任務(wù)的安排。


Step 3:Presenter

Presenter的主要作用是用來溝通M層和V層兜蠕,所以我們的Presenter需要持有V層和M層扰肌,但P層和V層的溝通主要還是通過接口,果然接口拯救世界靶苎睢(畢竟咱們沒有多繼承對(duì)吧)

于是我們的Presenter這么寫:

/**
 * Created by 大燈泡 on 2016/3/17.
 * mvp - 引導(dǎo)層
 * 用于接收view的操作命令分發(fā)到model層實(shí)現(xiàn)
 */
public class DynamicPresenterImpl implements DynamicResultCallBack {
    private DynamicModelImpl mModel;
    private DynamicView mView;

    public DynamicPresenterImpl(DynamicView view) {
        mView = view;
        mModel = new DynamicModelImpl(this);
    }

    // 點(diǎn)贊
    public void addPraise(int curDynamicPos, long dynamicId) {
        mModel.addPraise(curDynamicPos, LocalHostInfo.INSTANCE.getHostId(), dynamicId);
    }

    // 取消贊
    public void cancelPraise(int curDynamicPos, long dynamicId) {
        mModel.cancelPraise(curDynamicPos, LocalHostInfo.INSTANCE.getHostId(), dynamicId);
    }

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

其中DynamicResultCallBack我們說過曙旭,是用來回調(diào)接受M層處理后的數(shù)據(jù)的,它只有一個(gè)方法:onResultCallBack(BaseResponse response) { }晶府,當(dāng)請(qǐng)求成功后回調(diào)桂躏。

至此,我們的MVP結(jié)構(gòu)大致寫好川陆。接下來需要做的剂习,就是去Activity改造


Step 4:Activity(View)改造

回到我們的FriendCircleDemoActivity,首先我們實(shí)現(xiàn)DynamicView這個(gè)接口较沪,并在onCreate里面把presenter給new出來:

/**
 * Created by 大燈泡 on 2016/2/25.
 * 朋友圈demo窗口
 */
public class FriendCircleDemoActivity extends FriendCircleBaseActivity implements DynamicView, View.OnClickListener {
    private FriendCircleRequest mCircleRequest;
    private DynamicPresenterImpl mPresenter;
...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mPresenter = new DynamicPresenterImpl(this);
        initView();
        initReq();
        //mListView.manualRefresh();
    }
...

    //=============================================================mvp - view's method

    @Override
    public void refreshPraiseData(int currentDynamicPos,
                                  @CommonValue.PraiseState int praiseState, @NonNull List<UserInfo> praiseList) {

    }

    @Override
    public void refreshCommentData(int currentDynamicPos,
                                   @RequestType.CommentRequestType int requestType,
                                   @NonNull List<CommentInfo> commentList) {

    }

    @Override
    public void showInputBox(int currentDynamicPos, @CommonValue.CommentType int commentType, CommentInfo commentInfo) {

    }
}

事實(shí)上鳞绕,為了更方便的針對(duì)生命期控制,我們的presenter理論上來說還需要寫上對(duì)應(yīng)生命期方法尸曼,但们何,這里咱們就算了,畢竟玩來玩去也就一個(gè)Activity控轿。

我們的view實(shí)現(xiàn)了DynamicView的三個(gè)方法冤竹,這里將會(huì)是在數(shù)據(jù)請(qǐng)求完成后presenter通知我們更新的。

事實(shí)上我們可以現(xiàn)在就補(bǔ)全我們的代碼了解幽,比如點(diǎn)贊:

    @Override
    public void refreshPraiseData(int currentDynamicPos,
                                  @CommonValue.PraiseState int praiseState, @NonNull List<UserInfo> praiseList) {
        MomentsInfo info = mAdapter.getItem(currentDynamicPos);
        if (info != null) {
            info.dynamicInfo.praiseState = praiseState;
            if (info.praiseList != null) {
                info.praiseList.clear();
                info.praiseList.addAll(praiseList);
            }
            else {
                info.praiseList = praiseList;
            }
        }
        mAdapter.notifyDataSetChanged();
    }

相比于以前的代碼贴见,這次的代碼簡潔了很多,現(xiàn)在回頭看看歷史代碼躲株,我都不敢看了TAT片部。。。

同樣档悠,我們也可以提前寫好其他實(shí)現(xiàn)的代碼廊鸥。這里就略過了。


Step 5:補(bǔ)全代碼

首先我們補(bǔ)全Model的代碼辖所,事實(shí)上惰说,Model的代碼跟上篇寫的controller差異不大,這里就展示一下點(diǎn)贊的代碼缘回,取消贊也是差不多的吆视。

   @Override
    public void addPraise(int currentDynamicPos, long userid, long dynamicid) {
        if (mDynamicAddPraiseRequest == null) {
            mDynamicAddPraiseRequest = new DynamicAddPraiseRequest();
            mDynamicAddPraiseRequest.setOnResponseListener(this);
            mDynamicAddPraiseRequest.setRequestType(RequestType.ADD_PRAISE);
        }
        mDynamicAddPraiseRequest.setCurrentDynamicPos(currentDynamicPos);
        mDynamicAddPraiseRequest.userid = userid;
        mDynamicAddPraiseRequest.dynamicid = dynamicid;
        mDynamicAddPraiseRequest.execute();
    }

通過request我們需要把當(dāng)前的動(dòng)態(tài)位置傳進(jìn)去,然后在完成后把值設(shè)置到BaseResponse回調(diào)給presenter.

接下來補(bǔ)全Presenter的代碼:

Presenter相對(duì)輕松很多酥宴,因?yàn)閺?fù)雜的都在model完成了啦吧,presenter做的僅僅是分發(fā)一下:

我們主要關(guān)注callback里面:

@Override
    public void onResultCallBack(BaseResponse response) {
        if (mView != null) {
            final int curDynamicPos = (int) response.getData();
            switch (response.getRequestType()) {
                case RequestType.ADD_PRAISE:
                    List<UserInfo> praiseList = (List<UserInfo>) response.getDatas();
                    mView.refreshPraiseData(curDynamicPos, CommonValue.HAS_PRAISE, praiseList);
                    break;
                case RequestType.CANCEL_PRAISE:
                    List<UserInfo> praiseList2 = (List<UserInfo>) response.getDatas();
                    mView.refreshPraiseData(curDynamicPos, CommonValue.NOT_PRAISE, praiseList2);
                    break;
            }
        }
    }

callback里面我們針對(duì)不同的請(qǐng)求類型通知view進(jìn)行不同的數(shù)據(jù)刷新操作,至于view的代碼拙寡,在上面我們已經(jīng)寫過了授滓。


Finally

這次也算是我的第一次使用MVP,看著教程對(duì)擼的肆糕,也不知道寫的對(duì)不對(duì)般堆,但感覺MVP在結(jié)構(gòu)上真的看的很舒服,職責(zé)分明诚啃,維護(hù)很爽淮摔。

噢,忘了绍申,咱么重構(gòu)還有第二點(diǎn)噩咪,評(píng)論控件的重構(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, "雖然在下覺得不可能會(huì)有這個(gè)情況筋搏,但還是捕捉下吧仆百,萬一被打臉呢。奔脐。俄周。");
}
}
}


這是之前的代碼
```java
    public void setCommentText(CommentInfo info) {
        if (info == null) return;
        try {
            setTag(info);
            createCommentStringBuilder(info);
        } catch (NullPointerException e) {
            e.printStackTrace();
            Log.e(TAG, "雖然在下覺得不可能會(huì)有這個(gè)情況,但還是捕捉下吧髓迎,萬一被打臉呢峦朗。。排龄。");
        }
    }

private void createCommentStringBuilder(@NonNull CommentInfo info) {
        if (mSpannableStringBuilderAllVer == null) {
            mSpannableStringBuilderAllVer = new SpannableStringBuilderAllVer();
        }
        else {
            mSpannableStringBuilderAllVer.clear();
            mSpannableStringBuilderAllVer.clearSpans();
        }
...
}

這是現(xiàn)在的代碼波势。

兩者不同的區(qū)別在于mSpannableStringBuilderAllVer這個(gè)對(duì)象。

在之前我的想法是這樣的:

如果更新前后兩個(gè)commentInfo對(duì)象一致,同時(shí)mSpannableStringBuilderAllVer存在尺铣,則直接使用拴曲,無需更新

但因?yàn)槲覀冡槍?duì)CommentWidget進(jìn)行過優(yōu)化,也就是對(duì)commentwidget進(jìn)行removeView入池文章點(diǎn)我

所以導(dǎo)致了info可能是一樣凛忿,但呈現(xiàn)起來卻是本該屬于這個(gè)動(dòng)態(tài)的評(píng)論跑到了別的動(dòng)態(tài)里了澈灼,原因就是這個(gè)池里面的對(duì)象引用的是同一個(gè)。

所以這次我們換成無論如何都需要重新進(jìn)行text的賦值店溢,問題得以解決叁熔。

下一篇,我們完成評(píng)論功能床牧。

ps:剛剛弄了一下數(shù)據(jù)庫者疤,有一條動(dòng)態(tài)是50條評(píng)論哦。叠赦。。革砸。對(duì)于這個(gè)假數(shù)據(jù)的構(gòu)造除秀,每天都在精分現(xiàn)場(chǎng)呢。算利。册踩。。等評(píng)論功能弄好后效拭,就不需要那么痛苦了暂吉。。缎患。雖然每個(gè)評(píng)論都是由“羽翼君”這位用戶進(jìn)行評(píng)論←_←

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末慕的,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挤渔,更是在濱河造成了極大的恐慌肮街,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件判导,死亡現(xiàn)場(chǎng)離奇詭異嫉父,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)眼刃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門绕辖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人擂红,你說我怎么就攤上這事仪际。” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵弟头,是天一觀的道長吩抓。 經(jīng)常有香客問我,道長赴恨,這世上最難降的妖魔是什么疹娶? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮伦连,結(jié)果婚禮上雨饺,老公的妹妹穿的比我還像新娘。我一直安慰自己惑淳,他們只是感情好额港,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歧焦,像睡著了一般移斩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绢馍,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天向瓷,我揣著相機(jī)與錄音,去河邊找鬼舰涌。 笑死猖任,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓷耙。 我是一名探鬼主播朱躺,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼搁痛!你這毒婦竟也來了长搀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤落追,失蹤者是張志新(化名)和其女友劉穎盈滴,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轿钠,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巢钓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疗垛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片症汹。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贷腕,靈堂內(nèi)的尸體忽然破棺而出背镇,到底是詐尸還是另有隱情咬展,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布瞒斩,位于F島的核電站破婆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏胸囱。R本人自食惡果不足惜祷舀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烹笔。 院中可真熱鬧裳扯,春花似錦、人聲如沸谤职。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽允蜈。三九已至冤吨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饶套,已是汗流浹背锅很。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凤跑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓叛复,卻偏偏與公主長得像仔引,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子褐奥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,515評(píng)論 25 707
  • 前言 寫Android:你是如何把Activity寫的如此“萬能”的這篇文章到現(xiàn)在已經(jīng)好久了咖耘,但是由于最近事情較多...
    牛曉偉閱讀 17,365評(píng)論 63 109
  • 作者:李旺成 時(shí)間:2016年4月3日 “Android MVP 詳解(下)”已經(jīng)發(fā)布,歡迎大家提建議撬码。 MVP ...
    diygreen閱讀 128,817評(píng)論 86 1,321
  • 轉(zhuǎn)載至:http://www.reibang.com/p/9a6845b26856 “Android MVP 詳解...
    SnowDragonYY閱讀 10,319評(píng)論 5 241
  • 日精近打卡第45天 姓名:耿洪力 單位:寧波慈星股份有限公司 組名:六項(xiàng)精進(jìn)277期謙虛三組 【知~學(xué)習(xí)】 【六項(xiàng)...
    耿洪力閱讀 206評(píng)論 0 0