Jetpack學(xué)習(xí)-Navigation

個人博客

http://www.milovetingting.cn

Jetpack學(xué)習(xí)-Navigation

Navigation是什么

Navigation翻譯過來就是導(dǎo)航。

導(dǎo)航是指支持用戶導(dǎo)航、進(jìn)入和退出應(yīng)用中不同內(nèi)容片段的交互褂微。Android Jetpack 的導(dǎo)航組件可幫助您實現(xiàn)導(dǎo)航呐籽,無論是簡單的按鈕點擊随闪,還是應(yīng)用欄和抽屜式導(dǎo)航欄等更為復(fù)雜的模式搀崭,該組件均可應(yīng)對捻浦。導(dǎo)航組件還通過遵循一套既定原則來確保一致且可預(yù)測的用戶體驗粗卜。

導(dǎo)航組件由以下三個關(guān)鍵部分組成:

  • 導(dǎo)航圖:在一個集中位置包含所有導(dǎo)航相關(guān)信息的 XML 資源屋确。這包括應(yīng)用內(nèi)所有單個內(nèi)容區(qū)域(稱為目標(biāo))以及用戶可以通過應(yīng)用獲取的可能路徑。

  • NavHost:顯示導(dǎo)航圖中目標(biāo)的空白容器续扔。導(dǎo)航組件包含一個默認(rèn) NavHost 實現(xiàn) (NavHostFragment)攻臀,可顯示 Fragment 目標(biāo)。

  • NavController:在 NavHost 中管理應(yīng)用導(dǎo)航的對象纱昧。當(dāng)用戶在整個應(yīng)用中移動時刨啸,NavController 會安排 NavHost 中目標(biāo)內(nèi)容的交換。

在應(yīng)用中導(dǎo)航時识脆,您告訴 NavController设联,您想沿導(dǎo)航圖中的特定路徑導(dǎo)航至特定目標(biāo)善已,或直接導(dǎo)航至特定目標(biāo)。NavController 便會在 NavHost 中顯示相應(yīng)目標(biāo)离例。

導(dǎo)航組件提供各種其他優(yōu)勢换团,包括以下內(nèi)容:

  • 處理 Fragment 事務(wù)。

  • 默認(rèn)情況下宫蛆,正確處理往返操作艘包。

  • 為動畫和轉(zhuǎn)換提供標(biāo)準(zhǔn)化資源。

  • 實現(xiàn)和處理深層鏈接耀盗。

  • 包括導(dǎo)航界面模式(例如抽屜式導(dǎo)航欄和底部導(dǎo)航)想虎,用戶只需完成極少的額外工作。

  • Safe Args - 可在目標(biāo)之間導(dǎo)航和傳遞數(shù)據(jù)時提供類型安全的 Gradle 插件袍冷。

  • ViewModel 支持 - 您可以將 ViewModel 的范圍限定為導(dǎo)航圖磷醋,以在圖表的目標(biāo)之間共享與界面相關(guān)的數(shù)據(jù)。

  • 此外胡诗,您還可以使用 Android Studio 的 Navigation Editor 來查看和編輯導(dǎo)航圖邓线。

以上內(nèi)容來自官方文檔(我只是一個搬運工\(^o^)/)

簡單使用

引入Navigation

在需要使用Navigation的模塊的build.gradle中引入

    def nav_version = "2.3.0-alpha01"
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-ui:$nav_version"

建立導(dǎo)航圖

在res目錄右鍵-New-Android Resource File

nav-graph.png

在彈出的界面中,F(xiàn)ile name可隨意輸入煌恢,Resource type選擇Navigation,點擊確定

nav-graph2.png

點擊確定后骇陈,會在res目錄下創(chuàng)建navigation目錄,以及剛才定義的導(dǎo)航文件

nav-graph3.png

雙擊打開剛才創(chuàng)建的導(dǎo)航文件瑰抵,在Design界面可以看到目前還沒有內(nèi)容你雌,可以點擊上方的+號圖標(biāo)添加fragment,也可以自己手動在xml中添加

nav-graph4.png

我們需要為這個文件指定startDestination二汛,即起始的界面

nav-graph5.png

startDestination指定為mainFragment婿崭,mainFragment對應(yīng)的布局為fragment_main

Navigation首先會加載一個默認(rèn)的Fragment,這個需要在Activity中指定

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

</LinearLayout>

配置defaultNavHost為true,即指定這個fragment為默認(rèn)的NavHost肴颊,每個導(dǎo)航圖只能指定一個默認(rèn)的NavHost氓栈。這里的name配置為androidx.navigation.fragment.NavHostFragment,navGraph配置為nav_graph,即指定nav_graph為導(dǎo)航圖婿着。這樣當(dāng)Activity啟動時授瘦,會首先通過activity布局里的fragment去加載導(dǎo)航圖中的startDestination配置的fragment。

導(dǎo)航

通過一個Fragment導(dǎo)航到另一個Fragment竟宋,可以通過

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        loginBtn = view.findViewById(R.id.fragment_main_login);
        loginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bundle bundle = new Bundle();
                bundle.putString("name", "zs");
                Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_loginFragment, bundle);
            }
        });
        return view;
    }

這里通過點擊一個按鈕進(jìn)行跳轉(zhuǎn)提完,通過Navigation.findNavController(v).navigate()方法導(dǎo)航。這里還可以通過Bundle進(jìn)行傳值丘侠。

在目的Fragment,可以通過getArguments()來獲取到傳遞過來的數(shù)據(jù)

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        String name = getArguments().getString("name", "null");
        Toast.makeText(getContext(), name, Toast.LENGTH_SHORT).show();
        View view = inflater.inflate(R.layout.fragment_login, container, false);
        backBtn = view.findViewById(R.id.fragment_login_back);
        backBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Navigation.findNavController(v).popBackStack();
            }
        });
        return view;
    }

在目的Fragment徒欣,還可以通過一個按鈕返回上一個Fragment:Navigation.findNavController(v).popBackStack()

原理

Navigation的簡單使用流程就介紹到這,可以在官方文檔上看更多相關(guān)的使用方法蜗字。下面來分析下Navigation的流程

顯示起始Fragment

在Activity啟動時帚称,會先實例化NavHostFragment,這個是我們前面在布局中指定的官研。

首先會執(zhí)行onInflate方法

public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
            @Nullable Bundle savedInstanceState) {
        super.onInflate(context, attrs, savedInstanceState);

        final TypedArray navHost = context.obtainStyledAttributes(attrs,
                androidx.navigation.R.styleable.NavHost);
        final int graphId = navHost.getResourceId(
                androidx.navigation.R.styleable.NavHost_navGraph, 0);
        if (graphId != 0) {
            mGraphId = graphId;
        }
        navHost.recycle();

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
        final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
        if (defaultHost) {
            mDefaultNavHost = true;
        }
        a.recycle();
    }

這個方法秽澳,其實就是解析出來我們要使用哪個導(dǎo)航圖闯睹,獲取到了graphId。還獲取了是否為默認(rèn)的Host:defaultHost

然后會執(zhí)行onAttach方法

public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        // TODO This feature should probably be a first-class feature of the Fragment system,
        // but it can stay here until we can add the necessary attr resources to
        // the fragment lib.
        if (mDefaultNavHost) {
            getParentFragmentManager().beginTransaction()
                    .setPrimaryNavigationFragment(this)
                    .commit();
        }
    }

由于在onInflate已經(jīng)獲取到mDefaultNavHost為true,因此這里會將當(dāng)前Fragment通過commit加入到FragmentManager()中

然后執(zhí)行onCreate方法

public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Context context = requireContext();

        //設(shè)置NavHostController,NavHostController里初始化了NavigatorProvider
        mNavController = new NavHostController(context);
        mNavController.setLifecycleOwner(this);
        mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
        // Set the default state - this will be updated whenever
        // onPrimaryNavigationFragmentChanged() is called
        mNavController.enableOnBackPressed(
                mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
        mIsPrimaryBeforeOnCreate = null;
        mNavController.setViewModelStore(getViewModelStore());
        onCreateNavController(mNavController);

        Bundle navState = null;
        if (savedInstanceState != null) {
            navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
            if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
                mDefaultNavHost = true;
                getParentFragmentManager().beginTransaction()
                        .setPrimaryNavigationFragment(this)
                        .commit();
            }
            mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID);
        }

        if (navState != null) {
            // Navigation controller state overrides arguments
            mNavController.restoreState(navState);
        }
        if (mGraphId != 0) {
            // Set from onInflate()
            //前面執(zhí)行onInflate后担神,已經(jīng)獲取到mGraphId楼吃,因此會執(zhí)行下面的setGraph代碼
            mNavController.setGraph(mGraphId);
        } else {
            // See if it was set by NavHostFragment.create()
            final Bundle args = getArguments();
            final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;
            final Bundle startDestinationArgs = args != null
                    ? args.getBundle(KEY_START_DESTINATION_ARGS)
                    : null;
            if (graphId != 0) {
                mNavController.setGraph(graphId, startDestinationArgs);
            }
        }
    }

在這個方法里,設(shè)置了NavHostController及NavigatorProvider妄讯,然后執(zhí)行NavController.setGraph方法

public void setGraph(@NavigationRes int graphResId) {
        setGraph(graphResId, null);
    }

繼續(xù)調(diào)用setGraph

public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
        setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
    }

繼續(xù)調(diào)用setGraph

public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
        if (mGraph != null) {
            // Pop everything from the old graph off the back stack
            popBackStackInternal(mGraph.getId(), true);
        }
        mGraph = graph;
        onGraphCreated(startDestinationArgs);
    }

調(diào)用onGraphCreated

private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
        if (mNavigatorStateToRestore != null) {
            ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList(
                    KEY_NAVIGATOR_STATE_NAMES);
            if (navigatorNames != null) {
                for (String name : navigatorNames) {
                    Navigator<?> navigator = mNavigatorProvider.getNavigator(name);
                    Bundle bundle = mNavigatorStateToRestore.getBundle(name);
                    if (bundle != null) {
                        navigator.onRestoreState(bundle);
                    }
                }
            }
        }
        if (mBackStackToRestore != null) {
            for (Parcelable parcelable : mBackStackToRestore) {
                NavBackStackEntryState state = (NavBackStackEntryState) parcelable;
                NavDestination node = findDestination(state.getDestinationId());
                if (node == null) {
                    throw new IllegalStateException("unknown destination during restore: "
                            + mContext.getResources().getResourceName(state.getDestinationId()));
                }
                Bundle args = state.getArgs();
                if (args != null) {
                    args.setClassLoader(mContext.getClassLoader());
                }
                NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args,
                        mLifecycleOwner, mViewModel,
                        state.getUUID(), state.getSavedState());
                mBackStack.add(entry);
            }
            updateOnBackPressedCallbackEnabled();
            mBackStackToRestore = null;
        }
        if (mGraph != null && mBackStack.isEmpty()) {
            boolean deepLinked = !mDeepLinkHandled && mActivity != null
                    && handleDeepLink(mActivity.getIntent());
            if (!deepLinked) {
                // Navigate to the first destination in the graph
                // if we haven't deep linked to a destination

                //首次進(jìn)入時孩锡,會執(zhí)行這個代碼
                navigate(mGraph, startDestinationArgs, null, null);
            }
        }
    }

首次進(jìn)入Activity,會最終執(zhí)行navigate(mGraph, startDestinationArgs, null, null)方法來導(dǎo)航到起始的目的Fragment

private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        boolean popped = false;
       //...
        Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                node.getNavigatorName());
        Bundle finalArgs = node.addInDefaultArgs(args);
        NavDestination newDest = navigator.navigate(node, finalArgs,
                navOptions, navigatorExtras);
        //...
    }

這個方法亥贸,會調(diào)用navigator.navigate(node, finalArgs,navOptions, navigatorExtras)方法躬窜,這個方法的實現(xiàn)在NavGraphNavigator

public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
        int startId = destination.getStartDestination();
        if (startId == 0) {
            throw new IllegalStateException("no start destination defined via"
                    + " app:startDestination for "
                    + destination.getDisplayName());
        }
        NavDestination startDestination = destination.findNode(startId, false);
        if (startDestination == null) {
            final String dest = destination.getStartDestDisplayName();
            throw new IllegalArgumentException("navigation destination " + dest
                    + " is not a direct child of this NavGraph");
        }
        Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                startDestination.getNavigatorName());
        return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args),
                navOptions, navigatorExtras);
    }

在這個方法里調(diào)用navigator.navigate方法,這個方法實現(xiàn)在FragmentNavigator

public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                    + " saved its state");
            return null;
        }
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        //通過反射實例化Fragment
        final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                className, args);
        frag.setArguments(args);
        final FragmentTransaction ft = mFragmentManager.beginTransaction();

        int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
        int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
        int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
        int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
            popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }

        //替換fragment
        ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

        final @IdRes int destId = destination.getId();
        final boolean initialNavigation = mBackStack.isEmpty();
        // TODO Build first class singleTop behavior for fragments
        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId;

        boolean isAdded;
        if (initialNavigation) {
            isAdded = true;
        } else if (isSingleTopReplacement) {
            // Single Top means we only want one instance on the back stack
            if (mBackStack.size() > 1) {
                // If the Fragment to be replaced is on the FragmentManager's
                // back stack, a simple replace() isn't enough so we
                // remove it from the back stack and put our replacement
                // on the back stack in its place
                mFragmentManager.popBackStack(
                        generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
                ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
            }
            isAdded = false;
        } else {
            ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
            isAdded = true;
        }
        if (navigatorExtras instanceof Extras) {
            Extras extras = (Extras) navigatorExtras;
            for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
                ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
            }
        }
        ft.setReorderingAllowed(true);
        //提交
        ft.commit();
        // The commit succeeded, update our view of the world
        if (isAdded) {
            mBackStack.add(destId);
            return destination;
        } else {
            return null;
        }
    }

在這個方法中炕置,通過反射實例化目的Fragment荣挨,然后replace原來的Fragment,并commit,這樣目的Fragment就顯示出來了朴摊。

導(dǎo)航到其它Fragment

通過Navigation.findNavController(v).navigate(resId)可以導(dǎo)航到指定的Fragment

public static NavController findNavController(@NonNull View view) {
        NavController navController = findViewNavController(view);
        if (navController == null) {
            throw new IllegalStateException("View " + view + " does not have a NavController set");
        }
        return navController;
    }

然后調(diào)用findViewNavController

private static NavController findViewNavController(@NonNull View view) {
        while (view != null) {
            NavController controller = getViewNavController(view);
            if (controller != null) {
                return controller;
            }
            ViewParent parent = view.getParent();
            view = parent instanceof View ? (View) parent : null;
        }
        return null;
    }

這里通過子View往父View不停查找NavController默垄,這個NavController在前面onCreate的時候已經(jīng)附加到了view上。

找到NavController后甚纲,調(diào)用navigate口锭。這個過程和前面第一次導(dǎo)航到起始Fragment是一樣的流程,這里不再分析介杆。

其實這里只是比較粗的一個梳理鹃操,涉及很多細(xì)節(jié)并沒有具體去看,暫且先有一個流程的印象吧春哨。

附上一張時序圖

Navigation時序圖.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荆隘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子悲靴,更是在濱河造成了極大的恐慌臭胜,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件癞尚,死亡現(xiàn)場離奇詭異耸三,居然都是意外死亡,警方通過查閱死者的電腦和手機浇揩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門仪壮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胳徽,你說我怎么就攤上這事积锅∷” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵缚陷,是天一觀的道長适篙。 經(jīng)常有香客問我,道長箫爷,這世上最難降的妖魔是什么嚷节? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮虎锚,結(jié)果婚禮上硫痰,老公的妹妹穿的比我還像新娘。我一直安慰自己窜护,他們只是感情好效斑,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柱徙,像睡著了一般缓屠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坐搔,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天藏研,我揣著相機與錄音,去河邊找鬼概行。 笑死蠢挡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凳忙。 我是一名探鬼主播业踏,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼涧卵!你這毒婦竟也來了勤家?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤柳恐,失蹤者是張志新(化名)和其女友劉穎伐脖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乐设,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡讼庇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了近尚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蠕啄。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出歼跟,到底是詐尸還是另有隱情和媳,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布哈街,位于F島的核電站留瞳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏叹卷。R本人自食惡果不足惜撼港,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骤竹。 院中可真熱鬧,春花似錦往毡、人聲如沸蒙揣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懒震。三九已至,卻和暖如春嗤详,著一層夾襖步出監(jiān)牢的瞬間个扰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工葱色, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留递宅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓苍狰,卻偏偏與公主長得像办龄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子淋昭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

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