Android讀書筆記(4)—— View的工作原理

一驯耻、解析Activity的構(gòu)成

1发皿、DecorView的創(chuàng)建

當(dāng)我們調(diào)用startActivity方法時(shí)崔慧,最終調(diào)用ActivityThread#handleLaunchActivity,該方法中會(huì)首先會(huì)調(diào)用Activity的onCreate方法穴墅。在onCreate方法中惶室,會(huì)調(diào)用Activity#setContentViewsetContentView內(nèi)部會(huì)調(diào)用Activity的成員變量mWindow的(Window是抽象類玄货,其實(shí)現(xiàn)類是PhoneWindow皇钞,mWindow是PhoneWindow的一個(gè)實(shí)例)setContentView。其setContentView方法中松捉,首先new一個(gè)DecorView對(duì)象夹界,然后DecorView對(duì)象會(huì)根據(jù)不同的情況(主題,Window的feature等)加載不同的布局資源隘世。DecorView是Activity中的根View可柿,繼承了FrameLayout。至此DecorView創(chuàng)建完成丙者。

2复斥、添加DecorView到Window

完成DecorView的創(chuàng)建之后,接著調(diào)用ActivityThread#handleResumeActivity方法械媒。在handleResumeActivity方法中目锭,首先調(diào)用Activity#onResume方法,handleResumeActivity方法接著會(huì)得到一個(gè)DecorView對(duì)象和一個(gè)WindowManager對(duì)象(接口滥沫,實(shí)現(xiàn)類是WindowManagerImpl)侣集,然后調(diào)用WindowManagerImpl#addView方法,DecorView對(duì)象作為入?yún)魅肜夹濉T赪indowManager#addView中世分,創(chuàng)建了一個(gè)ViewRootImpl對(duì)象(ViewRoot的實(shí)現(xiàn)類),并調(diào)用了ViewRootImpl#setView缀辩,DecorView對(duì)象作為入?yún)⒊袈瘛T赩iewRootImpl#setView方法內(nèi)部,會(huì)通過(guò)跨進(jìn)程的方式向WMS(WindowManagerService)發(fā)起一個(gè)調(diào)用臀玄,從而將DecorView最終添加到Window上瓢阴,才能真正顯示出來(lái)。在這個(gè)過(guò)程中健无,ViewRootImpl荣恐、DecorView和WMS會(huì)彼此關(guān)聯(lián),最后通過(guò)WMS調(diào)用ViewRootImpl#performTraverals方法開(kāi)始View的測(cè)量、布局叠穆、繪制流程少漆。

Window是一個(gè)抽象類,具體是實(shí)現(xiàn)是PhoneWindow硼被,Activity示损、Dialog等的視圖都需要附加到Window上來(lái)呈現(xiàn)。
WindowManager是外界訪問(wèn)Window的入口嚷硫,實(shí)現(xiàn)類是WindowManagerImpl检访,Window的具體實(shí)現(xiàn)是在WindowManagerService中,WindowManager和WindowManagerService的交互是一個(gè)IPC過(guò)程仔掸。脆贵。
DecorView是頂級(jí)View,是一個(gè)FrameLayout布局嘉汰,代表了整個(gè)應(yīng)用的界面丹禀。內(nèi)部有titlebar和contentParent兩個(gè)子元素,contentParent的id是content鞋怀,而我們?cè)O(shè)置的main.xml布局則是contentParent里面的一個(gè)子元素双泪。
ViewRoot的實(shí)現(xiàn)類是ViewRootImpl,在WindowManager中創(chuàng)建密似,用于將DecorView添加到Window中焙矛。

二、理解MeasureSpec

MeasureSpec代表一個(gè)32位int值残腌,高2位代表SpecMode(測(cè)量模式)村斟,低30位代表SpecSize(某種測(cè)量模式下的規(guī)格大小)。

//主要理解 & ~ | 位運(yùn)算的作用抛猫,體會(huì)這樣設(shè)計(jì)的妙處
public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;//11000000 0000...000
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        public static final int AT_MOST     = 2 << MODE_SHIFT;
       
        public static int makeMeasureSpec(int size,int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
}

MeasureSpec通過(guò)將SpecSize和SpecMode打包成了一個(gè)int值來(lái)避免過(guò)多對(duì)象的內(nèi)存分配蟆盹。
SpecMode有三類:

UNSPECIFIED :父容器不對(duì)View進(jìn)行任何限制,要多大給多大闺金,一般用于系統(tǒng)內(nèi)部逾滥。
EXACTLY:父容器檢測(cè)到View所需要的精確大小,這時(shí)候View的最終大小就是SpecSize所指定的值败匹,對(duì)應(yīng)LayoutParams中的match_parent和具體數(shù)值這兩種模式(也不一定寨昙,還受父容器影響,詳見(jiàn)下面的表格)掀亩。
AT_MOST:父容器指定了一個(gè)可用大小即SpecSize舔哪,View的大小不能大于這個(gè)值,對(duì)LayoutParams中的wrap_content槽棍。
說(shuō)明:上面描述的是理論上應(yīng)該有的邏輯捉蚤。

對(duì)于頂級(jí)DecorView抬驴,其MeasureSpec是由窗口尺寸和自身的LayoutParams共同確定。對(duì)于普通的View缆巧,其MeasureSpec由父容器和自身的LayoutParams共同確定怎爵。一旦MeasureSpec確定,onMeasure中就可以確定View的測(cè)量寬/高盅蝗。

三、View的工作流程

主要指measure姆蘸、layout墩莫、draw這三大流程。measure確定View的測(cè)量寬/高逞敷,layout確定View的最終寬/高和四個(gè)頂點(diǎn)的位置狂秦,而draw則將View繪制到屏幕上。

ViewRootImpl#performTraversals會(huì)依次調(diào)用performMeasure推捐、performLayoutperformDraw三個(gè)方法裂问,這三個(gè)方法分別開(kāi)啟頂級(jí)View的measure、layout和draw這三大流程牛柒。

其中performMeasure中會(huì)調(diào)用頂級(jí)View#measure 方法堪簿,measure調(diào)用onMeasure,在onMeasure 方法中則會(huì)測(cè)量自身并調(diào)用所有子元素measure方法皮壁,這樣就完成了一次measure過(guò)程椭更;子元素會(huì)重復(fù)父容器的measure過(guò)程,如此反復(fù)完成了整個(gè)View樹(shù)的遍歷蛾魄。另外兩個(gè)過(guò)程同理虑瀑。

1、ViewGroup的Measure流程

對(duì)于ViewGroup既要測(cè)量自身滴须,也要遍歷子元素的measure方法(通過(guò)實(shí)現(xiàn)onMeasure方法)舌狗。
在performMeasure方法中,調(diào)用了DecorView#measure(繼承自View扔水,其實(shí)調(diào)用的是View#measure)痛侍,measure會(huì)調(diào)用onMeasure方法。ViewGroup并沒(méi)有定義onMeasure铭污,這個(gè)方法需要子類去實(shí)現(xiàn)恋日,主要需要實(shí)現(xiàn)兩個(gè)功能:①測(cè)量自身②測(cè)量子View。

ViewGroup提供了measureChildWithMarginsmeasureChildren方法嘹狞。

1.1岂膳、measureChildWithMargins方法
protected void measureChildWithMargins(View child,
    int parentWidthMeasureSpec, int widthUsed,
    int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //入?yún)ⅲ焊溉萜鞯腗easureSpec;父的padding和自身的margin(剩下為子元素可用空間)磅网;自身的寬度谈截。
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
            + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
         mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
            + heightUsed, lp.height);
    //注意:此時(shí)的入?yún)⑹亲陨淼腗easureSpec。measure又會(huì)調(diào)用child#onMeasure方法
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

從上面的方法可以看出,View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定簸喂,MeasureSpec一旦確定毙死,onMeasure中就可以確定View的測(cè)量寬/高。getChildMeasureSpec(int spec, int padding, int childDimension)方法的邏輯整理出如下表格:


表中的parentSize是指父容器目前可以使用的大小喻鳄,即父容器的specSize減去入?yún)adding扼倘。

ViewGroup并沒(méi)有定義onMeasure,需要其子類去實(shí)現(xiàn)除呵,為什么ViewGroup不像View一樣對(duì)其onMeasure做統(tǒng)一呢再菊?因?yàn)椴煌腣iewGroup子類有不同的布局特征,導(dǎo)致測(cè)量細(xì)節(jié)各不相同颜曾,無(wú)法統(tǒng)一纠拔。

根據(jù)上面的表格,我們發(fā)現(xiàn)父容器的MeasureSpec屬性為AT_MOST泛豪,子元素的LayoutParams為WRAP_CONTENT的時(shí)候稠诲,子元素的測(cè)量模式為AT_MOST,它的SpecSize為父容器的SpecSize減去padding(入?yún)ⅲ┕钍铮簿褪钦f(shuō)子元素WRAP_CONTENT和MATCH_PARENT一樣的臀叙。為了解決這個(gè)問(wèn)題,需要在WRAP_CONTENT時(shí)指定一下默認(rèn)的寬高岗仑。

1.2匹耕、measureChildren方法

measureChildren中會(huì)循環(huán)調(diào)用measureChild方法,在measureChild中荠雕,首先會(huì)調(diào)用getChildMeasureSpec方法稳其,入?yún)⒑蜕厦骖愃疲瑓^(qū)別在于padding入?yún)H僅為自身的padding炸卑,然后會(huì)調(diào)用子元素的measure方法(和measureChildWithMargins非常類似)既鞠。

2、View的Measure過(guò)程

View的measure方法是一個(gè)final方法盖文,會(huì)調(diào)用onMeasure方法嘱蛋,因此只需要關(guān)注onMeasure方法,入?yún)樽约旱膍easureSpec

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

setMeasuredDimension用于設(shè)置測(cè)量的寬高五续,測(cè)量好之后洒敏,必須調(diào)用

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

簡(jiǎn)單理解疙驾,getDefaultSize返回的就是measureSpec中的specSize凶伙,這就是View測(cè)量后的大小。在AT_MOST和EXACTLY模式下它碎,都返回了specSize函荣。也就是說(shuō)對(duì)于一個(gè)直接繼承View的自定義View显押,它的wrap_content和match_parent效果一樣,因此如果要實(shí)現(xiàn)自定義View的wrap_content傻挂,則要重寫onMeasure方法乘碑。解決問(wèn)題:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
      // 在 MeasureSpec.AT_MOST 模式下,給定一個(gè)默認(rèn)值mWidth,mHeight金拒。默認(rèn)寬高靈活指定
      //參考TextView兽肤、ImageView的處理方式
      //其他情況下沿用系統(tǒng)測(cè)量規(guī)則即可
    if (widthSpecMode == MeasureSpec.AT_MOST
            && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWith, mHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWith, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSpecSize, mHeight);
    }
}

getSuggestedMinimumWidth()方法就是:如果View沒(méi)有設(shè)置背景,就返回minWidth屬性值(可以為0)绪抛;如果設(shè)置了背景轿衔,就返回minWidth和背景的最小寬度之間的最大值。

View的measure過(guò)程是三大流程中最復(fù)雜的一個(gè)睦疫,measure完成以后,通過(guò) getMeasuredWidth/Height 方法就可以正確獲取到View的測(cè)量后寬/高鞭呕。在某些情況下蛤育,系統(tǒng)可能需要多次measure才能確定最終的測(cè)量寬/高,所以在onMeasure中拿到的寬/高很可能不是準(zhǔn)確的葫松。一個(gè)較好的習(xí)慣是在onLayout方法中瓦糕,去獲取View測(cè)量寬高或最終寬高。

3腋么、如何正確獲得寬高

如果我們想要在Activity啟動(dòng)的時(shí)候就獲取一個(gè)View的寬高咕娄,怎么操作呢?因?yàn)閂iew的measure過(guò)程和Activity的生命周期并不是同步執(zhí)行珊擂,無(wú)法保證在Activity的 onCreate圣勒、onStart、onResume 時(shí)某個(gè)View就已經(jīng)測(cè)量完畢摧扇。所以有以下四種方式來(lái)獲取View的寬高:

3.1圣贸、Activity/View#onWindowFocusChanged

onWindowFocusChanged這個(gè)方法的含義是:VieW已經(jīng)初始化完畢了,寬高已經(jīng)準(zhǔn)備好了扛稽,需要注意:它會(huì)被調(diào)用多次吁峻,當(dāng)Activity的窗口得到焦點(diǎn)和失去焦點(diǎn)均會(huì)被調(diào)用。

3.2在张、view.post(runnable)

通過(guò)post將一個(gè)runnable投遞到消息隊(duì)列的尾部用含,當(dāng)Looper調(diào)用此runnable的時(shí)候,View也初始化好了帮匾。

3.3啄骇、ViewTreeObserver

使用 ViewTreeObserver 的眾多回調(diào)可以完成這個(gè)功能,比如OnGlobalLayoutListener 這個(gè)接口辟狈,當(dāng)View樹(shù)的狀態(tài)發(fā)送改變或View樹(shù)內(nèi)部的View的可見(jiàn)性發(fā)生改變時(shí)肠缔,onGlobalLayout 方法會(huì)被回調(diào)夏跷,這是獲取View寬高的好時(shí)機(jī)。需要注意的是明未,伴隨著View樹(shù)狀態(tài)的改變槽华, onGlobalLayout 會(huì)被回調(diào)多次。

3.4趟妥、view.measure(int widthMeasureSpec,intheightMeasureSpec)

手動(dòng)對(duì)view進(jìn)行measure猫态。需要根據(jù)View的layoutParams分情況處理:

  • match_parent:直接放棄。根據(jù)上表所示披摄,需要知道parentSize亲雪,即父容器剩余空間,而此時(shí)無(wú)法知道這個(gè)值疚膊。
  • 具體的數(shù)值( dp/px):
  int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
  int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
  view.measure(widthMeasureSpec,heightMeasureSpec);
  • wrap_content:
  int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
  // View的尺寸使用30位二進(jìn)制表示义辕,最大值30個(gè)1,在AT_MOST模式下寓盗,我們用View理論上能支持的最大值去構(gòu)造MeasureSpec是合理的
  int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
  view.measure(widthMeasureSpec,heightMeasureSpec);

四灌砖、layout過(guò)程

layout方法確定View本身的位置,會(huì)調(diào)用onLayout方法傀蚌。onLayout確定所有子元素的位置基显,通過(guò)遍歷所有的子View并調(diào)用其layout方法。

View#layout中善炫,setFrame確定View的四個(gè)頂點(diǎn)位置撩幽,即初始化mLeft,mRight箩艺,mTop窜醉,mBottom這四個(gè)值(確定了最終的寬高),也就確定了View在父容器中的位置艺谆。接著調(diào)用onLayout方法酱虎,確定所有子View的位置,和onMeasure一樣擂涛,onLayout的具體實(shí)現(xiàn)和布局有關(guān)读串,因此View和ViewGroup均沒(méi)有真正實(shí)現(xiàn)onLayout方法。

View的測(cè)量寬高和最終寬高的區(qū)別:
在View的默認(rèn)實(shí)現(xiàn)中撒妈,View的測(cè)量寬高和最終寬高相等恢暖,只不過(guò)測(cè)量寬高形成于measure過(guò)程,最終寬高形成于layout過(guò)程狰右。即便View需要多次測(cè)量才能確定自己的測(cè)量寬高杰捂,但最終來(lái)說(shuō),測(cè)量寬高和最終寬高還是一致棋蚌。

五嫁佳、draw過(guò)程

View的繪制過(guò)程遵循如下幾步:

  • 繪制背景 drawBackground(canvas)
  • 繪制自己 onDraw
  • 繪制children dispatchDraw 遍歷所有子View的 draw 方法
  • 繪制裝飾 onDrawScrollBars

View#setWillNotDraw挨队,如果一個(gè)View不需要繪制任何內(nèi)容,那么置為ture蒿往,系統(tǒng)會(huì)進(jìn)行相應(yīng)的優(yōu)化盛垦。默認(rèn)情況下,View為false瓤漏,ViewGroup為true腾夯。所以自定義ViewGroup需要通過(guò)onDraw來(lái)繪制內(nèi)容時(shí),必須顯式的關(guān)閉 WILL_NOT_DRAW 這個(gè)優(yōu)化標(biāo)記位蔬充,即調(diào)用 setWillNotDraw(false)蝶俱。

六、自定義View

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饥漫,一起剝皮案震驚了整個(gè)濱河市榨呆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌庸队,老刑警劉巖愕提,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異皿哨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)纽谒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門证膨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鼓黔,你說(shuō)我怎么就攤上這事央勒。” “怎么了澳化?”我有些...
    開(kāi)封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵崔步,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缎谷,道長(zhǎng)井濒,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任列林,我火速辦了婚禮瑞你,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘希痴。我一直安慰自己者甲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布砌创。 她就那樣靜靜地躺著虏缸,像睡著了一般鲫懒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刽辙,一...
    開(kāi)封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天窥岩,我揣著相機(jī)與錄音,去河邊找鬼扫倡。 笑死谦秧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撵溃。 我是一名探鬼主播疚鲤,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缘挑!你這毒婦竟也來(lái)了集歇?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤语淘,失蹤者是張志新(化名)和其女友劉穎诲宇,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體惶翻,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姑蓝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吕粗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纺荧。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖颅筋,靈堂內(nèi)的尸體忽然破棺而出宙暇,到底是詐尸還是另有隱情,我是刑警寧澤议泵,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布占贫,位于F島的核電站,受9級(jí)特大地震影響先口,放射性物質(zhì)發(fā)生泄漏型奥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一碉京、第九天 我趴在偏房一處隱蔽的房頂上張望桩引。 院中可真熱鬧,春花似錦收夸、人聲如沸坑匠。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厘灼。三九已至夹纫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間设凹,已是汗流浹背舰讹。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闪朱,地道東北人月匣。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像奋姿,于是被迫代替她去往敵國(guó)和親锄开。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353