優(yōu)雅地處理加載中(loading)沫勿,重試(retry)和無數(shù)據(jù)(empty)等

LoadSir是一個(gè)高效易用挨约,低碳環(huán)保,擴(kuò)展性良好的加載反饋頁管理框架产雹,在加載網(wǎng)絡(luò)或其他數(shù)據(jù)時(shí)候诫惭,根據(jù)需求切換狀態(tài)頁面,可添加自定義狀態(tài)頁面蔓挖,如加載中夕土,加載失敗,無數(shù)據(jù)瘟判,網(wǎng)絡(luò)超時(shí)怨绣,占位圖,登錄失效等常用頁面拷获±嫖酰可配合網(wǎng)絡(luò)加載框架,結(jié)合返回狀態(tài)碼刀诬,錯(cuò)誤碼咽扇,數(shù)據(jù)進(jìn)行狀態(tài)頁自動(dòng)切換邪财,封裝使用效果更佳。
LoadSir現(xiàn)在版本已經(jīng)升級(jí)至1.3.6质欲,相關(guān)內(nèi)容請(qǐng)參考Github最新說明Github傳送門

本文前面是使用流程树埠,基于1.2.2完成,后面是原理解析嘶伟,如果大家有興趣怎憋,可耐心看完。

效果預(yù)覽

in Activity in View in Fragment
Placeholder Muitl-Fragment ViewPage+Fragment

使用場景

下面為大家常見的加載反饋頁面:

loading error timeout
empty custom placeholder

面對(duì)這么多狀態(tài)頁面九昧,你是不是還在用include的方式绊袋,setVisibility(View.VISIBLE/GONE),這種方式即不方便控制铸鹰,也造成了視圖層級(jí)冗余(你要把所有狀態(tài)布局include進(jìn)一個(gè)視圖)癌别。如果有一種工具,能把這些事都做了就好了蹋笼。恰好展姐, LoadSir 把這些事做了,接下來我們就來了解一下它剖毯。

LoadSir的功能及特點(diǎn)

  • 支持Activity圾笨,F(xiàn)ragment,F(xiàn)ragment(v4)逊谋,View狀態(tài)回調(diào)
  • 適配多個(gè)Fragment切換擂达,及Fragment+ViewPager切換,不會(huì)狀態(tài)疊加或者狀態(tài)錯(cuò)亂
  • 利用泛型轉(zhuǎn)換輸入信號(hào)和輸出狀態(tài)胶滋,可根據(jù)網(wǎng)絡(luò)返回體的狀態(tài)碼或者數(shù)據(jù)返回自動(dòng)適配狀態(tài)頁板鬓,實(shí)現(xiàn)全局自動(dòng)狀態(tài)切換
  • 只加載唯一一個(gè)狀態(tài)視圖,不會(huì)預(yù)加載全部視圖
  • 可保留標(biāo)題欄(Toolbar,titile view等)
  • 可設(shè)置重新加載點(diǎn)擊事件(OnReloadListener)
  • 可自定義狀態(tài)頁(繼承Callback類)
  • 可在子線程直接切換狀態(tài)
  • 可設(shè)置初始狀態(tài)頁(常用進(jìn)度頁作為初始狀態(tài))
  • 不需要設(shè)置枚舉或者常量狀態(tài)值镀钓,直接用狀態(tài)頁類類型(xxx.class)作為狀態(tài)碼
  • 可擴(kuò)展?fàn)顟B(tài)頁面穗熬,在配置中添加自定義狀態(tài)頁
  • 可對(duì)單個(gè)狀態(tài)頁單獨(dú)設(shè)置點(diǎn)擊事件,根據(jù)返回boolean值覆蓋或者結(jié)合OnReloadListener使用丁溅,如網(wǎng)絡(luò)錯(cuò)誤可跳轉(zhuǎn)設(shè)置頁
  • 可全局單例配置唤蔗,也可以單獨(dú)配置
  • 無預(yù)設(shè)頁面,低耦合窟赏,開發(fā)者隨心配置

開始使用LoadSir

LoadSir的使用只需要簡單的三步妓柜,三步上籃的三步。

添加依賴
compile 'com.kingja.loadsir:loadsir:1.3.6'

第一步: 配置

全局配置方式

全局配置方式涯穷,使用的是單例模式棍掐,即獲取的配置都是一樣的】娇觯可在Application中配置作煌,添加狀態(tài)頁掘殴,設(shè)置初始化狀態(tài)頁,建議使用這種配置方式粟誓。

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LoadSir.beginBuilder()
                .addCallback(new ErrorCallback())//'添加各種狀態(tài)頁
                .addCallback(new EmptyCallback())
                .addCallback(new LoadingCallback())
                .addCallback(new TimeoutCallback())
                .addCallback(new CustomCallback())
                .setDefaultCallback(LoadingCallback.class)//設(shè)置默認(rèn)狀態(tài)頁
                .commit();
    }
}
單獨(dú)配置方式

如果你即想保留全局配置奏寨,又想在某個(gè)特殊頁面加點(diǎn)不同的配置,可采用該方式鹰服。

LoadSir loadSir = new LoadSir.Builder()
                .addCallback(new LoadingCallback())
                .addCallback(new EmptyCallback())
                .addCallback(new ErrorCallback())
                .build();
        loadService = loadSir.register(this, new Callback.OnReloadListener() {
            @Override
            public void onReload(View v) {
                // 重新加載邏輯
            }
        });

第二步: 注冊(cè)

在Activity中使用
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_content);
    // Your can change the callback on sub thread directly.
    LoadService loadService = LoadSir.getDefault().register(this, new Callback.OnReloadListener() {
        @Override
        public void onReload(View v) {
            // 重新加載邏輯
        }
    });
}}
在View 中使用
ImageView imageView = (ImageView) findViewById(R.id.iv_img);
LoadSir loadSir = new LoadSir.Builder()
        .addCallback(new TimeoutCallback())
        .setDefaultCallback(LoadingCallback.class)
        .build();
loadService = loadSir.register(imageView, new Callback.OnReloadListener() {
    @Override
    public void onReload(View v) {
        loadService.showCallback(LoadingCallback.class);
        // 重新加載邏輯
    }
});
在Fragment 中使用

由于Fragment添加到Activitiy方式多樣病瞳,比較特別,所以在Fragment中注冊(cè)方式不同于上面兩種悲酷,大家先看模板代碼:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
        savedInstanceState) {
    //第一步:獲取布局View
    rootView = View.inflate(getActivity(), R.layout.fragment_a_content, null);
    //第二步:注冊(cè)布局View
    LoadService loadService = LoadSir.getDefault().register(rootView, new Callback.OnReloadListener() {
        @Override
        public void onReload(View v) {
            // 重新加載邏輯
        }
    });
    //第三步:返回LoadSir生成的LoadLayout
    return loadService.getLoadLayout();
}

第三步: 回調(diào)

直接回調(diào)
protected void loadNet() {
        // 進(jìn)行網(wǎng)絡(luò)訪問...
        // 進(jìn)行回調(diào)
        loadService.showSuccess();//成功回調(diào)
        loadService.showCallback(EmptyCallback.class);//其他回調(diào)
    }
轉(zhuǎn)換器回調(diào) (推薦使用)

如果你不想再每次回調(diào)都要手動(dòng)進(jìn)行的話套菜,可以選擇注冊(cè)的時(shí)候加入轉(zhuǎn)換器,可根據(jù)返回的數(shù)據(jù)设易,適配對(duì)應(yīng)的回調(diào)逗柴。

LoadService loadService = LoadSir.getDefault().register(this, new Callback.OnReloadListener() {
    @Override
    public void onReload(View v) {
            // 重新加載邏輯
    }}, new Convertor<HttpResult>() {
    @Override
    public Class<? extends Callback> map(HttpResult httpResult) {
        Class<? extends Callback> resultCode = SuccessCallback.class;
        switch (httpResult.getResultCode()) {
            case SUCCESS_CODE://成功回調(diào)
                if (httpResult.getData().size() == 0) {
                    resultCode = EmptyCallback.class;
                }else{
                    resultCode = SuccessCallback.class;
                }
                break;
            case ERROR_CODE:
                resultCode = ErrorCallback.class;
                break;
        }
        return resultCode;
    }
});

回調(diào)的時(shí)候直接傳入轉(zhuǎn)換器指定的數(shù)據(jù)類型。

loadService.showWithConvertor(httpResult);

自定義回調(diào)頁

LoadSir為了完全解耦亡嫌,沒有預(yù)設(shè)任何狀態(tài)頁嚎于,開發(fā)者根據(jù)需求自定義自己的回調(diào)頁面掘而,比如加載中挟冠,沒數(shù)據(jù),錯(cuò)誤袍睡,超時(shí)等常用頁面知染,
設(shè)置布局及自定義點(diǎn)擊邏輯

public class CustomCallback extends Callback {
    @Override
    protected int onCreateView() {
        return R.layout.layout_custom;
    }

    @Override
    protected boolean onRetry(final Context context, View view) {
        //布局點(diǎn)擊事件
        Toast.makeText(context.getApplicationContext(), "Hello mother fuck! :p", Toast.LENGTH_SHORT).show();
        //子控件事件
        (view.findViewById(R.id.iv_gift)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context.getApplicationContext(), "It's your gift! :p", Toast.LENGTH_SHORT).show();
            }
        });
        return true;//返回true則覆蓋了register時(shí)傳入的重試點(diǎn)擊事件,返回false則兩個(gè)都執(zhí)行
    }

    //是否在顯示Callback視圖的時(shí)候顯示原始圖(SuccessView)斑胜,返回true顯示控淡,false隱藏
    @Override
    public boolean getSuccessVisible() {
        return super.getSuccessVisible();
    }

    //將Callback添加到當(dāng)前視圖時(shí)的回調(diào),View為當(dāng)前Callback的布局View
    @Override
    public void onAttach(Context context, View view) {
        super.onAttach(context, view);
    }

    //將Callback從當(dāng)前視圖刪除時(shí)的回調(diào)止潘,View為當(dāng)前Callback的布局View
    @Override
    public void onDetach() {
        super.onDetach(context, view);
    }
}

動(dòng)態(tài)修改Callback

loadService = LoadSir.getDefault().register(...);
loadService.setCallBack(EmptyCallback.class, new Transport() {
   @Override
   public void order(Context context, View view) {
       TextView mTvEmpty = (TextView) view.findViewById(R.id.tv_empty);
       mTvEmpty.setText("fine, no data. You must fill it!");
   }
});

代碼混淆

-dontwarn com.kingja.loadsir.**
-keep class com.kingja.loadsir.** {*;}

占位圖布局效果

placeholder效果狀態(tài)頁類似ShimmerRecyclerView的效果. LoadSir只用了一個(gè)自定義狀態(tài)頁P(yáng)laceHolderCallback就完成類似的效果掺炭,是不是很棒 :p

看到這,想必各位使用LoadSir應(yīng)該沒問題了凭戴,如果還想再進(jìn)一步了解它的內(nèi)部結(jié)構(gòu)涧狮,可以繼續(xù)往下看。

原理解析

流程圖

關(guān)鍵類

  • LoadSir:提供單例模式獲取全局唯一實(shí)例么夫,內(nèi)部保存配置信息者冤,根據(jù)配置創(chuàng)建LoadService。
  • LoadService:具體操作服務(wù)類档痪,提供showSuccess涉枫,showCallback,showWithCoverator等方法來進(jìn)行狀態(tài)頁回調(diào)腐螟。
  • LoadLayout:最終顯示在用戶面前的視圖View愿汰,替換了原布局困后,是LoadService直接操作對(duì)象,要顯示的狀態(tài)頁的視圖會(huì)被添加到LoadLayout上衬廷。
  • Callback:狀態(tài)頁抽象類操灿,抽象自定義布局和自定義點(diǎn)擊事件兩個(gè)方法留給子類實(shí)現(xiàn)。
  • Coverator:轉(zhuǎn)換接口泵督,可將網(wǎng)絡(luò)返回實(shí)體轉(zhuǎn)換成對(duì)應(yīng)的狀態(tài)頁趾盐,達(dá)到自動(dòng)適配狀態(tài)頁的目的。

我們直接觀察在Activity中普通加載和使用LoadSir加載視圖的區(qū)別

>>>沒使用LoadSir
>>>使用LoadSir

大家可以看到小腊,LoadSir用LoadLayout把原來的布局給替代掉了救鲤,原來的布局加在了LoadLayout上,其它自定義的狀態(tài)頁也同樣會(huì)被加到這個(gè)LoadLayout上(顯示的時(shí)候)秩冈,而且LoadLayout的子View只有一個(gè)本缠,就是當(dāng)前要顯示的狀態(tài)頁布局,并沒有把當(dāng)前不顯示的比如加載中布局入问,錯(cuò)誤布局丹锹,無數(shù)據(jù)布局加載進(jìn)來,這也是LoadSir的優(yōu)點(diǎn)之一芬失,按需加載楣黍,并且只加載一個(gè)狀態(tài)布局。

>>>替換邏輯
public static TargetContext getTargetContext(Object target) {
        ViewGroup contentParent;
        Context context;
        if (target instanceof Activity) {
            Activity activity = (Activity) target;
            context = activity;
            contentParent = (ViewGroup) activity.findViewById(android.R.id.content);
        } else if (target instanceof View) {
            View view = (View) target;
            contentParent = (ViewGroup) (view.getParent());
            context = view.getContext();
        } else {
            throw new IllegalArgumentException("The target must be within Activity, Fragment, View.");
        }
       ...
        if (contentParent != null) {
            contentParent.removeView(oldContent);
        }
        return new TargetContext(context, contentParent, oldContent, childIndex);
    }

大家可以看到棱烂,在Activity和View中的情況都比較簡單租漂,直接獲取target的父控件,然后在父控件中替換掉該布局即可颊糜。在Fragment中哩治,由于可能多個(gè)Fragment的布局View并存在一個(gè)父控件里,所以不能簡單地使用父控件刪除子View方式替換衬鱼,也有可能父控件是ViewPager业筏,不能通過addView()的方式添加LoadLayout。因此Fragment的注冊(cè)方式是直接返回了LoadLayout到Activity上鸟赫。這樣也達(dá)到了一樣的目的蒜胖。

下面是ViewPager+Fragment場景中使用LoadSir的視圖,兩個(gè)Fragment用各自的LoadLayout進(jìn)行視圖分離惯疙,避免了狀態(tài)頁疊加或錯(cuò)位翠勉。



看到這的童鞋應(yīng)該也大概知道LoadSir是怎么回事了,如果想明白LoadSir的代碼實(shí)現(xiàn)霉颠,請(qǐng)繼續(xù)往下看对碌。

源碼解析

我們按上面三步上籃的步驟來稍微分析下源碼

>>>第一步:配置

單例模式獲取LoadSir,在LoadSir構(gòu)造的時(shí)候創(chuàng)建默認(rèn)配置

public static LoadSir getDefault() {
        if (loadSir == null) {
            synchronized (LoadSir.class) {
                if (loadSir == null) {
                    loadSir = new LoadSir();
                }
            }
        }
        return loadSir;
    }

    private LoadSir() {
        this.builder = new Builder();
    }

Builder主要提供添加狀態(tài)頁蒿偎,和設(shè)置默認(rèn)狀態(tài)頁的方法

public static class Builder {
        private List<Callback> callbacks = new ArrayList<>();
        private Class<? extends Callback> defaultCallback;

        public Builder addCallback(Callback callback) {
            callbacks.add(callback);
            return this;
        }

        public Builder setDefaultCallback(Class<? extends Callback> defaultCallback) {
            this.defaultCallback = defaultCallback;
            return this;
        }
      ...
        public LoadSir build() {
            return new LoadSir(this);
        }

    }

LoadSir提供beginBuilder()...commit()來設(shè)置全局配置朽们。

public class LoadSir  {
   ...
    public static Builder beginBuilder() {
        return new Builder();
    }

    public static class Builder {
      
        public void commit() {
            getDefault().setBuilder(this);
        }
      ...
    }
}

>>>第二步:注冊(cè)

LoadSir注冊(cè)后返回的是LoadService怀读,一看名字大家就明白這是服務(wù)類,就是我們所說的Service層骑脱。

public LoadService register(Object target, Callback.OnReloadListener onReloadListener) {
        return register(target, onReloadListener, null);
    }

    public <T> LoadService register(Object target, Callback.OnReloadListener onReloadListener, Convertor<T>
            convertor) {
        TargetContext targetContext = LoadSirUtil.getTargetContext(target);
        return new LoadService<>(convertor, targetContext, onReloadListener, builder);
    }

在LoadService的構(gòu)造方法中根據(jù)target等信息創(chuàng)建Success視圖菜枷,并且生成LoadLayout,相當(dāng)于LoadSir每次注冊(cè)都會(huì)創(chuàng)建一個(gè)LoadLayout叁丧。

LoadService(Convertor<T> convertor, TargetContext targetContext, Callback
            .OnReloadListener onReloadListener, LoadSir.Builder builder) {
        this.convertor = convertor;
        Context context = targetContext.getContext();
        View oldContent = targetContext.getOldContent();
        loadLayout = new LoadLayout(context, onReloadListener);
        loadLayout.addCallback(new SuccessCallback(oldContent, context,
                onReloadListener));
        if (targetContext.getParentView() != null) {
            targetContext.getParentView().addView(loadLayout, targetContext.getChildIndex(), oldContent
                    .getLayoutParams());
        }
        initCallback(builder);
    }
>>>第三步:回調(diào)

LoadService的三個(gè)回調(diào)方法最終調(diào)用的都是loadLayout.showCallback(callback);

public void showSuccess() {
        loadLayout.showCallback(SuccessCallback.class);
    }

    public void showCallback(Class<? extends Callback> callback) {
        loadLayout.showCallback(callback);
    }

    public void showWithConvertor(T t) {
        if (convertor == null) {
            throw new IllegalArgumentException("You haven't set the Convertor.");
        }
        loadLayout.showCallback(convertor.map(t));
    }

我們直接看LoadLayout的showCallback方法啤誊,先做Callback是否配置判斷,然后進(jìn)行線程安全操作拥娄。重點(diǎn)還是showCallbackView(callback);

public void showCallback(final Class<? extends Callback> callback) {
        if (!callbacks.containsKey(callback)) {
            throw new IllegalArgumentException(String.format("The Callback (%s) is nonexistent.", callback
                    .getSimpleName()));
        }
        if (LoadSirUtil.isMainThread()) {
            showCallbackView(callback);
        } else {
            postToMainThread(callback);
        }
    }

這個(gè)方法可以說是最后的執(zhí)行者蚊锹,就做兩件事,刪除LoadLayout所有子View(重置)稚瘾,添加指定的布局頁View(回調(diào))牡昆。

private void showCallbackView(Class<? extends Callback> status) {
        if (getChildCount() > 0) {
            removeAllViews();
        }
        for (Class key : callbacks.keySet()) {
            if (key == status) {
                addView(callbacks.get(key).getRootView());
            }
        }
    }

自此,LoadSir一個(gè)完整的配置摊欠,注冊(cè)丢烘,回調(diào)的過程完成了。不知道你們明白了沒些椒,反正我是有點(diǎn)口渴了播瞳。

總結(jié)

建議在Application中全局配置,在BaseActivity摊沉,BaseFragment或者M(jìn)VP中封裝使用狐史,能極大的減少代碼量痒给,讓你的代碼更加優(yōu)雅说墨,生活更加愉快。時(shí)間和個(gè)人能力有限苍柏,如果大家發(fā)現(xiàn)需要改進(jìn)的地方尼斧,歡迎提交issue。
如果這個(gè)庫對(duì)你有用的話试吁,也請(qǐng)點(diǎn)個(gè)star:p Github傳送門

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棺棵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子熄捍,更是在濱河造成了極大的恐慌烛恤,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件余耽,死亡現(xiàn)場離奇詭異缚柏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)碟贾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門币喧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轨域,“玉大人,你說我怎么就攤上這事杀餐「煞ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵史翘,是天一觀的道長枉长。 經(jīng)常有香客問我,道長琼讽,這世上最難降的妖魔是什么搀暑? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮跨琳,結(jié)果婚禮上自点,老公的妹妹穿的比我還像新娘。我一直安慰自己脉让,他們只是感情好桂敛,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溅潜,像睡著了一般术唬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滚澜,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天粗仓,我揣著相機(jī)與錄音,去河邊找鬼设捐。 笑死借浊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萝招。 我是一名探鬼主播蚂斤,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼槐沼!你這毒婦竟也來了曙蒸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤岗钩,失蹤者是張志新(化名)和其女友劉穎纽窟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兼吓,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡臂港,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趋艘。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疲恢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓷胧,到底是詐尸還是另有隱情显拳,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布搓萧,位于F島的核電站杂数,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瘸洛。R本人自食惡果不足惜揍移,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望反肋。 院中可真熱鬧那伐,春花似錦、人聲如沸石蔗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽养距。三九已至诉探,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棍厌,已是汗流浹背肾胯。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耘纱,地道東北人敬肚。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像揣炕,于是被迫代替她去往敵國和親帘皿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,506評(píng)論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,708評(píng)論 22 664
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫畸陡、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,024評(píng)論 4 62
  • 序 我可以去選擇一個(gè)我想從事的職業(yè)虽填,而不是因?yàn)槟硞€(gè)職業(yè)安穩(wěn)或富利好丁恭、薪水高我才去選擇它。我將自由地去做我想做的事斋日,...
    zhpooer閱讀 433評(píng)論 0 9
  • 在夢里牲览, 我想開一間花店 每天侍弄花花草草 空閑里就坐在窗前看著大街上來來往往的行人發(fā)呆 下雨天我也不會(huì)關(guān)門 歡迎...
    初夏的時(shí)光閱讀 229評(píng)論 8 10