Android入门教程:ConstraintLayout约束布局

翻译By Leelion6。关于 ConstraintLayout 的文章其实已经不少了,不过看到这篇文章写的很有趣,以及在翻译的过程中,感受到了不同文化环境下,写作思维的不同。最关键的是,这篇文章的内容很细致,对初学者比较友好,所以翻译过来以供需要的开发者去学习。如果需要进阶的用法欢迎去看郭霖等大神更深入一些的相关文章。

在本教程中,你将会使用ConstraintLayout从头开始构建一个登录界面,从中学习创建Android 视图相关的基础知识。

作者:Fuad Kamal

原文链接:https://www.raywenderlich.com/9193-constraintlayout-tutorial-for-android-getting-started

所需材料及源码下载:https://koenig-media.raywenderlich.com/uploads/2019/01/RazeGalactic-1.zip

发布日期:2019年1月30日



一款优秀的Android应用程序,需要的不仅仅是美观的UI界面,同时还要有良好的性能表现。在绘制页面的时候,你需要把控视图在用户屏幕上的何处出现,以及它出现的方式

Android提供了多种布局类型,这些布局中确定子View的方式都是不同的。所有布局都来自ViewGroup类。



构建Android UI常用的布局有FrameLayout, LinearLayoutRelativeLayout.

这些布局易于使用,但当视图结构变得复杂时,它们每个都有其局限性和一些性能问题:

  • FrameLayout 只能通过设置相对于父View的Gravity属性来定位子View
  • LinearLayout 不允许各View相互重叠。大多数情况下,你必须使用多个LinearLayout进行嵌套
  • RelativeLayout 需要的性能较多,因为它总是会做两次布局测量



将包含 layout_weight属性的 LinearLayoutRelativeLayout一起嵌套使用,将会指数级地增加布局性能成本。这时就需要 ConstraintLayout 约束布局来进行拯救了。

在较新版本的Android Studio中,ConstraintLayout已经作为默认布局存在了,并且提供了许多放置对象的方法:可以相对于它们的容器本身进行约束、相对于其他对象约束或者彼此之间进行约束、以及相对于你自己创建的guileline(辅助线)进行约束。这些方法可以让你在同一层次中创建大型、复杂、动态和响应式的视图,甚至还能支持动画!


Raze Galactic - 一款星际旅行应用程序

您可以使用该应用程序预定行星之间的旅行,计划周末在空间站的度假,并在您抵达后预定月球车。

在本教程中,你将通过从头开始构建星际旅行应用程序的登录界面UI,以了解ConstraintLayout的基本功能。

image

在此过程中,你将学习:

  • 如何添加和删除约束
  • 如何在界面上相对于其他元素动态定位UI元素

注意:本教程假设你熟悉 Android 和 Android Studio 的基础知识。如果你不熟悉 Android 开发,请先查看我们的 Android入门教程 系列。



入门

打开Android Studio 3.2.1或更高版本,通过从启动界面选择 Start a new Android Studio project 来创建新的Android Studio项目。

image

在下一个界面中,输入 Raze Galactic 作为 Application name (应用程序名称)。

对于 Company domain (公司域名),你可以输入任意你喜欢的内容。该示例使用 raywenderlich.com

对于 Project location (项目位置),请选择计算机上对你来说有意义的地址,并确保地址路径中没有空格。你可以点击项目位置字段右侧的省略号 ... 以直接选择计算机上的目录。

最后,确保已选中 Include Kotlin support (添加Kotlin语言支持) 并点击 Next (下一步) 按钮。

译者注:勾选Kotlin语言支持在本文章中并非必要,后续的内容没有直接使用到Kotilin,所以如果你是纯粹的JAVA语言开发者或者不想使用Kotlin,这里不勾选也完全OK。

image

在接下来的界面中,进行Android设备选择,你需要选择 Phone and Tablet (手机和平板电脑),然后选择 API 28: Android 9.0 (Pie) 并点击Next。

译者注:API部分可以按自己习惯选择,不一定非要到Android9.0这么高的版本。

image

在“Add an Activity to Mobile” (为移动设备添加活动) 界面,选择 Empty Activity (空活动)。在之后你将向这个空活动中添加UI元素,以了解Android中的布局操作。

接下来———你猜对了———点击Next!

image

在最后一个向导页,Configure Activity(配置活动),保留所有默认配置,需要按Finish即可完成项目配置。

image



检查ConstraintLayout版本

在开始创建新布局之前,你需要检查ConstraintLayout是否需要版本更新。

你可以在app模块的构建Gradle文件中找到此信息。在Andoird Studio中,打开 app ▸ build.gradle 并查看其中的 dependencies { … }部分。

在各种构建依赖项中,你应该能找到 implementation 'com.android.support.constraint:constraint-layout:x',其中x是项目中ConstraintLayout所使用的版本。

image

要完成本教程,你需要ConstraintLayout 1.1.3或更高版本。为了确保你拥有最新版本的ConstraintLayout库,请从菜单中选择 Tools ▸ SDK Manager

image

单击 SDK Tools (SDK工具) 选项卡,在 Support Repository (支持的库) 下查看 ConstraintLayout for Android ,如果显示 installed (已安装),则表示你使用的是最新版本

image

接下来,选中名为 Show Package Details (显示包详情) 的复选框,以查看项目中包含的库的版本,Gradle文件中的版本号需要与 SDK manager (SDK 管理器) 中安装的版本相匹配。



通过配置 Android Studio 的设计界面以实现高效的约束布局开发

在继续学习本教程之前,请设置Android Studio视图显示方式,以便更轻松地添加和查看约束以及其相关元素。

首先打开 activity_main.xml ,然后单击工具栏中的 Select Design Surface (选择设计界面) 图标,并选择 Design + Blueprint (设计+蓝图)。

image

现在你在工作时就能看到设计预览界面旁边的蓝图界面了。

image

蓝图视图可以帮助你更清晰地查看约束和 guidelines (辅助线),而不会被内容或背景分散注意力。

image

在开发 APP UI 时,你可以在代码视图 (Text tab) 和设计视图 (Design tab) 之间来回切换。

代码视图允许你查看和编辑布局背后的XML,而设计视图对于可视化操作UI元素非常有用。设计视图还提供了 Component Tree (组件树) , 它能让你查看和选择节目中存在的所有UI元素。

image

当你处于代码视图中,能够看到视觉预览和蓝图是很有用的。这样,当你在预览界面中选择任何元素时,XML中相应的代码块将变成高亮显示。

如果你在代码视图中没有看到预览界面,请单击右侧的 Preview (预览) 选项卡。



从头开始创建新的ConstraintLayout

对于接下来的这一步,请打开 activity_main.xml 并切换到代码视图以查看该文件的源代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".MainActivity">

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

请注意,此布局的默认根元素是android.support.constraint.ConstraintLayout。另外,带有 “Hello World!” 文本的TextView元素已经有了一些约束属性,例如app:layout_constraintBottom_toBottomOf="parent",它的作用是将此视图的底部约束到其父容器的底部。

在相对其父容器顶部、底部、左侧还有右侧都约束后,包含 “Hello World!”的 TextView便处于了屏幕的中心。

切换到设计视图并将鼠标光标移到包含 “Hello World!” 的TextView上, 可以看到有四条波浪线将TextView连接到其父容器上,这些线表示TextView已经应用了约束规则。

image

如果没有出现这些线,请单击TextView,然后它们就会出现。如果已选中 Show Constraints (显示约束) 视图选项,则无需使用鼠标光标悬停或选择任何视图,就可以看到所有约束。

image



向APP中添加图像

对于下一步,你将需要在本教程开头的应用程序屏幕截图中看到的火箭图像。使用教程开头的“ 下载材质”按钮下载本教程的材料,或者点此下载。你可以在 RazeGalactic-starter 文件夹中找到 track_icon.png

获取 track_icon.png 并将其添加到项目的 drawable 文件夹中。您可以通过在Mac上使用 command-C 或在Windows上使用 control-C 进行复制,然后右键单击Android项目中的 drawable 文件夹并使用Mac上的 command-V 或Windows上的 control-V 粘贴图像到项目中。

您可以将UI元素从 Palette (调色板) 拖放到设计界面中。如果没有看到 Palette ,请单击垂直 Palette 选项卡图标。

image

删除“Hello World!”,然后将以下UI元素拖到 Palette 中的视图中:

  • 1个 ImageView, 提示选择图像时选择火箭图像。

    image
  • 3个 Button

  • 3个 TextView

更改UI元素的文本并将其拖动到屏幕周围,以使它们看起来像本教程开头的最终布局预览。不要过分在意视图的尺寸、间距或对齐的精度。

接下来,你需要添加android:textAppearance="@style/TextAppearance.AppCompat.Headline"Raze Galactic TextView以获得合适的样式。

image

请注意,当你在屏幕上拖动视图时,这些小对齐线是能帮助到你的,这些线条可以很容易地将视图们相互排列。

image



测试视图放置的位置

现在,构建并运行你的应用程序。

image

天了噜!视图没有按你在设计视图中分配好的位置排列!一切都在屏幕的左上角蜷缩着,这是肿么回事?

嗯..好吧,Android得到如何放置UI元素的这部分信息,因为你添加的视图没有任何已定义的约束。所以你需要解决这个问题。

回到Android Studio,可以看到 Component Tree (组件树) 中的每个新视图现在都有一系列错误。要查看错误的完整文本,请单击Component Tree (组件树)中的红色感叹号。

image

错误如下:

此视图不受约束。它仅包含在刚添加时的位置,所以它会在运行时放置于(0,0)的位置,除非你添加约束。

切换回代码视图以检查布局XML的源代码。请注意,任何视图的源代码都有一些带tools前缀的属性,例如:

<ImageView
    android:id="@+id/imageView"
    android:layout_width="46dp"
    android:layout_height="46dp"
    app:srcCompat="@drawable/track_icon"
    tools:layout_editor_absoluteX="16dp"
    tools:layout_editor_absoluteY="16dp" />

布局编辑器允许你将小部件放在画布上的任何位置,并使用tools前缀属性记录当前位置。这些属性在实际运行时并不会被应用,请记住这一点。

译者补充: tools前缀的属性都是服务于布局编辑器中的,只在XML的预览视图中起作用,实际运行时是没有用的。


开始了解布局编辑工具栏

切换回布局的设计视图,你可以在布局预览得上方看到一堆小控件。如果将鼠标悬停在每个控件上,可以阅读该控件的简要说明。


自动连接

image

单击工具栏中的磁铁图标以启用 Autoconnect (自动连接) ,将任何视图拖动到父视图角落附近的区域,此时Android Studio将自动为你创建新约束。

image

请注意,这仅适用于在UI元素及其父视图之间创建约束,而不是在UI元素之间创建约束,因此 Autoconnect 的用处非常有限。大多数情况下,你还是关闭 Autoconnect 吧。


自动连接(推测约束)

image

接下来,单击 Infer Constraints (推测约束) 按钮,Android Studio会自动添加布局中缺少的约束。

再次构建并运行您的应用,会发现现在这些组件不在角落,而是出现在你放置它们的位置了。

image

这是添加约束的最简单方法,但也可能是最不好的。因为Android Studio布局的意图往往和你实际的意图不同。

使用自动连接在特定大小的情况下布局可能是正常的,但在不同的屏幕大小或屏幕方向上,可能就不会像你所希望的样子布局了。



清除所有约束

image

如果约束混乱,你可以清除所有约束并从头开始重新添加约束。具体方法是,点击 Clear All Constraints (清楚所有约束) 按钮清除当前页面所有约束。



添加和删除个别约束

Android提供了多种选项,可以将UI元素限制在屏幕的不同部分,让你灵活地设计布局。

在本教程的这一部分中,你将学习如何将对象约束到其容器,删除单个约束以及将对象约束到另一个对象以达到动态布局的效果。


将对象约束到其容器

请注意,当你单击ImageView时,它会突出显示,并且视图的顶部,底部,左侧和右侧会出现小圆圈。这些圆圈是你的约束锚点。

image

当你将鼠标悬停在其中一个约束锚点上时,它将闪烁绿色。Android Studio会提示你创建约束连接。

image

单击元素左侧的圆圈并将其拖动到左侧。在拖动时,将出现带箭头的线条,在UI元素的左侧和要将其连接到的元素之间创建约束。

将圆拖动到视图的左侧,并将圆圈ImageView的 左侧约束到父视图的左部。对圆圈ImageView的顶部重复此操作,将其约束到父视图顶部。您现在已将ImageView的约束限制在视图的左上角。

将图像向下拖动一点,Android Studio将在顶部创建一个边距。通过在代码视图中编辑XML代码或在设计视图的 Attributes (属性) 检查器中对其进行编辑,将顶部边距设置为30dp。

image

接下来,选择 Raze Galactic TextView, 将左约束锚点拖动到父视图的左侧,将右约束锚点拖动到父视图的右侧。通过将此UI元素约束到左侧和右侧,Android明白你是想将其达到居中的效果。

image



删除个别约束

接下来,将鼠标光标放在已设置约束的约束锚点之一上,表示箭头的圆圈现在应该闪烁红色,约束也会以红色突出显示。

image

单击锚点就会删除约束。现在先不要单击,并将约束锚点保留在原位,但如果以后需要删除约束,请记住该操作。

现在你知道如何将UI元素约束到其父容器的边框,接下来到了学习如何将UI元素相互约束的时候了。


将对象彼此之间进行约束

在本教程的这一步中,你将实现 Raze Galactic TextView始终与火箭图像对齐。

要做到这一点,你需要把 Raze Galactic TextView的顶部锚点约束到ImageView的顶部锚点,同时将TextView底部锚点固定到ImageView的底部锚点。两个视图便会垂直对齐。

image

现在,如果你按住火箭上下拖动,你将看到 Raze Galactic 随着图像上下移动。

image

稍后,你将看到如何使用对齐菜单创建这样的对齐。但是,该方法并不总是完美无缺的,所以知道如何手动完成它也是一件好事。

切换到Android Studio中的代码视图,并检查刚才添加约束的视图的代码:

  <ImageView
    android:id="@+id/imageView2"
    android:layout_width="46dp"
    android:layout_height="46dp"
    android:layout_marginStart="16dp"
    android:layout_marginTop="30dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:srcCompat="@drawable/track_icon"/>

  <TextView
    android:id="@+id/textView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:text="Raze Galactic"
    android:textAppearance="@style/TextAppearance.AppCompat.Headline"
    app:layout_constraintBottom_toBottomOf="@+id/imageView2"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@+id/imageView2"/>

现在你已经添加了一些约束,带有tools前缀的属性已经消失,因为Android Studio现在不再需要仅设计时生效的这些属性了。

你添加的每个约束都会在代码中提现,比如app:layout_constraintTop_toTopOf="parent",代表该元素的顶部约束到了父布局的顶部。

你可以看到Android Studio为你设置了一些边距,也许你并没有想过去添加。这些边距可能看起来像android:layout_marginStart="16dp"

继续往下看,在 Raze Galactic TextView中 删除边距属性后切换回设计视图,现在你会发现, Raze Galactic TextView应该与火箭图像对齐了。

这两个视图不会再有错误出现了,因为已经满足了Android Studio放置这两个UI元素所需要的信息量下限。

注意:你仍可能会看到有关使用硬编码字符串或丢失的警告contentDescription。不过你现在现在可以忽略这些。

现在,你应该将 ** Login** (登录)按钮与 Sign Up (注册) 按钮对齐,就像将 Raze Galactic TextView与火箭图像对齐一样。为此,你将需要设置三个约束:

“登录”按钮的顶部锚点位于“注册”按钮的顶部。
“登录”按钮的底部锚点位于“注册”按钮的底部。
“登录”按钮的左侧锚点位于“注册”按钮的右部,并设置从左起30dp的起始边距以在它们之间留出一些空间。

image



应用对齐约束

选择 Raze Galactic TextView并单击右侧锚点以删除这些约束。然后,在TextView仍然选中时,单击工具栏中的 Align (对齐)工具,然后在选择 Horizontally in Parent (水平布局)。

image

这会自动将Raze Galactic TextView相对父容器进行居中布局。这与你在手动添加约束时之前实现的效果相同。

事实上,如果你切换到代码视图并仔细检查Raze Galactic TextView,你会注意到Android Studio添加了以下约束属性:

app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"

这时你可能问了,Android Studio中自动添加的layout_constraintHorizontal_bias是什么?


Constraint Bias(约束偏差)

当视图在水平或垂直方向上受到约束时,无论是父视图还是其他视图,默认情况下它具有0.5或50%的约束偏差。换句话说,视图保持在它受约束的两条边之间的中心。

约束偏差范围从0.0(0%)到1.0(100%),水平约束偏差从左到右增长,而垂直约束偏差从上到下增长。约束偏差对于为不同的屏幕尺寸动态定位视图很有用。

要更轻松地了解约束偏差的工作原理,请切换回设计视图。选中 Raze Galactic TextView,在右侧查看 view inspector (视图查看器) 以及各种 Attributes(属性)。

image

视图查看器左侧的垂直滑块控制垂直约束偏差,底部的垂直滑块控制水平约束偏差。拖动每个滑块以查看约束偏差的工作原理:

image

在继续下一步之前,将水平和垂直约束的偏差重置为50%。



对齐左边缘并垂直分布

接下来,同时选择所有TextViewGoogle Sign-In(从谷歌登录) 和 Sign-Up (注册) 按钮。你可以按住Shift键单击每个UI元素依次全部选中它们。然后,从工具栏中单击 Align (对齐) 并选择 Left Edges (左边缘) 。

image

这是应用左对齐后的布局:

image

你注意到这里有什么问题了吗? Raze Galactic TextView的水平约束变不见了。

左边缘将视图与所选择的最左侧视图对齐,它实际上做的是按降序从一个视图创建左约束依赖关系,以最底部的视图充当锚点。

因此,要使 Left Edges (左边缘)命令起作用,它必须删除其余所选视图的现有水平约束。

要反转约束依赖关系顺序,请再次应用 Left Edges (左边缘)命令。您可以看到约束箭头现在指向上方。

image

现在,使用与上一步骤中选择的相同UI元素,单击 Pack (包) 菜单并选择 Distribute Vertically (垂直分布) 。

image

垂直分布后,你的屏幕将如下所示:

image

同样,你可能已经注意到与上述类似的问题:连接火箭图标底部和 Raze Galactic TextView的约束属性已经消失。

约束依赖性按降序排列,就像在第一个 Left Edges (左边缘) 命令之后一样。不幸的是,没有简单的方法来解决这个问题,因此您必须手动创建垂直分布式约束。

本教程将介绍如何在下一部分中执行此操作,因此请继续并撤消 Distribute Vertically (垂直分布) 命令。

注意:AlignPack 命令可能无法正常工作。它们可能会删除现有的约束,并且可能不会移动某些受约束的视图。在应用这些命令之前和之后,请务必检查受影响视图的约束。



使用默认边距

要创建垂直分布式约束,只需连接具有相同边距的约束即可。快速完成此操作的一个技巧是使用 Default Margin (默认边距) 工具。

image

现在,单击 Default Margin (默认边距) 按钮并设置值为60dp,然后将Google登录按钮的顶部约束连接到 Raze Galactic TextView的底部。你会发现Google登录按钮会自动变换位置,在它与 Raze Galactic TextView之间留出60dp的间距。
魔法 :]

image

创建其余的垂直约束,如上面的GIF所示。最后,将到 Raze Galactic TextView的左侧约束到火箭图标的右侧,边距为30dp。

检查组件树以查看是否存在其他错误。如果没有错误,恭喜!

image

构建并运行你的应用程序,现在所有内容应该在模拟器中以适当的布局显示了。

image



接下来做什么?

你可以使用本教程顶部的下载材质按钮下载此项目的最终版本。

ConstraintLayout 在布局编辑器中构建UI 可能会令人沮丧,因为某些工具不够智能。但是,如果如果你知道如何正确的去使用,则可以节省大量时间。

本教程中未提及其他布局编辑器工具,你可以去使用它们来了解它们的工作原理。看看谷歌ConstraintLayout的文档上,以了解更多信息。

有关更复杂的ConstraintLayout示例,请参阅我们的后续文章ConstraintLayout Android教程:复杂布局

要查看更多ConstraintLayout的示例,请查看我们的 Android Apprentice 一书,该书使用ConstraintLayout作为所有页面的布局。

你现在已经掌握了ConstraintLayout基本的概念,要了解更多高级功能并获得处理复杂布局的提示,请继续关注我们即将推出的构建复杂布局的教程ConstraintLayout Android教程:复杂布局,你将在其中为 Raze Galactic 旅行应用程序构建更复杂的约束视图,然后为其制作动画!

如果你有任何问题或意见,欢迎在下方留言讨论~

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

推荐阅读更多精彩内容