在 Kotlin 中使用 Room 數(shù)據(jù)庫的可擴展項目

有時我們想顯示一個關(guān)于列表項的額外內(nèi)容疆液,它不一定需要一個單獨的屏幕,通常稱為詳細屏幕劫窒,這就是 Expandable RecyclerView 的用武之地椎麦,我們將學(xué)習(xí)如何使用可擴展的 recyclerview 創(chuàng)建可擴展的THOUGHBOTrecyclerview回收視圖庫。我們還將使用它從本地數(shù)據(jù)庫中獲取我們的項目匪傍, Room Persistence Library 這是Android Architecture Components

我們將顯示來自本地數(shù)據(jù)庫的大陸列表及其下的一些國家/地區(qū)您市,該數(shù)據(jù)庫僅在創(chuàng)建數(shù)據(jù)庫時添加一次,最終結(jié)果應(yīng)如下圖所示役衡。

在您創(chuàng)建一個帶有空活動的新項目后墨坚,將以下依賴項添加到您的應(yīng)用級別 build.gradle

// Room components
    implementation "androidx.room:room-runtime:$rootProject.roomVersion"
    kapt "androidx.room:room-compiler:$rootProject.roomVersion"
    implementation "androidx.room:room-ktx:$rootProject.roomVersion"
    androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

//Gson
    implementation 'com.google.code.gson:gson:2.8.6'

// Lifecycle components
    implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
    kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"

// Kotlin components
 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
 api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
 api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"

// Material design
    implementation "com.google.android.material:material:$rootProject.materialVersion"

//Expandable
    implementation 'com.thoughtbot:expandablerecyclerview:1.3'
    implementation 'com.thoughtbot:expandablecheckrecyclerview:1.4'

還將版本添加到項目級別 build.gradle

ext {
    roomVersion = '2.2.5'
    archLifecycleVersion = '2.2.0'
    coreTestingVersion = '2.1.0'
    materialVersion = '1.1.0'
    coroutines = '1.3.4'
}

確保以下插件存在于應(yīng)用程序級別構(gòu)建的頂部。

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

使用 Room 時需要三個主要的Entity類映挂,代表數(shù)據(jù)庫中表的DAO類,顧名思義盗尸,該類是一個包含用于訪問數(shù)據(jù)庫的方法的數(shù)據(jù)訪問對象柑船,即database類。

ContinentEntity.kt

package com.developer.kulloveth.expandablelistsamplewithroom.data.model

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters

@Entity(tableName = "continent-table")
@TypeConverters(ContinentConverter::class)
data class ContinentEntity
 (@PrimaryKey @ColumnInfo(name = "continent") 
val continentName: String,  val countrys: List<Country>
)

此類具有 @Entity 注釋泼各,將要創(chuàng)建的表名傳遞到其參數(shù)中鞍时,如果您不希望將類名用作表名,這是可選的,@ColumnInfo 告訴數(shù)據(jù)庫使用大陸作為列名所有表必須具有的continentName 變量和@PrimaryKey逆巍。還要注意@TypeConverters及塘,它是告訴空間用 ContinentConverter 類轉(zhuǎn)換 List 的注釋

package com.developer.kulloveth.expandablelistsamplewithroom.data.model

import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
import java.util.*


class ContinentConverter {
companion object {
        var gson: Gson = Gson()

  @TypeConverter
  @JvmStatic
  fun stringToSomeObjectList(data: String?): List<Country> {
  val listType: Type =
  object : TypeToken<List<Country?>?>() {}.getType()
  return gson.fromJson(data, listType)
        }

   @TypeConverter
   @JvmStatic
   fun someObjectListToString(someObjects: List<Country>?): String {
   return gson.toJson(someObjects)
        }
    }
}

這是在每個使用 Gson 庫執(zhí)行轉(zhuǎn)換的方法上都有 @TypeConverter 的轉(zhuǎn)換器類

ContinentDao.kt

package com.developer.kulloveth.expandablelistsamplewithroom.data.db

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.ContinentEntity
import 
com.developer.kulloveth.expandablelistsamplewithroom.data.model.Continents


@Dao
interface ContinentDao {
    @Query("SELECT * from `continent-table` ORDER BY continent ASC")
    fun getAllContinent(): LiveData<List<ContinentEntity>>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(continent: ContinentEntity)
}

這是訪問數(shù)據(jù)庫的 dao 接口,getAllContinent 方法有 @Query 注解锐极,它按升序獲取所有數(shù)據(jù)笙僚,它返回一個LiveData有助于保持數(shù)據(jù)更新并自動在后臺線程上異步運行操作。insert 方法具有 @Insert 注釋灵再,用于插入數(shù)據(jù)以處理可能發(fā)生的沖突肋层,它使用掛起函數(shù)來指示該方法需要時間來執(zhí)行,因為我們不想阻塞主線程翎迁。

ContinentDatabase.kt

package com.developer.kulloveth.expandablelistsamplewithroom.data.db

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import com.developer.kulloveth.expandablelistsamplewithroom.data.DataGenerator
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.ContinentEntity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

@Database(entities = [ContinentEntity::class], version = 1, exportSchema = false)

abstract class ContinentDatabase : RoomDatabase() {

    abstract fun continentDao(): ContinentDao


    companion object {
        @Volatile
        private var INSTANCE: ContinentDatabase? = null

        fun getDatabase(context: Context, scope: CoroutineScope): ContinentDatabase {

            return INSTANCE ?: synchronized(this) {
                INSTANCE ?: buildDatabase(context, scope).also {
                    INSTANCE = it
                }
            }
        }

 private fun buildDatabase(context: Context, scope: CoroutineScope): ContinentDatabase {
   return Room.databaseBuilder(context, ContinentDatabase::class.java, "place_db")
 .addCallback(object : RoomDatabase.Callback() 
 override fun onCreate(db: SupportSQLiteDatabase) {
                        super.onCreate(db)

               scope.launch {
                 INSTANCE?.let {
                      for (continent: ContinentEntity in DataGenerator.getContinents()) {
            it.continentDao().insert(
                  ContinentEntity(
                           continent.continentName,
                                            continent.countrys
                                        ) )
                                }}}}}).build()
}}}

這是一個數(shù)據(jù)庫類栋猖,它必須是一個抽象類,并且必須包含一個表示 dao 接口類的抽象方法汪榔,它具有 @Database 及其實體蒲拉、版本并將 export-schema 設(shè)置為 false,因為我們沒有將數(shù)據(jù)庫導(dǎo)出到文件夾中. getDatabase 方法是一個單例痴腌,它確保在任何時候只打開一個數(shù)據(jù)庫實例雌团,我們還添加了一個 roomCallback 以在使用其 onCreate 方法創(chuàng)建房間時只插入一次數(shù)據(jù)。請注意衷掷,插入方法是在協(xié)程范圍內(nèi)調(diào)用的辱姨,因為它是一個掛起函數(shù),以確保在后臺線程上執(zhí)行操作戚嗅。

DataGenerator.kt

package com.developer.kulloveth.expandablelistsamplewithroom.data

import com.developer.kulloveth.expandablelistsamplewithroom.data.model.ContinentEntity
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.Country

class DataGenerator {

    companion object {
        fun getContinents(): List<ContinentEntity> {
            return listOf(
    ContinentEntity("Europe", europeCountrys()),
    ContinentEntity("Africa", africaCountrys()),
    ContinentEntity("Asia", asiaCountrys()),
    ContinentEntity("North America", northAmericaCountrys()),
    ContinentEntity("South America", southAmericaCountrys()),
    ContinentEntity("Antarctica", antarcticaCountrys()),
    ContinentEntity("Oceania", oceaniaCountrys())
            )
        }

        fun europeCountrys(): List<Country> {
            return listOf(
                Country("Germany"),
                Country("Italy"),
                Country("France"),
                Country("United Kingdom"),
                Country("NertherLand")
            )
        }

        fun africaCountrys(): List<Country> {
            return listOf(
                Country("South Africa"),
                Country("Nigeria"),
                Country("Kenya"),
                Country("Ghana"),
                Country("Ethiopia")
            )

        }

        fun asiaCountrys(): List<Country> {
            return listOf(
                Country("Japan"),
                Country("India"),
                Country("Indonesi"),
                Country("China"),
                Country("Thailand")
            )
        }

        fun northAmericaCountrys(): List<Country> {
            return listOf(
                Country("United States"),
                Country("Mexico"),
                Country("Cuba"),
                Country("Green Land")
            )
        }


        fun southAmericaCountrys(): List<Country> {
            return listOf(
                Country("Brazil"),
                Country("Argentina"),
                Country("Columbia"),
                Country("Peru"),
                Country("Chile")
            )}

       fun antarcticaCountrys(): List<Country> {
            return listOf(
                Country("Esperenza Base"),
                Country("Villa az Estrellaz"),
                Country("General Bernando O'Higging"),
                Country("Bellgrano II base"),
                Country("Carlini Base") )}

        fun oceaniaCountrys(): List<Country> {
            return listOf(
                Country("Australia"),
                Country("Newzeland"),
                Country("Fiji"),
                Country("Samao"),
                Country("Federated States")
            )}}}

接下來我們將創(chuàng)建適配器雨涛,它的數(shù)據(jù)類,觀察我們添加到 Room 的數(shù)據(jù)并設(shè)置 recyclerView懦胞。

Continent.kt

package com.developer.kulloveth.expandablelistsamplewithroom.data.model
import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup
data class Continent(
    val continentName: String, val countries: List<Country>
):  ExpandableGroup<Country>(continentName, countries)

Country.kt

package com.developer.kulloveth.expandablelistsamplewithroom.data.model

import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize

@Parcelize
data class Country(val countryName: String) : Parcelable

Continent 類是與適配器一起使用的父類替久,它將通過子類 Country 擴展 ExpandableGroup

continent_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="wrap_content"
    android:padding="0dp"
    app:cardUseCompatPadding="true">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <ImageView
            android:id="@+id/arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_marginEnd="16dp"
            android:src="@drawable/ic_arrow_drop_down_black_24dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/continent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="?listPreferredItemPaddingLeft"
            android:textAppearance="@style/TextAppearance.AppCompat.Large"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>

countrys_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:padding="0dp">

    <TextView
        android:id="@+id/countryName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        tools:text="Niger" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@android:color/black" />

</androidx.cardview.widget.CardView>

以上布局是要在各自的視圖中引用的項目的父子布局

MainViewHolder.kt

package com.developer.kulloveth.expandablelistsamplewithroom.data

import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.developer.kulloveth.expandablelistsamplewithroom.R
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.Continent
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.Country
import com.thoughtbot.expandablerecyclerview.viewholders.ChildViewHolder
import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder


class CountryViewHolder(itemView: View) : ChildViewHolder(itemView) {
    val countryName = itemView.findViewById<TextView>(R.id.countryName)

    fun bind(country: Country) {
        countryName.text = country.countryName
    }
}

class ContinentViewHolder(itemView: View) : GroupViewHolder(itemView) {
    val continentName = itemView.findViewById<TextView>(R.id.continent)
    val arrow = itemView.findViewById<ImageView>(R.id.arrow)

    fun bind(continent: Continent) {
        continentName.text = continent.continentName
    }

}

MainViewHolder 是一個 kotlin 文件,包含父視圖和子視圖

ContinentAdapter.kt

package com.developer.kulloveth.expandablelistsamplewithroom.data.ui

import android.view.LayoutInflater
import android.view.ViewGroup
import com.developer.kulloveth.expandablelistsamplewithroom.R
import com.developer.kulloveth.expandablelistsamplewithroom.data.ContinentViewHolder
import com.developer.kulloveth.expandablelistsamplewithroom.data.CountryViewHolder
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.Continent
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.Country
import com.thoughtbot.expandablerecyclerview.ExpandableRecyclerViewAdapter
import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup

class ContinentAdapter(groups: List<ExpandableGroup<*>>?) :
    ExpandableRecyclerViewAdapter<ContinentViewHolder, CountryViewHolder>(
        groups
    ) {


    override fun onCreateGroupViewHolder(parent: ViewGroup?, viewType: Int): ContinentViewHolder {
        val itemView =
            LayoutInflater.from(parent?.context).inflate(R.layout.continent_layout, parent, false)
        return ContinentViewHolder(itemView)
    }

    override fun onCreateChildViewHolder(parent: ViewGroup?, viewType: Int): CountryViewHolder {
        val itemView =
            LayoutInflater.from(parent?.context).inflate(R.layout.countrys_layout, parent, false)
        return CountryViewHolder(itemView)
    }

    override fun onBindChildViewHolder(
        holder: CountryViewHolder?,
        flatPosition: Int,
        group: ExpandableGroup<*>?,
        childIndex: Int
    ) {
        val country: Country = group?.items?.get(childIndex) as Country
        holder?.bind(country)
    }

    override fun onBindGroupViewHolder(
        holder: ContinentViewHolder?,
        flatPosition: Int,
        group: ExpandableGroup<*>?
    ) {
        val continent: Continent = group as Continent
        holder?.bind(continent)
    }
}

適配器類接受一個擴展ExpandableAdapter的ExpandableGroup類型的List

Repository.kt

package com.developer.kulloveth.expandablelistsamplewithroom.data.model

import androidx.lifecycle.LiveData
import com.developer.kulloveth.expandablelistsamplewithroom.data.db.ContinentDao

class Repository(continentDao: ContinentDao) {

    val allContinents: LiveData<List<ContinentEntity>> = continentDao.getAllContinent()


}

MainActivityViewModel.kt

package com.developer.kulloveth.expandablelistsamplewithroom.ui

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import com.developer.kulloveth.expandablelistsamplewithroom.data.db.ContinentDatabase
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.ContinentEntity
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.Repository

class MainActivityViewModel(application: Application) : AndroidViewModel(application) {

    private val repository: Repository
    val continents: LiveData<List<ContinentEntity>>
    init {
        val continentDao = ContinentDatabase.getDatabase(application, viewModelScope).continentDao()
        repository = Repository(continentDao)
        continents = repository.allContinents
    }
}

存儲庫模式有助于將業(yè)務(wù)邏輯與 UI 邏輯分開躏尉,這在您從不同來源獲取數(shù)據(jù)時最有用蚯根。viewmodel 類為 UI 提供數(shù)據(jù),并且在配置更改后仍然存在

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".data.ui.MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:id="@+id/rvConinent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

package com.developer.kulloveth.expandablelistsamplewithroom.ui

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.developer.kulloveth.expandablelistsamplewithroom.R
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.ContinentEntity
import com.developer.kulloveth.expandablelistsamplewithroom.data.model.Continent
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    lateinit var viewModel: MainActivityViewModel
    val continents = ArrayList<Continent>()
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
   viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]

    viewModel.continents.observe(this, Observer {

   for (continentEntity: ContinentEntity in it) {

    val continent = Continent(continentEntity.continentName, continentEntity.countrys)
                continents.add(continent)}
            val adapter = ContinentAdapter(continents)
            rvConinent.apply {
                layoutManager = LinearLayoutManager(this@MainActivity)
                rvConinent.adapter = adapter
            } })}}

最后是主要布局及其從 MainActivityViewModel 觀察數(shù)據(jù)的活動胀糜,添加到新列表并顯示在 recyclerView 上颅拦。

鏈接:https://dev.to/kulloveth/expandablerecyclerview-expandable-items-with-room-database-in-kotlin-part-1-5190

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市教藻,隨后出現(xiàn)的幾起案子距帅,更是在濱河造成了極大的恐慌,老刑警劉巖括堤,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碌秸,死亡現(xiàn)場離奇詭異绍移,居然都是意外死亡,警方通過查閱死者的電腦和手機讥电,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門蹂窖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恩敌,你說我怎么就攤上這事瞬测。” “怎么了潮剪?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵涣楷,是天一觀的道長。 經(jīng)常有香客問我抗碰,道長狮斗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任弧蝇,我火速辦了婚禮碳褒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘看疗。我一直安慰自己沙峻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布两芳。 她就那樣靜靜地躺著摔寨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怖辆。 梳的紋絲不亂的頭發(fā)上是复,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音竖螃,去河邊找鬼淑廊。 笑死,一個胖子當(dāng)著我的面吹牛特咆,可吹牛的內(nèi)容都是我干的季惩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼腻格,長吁一口氣:“原來是場噩夢啊……” “哼画拾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起菜职,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤青抛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后些楣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年愁茁,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚕钦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹅很,死狀恐怖嘶居,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情促煮,我是刑警寧澤邮屁,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站菠齿,受9級特大地震影響佑吝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绳匀,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一芋忿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疾棵,春花似錦戈钢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拟枚,卻和暖如春薪铜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梨州。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工痕囱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人暴匠。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓鞍恢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親每窖。 傳聞我的和親對象是個殘疾皇子帮掉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

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