Android Compose 介紹與實(shí)踐

簡(jiǎn)介

Jetpack Compose 是 Google 官方 2019 年推出的UI框架种吸,它可簡(jiǎn)化并加快 Android 的 UI 開(kāi)發(fā)工作还栓。使用更少的代碼钮莲、強(qiáng)大的工具和直觀的 Kotlin API忿磅,快速構(gòu)建 App 的 UI另患。2021年馬上就將迎來(lái) Compose 的正式版囊陡,是時(shí)候來(lái)了解一下這個(gè)官方強(qiáng)推的芳绩,布局機(jī)制、渲染機(jī)制撞反、具體寫(xiě)法等可以說(shuō)是全新的UI框架了妥色。

先來(lái)看一段簡(jiǎn)單的 Compose 代碼:

Column {
    Text("Hello world")
    Image()
}

OK這就是一個(gè)完整的UI界面了,對(duì)比原來(lái)定義在 xml 文件中的方式有著天壤之別遏片,展現(xiàn)一個(gè)UI不再是去創(chuàng)建一個(gè) TextView 之類(lèi)的控件嘹害,而是變成了一次函數(shù)調(diào)用。雖然 Text 以大寫(xiě)開(kāi)頭吮便,但它其實(shí)就是一個(gè)普通函數(shù)笔呀,嚴(yán)格說(shuō)是個(gè)帶 @Composable 注解的 Compose 函數(shù):

@Composable
fun Text(...) {
    ...
}

來(lái)看一段完善一些的 Compose 代碼:

@Composable
fun NewsStory() {
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Image(
                painter = painterResource(R.drawable.header),
                contentDescription = null,
                modifier = Modifier
                    .height(180.dp)
                    .fillMaxWidth()
                    .clip(shape = RoundedCornerShape(4.dp)),
                contentScale = ContentScale.Crop
            )
            Spacer(Modifier.height(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California", style = typography.body2)
            Text("December 2018", style = typography.body2)
        }
    }
}

以 Column、Row 代替 LinearLayout 等布局髓需,以 Text许师、Image 等代替 TextView、ImageView 等控件僚匆,以 Modifier 等用作細(xì)節(jié)和修飾微渠,所以其實(shí) Compose 就是這樣由多個(gè)函數(shù)調(diào)用組合起來(lái),形成一個(gè)完整的 UI 界面。

Compose 改變了原有的基于 xml 和 View 的體系,純?cè)诖a中實(shí)現(xiàn)頁(yè)面UI蚕苇,那么它比起老的方式有什么優(yōu)勢(shì)呢?

Compose 的特點(diǎn)

Jetpack Compose is Android’s modern toolkit for building native UI.

這是官方對(duì) Compose 的定義云芦,比起舊有體系,Compose 更加 “現(xiàn)代”攻臀。

現(xiàn)有的 Android 視圖體系從 2010年以來(lái)沒(méi)有發(fā)生太大變化焕数,10年間無(wú)論從硬件規(guī)格還是APP復(fù)雜度都發(fā)生了極大變化纱昧,這套已經(jīng)跑了10年的技術(shù)體系也已經(jīng)顯得有些落伍刨啸。

聲明式 vs 命令式

說(shuō)起 Compose 最大的特點(diǎn),就是它是聲明式的识脆,而現(xiàn)有體系是命令式的设联。

  • 命令式:現(xiàn)有視圖體系要先將UI定義在 xml 文件中善已,當(dāng)需要刷新時(shí),需要在代碼中先 findViewById 獲取控件的引用离例,再下達(dá)如 setText换团、setVisibility 等命令,主動(dòng)要求更新?tīng)顟B(tài)宫蛆、刷新UI艘包。

    隨著界面越來(lái)越復(fù)雜,控件越來(lái)越多耀盗,各控件狀態(tài)難以保持同步想虎,UI顯示不一致的Bug頻發(fā)。我們的很多精力花費(fèi)在了如何能準(zhǔn)確且不遺漏地更新所有該更新的控件上叛拷。

  • 聲明式:聲明式UI以一個(gè)“純函數(shù)”的方式運(yùn)行舌厨,當(dāng) State 變化時(shí)函數(shù)根據(jù)傳入?yún)?shù)重新執(zhí)行刷新UI。

    Compose 會(huì)對(duì)界面中用到的數(shù)據(jù)自動(dòng)進(jìn)行訂閱——不管是字符串還是圖像還是別的什么忿薇,Compose 全部能夠自動(dòng)訂閱——這樣當(dāng)數(shù)據(jù)改變的時(shí)候裙椭,Compose 會(huì)直接把新的數(shù)據(jù)更新到界面。

    var text by mutableStateOf("Hello")
    

    個(gè)人理解就是署浩,只需要把界面給提前“聲明”出來(lái)揉燃,先定義好在各種 state 時(shí) UI 應(yīng)該是個(gè)什么樣子,當(dāng)數(shù)據(jù)產(chǎn)生變化時(shí)筋栋,就不再需要去主動(dòng)下達(dá) setVisibility 等各種命令你雌,界面會(huì)自動(dòng)更新。

    現(xiàn)有的 Data Binding 其實(shí)就是聲明式的二汛,但它通過(guò)數(shù)據(jù)更新的只能是界面元素的值婿崭,而 Compose 可以更新界面中的任何內(nèi)容,包括界面的結(jié)構(gòu)肴颊。

    比如以下根據(jù)數(shù)據(jù)變化整個(gè)UI結(jié)構(gòu)氓栈,Data Binding 就無(wú)法做到:

    @Composable
    fun MessageList(messages: List<String>) {
        Column {
            if (message.size == 0) {
                Text("No messages")
            } else {
                message.forEach { message ->
                    Text(text=messag)
                }
            }
        }
    }
    

高性能的重組(重繪)

在上面的例子里,當(dāng) message 發(fā)生變化時(shí)婿着,MessageList 重新執(zhí)行授瘦,這個(gè)過(guò)程叫重組(recomposition)。Composee 的 UI 正是通過(guò)不斷重組來(lái)實(shí)現(xiàn)刷新竟宋。

但如果數(shù)據(jù)變化時(shí)會(huì)觸發(fā)重組提完,大面積的重組是否會(huì)影響性能呢?

Compose 會(huì)通過(guò)在 Gap Buffer 這樣的線性結(jié)構(gòu)上進(jìn)行 diff 實(shí)現(xiàn)局部刷新丘侠。 Gap Buffer 可以理解為一個(gè)樹(shù)形結(jié)構(gòu)經(jīng) DFS 處理后的數(shù)組徒欣,數(shù)組單元通過(guò) key 標(biāo)記其在樹(shù)上的位置信息。Compose 在編譯期為 Composable 生成帶有位置信息的 key蜗字,存入到 Gap Buffer 數(shù)組的對(duì)應(yīng)位置打肝。運(yùn)行時(shí)可以根據(jù) key 來(lái)識(shí)別 Composable 節(jié)點(diǎn)是否發(fā)生了位置變化脂新,以決定是否參與重組。同時(shí)粗梭,Gap Buffer 還會(huì)記錄 Composable 對(duì)象關(guān)聯(lián)的狀態(tài)(State 或 Parameters)争便,僅僅當(dāng)關(guān)聯(lián)狀態(tài)變化時(shí),Composable 才會(huì)參與重組断医,函數(shù)才會(huì)重新執(zhí)行滞乙。

布局層級(jí)嵌套

做 Android 開(kāi)發(fā)的都知道一個(gè)規(guī)矩:布局文件的界面層級(jí)要盡量地少,因?yàn)閷蛹?jí)的增加會(huì)大幅拖慢界面的加載鉴嗤。這種拖慢的主要原因就在于各種 Layout 的重復(fù)測(cè)量酷宵。雖然重復(fù)測(cè)量對(duì)于布局過(guò)程是必不可少的,但這也確實(shí)讓界面層級(jí)的數(shù)量對(duì)加載時(shí)間的影響變成了指數(shù)級(jí)躬窜。

而 Compose 是不怕層級(jí)嵌套的浇垦,因?yàn)樗鼜母瓷辖鉀Q了這種問(wèn)題。它解決的方式也非常巧妙而簡(jiǎn)單——它不許重復(fù)測(cè)量荣挨。

Compose 通過(guò)一種叫做 Intrinsic Measurement(固有特性測(cè)量)的機(jī)制男韧,避免了隨著層級(jí)增多,重復(fù)測(cè)量導(dǎo)致繪制時(shí)間指數(shù)式增加的性能陷阱默垄,也就是說(shuō)此虑,使用 Compose 時(shí)瘋狂嵌套,和把所有組件寫(xiě)在同一層級(jí)里口锭,性能上是一樣的朦前!這是比起原體系的一大進(jìn)步。

配合其他 Jetpack 組件

@Composable
fun ConversationScreen() {
    val viewModel: ConversatioinViewModel = viewModel()
    val message by viewModel.messages.observeAsState()
    MessageLit(messages)
}

@Composable
fun MessageList(message: List<String>){
    ...
}

Compose 可以配合現(xiàn)有 Jetpack 組件的使用鹃操,例如 ViewModel韭寸、LiveData 等,對(duì)于一個(gè)標(biāo)準(zhǔn)的 Jetpack MVVM項(xiàng)目荆隘,將很容易將 UI 部分替換成 Compose恩伺。

Composalbe 中調(diào)用 viewModel() 可以獲取當(dāng)前 Context 的 ViewModel, observeAsState() 將 LiveData 轉(zhuǎn)換為 Compose State 并建立綁定椰拒。當(dāng) LiveData 變化時(shí)晶渠,ConversationScreen 會(huì)發(fā)生重組,內(nèi)部的 MessageLit 燃观、MessageItem 由于依賴了參數(shù) messages褒脯,都會(huì)參與重組。

功能完備的UI系統(tǒng)

Compose目前的 UI 系統(tǒng)功能完備缆毁,可以完全覆蓋 Android 現(xiàn)有視圖系統(tǒng)的所有能力番川。

  • 各種UI組件:所有常見(jiàn)的UI組件在 Compose 中都能找到對(duì)應(yīng)實(shí)現(xiàn),甚至Card、Fab爽彤、AppBar等 Material Designe 的控件也一應(yīng)俱全、開(kāi)箱即用 缚陷。

  • 列表 List:Compose 的列表非常簡(jiǎn)單适篙,無(wú)需再寫(xiě)煩人的 Adapter。

    @Composable
    fun MessageList(list: List<Message>) {
        Column {  
            LazyList { // this :LazyListScope
                items(list) { item ->
                    when(item.type) {
                        Unread -> UnreadItem(message)
                        Readed -> ReadedItem(message)
                    }
                }
            }
        }
    }
    
  • 布局 Layout:Compose 提供了多種容器類(lèi)Composalbe箫爷,可以對(duì)子組件進(jìn)行布局嚷节,簡(jiǎn)單易用且功能強(qiáng)大。

    • Row ≈ Horizontal LinearLayout
    • Column ≈ Vertical LinearLayout
    • Box ≈ FragmeLayout
  • 自定義布局:通過(guò)簡(jiǎn)單的函數(shù)調(diào)用完成 measure 和 layout 過(guò)程虎锚。

    Layout(
        content = content,
        modifier = modifier
    ) { measurables, constraints ->
        // Measure the composables
        val placeables = measurable.measure(constraints)
        // Layout the comopsables
        layout(width, height) {
            placeables.forEach { placeable ->
                placeable.place(x, y)
            }
        }
    }
    
  • Modifier 操作符:Compose 通過(guò)一系列鏈?zhǔn)秸{(diào)用的 Modifier 操作符來(lái)裝飾 Composable 的外觀硫痰。操作符種類(lèi)繁多,例如 size窜护、backgrounds效斑、padding 的設(shè)置以及 click 事件的添加等。

  • 動(dòng)畫(huà) Animatioin:Compose 動(dòng)畫(huà)也是基于 State 驅(qū)動(dòng)不斷重組完成的柱徙。

    @Composable
    fun AnimateAsStateDemo() {
        var isHighLight by remember { mutableStateOf(false) }
        val color by animateColorAsState (
            if (isHighLight) Red else Blue,
        )    
        val size by animateDpAsState (
            if (isHighLight) LargeSize else SizeSize,
        )
        Box(Modifier.size(size).background(color))
    }
    

開(kāi)發(fā)中預(yù)覽

目前的基于 xml 的預(yù)覽效果很雞肋缓屠,導(dǎo)致很多開(kāi)發(fā)者都習(xí)慣于實(shí)機(jī)運(yùn)行查看UI。Compose 預(yù)覽機(jī)制可以做到與真機(jī)無(wú)異护侮,真正的所見(jiàn)所即得敌完。

預(yù)覽時(shí)只需創(chuàng)建一個(gè)無(wú)參的 Composalbe,并添加 @Preview 注解即可羊初。

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}

與現(xiàn)有體系良好的互操作性

Compose 能夠與現(xiàn)有 View 體系能一起使用滨溉,比如在現(xiàn)有布局中使用 Compose,或在 Compose 布局中使用舊視圖體系长赞。所以遷移到 Compose 很方便晦攒,可以為一個(gè)已有項(xiàng)目先引入 Compose,再逐漸切換得哆,不要求一次性將舊UI全替換為新的勤家,有很大的緩沖空間。

實(shí)踐

現(xiàn)在來(lái)嘗試動(dòng)手寫(xiě)一個(gè)簡(jiǎn)單的 Compose 界面柳恐。

配置 Kotlin
plugins {
    id("org.jetbrains.kotlin.android") version "1.4.32"
}
配置 Gradle
android {
    defaultConfig {
        ...
        minSdkVersion(21)
    }

    buildFeatures {
        // Enables Jetpack Compose for this module
        compose = true
    }
    ...

    // Set both the Java and Kotlin compilers to target Java 8.

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
        useIR = true
    }

    composeOptions {
        kotlinCompilerVersion = "1.4.32"
        kotlinCompilerExtensionVersion = "1.0.0-beta07"
    }
}
添加 Jetpack Compose 工具包依賴項(xiàng)
dependencies {
    implementation("androidx.compose.ui:ui:1.0.0-beta07")
    // Tooling support (Previews, etc.)
    implementation("androidx.compose.ui:ui-tooling:1.0.0-beta07")
    // Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
    implementation("androidx.compose.foundation:foundation:1.0.0-beta07")
    // Material Design
    implementation("androidx.compose.material:material:1.0.0-beta07")
    // Material design icons
    implementation("androidx.compose.material:material-icons-core:1.0.0-beta07")
    implementation("androidx.compose.material:material-icons-extended:1.0.0-beta07")
    // Integration with observables
    implementation("androidx.compose.runtime:runtime-livedata:1.0.0-beta07")
    implementation("androidx.compose.runtime:runtime-rxjava2:1.0.0-beta07")

    // UI Tests
    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.0.0-beta07")
}
用 Compose 來(lái)重新寫(xiě)一下 賬號(hào)登錄 頁(yè)面:
class MainActivity : ComponentActivity() {

    private val isLogInning = mutableStateOf(false)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LoginScreen(isLogInning)
        }
    }

    private fun login() {
        isLogInning.value = true
        Toast.makeText(this, "登錄中", Toast.LENGTH_SHORT).show()
    }

    @Composable
    fun LoginScreen(isLogInning: MutableState<Boolean> = mutableStateOf(false)) {
        Column {
            Image(
                painter = painterResource(R.drawable.titlebar_back_light),
                contentDescription = null,
                modifier = Modifier
                    .height(40.dp)
                    .width(15.dp)
                    .absoluteOffset(10.dp)
            )
            Text(
                text = "登錄TP-LINK ID",
                fontSize = 30.sp,
                fontWeight = FontWeight.Bold,
                modifier = Modifier.padding(10.dp)
            )
            TextField(
                value = TextFieldValue(),
                onValueChange = {},
                placeholder = { Text(text = "TP-LINK ID") },
                colors = TextFieldDefaults.textFieldColors(
                    backgroundColor = Color.White,
                    placeholderColor = Color.LightGray
                ),
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 10.dp)
            )
            TextField(
                value = TextFieldValue(),
                onValueChange = {},
                placeholder = { Text(text = "密碼") },
                colors = TextFieldDefaults.textFieldColors(
                    backgroundColor = Color.White,
                    placeholderColor = Color.LightGray
                ),
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 10.dp)
            )
            Text(
                text = "忘記密碼",
                fontSize = 16.sp,
                color = Color.Gray,
                textAlign = TextAlign.End,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 10.dp, vertical = 20.dp)
            )
            Button(
                content = {
                    if (isLogInning.value)
                        Text("登錄中...")
                    else
                        Text("登錄")
                },
                onClick = { login() },
                colors = ButtonDefaults.buttonColors(
                    backgroundColor =
                        if (isLogInning.value)
                            Color(0xFFA6B7F7)
                        else
                            Color(0xFF3C65FC),
                    contentColor = Color.White
                ),
                modifier = Modifier
                    .height(65.dp)
                    .fillMaxWidth()
                    .padding(10.dp)
            )
            Row {
                Text(
                    text = "新用戶注冊(cè)",
                    fontSize = 16.sp,
                    color = Color(0xFF3C65FC),
                    modifier = Modifier.padding(10.dp)
                )
                Text(
                    text = "暫不登錄",
                    fontSize = 16.sp,
                    color = Color.Gray,
                    modifier = Modifier
                        .padding(10.dp)
                        .offset(205.dp)
                )
            }
        }
    }

    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        LoginScreen()
    }
}
  • 簡(jiǎn)單使用 Column 做一個(gè)垂直線性布局

  • .dp 是 Int 的擴(kuò)展函數(shù)伐脖,方便在代碼中直接定義以 dp 為單位的數(shù)值

  • 運(yùn)用 ModifieroffSetpadding乐设、fillMaxWidth 等調(diào)整組件細(xì)節(jié)

  • 定義一個(gè) MutableState 類(lèi)型的 isLogInning 作為參數(shù)傳入 Composable 函數(shù) LoginScreen讼庇,讓 Compose 自動(dòng)訂閱,自動(dòng)根據(jù)這個(gè)值的變化而重組 LoginScreen 方法近尚,更新UI顯示

  • 當(dāng)?shù)卿洶粹o點(diǎn)擊時(shí)回調(diào) onClick 方法蠕啄,改變 isLogInning 的值

  • 當(dāng) isLogInning 的值改變時(shí),能看到“登錄”按鈕自動(dòng)改變了顏色,并變?yōu)椤暗卿浿?..”歼跟,而我們并沒(méi)有主動(dòng)去命令它 setTextsetBackground和媳。這和 Data Binding 類(lèi)似,但更加簡(jiǎn)單和靈活

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哈街,一起剝皮案震驚了整個(gè)濱河市留瞳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骚秦,老刑警劉巖她倘,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異作箍,居然都是意外死亡硬梁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)胞得,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)荧止,“玉大人,你說(shuō)我怎么就攤上這事阶剑≌窒ⅲ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵个扰,是天一觀的道長(zhǎng)瓷炮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)递宅,這世上最難降的妖魔是什么娘香? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮办龄,結(jié)果婚禮上烘绽,老公的妹妹穿的比我還像新娘。我一直安慰自己俐填,他們只是感情好安接,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著英融,像睡著了一般盏檐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驶悟,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天胡野,我揣著相機(jī)與錄音,去河邊找鬼痕鳍。 笑死硫豆,一個(gè)胖子當(dāng)著我的面吹牛龙巨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播熊响,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼旨别,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了汗茄?” 一聲冷哼從身側(cè)響起秸弛,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剔难,沒(méi)想到半個(gè)月后胆屿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奥喻,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偶宫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了环鲤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纯趋。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖冷离,靈堂內(nèi)的尸體忽然破棺而出吵冒,到底是詐尸還是另有隱情,我是刑警寧澤西剥,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布痹栖,位于F島的核電站,受9級(jí)特大地震影響瞭空,放射性物質(zhì)發(fā)生泄漏揪阿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一咆畏、第九天 我趴在偏房一處隱蔽的房頂上張望南捂。 院中可真熱鬧,春花似錦旧找、人聲如沸溺健。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鞭缭。三九已至,卻和暖如春魏颓,著一層夾襖步出監(jiān)牢的瞬間缚去,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工琼开, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留易结,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像搞动,于是被迫代替她去往敵國(guó)和親躏精。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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

  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月鹦肿,有人笑有人哭矗烛,有人歡樂(lè)有人憂愁,有人驚喜有人失落箩溃,有的覺(jué)得收獲滿滿有...
    陌忘宇閱讀 8,535評(píng)論 28 53
  • 信任包括信任自己和信任他人 很多時(shí)候瞭吃,很多事情,失敗涣旨、遺憾歪架、錯(cuò)過(guò),源于不自信霹陡,不信任他人 覺(jué)得自己做不成和蚪,別人做不...
    吳氵晃閱讀 6,187評(píng)論 4 8
  • 步驟:發(fā)微博01-導(dǎo)航欄內(nèi)容 -> 發(fā)微博02-自定義TextView -> 發(fā)微博03-完善TextView和...
    dibadalu閱讀 3,134評(píng)論 1 3
  • 回這一趟老家,心里多了兩個(gè)疙瘩烹棉。第一是堂姐現(xiàn)在談了一個(gè)有婦之夫攒霹,在她的語(yǔ)言中感覺(jué),她不打算跟他有太長(zhǎng)遠(yuǎn)的計(jì)劃浆洗,這讓...
    安九閱讀 3,502評(píng)論 2 4