【架構(gòu)】可能是理想的Android歡迎界面實(shí)現(xiàn)

我們?cè)谑褂肁ndroid時(shí)漾唉,很多APP打開都會(huì)有啟動(dòng)畫面(歡迎界面),它會(huì)停留若干秒后再進(jìn)入主界面。

先看一下Demo效果他挎。

開機(jī)效果

源碼:GitHub地址


歡迎界面的意義

歡迎界面固然有展示品牌形象的作用烫葬,但關(guān)于歡迎界面我們需要明白是:

  1. 理論上界面越快消失越好界弧,讓用戶盡早使用到APP
  2. 歡迎界面的停留可能用于廣告的展示
  3. 顯示歡迎界面的意義不是為了單純的“炫”,它是給加載APP運(yùn)行時(shí)需要的數(shù)據(jù)作掩護(hù)
  4. 歡迎界面顯示過(guò)程中搭综,應(yīng)該是全屏的垢箕、不能返回的、不能取消的
  5. 歡迎界面所做的任務(wù)如果超時(shí)兑巾,我們不應(yīng)該一直停留在歡迎界面上条获,而是應(yīng)當(dāng)直接進(jìn)入主界面,防止用戶等待過(guò)久并認(rèn)為是APP卡死

關(guān)于上面第3點(diǎn)的啟動(dòng)時(shí)加載數(shù)據(jù)蒋歌,情況有不少帅掘,比如從服務(wù)器拿到一些用戶特有的配置(如Taplytics數(shù)據(jù))、從本地SharePreference或數(shù)據(jù)庫(kù)載入數(shù)據(jù)到內(nèi)存等堂油,這些都需要在進(jìn)入主界面前完成锄开。當(dāng)然,也有一些在進(jìn)入主界面前不用必須完成的称诗,比如檢測(cè)版本更新萍悴,如果檢測(cè)到一半,其它的啟動(dòng)任務(wù)完成了寓免,隨時(shí)可以關(guān)閉歡迎界面癣诱。

歡迎界面的設(shè)計(jì)點(diǎn)

在設(shè)計(jì)歡迎界面時(shí),我們可以得出幾點(diǎn)信息:

  1. 需要定義和實(shí)現(xiàn)APP的啟動(dòng)任務(wù)袜香,任務(wù)分兩類撕予,進(jìn)入主界面前必須完成的和不必須完成的
  2. APP啟動(dòng)時(shí)就執(zhí)行啟動(dòng)任務(wù)并展示歡迎界面,所有進(jìn)入主界面前必須完成的啟動(dòng)任務(wù)全部執(zhí)行完畢后蜈首,關(guān)閉歡迎界面实抡,進(jìn)入主界面并加載主界面的UI和數(shù)據(jù)
  3. 啟動(dòng)任務(wù)執(zhí)行時(shí)間超時(shí)后,直接關(guān)閉歡迎界面

流程圖如下欢策,倒也沒什么復(fù)雜吆寨。

但實(shí)現(xiàn)起來(lái)還是有不少要注意的細(xì)節(jié),請(qǐng)看下文踩寇。


歡迎界面的顯示流程圖

代碼的實(shí)現(xiàn)

1. 任務(wù)接口的定義

/**
 * 啟動(dòng)時(shí)的任務(wù)接口定義
 */
public interface StartTaskInterface {
    void execute(AppStarter.OnTaskListener listener);  // 啟動(dòng)任務(wù)的方法啄清,方法中當(dāng)任務(wù)結(jié)束時(shí)將回調(diào)listener

    // 隱藏歡迎界面前,是否需要確保這個(gè)任務(wù)已經(jīng)完成
    // 如果為true俺孙,則此任務(wù)是必要執(zhí)行的辣卒,在沒完成任務(wù)前歡迎界面將一直顯示(直到超時(shí))
    boolean isNeedDoneBeforeHideWelcomeDialog();
}

2. 我們定義三個(gè)任務(wù)

/**
 * 啟動(dòng)APP時(shí)得到配置信息任務(wù)(調(diào)用API或讀取本地?cái)?shù)據(jù))
 */
public class GetConfigStartTaskImpl implements StartTaskInterface {
    @Override
    public void execute(AppStarter.OnTaskListener listener) {

        // 這里可根據(jù)實(shí)際需求替換成網(wǎng)絡(luò)請(qǐng)求或異步任務(wù)
        new Handler().postDelayed(() -> {
            if (listener != null) {
                listener.onFinished("GetConfigFinished");
            }
        }, 3000);  // 假設(shè)花了3秒完成請(qǐng)求API得到APP的配置變量
    }

    @Override
    public boolean isNeedDoneBeforeHideWelcomeDialog() {
        return true;
    }
}
/**
 * 顯示廣告的任務(wù)
 */
public class ShowAdsStartTaskImpl implements StartTaskInterface {
    @Override
    public void execute(AppStarter.OnTaskListener listener) {

        // 這里可根據(jù)實(shí)際需求替換成網(wǎng)絡(luò)請(qǐng)求或異步任務(wù)
        new android.os.Handler().postDelayed(() -> {
            if (listener != null) {
                listener.onFinished("ShowAdsFinished");  // task完成后回調(diào)并傳入標(biāo)識(shí)掷贾,執(zhí)行結(jié)束操作
            }
        }, 2000);  // 假設(shè)展示廣告時(shí)間為2秒
    }

    @Override
    public boolean isNeedDoneBeforeHideWelcomeDialog() {
        return true;  // 這個(gè)任務(wù)沒完成前,不要關(guān)閉歡迎界面
    }
}
/**
 * 超時(shí)任務(wù)
 */
public class TimeOutTask implements StartTaskInterface {

    private static final int TIMEOUT = 5000;  // 我們?cè)O(shè)定歡迎界面顯示的超時(shí)時(shí)間為5秒

    private AppStarter appStarter;

    public TimeOutTask(AppStarter appStarter) {
        this.appStarter = appStarter;
    }

    @Override
    public void execute(AppStarter.OnTaskListener listener) {
        new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                appStarter.setOnTaskListener(null);
                WelcomeDialogController.getInstance().dismiss();  // 超時(shí)后關(guān)閉歡迎界面
            }
        }, TIMEOUT);
    }

    @Override
    public boolean isNeedDoneBeforeHideWelcomeDialog() {
        return false;  // 關(guān)掉歡迎界面時(shí)荣茫,不需要理睬這個(gè)任務(wù)是否完成想帅,其它必要的任務(wù)完成后就可以關(guān)閉歡迎界面了
    }
}

可以看到,這些任務(wù)的定義具有很好的靈活性啡莉,當(dāng)之后我們希望加入更多的任務(wù)時(shí)港准,直接實(shí)現(xiàn)StartTaskInterface接口就可以了。

另外票罐,上面的任務(wù)中叉趣,獲取配置的任務(wù)耗時(shí)3秒泞边,顯示廣告耗時(shí)2秒该押,超時(shí)任務(wù)5秒。前兩個(gè)是進(jìn)入主界面前必須完成的任務(wù)阵谚,如果沒意外蚕礼,這個(gè)歡迎界面將顯示3秒就會(huì)關(guān)閉。

3. 設(shè)計(jì)歡迎界面的Dialog梢什。

值得注意的是奠蹬,這里的歡迎界面是通過(guò)Dialog實(shí)現(xiàn)而不是Actvity。原因是歡迎界面使用Actvity太過(guò)于“殺雞用牛刀”嗡午,另外囤躁,我們顯示歡迎界面的目的是加載數(shù)據(jù)或廣告,理論上歡迎界面和主界面MainActivtiy的狀態(tài)是相關(guān)的荔睹。將Dialog和MainActivity的狀態(tài)附著在一起處理是更恰當(dāng)?shù)摹?/p>

public class WelcomeDialog extends Dialog {
    WelcomeDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, themeResId);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.dlg_welcome);

        // 動(dòng)態(tài)地將歡迎界面的圖片根據(jù)地屏幕參數(shù)撐滿整個(gè)屏幕
        ImageView iv = findViewById(R.id.iv_welcome_picture);
        iv.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                Matrix m = new Matrix();
                float scale;
                Drawable drawable = ContextCompat.getDrawable(iv.getContext(), R.drawable.welcome);
                final int dwidth = drawable.getIntrinsicWidth();
                final int dheight = drawable.getIntrinsicHeight();

                final int vwidth = iv.getMeasuredWidth();
                final int vheight = iv.getMeasuredHeight();

                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                }
                m.setScale(scale, scale);
                iv.setImageMatrix(m);

                iv.getViewTreeObserver().removeOnPreDrawListener(this);
                return false;
            }
        });
        WindowManager.LayoutParams lp = getWindow().getAttributes();
        lp.width = DeviceRelatedUtil.getInstance().getDisplayWidth();
        lp.height = DeviceRelatedUtil.getInstance().getDisplayHeight();
        getWindow().setAttributes(lp);
    }
}

4. 實(shí)現(xiàn)對(duì)歡迎界面Dialog進(jìn)行管理控制

/**
 * 歡迎界面的管理類
 */
public class WelcomeDialogController {

    private static final int DELAY = 500;
    private static WelcomeDialogController sInstance;
    // 從MainActivity傳過(guò)來(lái)的回調(diào)狸演,在歡迎界面關(guān)閉時(shí)執(zhí)行。一般來(lái)說(shuō)就是歡迎界面消失進(jìn)入主界面后加載view和data的操作
    private DialogInterface.OnDismissListener mOnDismissListener;
    private boolean mFinished;
    private Dialog mSplashDialog;
    private final Runnable mDismissRunnable = this::destroy;
    private AppStarter.OnTaskListener mWelcomeDialogShowListener;
    private boolean mIsDispatched;

    public static WelcomeDialogController getInstance() {
        if (sInstance == null) {
            sInstance = new WelcomeDialogController();

            // 關(guān)閉歡迎界面的回調(diào)僻他,將用于AppStarter里所有任務(wù)完成后執(zhí)行
            sInstance.mWelcomeDialogShowListener = key -> {
                if (!sInstance.mFinished) {
                    sInstance.dismiss();
                }
            };

        }
        return sInstance;
    }

    public boolean isNeedShow() {
        return !mFinished;
    }

    public void show(Activity activity, DialogInterface.OnDismissListener listener) {
        mOnDismissListener = listener;
        if (mSplashDialog == null) {

            // R.style.full_screen_dialog指定dialog不能返回宵距、全屏、沒有title吨拗、沒有背景等一堆限定满哪,讓用戶在歡迎界面出現(xiàn)時(shí)什么都不能操作
            mSplashDialog = new WelcomeDialog(activity, R.style.full_screen_dialog);

            mSplashDialog.setCancelable(false);
        }

        if (!activity.isFinishing()) {
            mSplashDialog.show();
        }
    }

    // 關(guān)閉歡迎界面和做一些操作
    public void dismiss() {
        sInstance.mFinished = true;
        dispatchListener();
        delayDismissDialog(DELAY);
    }

    // 執(zhí)行MainActivity傳過(guò)來(lái)的回調(diào),即在進(jìn)入主界面后要做的操作
    private void dispatchListener() {
        if (mOnDismissListener != null && !mIsDispatched) {
            mOnDismissListener.onDismiss(mSplashDialog);
            mIsDispatched = true;
        }
    }

    // 過(guò)一會(huì)關(guān)閉掉dialog(算是給dispatchListener做主界面的加載一點(diǎn)時(shí)間劝篷,提高用戶體驗(yàn))
    private void delayDismissDialog(int time) {
        new Handler(Looper.getMainLooper()).postDelayed(mDismissRunnable, time);  // 調(diào)用mDismissRunnable哨鸭,即destory方法
    }

    public void destroy() {
        try {
            mSplashDialog.dismiss();
        } catch (Exception ignored) {
        }
    }


    public AppStarter.OnTaskListener getListener() {
        return mWelcomeDialogShowListener;
    }
}

5. 實(shí)現(xiàn)對(duì)啟動(dòng)任務(wù)進(jìn)行控制

public class AppStarter {

    private static AppStarter sInstance = new AppStarter();
    private final List<StartTaskInterface> mStaterTasks = new ArrayList<>();  // 存儲(chǔ)所有的啟動(dòng)任務(wù)
    private OnTaskListener mWelcomeDialogShowListener;  // 關(guān)閉歡迎界面的回調(diào)

    private int mCountOfNeedDoneTask;  // 必要完成的啟動(dòng)任務(wù)個(gè)數(shù)
    // 這個(gè)回調(diào)用于每一個(gè)必要完成的啟動(dòng)任務(wù)執(zhí)行完成后調(diào)用,工作大致是記錄完成的個(gè)數(shù)娇妓,當(dāng)全部完成時(shí)兔跌,調(diào)用
    final OnTaskListener neeWaitTasksListener = new OnTaskListener() {
        private int mCountOfFinishedTask;  // 完成并調(diào)用了回調(diào)的任務(wù)個(gè)數(shù)

        @Override
        public synchronized void onFinished(String key) {
            mCountOfFinishedTask++;
            if (mCountOfFinishedTask == mCountOfNeedDoneTask && mWelcomeDialogShowListener != null) {
                mWelcomeDialogShowListener.onFinished(null);  // 當(dāng)必要完成的啟動(dòng)任務(wù)全部執(zhí)行完畢后,便可以關(guān)閉歡迎界面了
            }
        }
    };

    public static AppStarter getInstance() {
        if (sInstance == null) {
            sInstance = new AppStarter();
        }
        return sInstance;
    }

    public OnTaskListener getOnTaskListener() {
        return mWelcomeDialogShowListener;
    }

    public void setOnTaskListener(OnTaskListener onTaskListener) {
        this.mWelcomeDialogShowListener = onTaskListener;
    }

    // 增加一個(gè)啟動(dòng)任務(wù)
    public void add(StartTaskInterface task) {
        mStaterTasks.add(task);
    }

    public void start(OnTaskListener listener) {

        mWelcomeDialogShowListener = listener;

        // 計(jì)算下歡迎界面關(guān)閉前必需完成的任務(wù)數(shù)
        for (StartTaskInterface t : mStaterTasks) {
            if (t.isNeedDoneBeforeHideWelcomeDialog()) {
                mCountOfNeedDoneTask += 1;
            }
        }

        for (StartTaskInterface t : mStaterTasks) {
            if (t.isNeedDoneBeforeHideWelcomeDialog()) {
                t.execute(neeWaitTasksListener);  // 需要在歡迎界面關(guān)閉前完成的任務(wù)執(zhí)行完成后峡蟋,將會(huì)回調(diào)neeWaitTasksListener
            } else {
                t.execute(null);  // 在歡迎界面關(guān)閉不一定要完成的任務(wù)直接執(zhí)行
            }
        }
    }

    public interface OnTaskListener {
        void onFinished(String key);
    }
}

6. 啟動(dòng)應(yīng)用時(shí)執(zhí)行啟動(dòng)任務(wù)

關(guān)于啟動(dòng)任務(wù)的執(zhí)行坟桅,理論上越早執(zhí)行越好华望,我們把啟動(dòng)任務(wù)的執(zhí)行放在Application的子類里,關(guān)于Application類的官方介紹如下仅乓。

The Application class in Android is the base class within an Android app that contains all other components such as activities and services. The Application class, or any subclass of the Application class, is instantiated before any other class when the process for your application/package is created. This class is primarily used for initialization of global state before the first Activity is displayed. Note that custom Application objects should be used carefully and are often not needed at all.

Application與組件和數(shù)據(jù)的關(guān)系

/**
 * APP一啟動(dòng)赖舟,將調(diào)用Application類
 */
public class WelcomeApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        DeviceRelatedUtil.init(this);

        addStartTask();
    }

    // 添加啟動(dòng)任務(wù)到AppStarter里
    private void addStartTask() {
        AppStarter.getInstance().add(new ShowAdsStartTaskImpl());
        AppStarter.getInstance().add(new GetConfigStartTaskImpl());
        AppStarter.getInstance().add(new TimeOutTask(AppStarter.getInstance()));

        AppStarter.getInstance().start(WelcomeDialogController.getInstance().getListener());  // 執(zhí)行啟動(dòng)任務(wù)
    }
}

之后,在MainActivity中的onCreate方法夸楣,只要滿足首次進(jìn)入或重新加載的條件宾抓,就會(huì)顯示歡迎界面。當(dāng)所有在進(jìn)入主界面前必須完成的任務(wù)執(zhí)行完畢或顯示超時(shí)后關(guān)閉歡迎界面豫喧。

我們將整個(gè)APP的組件和數(shù)據(jù)概括在我們的自己實(shí)現(xiàn)的Application類里石洗。

<application
    android:name=".WelcomeApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:largeHeap="true"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity
        android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

這樣就大致實(shí)現(xiàn)了界面的顯示。

值得注意的

1. 打開APP時(shí)可能出現(xiàn)短暫的白屏或黑屏

因?yàn)锳PP是先打開MainActivity然后再加載歡迎界面的紧显,在歡迎界面加載前(很短暫的時(shí)間)讲衫,顯示的是MainActivity的空白狀態(tài)。如果沒有設(shè)置MainActivity的背景孵班,可能會(huì)造成啟動(dòng)APP時(shí)出現(xiàn)短暫的白屏或黑屏涉兽。

解決的方法有二,一是把MainActivity的背景設(shè)置為透明篙程,進(jìn)入主界面后再設(shè)置為白或空枷畏,另一個(gè)方法則是先把背景設(shè)置為和歡迎界面一樣,進(jìn)入主界面后再設(shè)置為白或空虱饿。
由于第一種方案會(huì)導(dǎo)致打開APP看起來(lái)有一小會(huì)沒動(dòng)靜的效果(即那【短暫的空白畫面被透明化】)拥诡,我們采用第二種,可以實(shí)現(xiàn)一打開APP就看到歡迎界面的圖片(當(dāng)然氮发,如果屏幕匹配不好渴肉,歡迎界面彈出來(lái)時(shí)可能會(huì)造成畫面的閃動(dòng))。

<activity
    android:name=".MainActivity"
    android:launchMode="singleTop"
    android:screenOrientation="portrait"
    android:theme="@style/MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

我們?cè)O(shè)定了MainActivity的主題@style/MainActivity折柠,里面便設(shè)置了MainActivity的背景宾娜。

<style name="MainActivity" parent="AppTheme">
    <item name="android:windowBackground">@drawable/welcome</item> 
    <item name="android:windowTranslucentStatus">true</item>
</style>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扇售,隨后出現(xiàn)的幾起案子前塔,更是在濱河造成了極大的恐慌,老刑警劉巖承冰,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件华弓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡困乒,警方通過(guò)查閱死者的電腦和手機(jī)寂屏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人迁霎,你說(shuō)我怎么就攤上這事吱抚。” “怎么了考廉?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵秘豹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我昌粤,道長(zhǎng)既绕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任涮坐,我火速辦了婚禮凄贩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袱讹。我一直安慰自己疲扎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布廓译。 她就那樣靜靜地躺著评肆,像睡著了一般债查。 火紅的嫁衣襯著肌膚如雪非区。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天盹廷,我揣著相機(jī)與錄音征绸,去河邊找鬼。 笑死俄占,一個(gè)胖子當(dāng)著我的面吹牛管怠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缸榄,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼渤弛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了甚带?” 一聲冷哼從身側(cè)響起她肯,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹰贵,沒想到半個(gè)月后晴氨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碉输,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年籽前,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枝哄,死狀恐怖肄梨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挠锥,我是刑警寧澤峭范,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站瘪贱,受9級(jí)特大地震影響纱控,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜菜秦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一甜害、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧球昨,春花似錦尔店、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至共螺,卻和暖如春该肴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藐不。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工匀哄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雏蛮。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓涎嚼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親挑秉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子法梯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,099評(píng)論 25 707
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料犀概? 從這篇文章中你...
    hw1212閱讀 12,723評(píng)論 2 59
  • 愛情就像相互牽制的積木阱冶,只有合適的凹槽才能夠塹造出真正的精品刁憋。 每個(gè)人都會(huì)遇到一個(gè)自己喜歡的人,和一個(gè)喜歡自己的人...
    墨小凝閱讀 386評(píng)論 1 3
  • 1
    kuangzhezero閱讀 131評(píng)論 0 0