Android Data Binding從抵觸到愛(ài)不釋手

1 引入

如何高效地實(shí)現(xiàn)以下界面叔营?

登錄/未登錄

有好幾年findViewById實(shí)戰(zhàn)經(jīng)驗(yàn)的我勺三,感覺(jué)并不難啊。一般會(huì)

  • 1.先定義一個(gè)User的Model類(lèi)代态,數(shù)據(jù)來(lái)自JSON解析寺惫;
  • 2.創(chuàng)建一個(gè)xml,隨后在xml中布局完所有View蹦疑,對(duì)頭像西雀、標(biāo)題、積分必尼、登錄按鈕一個(gè)id蒋搜;
  • 3.在Activity中通過(guò)findViewById獲取到頭像ImageView、標(biāo)題TextView判莉、積分TextView豆挽、登錄Button,然后給Button設(shè)置監(jiān)聽(tīng)器券盅,再根據(jù)登陸狀態(tài)展示對(duì)應(yīng)數(shù)據(jù)帮哈;

實(shí)現(xiàn)如下:

  • User.java
public class User {
    private String name;
    private int score;
    private int level;
    private int avatar;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public int getScore() { return score; }

    public void setScore(int score) { this.score = score; }

    public int getLevel() { return level; }

    public int getAvatar() { return avatar; }

    public void setAvatar(int avatar) { this.avatar = avatar; }

    public void setLevel(int level) { this.level = level; }

    public static User newInstance() {
        User user = new User();
        user.setName("王大錘:" + (int)(Math.random() * 10));
        user.setScore((int) (Math.random() * 999));
        user.setLevel((int) (Math.random() * 77));
        user.setAvatar(R.drawable.avatar);
        return user;
    }
}
  • activity_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:background="@color/detail_background"
        android:layout_width="match_parent"
        android:layout_height="66dp">
    </View>
    <ImageView
        android:id="@+id/detail_avatar"
        android:layout_gravity="center"
        android:src="@drawable/avatar"
        android:layout_marginTop="-33dp"
        android:layout_width="66dp"
        android:layout_height="66dp" />
    <TextView
        android:id="@+id/detail_name"
        android:textSize="17sp"
        android:textColor="@color/textColorPrimary"
        android:layout_marginTop="15dp"
        android:layout_gravity="center"
        android:text="王大錘"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/detail_desc"
        android:layout_marginTop="15dp"
        android:textSize="13sp"
        android:layout_gravity="center"
        android:text="積分:102 金幣:0"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/detail_action_button"
        android:layout_marginTop="15dp"
        android:layout_gravity="center"
        android:text="退出登陸"
        android:textColor="@color/white"
        android:background="@drawable/selector_g_button"
        android:layout_width="220dp"
        android:layout_height="wrap_content" />
</LinearLayout>
  • DetailActivity
public class DetailActivity extends AppCompatActivity {

    ImageView avatarIV;
    TextView nameTV;
    TextView descTV;
    Button actionBtn;

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

        initView();
        login();
    }

    private void login(){ fill(User.newInstance()); }

    private void logout(){ fill(null); }

    private void initView() {
        avatarIV = (ImageView) findViewById(R.id.detail_avatar);
        nameTV = (TextView) findViewById(R.id.detail_name);
        descTV = (TextView) findViewById(R.id.detail_desc);
        actionBtn = (Button) findViewById(R.id.detail_action_button);
    }

    private void fill(final User user){
        final int visibility = user != null ? View.VISIBLE : View.GONE;
        if (avatarIV != null){
            avatarIV.setVisibility(visibility);
            if (user != null)
                avatarIV.setImageDrawable(ContextCompat.getDrawable(this,user.getAvatar()));
        }

        if (nameTV != null){
            nameTV.setVisibility(visibility);
            if (user != null)
                nameTV.setText(user.getName());
        }

        if (descTV != null){
            descTV.setVisibility(visibility);
            if (user != null)
                descTV.setText(String.format("積分:%d 等級(jí):%d",user.getScore(),user.getLevel()));
        }

        if (actionBtn != null){
            actionBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (user == null) login();
                    else logout();
                }
            });
            actionBtn.setText(user == null ? "登錄":"退出登錄");
        }
    }
}

2 去掉煩人的findViewById(View注入)

可以看到,在Activity中View的定義锰镀、find娘侍、判空占據(jù)了大量篇幅,我們需要更優(yōu)雅的實(shí)現(xiàn)泳炉。

2.1 ButterKnife

你可能聽(tīng)說(shuō)過(guò)Jake Wharton的ButterKnife憾筏,這個(gè)庫(kù)只需要在定義View變量的時(shí)候通過(guò)注解傳入對(duì)應(yīng)id,隨后在onCreate時(shí)調(diào)用ButterKnife.bind(this)即可完成view的注入花鹅,示例如下:

class ExampleActivity extends Activity {
  @BindView(R.id.user) EditText username;
  @BindView(R.id.pass) EditText password;

  @Override public void onCreate(Bundle savedInstanceState{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
  }
}

2.2 Android Data Binding

如果使用了Android Data Binding氧腰,那么View的定義、find刨肃、判空這些都不用寫(xiě)了古拴,如何做呢?

2.2.1 準(zhǔn)備工作

首先真友,你需要滿(mǎn)足一個(gè)條件:你的Android Plugin for Gradle版本必須等于或高于1.5.0-alpha1版本黄痪,這個(gè)版本位于根目錄build.gradle中,示例如下:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0-rc1'
    }
}

接著盔然,你必須告訴編譯器開(kāi)啟Data Binding桅打,一般位于app:build.gradle的android標(biāo)簽中,示例如下:

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    dataBinding {
        enabled true
    }
    ...
}

2.2.2 修改layout.xml

activity_detail.xml為例愈案,原來(lái)的根節(jié)點(diǎn)為LinearLayout挺尾,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<!-- LinearLayout為根節(jié)點(diǎn) -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:background="@color/detail_background"
        android:layout_width="match_parent"
        android:layout_height="66dp">
    </View>
    ....
</LinearLayout>

我們拷一份activity_detail.xml,改為activity_detail2.xml刻帚,并且需要在外面wrap一層layout標(biāo)簽潦嘶,修改后的activity_detail2.xml為:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- LinearLayout為原布局根節(jié)點(diǎn) -->
    <LinearLayout
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
        <View
            android:background="@color/detail_background"
            android:layout_width="match_parent"
            android:layout_height="66dp">
        </View>
        ...
    </LinearLayout>
</layout>

2.2.3 開(kāi)始享受樂(lè)趣吧涩嚣!

在上述操作完成后崇众,編譯器會(huì)自動(dòng)為我們生成
com.asha.demo.databinding.ActivityDetail2Binding.java類(lèi)掂僵,這個(gè)類(lèi)的命令方式為:包名 + databinding + activity_detail2駝峰命名方式 + Binding.java。隨后顷歌,使用這個(gè)activity_detail2DetailActivity2.java的代碼可以簡(jiǎn)化為:

public class DetailActivity2 extends AppCompatActivity {

    ActivityDetail2Binding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this,R.layout.activity_detail2);
        
        login();
    }

    private void login(){ fill(User.newInstance()); }

    private void logout(){ fill(null); }

    private void fill(final User user){
        final int visibility = user != null ? View.VISIBLE : View.GONE;
        if (user != null){
            binding.detailAvatar.setImageDrawable(ContextCompat.getDrawable(this,user.getAvatar()));
            binding.detailName.setText(user.getName());
            binding.detailDesc.setText(String.format("積分:%d 等級(jí):%d",user.getScore(),user.getLevel()));
        }

        binding.detailAvatar.setVisibility(visibility);
        binding.detailName.setVisibility(visibility);
        binding.detailDesc.setVisibility(visibility);
        binding.detailActionButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                if (user == null) login();
                else logout();
            }
        });
        binding.detailActionButton.setText(user == null ? "登錄":"退出登錄");
    }
}

是的锰蓬,所有View的定義、find眯漩、判空都不見(jiàn)了芹扭,所有的這些操作都在編譯器為我們生成的ActivityDetail2Binding.java中完成,只需要在onCreate時(shí)調(diào)用如下代碼進(jìn)行setContentView即可實(shí)現(xiàn)赦抖,

binding = DataBindingUtil.setContentView(this,R.layout.activity_detail2);
我的天哪

2.2.4 ActivityDetail2Binding中注入View相關(guān)的代碼分析

可以在as中方便的查看編譯器自動(dòng)生成的類(lèi)舱卡,這個(gè)類(lèi)位于/app/build/intermediates/classes/debug/com/asha/demo/databinding/ActivityDetail2Binding.class中,縮減掉Binding邏輯后的代碼為:

public class ActivityDetail2Binding extends ViewDataBinding {
    private static final IncludedLayouts sIncludes = null;
    private static final SparseIntArray sViewsWithIds = new SparseIntArray();
    public final Button detailActionButton;
    public final ImageView detailAvatar;
    public final TextView detailDesc;
    public final TextView detailName;
    private final LinearLayout mboundView0;
    private long mDirtyFlags = -1L;

    public ActivityDetail2Binding(DataBindingComponent bindingComponent, View root) {
        super(bindingComponent, root, 0);
        Object[] bindings = mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds);
        this.detailActionButton = (Button)bindings[4];
        this.detailAvatar = (ImageView)bindings[1];
        this.detailDesc = (TextView)bindings[3];
        this.detailName = (TextView)bindings[2];
        this.mboundView0 = (LinearLayout)bindings[0];
        this.mboundView0.setTag((Object)null);
        this.setRootTag(root);
        this.invalidateAll();
    }
    ...
    static {
        sViewsWithIds.put(2131492948, 1);
        sViewsWithIds.put(2131492949, 2);
        sViewsWithIds.put(2131492950, 3);
        sViewsWithIds.put(2131492951, 4);
    }
}

其中全局靜態(tài)SparseIntArray數(shù)組中存放了4個(gè)數(shù)字队萤,這個(gè)四個(gè)數(shù)字為R.java中生成的對(duì)應(yīng)View的id轮锥,

public final class R {
    ...
    public static final class id {
        ...
        public static final int detail_action_button = 2131492951;
        public static final int detail_avatar = 2131492948;
        public static final int detail_desc = 2131492950;
        public static final int detail_name = 2131492949;
        ...
    }
    ...
}

ActvityDetail2Binding實(shí)例構(gòu)造的時(shí)候調(diào)用了mapBindings,一次解決了所有View的查找要尔,mapBindings函數(shù)在ActvityDetail2Binding父類(lèi)ViewDataBinding中實(shí)現(xiàn)舍杜。

3 使用表達(dá)式在layout.xml中填充model數(shù)據(jù)

ActivityDetail2.java中還存在大量的View控制、數(shù)據(jù)填充代碼赵辕,如何把這些代碼在交給layout.xml完成呢既绩?

3.1 ModelAdapter類(lèi)

第2節(jié)中已經(jīng)定義了User.java類(lèi)作為Model類(lèi),但是我們經(jīng)常會(huì)遇到Model類(lèi)和真正View展示不一致的情況还惠,本例子中定義一個(gè)來(lái)ModelAdapter類(lèi)來(lái)完整Model數(shù)據(jù)到展示數(shù)據(jù)的適配饲握。示例代碼為ActivityDetail3.java的內(nèi)部類(lèi),可以調(diào)用ActivityDetail3.java中的函數(shù)吸重,代碼定義如下:

public class DetailActivity3 extends AppCompatActivity {
    public class ModelAdapter {
        private User user;

        public ModelAdapter(User user) { this.user = user;}

        public String getName(){ return user != null ? user.getName() : null;}

        public Drawable getAvatar(){
            return user != null ? ContextCompat.getDrawable(DetailActivity3.this,user.getAvatar()) : null;
        }

        public String getDesc(){
            return user != null ? String.format("積分:%d 等級(jí):%d",user.getScore(),user.getLevel()) : null;
        }

        public String actionText(){ return user != null ? "退出登錄" : "登陸"; }

        public void clickHandler(View view){
            if (user != null) logout();
            else login();
        }
    }
}

3.2 activity_detail3.xml中使用model

同樣復(fù)制一份activity_detail2.xmlactivity_detail3.xml互拾,在<layout>節(jié)點(diǎn)加入<data>節(jié)點(diǎn),并且在里面定義需要用的model類(lèi)(比如ModelAdapter adapter)嚎幸,當(dāng)然也可以是基礎(chǔ)類(lèi)型變量(比如int visibility)颜矿;

隨后,就可以在下面的view中使用表達(dá)式了嫉晶,全部布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="adapter" type="com.asha.demo.DetailActivity3.ModelAdapter"/>
        <variable name="visibility" type="int"/>
    </data>
    <LinearLayout
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
        <View
            android:background="@color/detail_background"
            android:layout_width="match_parent"
            android:layout_height="66dp">
        </View>
        <ImageView
            android:src="@{adapter.avatar}"
            android:visibility="@{visibility}"
            android:id="@+id/detail_avatar"
            android:layout_gravity="center"
            android:layout_marginTop="-33dp"
            android:layout_width="66dp"
            android:layout_height="66dp" />
        <TextView
            android:visibility="@{visibility}"
            android:text="@{adapter.name}"
            android:id="@+id/detail_name"
            android:textSize="17sp"
            android:textColor="@color/textColorPrimary"
            android:layout_marginTop="15dp"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:visibility="@{visibility}"
            android:text="@{adapter.desc}"
            android:id="@+id/detail_desc"
            android:layout_marginTop="15dp"
            android:textSize="13sp"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <Button
            android:text="@{adapter.actionText}"
            android:onClick="@{adapter.clickHandler}"
            android:id="@+id/detail_action_button"
            android:layout_marginTop="15dp"
            android:layout_gravity="center"
            android:textColor="@color/white"
            android:background="@drawable/selector_g_button"
            android:layout_width="220dp"
            android:layout_height="wrap_content" />
    </LinearLayout>
</layout>

3.3 DetailActivity3.java中調(diào)用填充

如下代碼所示骑疆,只需要在登錄狀態(tài)改變的時(shí)候,給viewDataBinding設(shè)置所需要的adatper替废、visibility值箍铭,即可完成數(shù)據(jù)的填充

public class DetailActivity3 extends AppCompatActivity {

    ActivityDetail3Binding binding;
    public class ModelAdapter {
        ...
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this,R.layout.activity_detail3);
        login();
    }

    private void login(){
        fill(User.newInstance());
    }

    private void logout(){
        fill(null);
    }

    private void fill(final User user){
        binding.setAdapter(new ModelAdapter(user));
        binding.setVisibility( user != null ? View.VISIBLE : View.GONE);
    }
}

3.4 ActivityDetail3Binding中填充相關(guān)的代碼分析

同樣,ActivityDetail3Binding中椎镣,編譯器根據(jù)activity_detail3.xml中的<data>標(biāo)簽诈火,自動(dòng)生成了諸如setAdapter、setVisibility的代碼状答,setAdapter相關(guān)代碼如下:

public class ActivityDetail3Binding extends ViewDataBinding{
    
    private ModelAdapter mAdapter;
    ...
    public void setAdapter(ModelAdapter adapter) {
        this.mAdapter = adapter;
        synchronized(this) {
            this.mDirtyFlags |= 1L;
        }

        this.notifyPropertyChanged(1);
        super.requestRebind();
    }

    public ModelAdapter getAdapter() {
        return this.mAdapter;
    }
    ...
}

非常簡(jiǎn)單冷守,自動(dòng)生成了getter和setter刀崖,在完成set操作后,調(diào)用執(zhí)行notifyPropertyChangedsuper.requestRebind()

  • notifyPropertyChanged
    ViewDataBinding本身就是一個(gè)BaseObservable, 在往ViewDataBinding注冊(cè)觀察某個(gè)屬性的變化拍摇,如果注冊(cè)了mAdapter的變化亮钦,對(duì)應(yīng)的觀察器就會(huì)接收到回調(diào)。相關(guān)邏輯與反向Binding相關(guān)充活,谷歌官方還沒(méi)給出相關(guān)使用文檔蜂莉,不再深入分析;

  • super.requestRebind()
    1.此函數(shù)為ViewDataBinding中的函數(shù)混卵,具體實(shí)現(xiàn)為判斷現(xiàn)在是否有Rebind請(qǐng)求映穗,如果有則return;如果沒(méi)有則根據(jù)運(yùn)行時(shí)sdk版本交給handler或者choreographer插入到下一幀中執(zhí)行mRebindRunnable幕随。
    2.在mRebindRunnable中會(huì)根據(jù)當(dāng)前sdk版本男公,如果大于等于KITKAT,則需要在onAttachToWindow后執(zhí)行executePendingBindings合陵;否則直接執(zhí)行executePendingBindings枢赔。

public abstract class ViewDataBinding extends BaseObservable {
    protected void requestRebind() {
        synchronized (this) {
            if (mPendingRebind) {
                return;
            }
            mPendingRebind = true;
        }
        if (USE_CHOREOGRAPHER) {
            mChoreographer.postFrameCallback(mFrameCallback);
        } else {
            mUIThreadHandler.post(mRebindRunnable);
        }

    }

    /**
     * Runnable executed on animation heartbeat to rebind the dirty Views.
     */
    private final Runnable mRebindRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (this) {
                mPendingRebind = false;
            }
            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
                // Nested so that we don't get a lint warning in IntelliJ
                if (!mRoot.isAttachedToWindow()) {
                    // Don't execute the pending bindings until the View
                    // is attached again.
                    mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                    mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                    return;
                }
            }
            executePendingBindings();
        }
    };
}

3.在父類(lèi)ViewDataBinding中經(jīng)過(guò)一些的判斷,調(diào)用到ActivityDetail3Binding中的executeBindings拥知,在executeBindings中根據(jù)dirtyFlags執(zhí)行不同的View屬性賦值踏拜,以下所有ActivityDetail3Binding相關(guān)代碼都是編譯器自動(dòng)生成的

public class ActivityDetail3Binding extends ViewDataBinding{
    ...

    protected void executeBindings() {
        long dirtyFlags = 0L;
        synchronized(this) {
            dirtyFlags = this.mDirtyFlags;
            this.mDirtyFlags = 0L;
        }

        Drawable avatarAdapter = null;
        ModelAdapter adapter = this.mAdapter;
        String descAdapter = null;
        String nameAdapter = null;
        ActivityDetail3Binding.OnClickListenerImpl androidViewViewOnCli = null;
        String actionTextAdapter = null;
        int visibility = this.mVisibility;
        if((dirtyFlags & 5L) != 0L && adapter != null) {
            avatarAdapter = adapter.getAvatar();
            descAdapter = adapter.getDesc();
            nameAdapter = adapter.getName();
            androidViewViewOnCli = (this.mAndroidViewViewOnCl == null?(this.mAndroidViewViewOnCl = new ActivityDetail3Binding.OnClickListenerImpl()):this.mAndroidViewViewOnCl).setValue(adapter);
            actionTextAdapter = adapter.actionText();
        }

        if((dirtyFlags & 6L) != 0L) {
            ;
        }

        if((dirtyFlags & 5L) != 0L) {
            TextViewBindingAdapter.setText(this.detailActionButton, actionTextAdapter);
            this.detailActionButton.setOnClickListener(androidViewViewOnCli);
            ImageViewBindingAdapter.setImageDrawable(this.detailAvatar, avatarAdapter);
            TextViewBindingAdapter.setText(this.detailDesc, descAdapter);
            TextViewBindingAdapter.setText(this.detailName, nameAdapter);
        }

        if((dirtyFlags & 6L) != 0L) {
            this.detailAvatar.setVisibility(visibility);
            this.detailDesc.setVisibility(visibility);
            this.detailName.setVisibility(visibility);
        }

    }
    ...
}

至此,完成了View數(shù)據(jù)的填充分析低剔。

4 Binding

自動(dòng)生成的ViewDataBinding類(lèi)(例如ActivityDetail3Binding)內(nèi)包含了Model + View速梗,是MVVM中的MV的概念。

第2章的View注入襟齿,第3章的View賦值都是鋪墊姻锁,他們最后都是為Binding操作進(jìn)行服務(wù)。目前谷歌已經(jīng)支持雙向Binding猜欺,但上文已經(jīng)提到位隶,目前資料比較少。本文只關(guān)注單向的Binding开皿,即:Model的變化涧黄,自動(dòng)同步到View上。

4.1 使用ObservableField

目前所提供的ObservableField有:

Observable類(lèi)型 對(duì)應(yīng)原類(lèi)型
ObservableArrayList ArrayList
ObservableArrayMap ArrayMap
ObservableBoolean boolean
ObservableByte byte
ObservableChar char
ObservableFloat float
ObservableDouble double
ObservableLong long
ObservableInt int
ObservableParcelable<T extends Parcelable> <T extends Parcelable>
ObservableField<T> <T>

本文使用簡(jiǎn)單的ObservableInt作為示例赋荆,解決visibility的單項(xiàng)綁定問(wèn)題笋妥。

  • 改造activity_detail4.xml:定義類(lèi)型為ObservableInt的variable,name為visibility窄潭,隨后賦值給ImageView的android:visibility贴浙,示例如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="visibility" type="android.databinding.ObservableInt"/>
    </data>
    <LinearLayout
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
       ...
        <ImageView
            android:visibility="@{visibility.get()}"
            android:id="@+id/detail_avatar"
            android:layout_gravity="center"
            android:layout_marginTop="-33dp"
            android:layout_width="66dp"
            android:layout_height="66dp" />
        ...
    </LinearLayout>
</layout>
  • 改造DetailActivity4.java,只需要在onCreate時(shí)把visibility賦值給binding(ActivityDetail4Binding)即可澜建,后面對(duì)visibility的操作澄暮,就會(huì)更新到view上,示例代碼如下:
public class DetailActivity4 extends AppCompatActivity {
    ActivityDetail4Binding binding;
    ObservableInt visibility = new ObservableInt();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this,R.layout.activity_detail4,new MyComponent());
        binding.setVisibility(visibility);
        login();
    }

    private void login(){  fill(User.newInstance());  }

    private void logout(){ fill(null); }

    private void fill(final User user){
        visibility.set(user != null ? View.VISIBLE : View.GONE);
        ....
    }
    ....
}

4.2 ActivityDetail4Binding中單向綁定相關(guān)的代碼分析

與給ActivityDetail4Binding直接set純Model不同材义,所有的ObservableField都實(shí)現(xiàn)了Observable接口,只要實(shí)現(xiàn)了Observable接口嫁赏,都是單向Binding類(lèi)型,所以ActivityDetail4Binding中的setVisibility多加了一行代碼:this.updateRegistration(1, visibility)油挥,其中1為propertyId,目前一共自動(dòng)生成了2個(gè)潦蝇,0為adatper,1為visibility深寥,代碼如下:

public class ActivityDetail4Binding extends ViewDataBinding {
    ...
    public void setVisibility(ObservableInt visibility) {
        this.updateRegistration(1, visibility);
        this.mVisibility = visibility;
        synchronized(this) {
            this.mDirtyFlags |= 2L;
        }

        this.notifyPropertyChanged(3);
        super.requestRebind();
    }
    ...
}

updateRegistration函數(shù)為ViewDataBinding中的函數(shù)攘乒,會(huì)根據(jù) Observable、ObservableList惋鹅、ObservableMap三種類(lèi)型则酝,分別創(chuàng)建對(duì)應(yīng)的Listener。ObservableInt為Observable闰集,所以會(huì)使用CREATE_PROPERTY_LISTENER沽讹,在registerTo函數(shù)中創(chuàng)建WeakPropertyListener
代碼如下:

public abstract class ViewDataBinding extends BaseObservable {
    ...
    private boolean updateRegistration(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return unregisterFrom(localFieldId);
        }
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            registerTo(localFieldId, observable, listenerCreator);
            return true;
        }
        if (listener.getTarget() == observable) {
            return false;//nothing to do, same object
        }
        unregisterFrom(localFieldId);
        registerTo(localFieldId, observable, listenerCreator);
        return true;
    }
    ...
}

在WeakPropertyListener的mListener有個(gè)setTarget函數(shù)武鲁,這個(gè)函數(shù)會(huì)向mObservable(即外面?zhèn)鬟M(jìn)來(lái)的visibility)注冊(cè)一個(gè)監(jiān)聽(tīng)器爽雄,如果visibility值發(fā)生變化,這個(gè)listener就會(huì)得到通知沐鼠,回調(diào)到WeakPropertyListeneronPropertyChanged挚瘟,接著通知到binding(ActivityDetail4Binding)的handleFieldChange,在handleFieldChange中調(diào)用了ActivityDetail4BindingonFieldChange函數(shù)饲梭,如果返回值為true乘盖,則在handleFieldChange中調(diào)用requestRebind(),通知View進(jìn)行賦值更新界面憔涉,onFieldChange相關(guān)代碼如下:

public abstract class ViewDataBinding extends BaseObservable {
    ...
   private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
        if (result) {
            requestRebind();
        }
    }
    ...
}
public class ActivityDetail4Binding extends ViewDataBinding {
    ...
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch(localFieldId) {
        case 0:
            return this.onChangeAdapter((ModelAdapter)object, fieldId);
        case 1:
            return this.onChangeVisibility((ObservableInt)object, fieldId);
        default:
            return false;
        }
    }
    ...
}

4.3 Observable Objects

與4.1 ObservableField類(lèi)似订框,可以改造一下ModelAdapter:為getter方法增加@Bindable注解,為setter方法增加notifyPropertyChanged(com.asha.demo.BR.name)通知兜叨。其中布蔗,BR是根據(jù)@Bindalbe自動(dòng)生成的類(lèi),給getter方法增加@Bindable注解后浪腐,BR文件自動(dòng)會(huì)生成一個(gè)整型的name纵揍。改造后代碼如下:

public class DetailActivity4 extends AppCompatActivity {

    ActivityDetail4Binding binding;
    ObservableInt visibility = new ObservableInt();

    public class ModelAdapter extends BaseObservable{
        private User user;

        public ModelAdapter(User user) {
            this.user = user;
        }
        ...
        @Bindable
        public String getName(){
            return user != null ? user.getName() : null;
        }

        public void setName(String name){
            if (user != null) user.setName(name);
            notifyPropertyChanged(com.asha.demo.BR.name);
        }
        ...
    }
    ...
}

隨后,在DetailActivity4.java中調(diào)用測(cè)試代碼议街,執(zhí)行完會(huì)在1秒后改變adapter上的name值泽谨,并且同步到View上,測(cè)試代碼如下:

binding.detailActionButton.postDelayed(new Runnable() {
    @Override
    public void run() {
        adapter.setName("haha");
    }
},1000);

具體原理與4.1類(lèi)似,不再贅述吧雹。

5 layout.xml中View屬性的setter

在下述示例中骨杂,detail_name這個(gè)TextView想把a(bǔ)dapter.name賦值給自身的text屬性,就需要調(diào)用textView.setText(String)方法雄卷,這個(gè)方法就是View屬性的setter方法搓蚪。

<TextView
    android:text="@{adapter.name}"
    android:id="@+id/detail_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

5.1 @BindingAdapter

上述的setter方法,Data Binding庫(kù)幫我們實(shí)現(xiàn)了大部分默認(rèn)方法丁鹉,具體方法參見(jiàn)android.databinding.adapters包下的類(lèi)妒潭,下圖為ViewBindingAdatper具體實(shí)現(xiàn),

ViewBindingAdatper

其中setter方法都為static方法揣钦,第一個(gè)參數(shù)都為自身的實(shí)例雳灾,后面為xml中傳入的參數(shù),只要加入@BindingAdapter注解冯凹,編譯器就會(huì)全局搜索保存在一個(gè)temp文件中谎亩,并在生成類(lèi)似ActivityDetail4Binding過(guò)程中去查找所需的setter方法的。如果需要自定義宇姚,只需要在任意app代碼中定義@BindingAdapter即可匈庭,例如:

public class DetailActivity4 extends AppCompatActivity {
    @BindingAdapter("android:alpha")
    public static void globalSetAlpha(View view, float alpha) {
        view.setAlpha(alpha);
    }
}

5.2 DataBindingComponent

很多情況下只是某個(gè)Binding文件(例如ActivityDetail4Binding)需要自定義setter方法,這個(gè)時(shí)候就需要使用DataBindingComponent浑劳,

  • 首先嚎花,定義一個(gè)MyComponent,
public class MyComponent implements android.databinding.DataBindingComponent {
    @BindingAdapter("android:alpha")
    public void setAlpha(View view, float alpha) {
        view.setAlpha(0.5f);
    }

    @Override
    public MyComponent getMyComponent() {
        return new MyComponent();
    }
}
  • 接著呀洲,在生成Binding對(duì)象時(shí)傳入這個(gè)DataBindingComponent實(shí)例紊选,代碼如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = DataBindingUtil.setContentView(this,R.layout.activity_detail4,new MyComponent());
    ...
}

完成后,這個(gè)ActivityDetail4Binding范圍內(nèi)的所有android:alpha="@{foo}"的方式賦值alpha的setter函數(shù)都會(huì)使用MyComponent#setAlpha道逗。

5.3 @BindingConversion

有時(shí)候會(huì)遇到類(lèi)型不匹配的問(wèn)題兵罢,比如R.color.white是int,但是通過(guò)Data Binding賦值給android:background屬性后滓窍,需要把int轉(zhuǎn)換為ColorDrawable卖词,實(shí)現(xiàn)方式如下:

  • 1.定義一個(gè)靜態(tài)函數(shù),放在項(xiàng)目任意類(lèi)中吏夯,
@BindingConversion
public static Drawable convertColorToDrawable(int drawable) {
    return new ColorDrawable(drawable);
}
  • 2.在layout.xml中使用Data Binding此蜈,如:
<View
android:background="@{adapter.avatar != null ? @color/detail_background : @color/colorAccent }"
android:layout_width="match_parent"
android:layout_height="66dp">

對(duì)應(yīng)在ActivityDetail4Binding.java中生成的代碼如下所示,其中AvatarAdapterObjectn1為int類(lèi)型:

ViewBindingAdapter.setBackground(this.mboundView1, DetailActivity4.convertColorToDrawable(AvatarAdapterObjectn1));

5.4 @BindingMethod

例如layout.xml中android:onClick屬性噪生,在Binding中真正使用setter時(shí)裆赵,就對(duì)應(yīng)到了setOnClickListener方法,

@BindingMethod(type = View.class, attribute = "android:onClick", method = "setOnClickListener"),

6 Data Binding利用編譯器在背后做的那些事兒

Data Binding相關(guān)的jar包由四部分組成跺嗽,

  • 1.baseLibrary-2.1.0-rc1.jar
    作為運(yùn)行時(shí)類(lèi)庫(kù)被打進(jìn)APK中战授;

  • 2.DataBinderPlugin(gradle plugin)
    在編譯期使用页藻,利用gradle-api(之前叫transform-api,1.5生植兰,2.0改名)處理xml文件份帐,生成DataBindingInfo.java;

  • 3.compiler-2.1.0-rc1.jar
    在編譯器使用楣导,入口類(lèi)繼承自AbstractProcessor废境,用于處理注解,并生成Binding類(lèi)筒繁,DataBindingCompoent.java噩凹,DataBinderMapper.java類(lèi);

  • 4.compilerCommon-2.1.0-rc1.jar
    DataBinderPlugincompiler-2.1.0-rc1.jar所依賴(lài)

為了提高運(yùn)行時(shí)的效率膝晾,Data Binding在背后做了非常多的工作,下圖是我整理的編譯流程务冕,如圖所示:

Data Binding編譯流程

6.1 相關(guān)對(duì)象介紹

  • 白色部分為輸入血当,包括
    1.res/layout;
    2.源代碼中的注解禀忆;

  • 黃色部分為編譯器處理類(lèi)臊旭,包括
    1.aapt編譯時(shí)處理,入口類(lèi)名為MakeCopy.java;
    2.gradle-api處理箩退,入口類(lèi)名為DataBinderPlugin.java;
    3.AbstractProcessor處理离熏,入口類(lèi)名為ProcessDataBinding.java

  • 藍(lán)色部分為中間產(chǎn)物戴涝,包括
    1.data-binding-info文件夾滋戳,包含了layout的基本信息,導(dǎo)入的變量啥刻,View標(biāo)簽中的表達(dá)式奸鸯,標(biāo)簽的位置索引等等,如下所示為data-binding-info/activity_detail3-layout.xml

<?xml version="1.0" encoding="utf-8"?>
<Layout layout="activity_detail3" modulePackage="com.asha.demo" absoluteFilePath="/Users/Asha/Desktop/AndroidDataBinding/app/src/main/res/layout/activity_detail3.xml" directory="layout" isMerge="false">
  
  <Variables declared="true" type="com.asha.demo.DetailActivity3.ModelAdapter" name="adapter">
    <location startLine="3" startOffset="8" endLine="3" endOffset="83"/>
  </Variables>

  <Variables declared="true" type="int" name="visibility">
    <location startLine="4" startOffset="8" endLine="4" endOffset="47"/>
  </Variables>
  <Targets>

    <Target tag="layout/activity_detail3_0" view="LinearLayout">
      <Expressions/>
      <location startLine="6" startOffset="4" endLine="51" endOffset="18"/>
    </Target>

    <Target id="@+id/detail_avatar" tag="binding_1" view="ImageView">
      <Expressions>
        <Expression text="adapter.avatar" attribute="android:src">
          <Location startLine="15" startOffset="12" endLine="15" endOffset="42"/>
          <TwoWay>false</TwoWay>
          <ValueLocation startLine="15" startOffset="27" endLine="15" endOffset="40"/>
        </Expression>

        <Expression text="visibility" attribute="android:visibility">
          <Location startLine="16" startOffset="12" endLine="16" endOffset="45"/>
          <TwoWay>false</TwoWay>
          <ValueLocation startLine="16" startOffset="34" endLine="16" endOffset="43"/>
        </Expression>
      </Expressions>
      <location startLine="14" startOffset="8" endLine="21" endOffset="42"/>
    </Target>

    ....
  </Targets>
</Layout>

2.setter_store.bin可帽,包含所有setter相關(guān)信息娄涩;
3.layoutinfo.bin,包含所有l(wèi)ayout相關(guān)信息映跟;
4.br.bin蓄拣,包含所有BR相關(guān)信息;
以上bin文件都以Serializable方式序列化到磁盤(pán)上努隙,需要的時(shí)候進(jìn)行反序列化操作球恤;

  • 綠色部分為最終產(chǎn)物,包括
    1.data-binding-layout-out(最終輸出到res/layout)荸镊,即去掉根節(jié)點(diǎn)<layout>碎捺,去掉節(jié)點(diǎn)<data>路鹰,與不使用Data Binding時(shí)的layout相一致,例如data-binding-layout-out/activity_detail2.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent" android:tag="layout/activity_detail2_0" xmlns:android="http://schemas.android.com/apk/res/android">
    <View
        android:background="@color/detail_background"
        android:layout_width="match_parent"
        android:layout_height="66dp">
    </View>
    ...
</LinearLayout>

2.DataBindingInfo.class收厨,一個(gè)看似空的類(lèi)晋柱,但在SOURCE階段包含了一個(gè)@BindingBuildInfo注解,包含了基本DataBinding的基本信息诵叁,代碼如下:

// DataBindingInfo.class
public class DataBindingInfo {
    public DataBindingInfo() {
    }
}
// @BindingBuildInfo
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface BindingBuildInfo {
    String buildId();
    String modulePackage();
    String sdkRoot();
    int minSdk();
    String layoutInfoDir();
    String exportClassListTo();
    boolean isLibrary();
    boolean enableDebugLogs() default false;
    boolean printEncodedError() default false;
}

3.DataBindingComponent.class雁竞,會(huì)根據(jù)自定義的DataBindingComponent自動(dòng)生成對(duì)應(yīng)實(shí)例化方法,例如:

public interface DataBindingComponent {
    MyComponent getMyComponent();
}

4.ViewDataBinding.class的子類(lèi)(ActivityDetail2Binding.class等)
5.BR.class拧额,Bindable屬性索引表碑诉,例如:

public class BR {
    public static final int _all = 0;
    public static final int adapter = 1;
    public static final int name = 2;
    public static final int visibility = 3;

    public BR() {
    }
}

6.DataBindingMapper.class,Mapper侥锦,用于尋找某個(gè)layout.xml對(duì)應(yīng)的ViewDataBinding類(lèi)进栽,例如:

class DataBinderMapper {
    static final int TARGET_MIN_SDK = 16;

    public DataBinderMapper() {
    }

    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId) {
        switch(layoutId) {
        case 2130968602:
            return ActivityDetail2Binding.bind(view, bindingComponent);
        case 2130968603:
            return ActivityDetail3Binding.bind(view, bindingComponent);
       ....
        default:
            return null;
        }
    }

    ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] views, int layoutId) {
        return null;
    }

    int getLayoutId(String tag) {
        if(tag == null) {
            return 0;
        } else {
            int code = tag.hashCode();
            switch(code) {
            case -600937657:
                if(tag.equals("layout/activity_detail2_0")) {
                    return 2130968602;
                }
                break;
            case -600936696:
                if(tag.equals("layout/activity_detail3_0")) {
                    return 2130968603;
                }
                break;
            ....

            return 0;
        }
    }

    String convertBrIdToString(int id) {
        return id >= 0 && id < DataBinderMapper.InnerBrLookup.sKeys.length?DataBinderMapper.InnerBrLookup.sKeys[id]:null;
    }

    private static class InnerBrLookup {
        static String[] sKeys = new String[]{"_all", "adapter", "name", "visibility"};

        private InnerBrLookup() {
        }
    }
}

6.2 相關(guān)編譯流程

  • STEP1 資源處理
    aapt或者gradle執(zhí)行時(shí),都會(huì)觸發(fā)資源處理恭垦,在資源處理過(guò)程中快毛,DataBinding都會(huì)掃描一遍現(xiàn)有的資源,生成不包含<layout>data-binding-layout-out以及DataBinding所需要的data-binding-info番挺;
  • STEP2 DataBindingInfo.class生成
    在完成資源處理后唠帝,aapt或者gradle-api都會(huì)去執(zhí)行DataBindingInfo.class生成操作,把相關(guān)的信息寫(xiě)入DataBindingInfo.class@BindingBuildInfo注解中玄柏;
  • STEP3 監(jiān)聽(tīng)到注解變化
    生成@BindingBuildInfo注解襟衰,或者code中發(fā)現(xiàn)有新的注解寫(xiě)入,AbstractProcessor注解處理器就開(kāi)始執(zhí)行注解處理粪摘。DataBinding中有一個(gè)ProcessDataBinding.java類(lèi)專(zhuān)門(mén)來(lái)處理DataBinding相關(guān)的注解瀑晒;
  • STEP4 ProcessDataBinding處理注解,生成bin
    ProcessDataBinding中處理注解永遠(yuǎn)會(huì)按順執(zhí)行3步徘意,ProcessMethodAdapter瑰妄,ProcessExpressionsProcessBindable映砖。每次執(zhí)行都會(huì)從磁盤(pán)反序列化對(duì)應(yīng)的bin文件间坐,然后忘bin中寫(xiě)入新的,完成后再序列化到磁盤(pán)邑退;
  • STEP5 生成最終產(chǎn)物
    執(zhí)行ProcessMethodAdapter生成DataBindingComponents.class竹宋;執(zhí)行ProcessExpressions生成ViewDataBinding.class子類(lèi)(ActivityDetail2Binding.class),并觸發(fā)DataBindingMapper.class更新地技;執(zhí)行ProcessBindable生成BR.class蜈七,并觸發(fā)DataBindingMapper.class更新;

7 細(xì)節(jié)補(bǔ)充-View Tag的使用

第二章有講到View是如何注入的莫矗,其實(shí)需要分兩種情況:

  • 1.如果這個(gè)View標(biāo)簽屬性中只有id飒硅,沒(méi)有其他"@{表達(dá)式}"形式砂缩,則按照第2章提到的方式直接通過(guò)id查找;
  • 2.如果這個(gè)View標(biāo)簽屬性中有"@{表達(dá)式}"形式的值三娩,則編譯器會(huì)自動(dòng)給這個(gè)View加個(gè)android:tag="binding_{N}"庵芭, 其中{N}按順序從0開(kāi)始遞增,如android:tag="binding_0"雀监。當(dāng)執(zhí)行ViewDataBinding#mapBindings去注入View時(shí)双吆,會(huì)找tag為binding_開(kāi)頭的View,隨后執(zhí)行View注入会前;

另外好乐,如果View標(biāo)簽原來(lái)就有android:tag值,則編譯器會(huì)先保存原有值信息瓦宜,寫(xiě)入android:tag="binding_{N}"蔚万。當(dāng)執(zhí)行完view注入后,再把原來(lái)的值賦值給android:tag临庇。注意如果原來(lái)的android:tag值為"binding_0"反璃,那么在View注入時(shí)將會(huì)發(fā)生錯(cuò)亂。

在完成View注入后苔巨,ActivityDetail3Binding會(huì)執(zhí)行this.setRootTag(root)版扩,代碼如下:

public class ActivityDetail3Binding extends ViewDataBinding {

    public ActivityDetail3Binding(DataBindingComponent bindingComponent, View root) {
        super(bindingComponent, root, 0);
        Object[] bindings = mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds);
        this.detailActionButton = (Button)bindings[4];
        this.detailActionButton.setTag((Object)null);
        ...
        this.setRootTag(root);
        this.invalidateAll();
    }
}

這與ListView中的ViewHoloder實(shí)現(xiàn)方式相似废离,所以如果把DataBinding運(yùn)用到ListView的ViewHolder中侄泽,就不需要多生成一個(gè)ViewHolder,直接使用這個(gè)ViewDataBinding類(lèi)即可蜻韭,例如ListAdapter實(shí)現(xiàn):

public static class ListAdapter extends BaseAdapter{
    ...

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ItemFooBinding binding;
        if (convertView == null){
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            binding = DataBindingUtil.inflate(inflater,R.layout.item_foo,parent,false);
        } else {
            binding = DataBindingUtil.getBinding(convertView);
        }
        if (binding == null) return null;

        bind(binding,position);
        return binding.getRoot();
    }

    private void bind(ItemFooBinding binding, int position) {
        binding.title.setText("position:" + position);

        Context context = binding.avatar.getContext();
        int colorId = position % 2 == 0  ? R.color.colorAccent : R.color.colorPrimary;
        binding.avatar.setBackgroundColor(ContextCompat.getColor(context,colorId));
    }
}

8 總結(jié)

  • DataBinding 庫(kù)非常小
    目前Android Data Binding在運(yùn)行類(lèi)庫(kù)只有632個(gè)方法數(shù)悼尾,算上每個(gè)layout.xml自動(dòng)生成的ViewDataBinding子類(lèi)(demo中每個(gè)類(lèi)不超過(guò)20個(gè)方法數(shù)),方法數(shù)總和也非常有限肖方。


    Data Binding方法數(shù)
  • DataBinding 運(yùn)行時(shí)沒(méi)有多余性能損耗
    DataBinding所有的View注入闺魏、View賦值、Binding都是編譯器自動(dòng)生成的代碼俯画,這些重復(fù)的體力勞動(dòng)本身就需要去做析桥,只是交給了編譯器來(lái)完成,所以運(yùn)行時(shí)沒(méi)有多余的性能損耗艰垂。
  • DataBinding 可以減少錯(cuò)誤率
    既然View注入泡仗、View賦值、Binding都是編譯器自動(dòng)完成的猜憎,只要使用正確娩怎,100%無(wú)低級(jí)錯(cuò)誤保證,可以提高代碼質(zhì)量胰柑,讓開(kāi)發(fā)者心情愉悅截亦。
  • DataBinding 對(duì)編譯時(shí)長(zhǎng)的影響
    還沒(méi)實(shí)際運(yùn)用到生產(chǎn)環(huán)境爬泥,肯定有所延長(zhǎng),具體量級(jí)還未知崩瓤。

9 Reference

官方Data-Binding-Guide
楊輝的個(gè)人博客-(譯)Data Binding 指南
LyndonChin/MasteringAndroidDataBinding
googlesource/data-binding

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末袍啡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谷遂,更是在濱河造成了極大的恐慌葬馋,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肾扰,死亡現(xiàn)場(chǎng)離奇詭異畴嘶,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)集晚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)窗悯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人偷拔,你說(shuō)我怎么就攤上這事蒋院。” “怎么了莲绰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵欺旧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蛤签,道長(zhǎng)辞友,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任震肮,我火速辦了婚禮称龙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戳晌。我一直安慰自己鲫尊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布沦偎。 她就那樣靜靜地躺著疫向,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豪嚎。 梳的紋絲不亂的頭發(fā)上搔驼,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音疙渣,去河邊找鬼匙奴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛妄荔,可吹牛的內(nèi)容都是我干的泼菌。 我是一名探鬼主播谍肤,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼哗伯!你這毒婦竟也來(lái)了荒揣?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤焊刹,失蹤者是張志新(化名)和其女友劉穎系任,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體虐块,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俩滥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贺奠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霜旧。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖儡率,靈堂內(nèi)的尸體忽然破棺而出挂据,到底是詐尸還是另有隱情,我是刑警寧澤儿普,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布崎逃,位于F島的核電站,受9級(jí)特大地震影響眉孩,放射性物質(zhì)發(fā)生泄漏个绍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一勺像、第九天 我趴在偏房一處隱蔽的房頂上張望障贸。 院中可真熱鬧错森,春花似錦吟宦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至瓦阐,卻和暖如春蜗侈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睡蟋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工踏幻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人戳杀。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓该面,卻偏偏與公主長(zhǎng)得像夭苗,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子隔缀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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