Android - Fragment

Fragment

  1. Fragment,碎片,是 Android 3.0 开始引入的,其目的在于同时兼容手机和平板的开发,能让程序更合理地利用大屏幕的空间;
  2. Fragment 是可以嵌入到 Activity 中的 UI 片段,也必须嵌入到 Activity 中,它可以包含布局,也有自己的生命周期(其生命周期受到其宿主 Activity 的影响),是一个轻量型的 Activity;
  3. 一个 Activity 中可以包含多个 Fragment,一个 Fragment 也可以在多个 Activity 中复用;
  4. 下图取自官方文档,很好地解释了 Fragment 的应用:


Fragment 的使用

Fragment 有两种使用方式,一种是是静态添加到 Activity 中,另一种是动态添加 Activity 中。

静态添加

  1. 首先在一个 Activity 中添加两个 Fragment,这两个 Fragment 是水平分布,首先是左侧的 Fragment 布局文件,fragment_left.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#0000ff"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="左侧Fragment按钮" />

</LinearLayout>

我们将左侧的 Fragment 背景颜色设为蓝色,在里面放了一个 Button,并让它水平居中。

  1. 然后是右侧的 Fragment 的布局文件,fragment_right.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FF0000"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="右侧Fragment"
        android:textSize="36sp" />

</LinearLayout>

我们将右侧的 Fragment 背景颜色设为红色,在里面放了一个 TextView,并让它水平居中。

  1. 左侧的 XML 布局文件对应的 Java 代码,LeftFragment.java
public class LeftFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_left, container, false);
        return view;
    }

}
  1. 右侧的 XML 布局文件对应的 Java 代码,RightFragment.java
public class RightFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_right, container, false);
        return view;
    }
    
}

上面分别新建了两个类,并让它们继承自 Fragment 类,代表左右侧 Fragment,并将各自对应的布局传进去。

  1. 接下来是 activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/fragment_left"
        android:name="net.monkeychan.fragmenttest.LeftFragment"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />

    <fragment
        android:id="@+id/fragment_right"
        android:name="net.monkeychan.fragmenttest.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

</LinearLayout>

在 Activity 的布局中,我们定义了两个 fragment 控件,并让它们水平排列,其中右侧的 Fragment 充满了剩下的空间。
注意: fragment 控件中有个属性 android:name,其值为 包名.类名,包名不能省略。

  1. MainActivity 的代码,默认即可,MainActivity.java:
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

}
  1. 效果演示:

总结:静态添加 Fragment 的步骤

  1. 新建 Fragment 的布局;
  2. 新建一个继承自 Fragment 类 的类,并将对应的布局传进去;
  3. 在 Activity 布局中定义 fragment 控件,将 fragment 控件的 *android:name * 属性的值设为 包名.类名

动态添加

上面的程序中我们定义了一个按钮,但没有给它添加点击事件,现在我们把给它增加一个功能,使按钮被按下时,把右侧的 Fragment 更换成另一个 Fragment。

  1. 新建 fragment_right_2.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#00FF00"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="右侧第二个Fragment"
        android:textSize="36sp" />

</LinearLayout>

上面只是在原来 fragment_right.xml 上修改了背景颜色和 TextView 的颜色。

  1. SecondRightFragment.java:
public class SecondRightFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_right_2, container, false);
        return view;
    }

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

    <fragment
        android:id="@+id/fragment_left"
        android:name="net.monkeychan.fragmenttest.LeftFragment"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />

    <FrameLayout
        android:id="@+id/layout_right"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1">

        <fragment
            android:id="@+id/fragment_right"
            android:name="net.monkeychan.fragmenttest.RightFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </FrameLayout>
    
</LinearLayout>

activity_main.xml 中我们添加了在 LinearLayout 中嵌套了一个 FrameLayout 布局,并让它与左侧的 fragment 控件水平排列,后面我们将在代码中用另一个 Fragment 来取代 FrameLayout 里的内容,FrameLayout 在这里起占位作用,FrameLayout 也可以换成其他布局。

  1. MainActivity.java:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(this);

    }

    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn:
                replaceFragment();
                break;
            default:
                break;
        }
    }

    private void replaceFragment() {

        // 1. 创建需要添加的 Fragment 的对象
        SecondRightFragment fragment = new SecondRightFragment();

        // 2. 获取 FragmentManager,在  Activity 中直接调用 getFragmentManager() 方法获取
        FragmentManager fm = getFragmentManager();

        // 3. 开启一个事务,通过调用 beginTransaction() 方法开启
        FragmentTransaction transaction = fm.beginTransaction();

        // 4. 向布局中添加 Fragment,调用 replace() 方法实现,需要传入布局的 id 和待添加的 Fragment 的对象
        transaction.replace(R.id.layout_right, fragment);

        // 5. 提交事务,调用 commit() 方法来完成
        transaction.commit();

    }
    
}
  1. 效果演示:

点击按钮之后,变成下面的界面:

此时点击返回键,会发现直接退出程序。如果想要点击返回键不退出程序,而是返回上一个 Fragment 时,可以在调用 FragmentTransaction 的 commit() 方法之前调用 addToBackStack() 方法,并给它传入 null,之后按下返回键将不会直接退出程序,而是返回上一个 Fragment。

总结:动态添加 Fragment 的步骤

  1. 创建需要添加的 Fragment 的对象;
  2. 获取 FragmentManager,在 Activity 中直接调用 getFragmentManager() 方法获取;
  3. 开启一个事务,通过调用 FragmentManager 对象的 beginTransaction() 方法开启;
  4. 向布局中添加 Fragment,调用 FragmentTransaction 的 replace() 方法实现,需要传入布局的 id 和待添加的 Fragment 的对象;
  5. 提交事务,调用 FragmentTransaction 的 commit() 方法来完成。

Fragment 的生命周期

  1. 先通过一张图片来了解 Fragment 的生命周期,以下图片取自《第一行代码》:


  • onAttach(): 当 Fragment 和 Activity 第一次建立联系时调用,之后不再调用;
  • onCreate(): onAttach() 方法之后立刻调用;
  • onCreateView(): 加载布局时调用;
  • onActivityCreated(): 与 Fragment 关联的 Activity 创建之后调用;
  • onStart(): Fragment 可见时调用;
  • onResume(): onStart() 执行之后调用;
  • onPause(): 当另一个 Activity 在前台并获得焦点,但该 Activity 并没有占满整个屏幕,而 Fragment 的 宿主 Activity (即Fragment 所在的 Activity) 仍然可见时调用;
  • onStop(): Fragment 不可见时调用:当 Fragment 的宿主 Activity 处于 stopped 状态时调用;当 Fragment 从宿主 Activity 中删除 (调用 remove() 方法) 但没有被添加到返回栈 (没有调用 addToBackStack() 方法) 之中时调用。(处于 stopped 状态的 Fragment 依然存活,它的所有状态和信息会被系统保留起来,只是对于用户而言是不可见的,当 宿主 Activity 被杀死时,Fragment 相应地也会被杀死);
  • onDestroyView(): 当与 Fragment 关联的 View 被被移除时调用;
  • onDestroy(): 当 Fragment 不再被使用时调用,如按返回键时;
  • onDeatach(): 当 Fragment 与 Activity 解除关联时调用。
    详细内容请看这篇文章 Fragment 整个生命周期演示
  1. 接下来修改上面的 RightFragment.java 代码,重写图片中的方法,重写的规则是在每个方法里面增加一条 Log.d 语句,这样我们就能在 logcat 里面看到每个方法在什么时候被调用:
public class RightFragment extends Fragment {

    private static final String TAG = "Lifecycle";

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d(TAG,"onAttach");
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"onCreate");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(TAG,"onCreateView");
        View view = inflater.inflate(R.layout.fragment_right, container, false);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d(TAG,"onActivityCreated");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG,"onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG,"onResume");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG,"onPause");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.d(TAG,"onStop");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG,"onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d(TAG,"onDestroy");
    }

}

3. 好了,接下来运行程序,并打开 logcat 窗口,看看发生了什么:

可以看到,在程序启动时调用了 onCreate() 方法,接着在加载布局的时候调用了 onCreateView() 方法,紧接着又分别调用了 onActivityCeated() 、onStart() 、onResume() 这三个方法。哎?等等,好像有点不对,怎么一开始不是调用 onAttach() 方法?回到我们上面的代码,我们在重写 onAttach() 方法时,传入的参数为类型为 Context,所以在一开始是不会调用的,将传入的参数类型修改为 Activity 就可以了。另外,在 SDK API 23 的版本中,当我们传入的参数类型为 Activity 时会提示该方法已过时。

  1. 将 onAttach() 方法传入的参数类型改为 Activity,再次运行程序,此时执行的方法如下:

当我们点击左侧 Fragment 中的按钮时,将会动态替换掉 RightFragment,此时执行的方法如下:

  1. 让我们重新运行程序,此时执行的方法如下:

此时按下主页键,执行的方法如下:

再按下多任务键,回到程序,此时执行的方法如下:

  1. 再重新运行程序,然后按下多任务键,此时执行的方法如下:

再回到程序,此时执行的方法如下:

  1. 再重新运行程序,然后按下返回键,此时执行的方法如下:

通过上面的一系列操作,我们对 Fragment 的生命周期已经有了一些了解了。

Fragment 与 Activity 之间的通信

  1. 在 Activity 里调用 Fragment 里的方法:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

在 Activity 中调用 FragmentManager 对象的 findFragmentById() 方法,就能获得与 Activity 关联的 Fragment 的实例,进而通过该实例调用 Fragment 里的方法。

  1. 在 Fragment 里调用 Activity 里的方法:
MainActivity activity = (MainActivity) getActivity();

在 Fragment 中通过 getActivity() 方法,就能获得与 Fragment 关联的 Activity 的实例,进而通过该实例调用 Activity 里的方法。

  1. 下面来看一个实例:

左边是一个 Activity 里的 ListView,右边是一个 Fragment,当点击左边 ListView 的某一个子项时,在右边显示该子项的名字及大图。

  1. 首先是 Activity 的布局,activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="150dp"
        android:layout_height="match_parent"
        android:layout_marginTop="16dp" />

    <!-- FrameLayout 在此起占位作用,后面将用 Fragment 替代它里面的内容 -->
    <FrameLayout
        android:id="@+id/frame_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

</LinearLayout>
  1. 接下来是右边 Fragment 的布局,fragment_large.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_large"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:textColor="#FF7F00"
        android:textSize="24sp" />

    <ImageView
        android:id="@+id/iv_large"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:scaleType="centerCrop" />

</LinearLayout>
  1. 样式文件,控制 ListView 的外观,list_view_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/iv_small"
        android:layout_width="60dp"
        android:layout_height="60dp" />

    <TextView
        android:id="@+id/tv_small"
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        android:layout_marginLeft="10dp"
        android:gravity="center" />

</LinearLayout>
  1. 定义一个接口,接口里面定义了一个 send() 方法,此方法用于从 Activity 向 Fragment 发送数据,即当 Activity 里的 ListView 的某一子项被点击时,将该子项的名字与图片 id 传给右边的 Fragment,右边的 Fragment 接收到数据之后,将数据在自己的布局中显示出来。
    FragmentCallbackListener.java:
public interface FragmentCallbackListener {
    void send(String name, int imageId);
}
  1. MainActivity.java:
public class MainActivity extends AppCompatActivity {

    // 定义一个 int 型的数组,用于存放图片资源的 id
    private int[] imageId = {R.drawable.aries, R.drawable.taurus, R.drawable.gemini, R.drawable.cancer,
            R.drawable.leo, R.drawable.virgo, R.drawable.libra, R.drawable.scorpio, R.drawable.sagittarius,
            R.drawable.capricorn, R.drawable.aquarius, R.drawable.pisces, R.drawable.fuck_off};

    // 定义一个 String 类型的数组,用于存放名称
    private String[] titles = {"白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座",
            "天秤座", "天蝎座", "射手座", "摩羯座", "水瓶座", "双鱼座", "Surprise"};

    // 定义一个 FragmentCallbackListener 接口的引用
    private FragmentCallbackListener listener;

    public FragmentCallbackListener getListener() {
        return listener;
    }

    public void setListener(FragmentCallbackListener listener) {
        this.listener = listener;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        List<Map<String, Object>> listItems = new ArrayList<>();
        for (int i = 0; i < titles.length; i++) {
            Map<String, Object> listItem = new HashMap<>();
            listItem.put("imageId", imageId[i]);
            listItem.put("titles", titles[i]);
            listItems.add(listItem);
        }

        // 1. 实例化布局中的控件
        ListView listView = (ListView) findViewById(R.id.list_view);

        // 2. 创建一个 Adapter 对象,并进行设置
        SimpleAdapter adapter = new SimpleAdapter(this, listItems, R.layout.list_view_item,
                new String[]{"imageId", "titles"}, new int[]{R.id.iv_small, R.id.tv_small});

        // 3. 将 Adapter 设置到 AdapterView
        listView.setAdapter(adapter);

        // 4. 设置 AdapterView 的监听事件
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                // 调用 FragmentCallbackListener 接口的 send() 方法,
                // 把用户当前点击的子项的名称和图片 id 传给实现 FragmentCallbackListener 接口的地方
                listener.send(titles[i], imageId[i]);
            }
        });

        replaceFragment();
    }

    // 此方法用于动态添加 Fragment
    private void replaceFragment() {

        // 1. 创建一个待添加的 Fragment 的对象
        LargeViewFragment fragment = new LargeViewFragment();

        // 2. 获取 FragmentManager,通过 getFragmentManager() 方法获取
        FragmentManager fm = getFragmentManager();

        // 3. 开启一个事务,通过 FragmentManager 对象的 beginTransaction() 方法开启
        FragmentTransaction transaction = fm.beginTransaction();

        // 4. 向布局中添加 Fragment
        transaction.replace(R.id.frame_layout, fragment);

        // 5. 提交事务
        transaction.commit();
    }

}
  1. LargeViewFragment.java:
public class LargeViewFragment extends Fragment {

    private TextView tv_large;
    private ImageView iv_large;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        
        // 加载 Fragment 的布局
        View view = inflater.inflate(R.layout.fragment_large, container, false);
        
        // 实例化布局中的控件
        tv_large = (TextView) view.findViewById(R.id.tv_large);
        iv_large = (ImageView) view.findViewById(R.id.iv_large);

        // 获取宿主 Activity 的实例
        MainActivity activity = (MainActivity) getActivity();

        // 调用 Activity 中的 setListener 方法,并实现 FragmentCallbackListener 接口
        activity.setListener(new FragmentCallbackListener() {
            @Override
            public void send(String name, int imageId) {
                // 更新 UI
                tv_large.setText(name);
                iv_large.setImageResource(imageId);
            }
        });
        return view;
    }
    
}

注意: 上面的代码中使用了接口回调,在 MainActivity 中并不实现该接口,而是在 Fragment 中实现该接口;也就是说,发送数据的一方不实现接口,接收数据的一方负责实现接口。

  1. 演示效果:

Fragment 与 Fragment 之间的通信

在一个 Fragment 中可以得到与它关联的 Activity,然后再通过这个 Activity 去获取另外一个 Fragment 的实例,这样也就实现了不同碎片之间的通信功能。——《第一行代码》- 郭霖


参考资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容