MVVM之DataBinding入門

目錄

MVVM介紹

不習慣簡書界面,可以試試螞蟻筆記生成的博客地址 dataBinding,我的博客地址
本篇這是基礎入門篇,要想看源碼剖析請移步到 DataBinding實現原理探析

MVVM框架類似于早期的MVC和最熱的MVP,但是比起這兩個更為強勢。MV-VM相比于MVP,其實就是將Presenter層替換成了ViewModel層,我們都知道,MVP的好處就是將邏輯代碼從View層抽離出來,做到與UI層的低耦合,但是無形中會創造出許多的接口,有些接口很是冗余,不僅如此,當后期修改數據或者添加新的功能還需要修改或是添加接口,很是麻煩。

這個時候MV-VM的優勢就體現出來了,ViewModel層所需要做的完全就是跟邏輯相關的代碼,完全不會涉及到UI。當數據變化,直接驅動UI的改變,中間省去了冗余的接口。同時,在ViewModel層編寫代碼中,要求開發者需要將每個方法盡可能的做的功能單一,不與外部有任何的引用或者是聯系,無形中提高了代碼的健壯性,方便了后期的單元測試。

DataBinding其實就是谷歌出臺的工具,是實現UI和數據綁定的框架,ViewViewModel通過DataBinding實現單向綁定或雙向綁定,做到UI和數據的相互監聽,同時開發者的任務分配也就很明確了,負責ViewModel的小伙伴完全不用考慮UI如何實現,很大程度上提高了代碼的開發效率和后期出問題跟蹤的準確性,針對這些好處,采用MVVM進行代碼開發還是非常有必要的。

初步使用

1. modulebuild.gradle文件加上一行配置代碼

android {
    ...
    dataBinding {
        enabled = true
    }
}

2. 創建布局文件

只需要在之前布局的基礎上,外層嵌套 <layout></layout>即可。

<layout
    xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="student"
            type="com.xiaweizi.bean.Student"/>
        <!-- 這里 type 必須傳完整路徑,或者用 import 方式也是可以的 -->
        <!--
            <import type="com.xiaweizi.bean.Student"/>
            <variable
                name="student"
                type="Student"/>
        -->
    </data>

    <!-- 對應之前的XML文件 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical">

    </LinearLayout>
</layout>

因為XML是不支持自定義導包的,所以通過import先導包,如果類名相同的話可以通過alias進行區分:

<import type="android.view.View"/>
<import type="com.xiaweizi.View"
        alias="MyView"/>

<variable
    name="view1"
    type="View"/>

<variable
    name="view2"
    type="MyView"/>

這個時候會在app\build\generated\source\debug\包名路徑下生成對應的binding類,命名方式,舉個例子最為直接:

原XML名:activity_main  ----> 生成對應的binding名: ActivityMainBinding

3. Activity中替換原來的setContentView()代碼

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

4. 接下來就是關鍵的ViewModel

a. 單向綁定

咱們先從簡單的開始,DataBinding有個很大的好處就是摒棄原生findViewById頻繁的遍歷視圖層和ButterKnife的反射,采用的是數組記錄每個view

final Object[] bindings = mapBindings(bindingComponent, root, 8, sIncludes, sViewsWithIds);

XML創建一個TextView

<TextView
    android:id="@+id/tv_content"
    android:text="@{student.name}"
    android:layout_width="match_parent"
    android:layout_height="50dp"/>

在代碼中通過binding直接可以獲取到這個TextView

mBinding.tvContent

那么如何實現單向綁定呢?

Student student = new Student("xiaweizi", 12);
mBinding.setStudent(student);

這樣就可以直接改變TextView的值。

ViewModel就是簡單的數據

public class Student {
    public String name;
    public int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

b. 雙向綁定

之前說的單向綁定,即當數據變化,通過mBinding.setStudent(student)方式驅動UI的改變
而雙向綁定,無論View還是ViewModel誰改變,都會驅動另一方的改變,實現雙向綁定有兩種方式:繼承BaseObservable和使用ObservableField創建成員變量。

代碼實現:
第一種繼承BaseObservable

public class Student extends BaseObservable{

    // 如果是 public 則在成員變量上方加上 @Bindable 注解
    @Bindable
    public String sex;

    public void setSex(String sex) {
        this.sex = sex;
        notifyPropertyChanged(BR.sex);
    }
    
    /*************************** 我是分割線 ***************************/
    // 如果是 private 則在成員變量的 get 方法中添加 @Bindable 注解
    private String name;
    @Bindable
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
    
    public void setSexName(String name, String sex){
        this.name = name;
        this.sex = sex;
        notifyChange();
    }
}

這個時候當調用setName()方法,不僅數據改變,UI中的TextView內容也會隨之改變。

我們可以發現有兩個方法:notifyPropertyChanged()notifyChange,一個是更新指定的變量,第二個是更新所有該ViewModel中的對象。

notifyPropertyChanged(int fieldId)里面傳的參數,即上面通過@Bindable注解創建對應的變量id

第二種:使用ObservableField

public class Student extends BaseObservable{

    public ObservableField<String> name = new ObservableField<>();
    
    private ObservableInt age = new ObservableInt();
    public void setAge(int age) {
        this.age.set(age);
    }
    public int getAge() {
        return age.get();
    }
}

通過使用ObservableField創建的對象作用相當于第一種的方案,支持ObservableIntObservableBoolean或者是ObservableField<T>指定的類型、ObservableArrayMap<String, Object>ObservableArrayList<Object>等。

ObservableField內部已經封裝了getset方法,如果成員變量是public屬性,直接通過

mStudent.name.set("shabi");
String name = mStudent.name.get();

設置和獲取對應的成員變量的值。

其他使用

學會了上面基本的用戶還是遠遠不夠的,像按鈕的點擊事件或是EditText內容的監聽,這些也是非常重要的,不過學會了一種,其他的舉一反三就會容易的多了。

1. 事件處理

dataBinding需要你通過一些表達式來處理view的分發事件,除了少數例子外,事件元素的名稱是由監聽器中的方法所控制。比如View.OnLongClickListener內部有onLongClick()方法,所以XML定義的事件就為android:onLongClick.

可以直接在Activity內部定義一個類,用于處理事件的監聽

public class Presenter {
    public void onClickExample(View view) {
        Toast.makeText(SimpleActivity.this, "點到了", Toast.LENGTH_SHORT).show();
    }
    
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        mStudent.name.set(s.toString());
    }

    public void onClickListenerBinding(Student student) {
        Toast.makeText(SimpleActivity.this, student.name.get(),Toast.LENGTH_SHORT).show();
    }
}

XML中:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="輸入name"
    android:onTextChanged="@{presenter::onTextChanged}"/>


<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="@{presenter.onClickExample}"
    android:text='@{"年齡:" + student.age}'/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="5dp"
    android:onClick="@{() -> presenter.onClickListenerBinding(student)}"
    android:text='@{"姓名:" + student.name}'/>

首先從點擊事件開始分析,android:onClick="@{presenter.onClickExample}" 里面對應的方法自然是要與Presenter定義的方法名一致,名字可以不為onClickExample,但是參數必須是View,參數要對應于setOnClickListener(onClickListener listener)對應的onClickListener要實現的接口,即public void onClick(View)

同理,監聽EditText文本的變化,一般只要注意onTextChanged(CharSequence s, int start, int before, int count)方法即可,那么我們可以創建與之對應的方法,在XML文件中引用:android:onTextChanged="@{presenter::onTextChanged}"

最后再來看從UI中獲取數據,也就是數據的回調,即DataBinding的精髓支出,ViewViewModel雙向綁定。android:onClick="@{() -> presenter.onClickListenerBinding(student)}這里用到了lamda表達式,這樣就可以不遵循默認的方法簽名,將student對象直接傳回點擊方法中。來看一下實現效果:

簡單測試.gif

一目了然,我就不贅述了,我們可以發現一點,一開始我們并沒有給Student對象設置值,所以顯示的是null,并沒有報空指針異常,這也是DataBinding的有點之一。

其實dataBinding自帶對數據監聽的方法:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={student.name}"/>

代碼中:

student.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
        @Override
        public void onPropertyChanged(Observable observable, int i) {
            // i 為 BR 文件中對應的 int 值
            Log.i("xwz--->", student.getName());
            Log.i("xwz--->", student.getAge());
        }
});

這個對數據的監聽建立在,使用@Bindable作為雙向綁定為條件,當數據變化,便會出發onPropertyChanged方法。需要注意的是android:text="@={student.name}",@后面多了一個=

2. ViewStubinclude

dataBinding同樣是支持ViewStub的,使用起來也很簡單,直接貼代碼了。

<ViewStub
    android:id="@+id/view_stub"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout="@layout/viewstub"/>

代碼中:

View inflate = binding.viewStub.getViewStub().inflate();

inflate即為替代ViewStubView.

至于include更簡單,用法跟以前是差不多,唯一不同的是可以將ViewModel傳到下一個XML中:

<include layout="@layout/layout_include" bind:student="@{student}"/>

layout_include中同樣可以共享student這個對象。

3. BindingAdapter的使用

我們之前用的都是Android自帶的監聽或是屬性,比如textonClick,但是如果項目中需要動態改變ImageView的內容,那我們應該怎么辦呢?dataBinding給我們提供了BindingAdapter這個注解,方便我們定義自定義的屬性。
假如我們有個需求,點擊按鈕更換圖片,這個時候我們需要定義靜態的方法:

@BindingAdapter({"url", "name"})
public static void loadImageView(ImageView view, String url, String name) {
    Log.i("xwz--->", url + "\t" + name);
    Glide.with(view.getContext())
         .load(url)
         .into(view);
}

XML中使用

<ImageView
    android:layout_width="160dp"
    android:layout_height="160dp"
    bind:name="@{student.name}"
    bind:url="@{student.imgUrl}"/>

這里有必要解釋一下,靜態方法loadImageView里第一個參數為作用的View,這里是ImageView;后面的參數即分別對應于@BindingAdapter里面的參數。那這里是怎么跟View聯系在一塊呢?我們發現XML中有這樣一行代碼bind:name="@{student.name}這里的name對應的的@BindingAdapter注解里的參數name,并映射于ViewModel中的student.name。當student.name值改變,就會觸發loadImageView方法,從而執行里面的方法。

bind名稱是任意的定義的,不過要定義對應的命名空間xmlns:bind="http://schemas.android.com/apk/res-auto"

實現的效果就很簡單了:

bindAdapter.gif

更強大的在于可以覆蓋Android原生的元素設置屬性,比如android:text最常見不過了

@BindingAdapter ("android:text")
public static void setText(TextView view, String text) {
    view.setText(text + "xiaweizi");
    Log.i("xwz--->", text);
}

XML:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{"測試"}'/>

這個時候所有設置text的地方后綴全部加上了xiaweizi.

4. @BindingConversion

dataBinding還支持對數據的轉換,或者是類型的轉換

@BindingConversion
public static String addString(String text){
    Log.i("xwz--->", "DemoBindingAdapter:  " + "addString: " + text);
    return text + "xiaweizi";
}

這個時候會將項目中所有以@{String}方式用到的String后綴全部加上xiaweizi.

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color){
   return new ColorDrawable(color);
}

XML:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

這段代碼的作用在于將int類型的color值,轉換成了ColorDrawable類型.

5. DataBindingComponent

通過BindingAdapter是可以增加一些自定義的屬性或者是修改Android原生的屬性,但是它有一個弊端,就是全局修改所有的相關屬性,不過配合上DataBindingComponent就可以解決這個問題。

DataBindingUtil.setContentView(this, R.layout.activity_component, new FirstComponent());

開始的是以一個參數的形式傳入這個View中,那么只作用于當前的view。

DataBindingUtil.setDefaultComponent(new FirstComponent());

或者是這種設置全局的方式,也可以改變。

DataBindingComponent其實是一個空方法的接口,你需要先創建一個擁有@BindingAdapter的類,這里就不能定義為public,因為這樣DataBindingComponent就找不到對應的類,我們為了方便后期的開發,可以定義一個抽象類:

public abstract class AbstractAdapter {
    @BindingAdapter ("text")
    public abstract void setText(TextView textView, String text);
}

然后定義一個實現類:

public class FirstAdapter extends AbstractAdapter{
    @Override
    public void setText(TextView textView, String text) {
        Log.i("xwz--->", "FirstAdapter:  " + "setText: ");
        textView.setText(text+"first");
    }
}

這個時候當你創建一個實現DataBindingComponent接口的類時,會發現讓你實現一個方法:

public class FirstComponent implements android.databinding.DataBindingComponent {
    @Override
    public AbstractAdapter getAbstractAdapter() {
        return new FirstAdapter();
    }
}

這里返回的就是創建的adapter,可以根據需求創建對應的component.

RecyclerView中的應用

除了最基本的使用,還有一個頻繁出現的就是列表了,那么我們這里就拿RecyclerView作為代表進行演示。

RecyclerView的好處就不多說了,已經完全代替了之前的ListViewGridView,用法也就不贅述了,這里主要介紹一下適配器的編寫。雖然網上有很多大神已經幫我們創建了各種通用的adapter,不過作為入門,我們還是要學習一下使用dataBinding創建adapter.

先來個簡單的XML文件:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <import type="com.github.markzhai.sample.Person"/>
        <variable
            name="person"
            type="Person"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="24sp"
            android:padding="5dp"
            android:textColor="#f0f"
            android:text='@{"姓名:" + person.name, default="aaa"}'/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="15sp"
            android:padding="2dp"
            android:textColor="#090"
            android:gravity="right"
            android:layout_marginRight="80dp"
            android:text='@{"年齡:" + person.age, default=12}'/>
    </LinearLayout>
</layout>

至于ViewModel就是一個簡單的Person類,擁有nameage兩個屬性,接下來就是adapte的編寫。

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private List<Person> mList; 
    public MyAdapter() {
        mList = new ArrayList<>();
    }
    public void setData(List<Person> persons) {
        this.mList = persons;
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ItemRecyclerBinding itemBinding = 
                    DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
                                                                  R.layout.item_recycler,
                                                                  parent,
                                                                  false);

        return new ViewHolder(itemBinding);
    }

    @Override
    public void onBindViewHolder(MyAdapter.ViewHolder holder, int position) {
        holder.bind(mList.get(position));
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        final ItemRecyclerBinding itemBinding;

        public ViewHolder(ItemRecyclerBinding binding) {
            super(binding.getRoot());
            this.itemBinding = binding;
        }

        void bind(Person person) {
            itemBinding.setPerson(person);
        }
    }
}

通過DataBindingUtil.inflate()創建item布局,在ViewHolder中進行數據的綁定,這個時候,當數據源變化的時候,RecyclerView中的數據也跟著變化。

至于item的點擊事件可以上面的onClick寫法:

創建Presenter處理點擊事件:

public static class Presenter{
    ItemRecyclerBinding mBinding;
    public Presenter(ItemRecyclerBinding binding){
        this.mBinding = binding;
    }
    public void onItemClick(Person person){
        Log.i("xwz--->", "name: " + person.getName() + "\tage: " + person.getAge());
        Toast.makeText(mBinding.getRoot().getContext(), "name: " + person.getName() + "\tage: " + person.getAge(), Toast.LENGTH_SHORT).show();
    }
}

在之前的bind方法中進行綁定:

void bind(Person person) {
    itemBinding.setPerson(person);
    itemBinding.setPresenter(new Presenter(itemBinding));
}

XML中設置點擊事件即可:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{() -> presenter.onItemClick(person)}"
    android:orientation="vertical">
...
</LinearLayout>

這里就可以直接將數據直接回傳。

看一下運行效果:

RecyclerView的使用.gif

一些細節

databinding支持一些java的表達式

  • + - * / %
  • 字符串的連接"a"+"b"
  • 邏輯和位運算&& || & |
  • 一元運算+ - ! ~
  • 移位 >> >>> <<
  • 比較 == > < >= <=
  • instance of
  • 支持數據類型:character,String,numeric,null
  • 強轉cast
  • 方法的調用
  • 成員變量的訪問
  • 數組訪問
  • 三元表達式? :

簡單例子:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

dataBinding不支持的Java特性

  • this
  • super
  • new
  • 泛型

dataBinding判空處理

使用??來進行判空操作

android:text="@{user.displayName ?? user.lastName}"

如果不為空則選擇左側值,否則選擇右側值,類似于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

支持數組,集合,map

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List<String>"/>
    <variable name="sparse" type="SparseArray<String>"/>
    <variable name="map" type="Map<String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

資源的訪問

dataBinding支持一般語法對資源的訪問:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

小結

dataBinding主要的作用就在于減少ActivityFragment層的代碼,不再使用findViewById,讓XML從之前只用于顯示視圖,到現在可以做一些操作。在性能上更是有很大的提高,內部采用0反射,使用位標記檢測需要更新的view,每次數據的改變是在下一幀開始改變等等。

當然也有一些不足之處,Android StudioIDE支持還不是那么完善,在XML中一些方法不能智能生成和跳轉,還有就是報錯的錯誤信息,有的時候并不能定位到準確的位置。不過總體上來說dataBinding帶來的好處遠遠的超過這些不足,所以還沒有嘗試的小伙伴,不妨試一試,相信你會愛上他的。

感謝dataBinding視頻
markzhai
官方地址

我的博客

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,725評論 25 708
  • 1、概述 Databinding 是一種框架,MVVM是一種模式,兩者的概念是不一樣的。我的理解DataBindi...
    Kelin閱讀 76,861評論 68 521
  • 情商高的人,從來懂得換位思考,從來知道什么是適可而止,不會隨時隨地隨意的給別人添麻煩。
    主持人梓惟閱讀 47評論 0 0
  • 專注于所做事情中最關鍵的20%,要訣在于擺脫那些沒給你帶來很好回報的80%的事情。要做最重要的事。——《專注力》 ...
    鳳凰涅槃再涅槃閱讀 758評論 1 9
  • 《西游記》中的血腥場面 文/張守權 《西游記》一書是以唐僧遠赴西天取經為線索而寫的一部長篇神話小說,書中除...
    張守權閱讀 4,195評論 1 2