android ViewBinding, DataBinding

lzyprime 博客 (github)
創(chuàng)建時(shí)間:2021.04.23
qq及郵箱:2383518170

kotlin & android 筆記


λ:

# ViewBinding DataBinding
# 倉(cāng)庫(kù)地址: https://github.com/lzyprime/android_demos
# branch: viewBinding

git clone -b viewBinding https://github.com/lzyprime/android_demos

最近幾個(gè)月忙于寫(xiě)需求,積累了太多要總結(jié)的東西祥楣。當(dāng)然也正是這幾個(gè)月的大量實(shí)踐,對(duì)一些知識(shí)有了新的認(rèn)識(shí)和發(fā)現(xiàn)兽间。

ViewBinding DataBinding 通過(guò) xml 聲明嘀略,生成對(duì)應(yīng)代碼,刨開(kāi)生成的源碼看一下讼育,大概就能明白原理稠集。

有用的可能就是 val binding by viewBinding<T>() 的兩個(gè)拓展函數(shù)實(shí)現(xiàn)。其余就是如官網(wǎng)文檔一樣的備忘錄內(nèi)容忧饭,方便知識(shí)點(diǎn)查找词裤。

ViewBinding

ViewBinding 官網(wǎng)

生成的源碼

ViewBinding 庫(kù)代替之前的kotlin-android-extensions, 根據(jù)布局文件 layout/example.xml 生成對(duì)應(yīng)的[ExampleBinding].

[FragmentDetailBinding]為例, 看一下生成的源碼逆航。

public final class FragmentDetailBinding implements ViewBinding {
  @NonNull
  private final FrameLayout rootView;

  @NonNull
  public final ImageView imageView;

  private FragmentDetailBinding(@NonNull FrameLayout rootView, @NonNull ImageView imageView) {
    this.rootView = rootView;
    this.imageView = imageView;
  }

  @Override
  @NonNull
  public FrameLayout getRoot() {
    return rootView;
  }

  @NonNull
  public static FragmentDetailBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  @NonNull
  public static FragmentDetailBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    View root = inflater.inflate(R.layout.fragment_detail, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }

  @NonNull
  public static FragmentDetailBinding bind(@NonNull View rootView) {
    // The body of this method is generated in a way you would not otherwise write.
    // This is done to optimize the compiled bytecode for size and performance.
    int id;
    missingId: {
      id = R.id.imageView;
      ImageView imageView = rootView.findViewById(id);
      if (imageView == null) {
        break missingId;
      }

      return new FragmentDetailBinding((FrameLayout) rootView, imageView);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

基類[ViewBinding]interface, 只有一個(gè)getRoot方法,返回顯示的View

/** A type which binds the views in a layout XML to fields. */
public interface ViewBinding {
    /**
     * Returns the outermost {@link View} in the associated layout file. If this binding is for a
     * {@code <merge>} layout, this will return the first view inside of the merge tag.
     */
    @NonNull
    View getRoot();
}

每份生成的代碼:

  • 根據(jù)layout/fragment_detail.xml下劃線名稱生成對(duì)應(yīng)駝峰類名FragmentDetailBinding
  • 根據(jù)布局文件中組件id, 生成對(duì)應(yīng)駝峰式成員名渔肩,類型為組件類型. 如imageView: ImageView
  • 根部局生成為rootView

構(gòu)造函數(shù)私有因俐,需要的參數(shù)為上述根據(jù)id生成的成員.

private FragmentDetailBinding(@NonNull FrameLayout rootView, @NonNull ImageView imageView)

同時(shí)生成3個(gè)靜態(tài)函數(shù)作為工廠構(gòu)造

  • 兩個(gè)inflate用傳入的 [inflater: LayoutInflater] 獲得對(duì)應(yīng)的View.
  • 調(diào)用bind,通過(guò)findViewById獲得各個(gè)組件, 然后通過(guò)私有構(gòu)造得到[FragmentDetailBinding]

也就是說(shuō), findViewById 的過(guò)程靠生成代碼解決周偎,所以在拿到一個(gè)ViewBinding實(shí)例時(shí), 可以通過(guò)成員直接訪問(wèn)抹剩。

kotlin 偽代碼大概寫(xiě)一下工廠構(gòu)造的調(diào)用關(guān)系


fun inflate(inflater: LayoutInflater): FragmentDetailBinding = inflate(inflater, null, false)

fun inflate(inflater: LayoutInflater, 
            parent: ViewGroup, 
            attachToParent: Boolean,
        ): FragmentDetailBinding {
            ...
            val root: View = inflater.inflate(...)
            ...
            return bind(root)
        }

fun bind(rootView: View): FragmentDetailBinding {
    // findViewById
    val imageView = rootView.findViewById(R.id.imageView)

    return FragmentDetailBinding(rootView, imageView)
}

使用

  • 當(dāng)前沒(méi)有View, 需要新建
// 官網(wǎng)例子:
// Activity
class ResultProfileActivity : AppCompatActivity(){
    private lateinit var binding: ResultProfileBinding

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        // 通過(guò) inflate 新建
        binding = ResultProfileBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    }
}

// Fragment
class ResultProfileFragment : Fragment() {
    private var _binding: ResultProfileBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        _binding = ResultProfileBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}
  • 已有視圖,直接通過(guò)bind獲得
// Fragment 構(gòu)造直接傳 R.layout.fragment_detail
class DetailFragment : Fragment(R.layout.fragment_detail) {
    private var _binding: FragmentDetailBinding? = null
    private val binding get() = _binding!!
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 此時(shí)R.layout.fragment_detail對(duì)應(yīng)View已存在澳眷,直接 bind
        _binding = FragmentDetailBinding.bind(view)
        ...
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

同理其他地方,沒(méi)有視圖調(diào)用inflate構(gòu)造,有視圖調(diào)用bind直接獲得.

Activity, Fragment 使用優(yōu)化

存在的問(wèn)題:

  • 過(guò)程重復(fù)矢空。 每個(gè)ActivityFragment中复亏,流程相同耕突,僅僅是具體[ViewBinding]的區(qū)別培遵。
  • Fragment中, onDestroyView時(shí)要將_binding置空,對(duì)于binding的操作時(shí)機(jī)靠自己保證女轿,時(shí)序自己保證北救。
  • lateinit var 在代碼掃描中視為風(fēng)險(xiǎn)行為攘宙,不建議使用(個(gè)人項(xiàng)目隨意)缓淹。

仿照

val model: VM by viewModels<VM>()

通過(guò)拓展函數(shù), 委托, 反射, 實(shí)現(xiàn)類似

val binding: FragmentDetailBinding by viewBinding<FragmentDetailBinding>()
/**
 * 用于[Activity]生成對(duì)應(yīng)[ViewBinding].
 *
 * @exception ClassCastException 當(dāng) [VB] 無(wú)法通過(guò)
 * `VB.inflate(LayoutInflater.from(this#Activity))` 構(gòu)造成功時(shí)拋出
 * */
@MainThread
inline fun <reified VB : ViewBinding> Activity.viewBinding() = object : Lazy<VB> {
    private var cached: VB? = null
    override val value: VB
        get() =
            cached ?: VB::class.java.getMethod(
                "inflate",
                LayoutInflater::class.java,
            ).invoke(null, layoutInflater).let {
                if (it is VB) {
                    cached = it
                    it
                } else {
                    throw ClassCastException()
                }
            }

    override fun isInitialized(): Boolean = cached != null
}

// example
class MainActivity : AppCompatActivity() {
    private val binding by viewBinding<ActivityMainBinding>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 確保調(diào)用該函數(shù)設(shè)置binding.root
        setContentView(binding.root)
    }
}

Activity內(nèi)聯(lián)拓展函數(shù)匀借,通過(guò)調(diào)用inflate(inflater: LayoutInflater)版本生成binding。需要自己確保在onCreate之后使用芒率,否則拿不到Activity.layoutInflater, 構(gòu)造失敗

/**
 * 用于 [Fragment] 內(nèi)構(gòu)造對(duì)應(yīng) [ViewBinding].
 *
 *  @exception ClassCastException 當(dāng) [VB] 無(wú)法通過(guò) `VB.bind(view)` 構(gòu)造成功時(shí)拋出
 *
 * 函數(shù)會(huì)自動(dòng)注冊(cè)[Fragment.onDestroyView]時(shí)的注銷操作.
 * */
@MainThread
inline fun <reified VB : ViewBinding> Fragment.viewBinding() = object : Lazy<VB> {
    private var cached: VB? = null

    override val value: VB
        get() = cached ?: VB::class.java.getMethod(
            "bind",
            View::class.java,
        ).invoke(VB::class.java, this@viewBinding.requireView()).let {
            if (it is VB) {
                // 監(jiān)聽(tīng)Destroy事件
                viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
                    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
                    fun onDestroyView() {
                        cached = null
                    }
                })
                cached = it
                it
            } else {
                throw ClassCastException()
            }
        }

    override fun isInitialized(): Boolean = cached != null
}

// example
class ExampleFragment:Fragment(R.layout.example_fragment) {
    private val binding by viewBinding<ExampleFragmentBinding>()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 確保在此之后使用binding
        binding.xxxTextView.text = "sssss"
    }
}

Fragment內(nèi)聯(lián)拓展函數(shù)匪蟀,通過(guò)調(diào)用bind(rootView: View)版本生成binding查刻。

前提是調(diào)用Fragment(@LayoutRes)版本構(gòu)造, 利用Fragment默認(rèn)的onCreateView行為得到View仔沿。因此要在onViewCreated后使用binding净当。否則Fragment.requireView()拿不到view, bind失敗像啼。

通過(guò)viewLifecycleOwner.lifecycle監(jiān)聽(tīng)Destroy行為萄传,將cached賦為null, 當(dāng)重新構(gòu)建View時(shí)脊串,bindingisInitialized() == false, 認(rèn)為沒(méi)有初始化,重新走value get()中的邏輯,達(dá)到重新綁定的效果锭碳。


總結(jié):原有問(wèn)題仍有一部分未解決(如: 自己保證執(zhí)行時(shí)序), 但一定程度上減少了重復(fù)代碼袁稽,尤其是Fragment中。

DataBinding

DataBinding 官網(wǎng)

DataBinding相當(dāng)于ViewBinding++

xml中傳遞和使用數(shù)據(jù)

<?xml version="1.0" encoding="utf-8"?>
    <!-- layout作為根 -->
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- 數(shù)據(jù) -->
       <data>
           <variable name="user" type="com.example.User"/>
       </data>
        <!-- 布局 -->
       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"/> <!-- 使用數(shù)據(jù) -->
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.lastName}"/> <!-- 使用數(shù)據(jù) -->
       </LinearLayout>
    </layout>
// data class User(val firstName: String, val lastName: String)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding: ActivityMainBinding = DataBindingUtil.setContentView(
                this, R.layout.activity_main)

        binding.user = User("Test", "User")
    }

基類[ViewDataBinding]

public abstract class ViewDataBinding extends BaseObservable implements ViewBinding
  • 實(shí)現(xiàn)了[ViewBinding], 生成的代碼中inflate, bind函數(shù)簽名相同擒抛,內(nèi)部實(shí)現(xiàn)略有不同推汽,所以上邊by viewBinding<T>()仍然適用。

  • 同時(shí)繼承[BaseObservable], 使得本身成為[Observable], 可觀察者

除了像ViewBinding中構(gòu)造方式, 還可以使用DataBindingUtil

// Activity, 等價(jià)于 inflate + setContentView 
val binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

// or
val binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

綁定表達(dá)式

<data>

<data>
    <!-- 聲明 -->
    <variable name="user" type="com.example.User"/>
    <!-- 導(dǎo)入 -->
    <import type="android.view.View"/>
    <!-- 類型別名 -->
    <import type="com.example.real.estate.View" alias="Vista"/>

    <!-- 集合 -->
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
    <!-- 在布局中使用
        android:text="@{list[index]}"
        android:text="@{sparse[index]}"
        android:text="@{map[key]}" 
    -->
</data>

布局中歧沪,表達(dá)式

  • 算術(shù)運(yùn)算符 + - / * %
  • 字符串連接運(yùn)算符 +
  • 邏輯運(yùn)算符 && ||
  • 二元運(yùn)算符 & | ^
  • 一元運(yùn)算符 + - ! ~
  • 移位運(yùn)算符 >> >>> <<
  • 比較運(yùn)算符 == > < >= <=
  • instanceof
  • 分組運(yùn)算符 ()
  • 字面量運(yùn)算符 - 字符歹撒、字符串、數(shù)字诊胞、null
  • 類型轉(zhuǎn)換
  • 方法調(diào)用
  • 字段訪問(wèn)
  • 數(shù)組訪問(wèn) []
  • 三元運(yùn)算符 ?:
<!-- 當(dāng)鏈?zhǔn)秸{(diào)用中存在可空類型時(shí), 如: -->
<TextView android:text="@{a.b.c.d.e}"/>
<!-- 相當(dāng)于 -->
<TextView android:text="@{a?.b?.c?.d?.e}"/>
<!-- 其中有一環(huán)為空, 則表達(dá)式值為null -->
<TextView android:text="@{expr ?? defautValue}"/>
<!-- 相當(dāng)于 -->
<TextView android:text="@{expr != null ? expr : defautValue}"/>
<!-- 資源引用 -->
android:padding="@{large ? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{@string/nameFormat(firstName, lastName)}"
...
<!-- function -->
<data>
    <variable name="task" type="com.android.example.Task" />
    <variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:onClick="@{() -> presenter.onSaveClick(task)}" />
...
</LinearLayout>


<!--
class Presenter {
    fun onSaveClick(view: View, task: Task){}
}
-->
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

<!--
class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}
-->
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}"

<!-- ?: -->
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

適配器

現(xiàn)有的 資源引用表達(dá)式 滿足大多數(shù)情況暖夭,但也有例外,常見(jiàn)為ImageView中撵孤。所以用適配器指定處理方法

  • @BindingMethods
// 將 android:tint 交由 setImageTintList(ColorStateList) 處理, 而非原有 setTint()
@BindingMethods(value = [
    BindingMethod(
        type = android.widget.ImageView::class,
        attribute = "android:tint",
        method = "setImageTintList")])
  • @BindingAdapter
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {
    if (url == null) {
        imageView.setImageDrawable(placeholder);
    } else {
        MyImageLoader.loadInto(imageView, url, placeholder);
    }
}

//xml
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
  • @BindingConversion, 自定義轉(zhuǎn)換
@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)

//xml
<View android:background="@{isError ? @drawable/error : @color/white}" .../>
  • @TargetApi, 監(jiān)聽(tīng)器有多個(gè)方法時(shí)迈着,需要拆分處理
// View.OnAttachStateChangeListener 為例
// 有兩個(gè)方法:onViewAttachedToWindow(View) 和 onViewDetachedFromWindow(View)

// 1. 拆分

@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
interface OnViewDetachedFromWindow {
    fun onViewDetachedFromWindow(v: View)
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
interface OnViewAttachedToWindow {
    fun onViewAttachedToWindow(v: View)
}

// 2. BindAdapter

@BindingAdapter(
        "android:onViewDetachedFromWindow",
        "android:onViewAttachedToWindow",
        requireAll = false
)
fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach:OnViewAttachedToWindow?) {
   ...
}

// 3. xml中使用

Observable, LiveData作為數(shù)據(jù)

數(shù)據(jù)更新時(shí),UI自動(dòng)刷新

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable
  • ObservableArrayList
  • ObservableArrayMap
// 自定義
class User : BaseObservable() {
    @get:Bindable // 給getter方法打標(biāo)簽, BR中會(huì)生成對(duì)應(yīng)條目
    var firstName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName) // 刷新UI
        }
    @get:Bindable
    var lastName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName) // 刷新UI
        }
}

或者用 LiveData, 在代碼中需要調(diào)用setLifecycleOwner()

<!-- data class User(val firstName: LiveData<String>, val lastName: LiveData<String>) -->
<!-- xml中 -->
<data>
    <variable name="duration" type="LiveData<String>"/>
    <variable name="user" type="com.example.User"/>
</data>

<TextView android:text="@{user.firstName}"/>
<TextView android:text="@{duration}"/>
// kotlin
class ExampleFragment : Fragment(R.layout.example_fragment) {
    ...
    binding.duration = liveData<String> { emitSource(...) }
    binding.user = model.user
    binding.setLifecycleOwner(viewLifecycleOwner)
    ...
}

結(jié)合兩者使用:

open class ObservableViewModel : ViewModel(), Observable {
    private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()
    
    // 添加訂閱
    override fun addOnPropertyChangedCallback(
            callback: Observable.OnPropertyChangedCallback) {
        callbacks.add(callback)
    }

    // 取消訂閱
    override fun removeOnPropertyChangedCallback(
            callback: Observable.OnPropertyChangedCallback) {
        callbacks.remove(callback)
    }

    // 全量刷新
    fun notifyChange() {
        callbacks.notifyCallbacks(this, 0, null)
    }
    
    // 精確刷新
    fun notifyPropertyChanged(fieldId: Int) {
        callbacks.notifyCallbacks(this, fieldId, null)
    }
}

數(shù)據(jù)雙向綁定 @={}

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@={viewmodel.rememberMe}"
/>
class LoginViewModel : BaseObservable {
    // val data = ...

    @Bindable
    fun getRememberMe(): Boolean = data.rememberMe

    fun setRememberMe(value: Boolean) {
        if (data.rememberMe != value) {
            data.rememberMe = value

            // React to the change.
            saveData()

            notifyPropertyChanged(BR.remember_me)
        }
    }
}

使用@InverseBindingAdapter@InverseBindingMethod, 自定義雙向綁定

// 1. 數(shù)據(jù)變動(dòng)時(shí)調(diào)用的方法
@BindingAdapter("time")
@JvmStatic fun setTime(view: MyView, newValue: Time) {
    // Important to break potential infinite loops.
    if (view.time != newValue) {
        view.time = newValue
    }
}

// 2. view變動(dòng)時(shí)調(diào)用的方法
@InverseBindingAdapter("time")
@JvmStatic fun getTime(view: MyView) : Time {
    return view.getTime()
}

// 3. 變動(dòng)時(shí)機(jī)和方式, 后綴`AttrChanged`
@BindingAdapter("app:timeAttrChanged")
@JvmStatic fun setListeners(
        view: MyView,
        attrChange: InverseBindingListener
) {
    // 使用 InverseBindingListener 告知數(shù)據(jù)綁定系統(tǒng)邪码,特性已更改
    // 數(shù)據(jù)綁定系統(tǒng)調(diào)用@InverseBindingAdapter綁定的方法

    // warning: 避免陷入循環(huán)刷新.
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末裕菠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子闭专,更是在濱河造成了極大的恐慌奴潘,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件影钉,死亡現(xiàn)場(chǎng)離奇詭異画髓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)平委,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門雀扶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事愚墓∮枞ǎ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵浪册,是天一觀的道長(zhǎng)扫腺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)村象,這世上最難降的妖魔是什么笆环? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮厚者,結(jié)果婚禮上躁劣,老公的妹妹穿的比我還像新娘。我一直安慰自己库菲,他們只是感情好账忘,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著熙宇,像睡著了一般鳖擒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烫止,一...
    開(kāi)封第一講書(shū)人閱讀 52,584評(píng)論 1 312
  • 那天蒋荚,我揣著相機(jī)與錄音,去河邊找鬼馆蠕。 笑死期升,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的互躬。 我是一名探鬼主播播赁,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吨铸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起祖秒,我...
    開(kāi)封第一講書(shū)人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诞吱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后竭缝,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體房维,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年抬纸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咙俩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖阿趁,靈堂內(nèi)的尸體忽然破棺而出膜蛔,到底是詐尸還是另有隱情,我是刑警寧澤脖阵,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布皂股,位于F島的核電站,受9級(jí)特大地震影響命黔,放射性物質(zhì)發(fā)生泄漏呜呐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一悍募、第九天 我趴在偏房一處隱蔽的房頂上張望蘑辑。 院中可真熱鬧,春花似錦坠宴、人聲如沸洋魂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忧设。三九已至,卻和暖如春颠通,著一層夾襖步出監(jiān)牢的瞬間址晕,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工顿锰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谨垃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓硼控,卻偏偏與公主長(zhǎng)得像刘陶,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子牢撼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

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