AndroidMVVM(使用經驗篇)

樑潤生發表於2017-04-08

      MVVM的大名相信做手機開發的肯定不會陌生,我第一次聽到它是從做IOS開發的同學那裡聽到的,我們的專案之前應用了MVP,要說服大家從MVP到MVVM,肯定得說說為啥,他優秀在那裡?

      首先我們看看正常MVP的依賴關係圖:

      這是個經典的MVP依賴關係,View 層和Presenter,Presenter和Model層彼此依賴,但是不會出現MVC那種跨層依賴,例如如果你寫出來的View和Model層有依賴的話,那麼就不是正常的MVP結構咯~這個結構好處很明顯,Model和View層脫藕。因此修改Model層程式碼View層程式碼不用動~

      好了說了那麼多MVP的,MVP是個很優秀的脫藕的架構。那麼MVVM比MVP優秀在那裡,還是那句,看圖:
    
   

     我們看到,VM沒了對View層的依賴~依賴的程式碼不由我們寫是由框架程式碼生成~因此我們的進一步看到升級版的MVP~當然我知道最優秀當然是Spring~這裡不離題了。MVVM為我們帶來的就是這個,那麼MVVM運用困難嗎?我先跟你說:

     1.用了MVVM後,只要寫一次後以後不用再寫Adapter!

     2.用了MVVM後,只要寫一次後以後不用再寫Dialog!

     最重要是,Android官方提供了官方的針對MVVM開發的dataBinding . 所以主角出場,DataBinding!
    
    

     基本:

     然後我們說說配置,很簡單一句話,在BuildGradle增加:

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

   沒有其他了,除了你的Android Studio 要超過1.3版本。

   DataBinding XML和我們平時有點不一樣他,他的頂層必須是layout:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
 
    <data>
        <variable
            name="vm"
            type="com.silver.testdatabinding.AMainPresenterVM" />
 
        <variable
            name="user"
            type="com.silver.model.BindingUser" />
    </data>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:orientation="vertical"
        >
 
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"/>
 
        <Button
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_orange_light"
            android:gravity="center"
            android:layout_marginTop="20dp"
            android:onClick="@{()->vm.onClickInit()}"
            android:text="initList" />
 
         <Button
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_orange_light"
            android:gravity="center"
            android:layout_marginTop="20dp"
            android:onClick="@{()->vm.onClickTestUpdate()}"
            android:text="initList" />
 
        <ImageView
            android:id="@+id/image"
            android:layout_width="150dp"
            android:layout_height="150dp"
            bind:imageUrl="@{user.url}"/>
 
    </LinearLayout>
</layout>

 

      其中data標題表示資料,要繫結的資料,variable表示繫結變數,那麼怎麼繫結到某些Text或者什麼的,上面程式碼也看到”@{}“加這個標誌,譬如上面的Text我要顯示User的Name的話,就直接user.name,當然這個name 是public ,那麼馬上有人問,我不喜歡這樣,我是經過特殊處理的方法出來的欄位,是否能顯示,答案是可以的。例如user有過處理過Name的方法,叫getFirstName(),那麼 TextView哪裡要引用的話變成 以下:
      

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.getFirstName()}"/>

 

      除了欄位顯示欄位這些我們還有很多的才能做到VM層不再依賴V層的,所以我們再看,點選事件回撥,包括最普通的Onclick
      

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.name}"
   android:onClick="@{()->vm.onClickUser(view)}"/>

 

      就如上面,是直接呼叫Lambda表示式,預設是省略了View 如果你要引用呢?

      

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.name}"
   android:onClick="@{(view)->vm.onClickUser(view)}"/>

 

      當然其他View中有的點選事件也是類似炮製例如(onTextChange),這裡不再多說~那如果木有定義過的事件呢?例如上面的 我要為ImageView設定 圖片的URL?這裡先留個懸念,下面說。因為那個是個人認為DataBinding非常牛逼有用的地方。

      接下來先說說在Activity怎麼繫結這些~先看程式碼:

ActivityMainDataBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = DataBindingUtil.setContentView(this ,R.layout.activity_main);
    AMainVM vm = new AMainVM();
    binding.setVm(vm);
    binding.setUser(vm.user);
}
@Override
protected void onDestroy() {
        //  這裡不寫也可以,寫了加快回收~
        binding.unbind();
        super.onDestroy();
}

      其中 ActivityMainDataBinding,是自動生成的,生成規則是layout的名字去掉下劃線並首字母大寫加上DataBinding,不用clean ,rebuiding,寫完XML就有~爽吧~然後開始繫結View,呼叫DataBindingUtil 的靜態方法自動setContentView就自動繫結了View,然後呼叫進行進一步資料繫結,包括VM和其他資料。這裡有人發現其實這樣寫不是累贅了嗎?其實我們大可以不繫結User,而直接呼叫vm裡的User。優化如下:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{vm.user.name}"/>

      then,我們的Activity只要繫結vm就可以咯~是不是覺得突然Activity什麼都沒了?我的專案也差不多這樣的~

      再看看我們的User結構跟vm裡面是啥:

public class BindingUser{
    public String name;
    public Int clickCount;
    public String url;
}
 
public class BindingUser{
    public  ObservableField<String> name = new ObservableField<>();
    public final ObservableInt clickCount = new ObservableInt();
    public final ObservableField<String> url = new ObservableField<>();
}

      這個是User類,其中上面的是我們這裡用的,不需要動態更新的可以這樣,但是實際上我們需要動態更新因此引入Observable觀察者類,這樣可以動態重新整理,當我們改變其中的值得時候,View會得到及時的通知重新整理介面。這裡可能有同學又要問,我們平時都是用Gson啥的序列化進去,你這樣寫,我們就不能反序列化進去了,其實上面的BindingUser可以寫到如下面的效果,看程式碼:

public class BindingUser extends BaseObservable{
    @Bindable
    public String name;
    @Bindable
    public Int clickCount;
    @Bindable
    public String url;
 
   public void setName(String name) {
       this.name= name;
       notifyPropertyChanged(BR.name);
   }
   public void setClickCount(String clickCount) {
       this.clickCount= clickCount;
       notifyPropertyChanged(BR.clickCount);
   }
   public void setUrl(String url) {
       this.url= url;
       notifyPropertyChanged(BR.url);
   }
}

 

      其中BaseObservable是基礎觀察者,我們首先需要繼承他,否則之後的無效~然後在你需要動態重新整理的欄位上加上@Bindable 和寫他們的set方法進行重新整理,其中BR是DataBinding類動態生成的,對應每一個繫結變數variabl的ID值。

        再來看看VM裡的程式碼:

 

public class AMainVM{
 
    private ObservableField<BindingUser> bindingUser;
 
    public AMainVM(){
        bindingUser = new ObservableField<>();
    }
 
    @Override
    public void onClickUser() {
        // 每點一此加1
        bindingUser.clickCount.set(bindingUser.clickCount.get() + 1);
    }
 
    @Override
    public void onClickInit() {
        BindingUser bindingUserModel = new BindingUser();
        bindingUser.name.set("just test");
        bindingUser.clickCount.set(0);
        bindingUser.url.set("http://img0.imgtn.bdimg.com/it/u=2770060730,47109478&fm=21&gp=0.jpg");
        bindingUser.set(bindingUserModel);
    }
 
    @Override
    public void onClickTestUpdate() {
        new AsyncTask<Void, Void, Void>() {
 
            @Override
            protected Void doInBackground(Void... params) {
                bindingUser.name.set("update in thread");
                return null;
            }
        }.execute();
    }
}

      這裡強烈推薦Observable這個類,超級方便,但是要注意的是,我為什麼要再初始化VM的時候去初始化我們的User類的ObservableField,而不是點選的時候在方法onClickInit()中才懶載入?  那是因為如果那時我才初始化,會發現View介面木有任何變化。為啥?因為在Activity裡繫結VM的時候,dataBinding會幫我們繫結了VM以及VM裡面其他繫結在其他View的變數,所以那時如果你木有初始化VM內的變數需要繫結的變數,那麼DataBinding會登出哪個對應BR ID的變數。因此,當你在下面再重新新建一個Observable的時候壓根是沒做過任何View的繫結註冊,因此就算你怎麼更新資料,View也不會更新,除非這時你持有View,然後強制重新繫結,但是這樣就違揹我們VM不能依賴View的原則,因此這裡要注意初始化。

      大家都看到我還在下面做了個很邪惡的實驗,在非UI執行緒更新BindingUser的name,驗證下當我在非UI執行緒時候更新UI是否會出現問題,答案是不會。所以放心更新地運算元據,這裡不多說裡面實現了,無非就一個Handler嘛。

      這樣像上面的就是一個最簡單的MVVM的結構,what `s more?除了這些我們還想有其他控制,例如我想決定某個View是否顯示,看如下程式碼:

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"
            android:visibility="@{user.name == null ? View.GONE : View.VISIBLE}" />

     當然我們在引用View.Gone這個靜態常量時候要在上面data裡宣告,應用import欄位:

<data>
        ...
        <import type="android.view.View" />
</data>

      
     如果需要增加某個@String欄位拼接我們的變數,或者我們自己隨便寫的一個String組拼那麼怎樣?

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            bind:message=`@{"名字:" + user.name}`/>
 
<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            bind:message="@{@string/name + user.name}"/>

 

    新增的特殊符號:
    我們平時遇到的,我們兩個欄位,誰不空顯示誰,那麼按我們平時寫的如下:

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            bind:message="@{user.name == null? user.count:user.name}"/>
 
<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            bind:message="@{user.name ?? user.count}"/>

     下面就是DataBinding提供的簡化寫法~用操作符??,上下意思兩種表達意思一樣。

     DataBinding的大概使用就是這樣,當然還有很多我們探索的包括ObservableMap ,ObservableList這裡使用也很方便,喜歡的同學自行查詢~都很簡單,以下介紹DataBinding最強大功能。

    自定義設定器(Custom setter):

     註釋 @BindingAdapter ,為什麼我不說BindMethod,因為我覺得作用不是太大,沒使用,如果有同學覺得有大用,請一定告訴我~好繼續,大家看到我開始的XML中有個很奇怪的欄位,“bind:imageURL”這個是哪裡來的?首先這個就是自定義設定器,首先我們要使用的話必須在定義如下:
     

<layout xmlns:android="http://schemas.android.com/apk/res/android"
 
        xmlns:bind="http://schemas.android.com/apk/res-auto">
 
       ......
 
    <ImageView
            android:id="@+id/image"
            android:layout_width="150dp"
            android:layout_height="150dp"
            bind:imageUrl="@{user.url}"/>
</layout>

       加上“xmlns:bind=”http://schemas.android.com/apk/res-auto”” 有些同學會在之前的自定義View裡用到“xmlns:app=”http://schemas.android.com/apk/res-auto””,這個沒關係,將”bind:imageURL”改為”app:imageURL”就可以。那麼我們定義了這個後還要寫一個擺放這個imageUrl的處理方法,我自己起了一個類專門處理的,BindingUtil下面我們看看裡面怎樣的~

public class BindingUtil {
 
    @BindingAdapter({"imageUrl"})
    public static void loadImage(ImageView imageView, String imageUrl) {
        if (imageUrl != null) {
            Picasso.with(imageView.getContext()).load(imageUrl).into(imageView);
        }
    }
 
    @BindingAdapter({"showLoading", "message"})
    public static void showLoading(View view, boolean showLoading, String message) {
        if (showLoading) {
            // showLoading
        } else {
            // cancelLoading
        }
    }
 
    @BindingAdapter("showLoading")
    public static void showLoading(View view, boolean showLoading) {
        if (showLoading) {
            // showLoading
        } else {
            // cancelLoading
        }
    }
}

      做的不多方法頭加上註解@BindingAdapter ,方法必須是靜態方法哦,其中函式第一個引數是你繫結的View,這個函式呼叫的前提是ImageView裡有個引數bind:imageUrl,當繫結的user.url初始化或者改變了值的時候會呼叫這個loadImage方法。因此我們VM裡做資料轉換變化,View馬上就有響應去載入圖片。VM不用管View更新問題~這個給了我啟發,我們的ShowLoading,ShowToast是不是也可以這樣,因此有了下面方法。注意:當方法中有多個引數的時候,例如上面的ShowLoading 有一個布林的showLoading引數和message引數,隨便一個更改引數都會呼叫我們設定的方法,因此我們寫這裡方法時需要特別注意引數處理哦~
       
      BaseRecycleAdapter:

       接下來我會說說我寫的一個BaseRecycleAdapter主要針對RecycleView的,大家覺得好的可以直接拿去用,以後就不用再寫關於RecycleView的Adapter了,當然對於ListView和Dialog?也是類似的思想~先說說思路:

       我們用DataBinding寫一個Adapter會怎樣?

public class MainAdapter extends BaseAdapter {
 
    private LayoutInflater layoutInflater;
    private List<String> listData;
 
    public MainAdapter(Activity activity,List<String> listData) {
        this.listData = listData;
        layoutInflater = LayoutInflater.from(activity);
    }
 
    @Override
    public int getCount() {
        if (getData() != null) {
            return getData().size();
        } else {
            return 0;
        }
    }
 
    @Override
    public Object getItem(int position) {
        if (getData() != null) {
            return getData().get(position);
        } else {
            return null;
        }
    }
 
    @Override
    public long getItemId(int position) {
        return position;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        BindingHolder bindingHolder;
        if (convertView == null) {
            AdapterMainBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.adapter_main, null, false);
            bindingHolder = new BindingHolder();
            bindingHolder.setBinding(binding);
            convertView = binding.getRoot();
            convertView.setTag(bindingHolder);
        } else {
            bindingHolder = (BindingHolder) convertView.getTag();
        }
 
        bindingHolder.getBinding().setVariable(com.silver.testdatabinding.BR.testString, listData .get(position));
        bindingHolder.getBinding().executePendingBindings();
        return convertView;
    }
 
    public static class BindingHolder {
        private AdapterMainBinding binding;
 
        public AdapterMainBinding getBinding() {
            return binding;
        }
 
        public void setBinding(AdapterMainBinding binding) {
            this.binding = binding;
        }
    }
}

 

      首先我們寫一個Holder,其中我們的Holder中其實只有一個binding,而binding在DataBindingUtil.inflate()時已經對ContentView進行了繫結,因此我們重新資料來重新整理介面就可以,如上面setVariable然後再executePendingBindings()進行資料重新整理操作。

       那麼我們得思考一下,我們其實很多DataBinding的Adapter會非常類似,因此很有必要寫一個通用,以後省去我們很多寫Adapter的時間了~我們來考慮下一個Adapter會需要些什麼。

        1.我們需要告訴Adapter我們contentView的LayoutId是啥。

        2.首先我們需要一個List ,滾動的時候動態繫結裡面的每個Model進行資料重新整理。

        3.我們需要告訴getView方法裡我們需要繫結的Model 變數名 (BR的ID值)。

        4.我們要有個onItemClickListener作為事件點選。

        5.其他item裡的onClick事件。  

        6.當List變化的時候通知Adapter進行notify。

    

      這些基本構成了我們Adapter的要求了~如果還有還可以自行新增~

      首先看看初始化方法

public class BaseRecycleViewAdapter<T> extends RecyclerView.Adapter<BaseRecycleViewAdapter.BindingHolder> {
 
    private static final Object DATA_INVALIDATION = new Object();
    public @LayoutRes ObservableInt layoutId;
    private AdapterModule adapterModule;
 
    private final WeakReferenceOnListChangedCallback<T> callback = new WeakReferenceOnListChangedCallback<>(this);
    private LayoutInflater inflater;
 
    // Currently attached recyclerview, we don`t have to listen to notifications if null.
    @Nullable
    private RecyclerView recyclerView;
 
    public BaseRecycleViewAdapter(int layoutId) {
        this.layoutId = new ObservableInt(layoutId);
    }
 
    public void setAdapterModule(AdapterModule<T> adapterModule) {
        if (this.adapterModule == adapterModule || adapterModule == null) {
            return;
        }
        // If a recyclerview is listening, set up listeners. Otherwise wait until one is attached.
        // No need to make a sound if nobody is listening right?
        if (recyclerView != null) {
            if (this.adapterModule != null && this.adapterModule.list != null && this.adapterModule.list instanceof ObservableArrayList) {
                this.adapterModule.list.removeOnListChangedCallback(callback);
            }
            if (adapterModule != null && adapterModule.list != null && adapterModule.list instanceof ObservableList) {
                adapterModule.list.addOnListChangedCallback(callback);
            }
        }
        this.adapterModule = adapterModule;
        notifyDataSetChanged();
    }
 
    ......
}

     這是構造整個函式的基本引數,建構函式需要我們ContentView的LayoutId,而AdapterModule是針對Adapter的資料變數,其中對AdapterModule中list 進行addOnListChangedCallback 監聽更新從而對Adapter進行重新整理。Adapter是我們的資料變數組成類,我們來看看他內部:

     

public class AdapterModule<T> {
 
    public ObservableArrayList<T> list = new ObservableArrayList<>();
    // bindingVaiable id
    public ObservableInt bindingVaiable;
    // bind PositionId
    public ObservableInt bindPositionVaiableId;
    // the key is the resourceId!
    public WeakReference<SparseArray<OnClickListener>> listeners;
 
    public AdapterModule(ArrayList<T> list, int bindingVaiable,int bindPositionVaiableId) {
        if (list != null && !list.isEmpty()) {
            this.list.addAll(list);
        }
        this.bindingVaiable = new ObservableInt(bindingVaiable);
        this.bindPositionVaiableId = new ObservableInt(bindPositionVaiableId);
    }
 
    public AdapterModule(ArrayList<T> list, int bindingVaiable, SparseArray<OnClickListener> listeners) {
        if (list != null && !list.isEmpty()) {
            this.list.addAll(list);
        }
        this.bindingVaiable = new ObservableInt(bindingVaiable);
        this.listeners = new WeakReference<>(listeners);
    }
 
    public void setListeners(SparseArray<OnClickListener> listeners) {
        this.listeners = new WeakReference<>(listeners);
    }
}

     

      list就是我們顯示資料,bindingVaiable是我們每個getView 裡contentView要繫結的Module變數 ,第三個引數是個很有意思的引數,繫結當前getView的點position,只有BR指定的ID值,實際值是在Adapter中進行繫結。這裡下面看程式碼~最後一個是我們的onClickListener。這個  onClickListener是我自己寫的,其中也很簡單隻有一個帶position引數的onClick方法。至於onClickListener繫結變數的BR值巧用了下SparserArray的Key就是我們繫結的變數BR值。

      我們繼續看看Adapter的OnCreateViewHolder和OnBindHolder:
      

@Override
    public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (inflater == null) {
            inflater = LayoutInflater.from(parent.getContext());
        }
 
        ViewDataBinding binding = DataBindingUtil.inflate(inflater, layoutId.get(), null, false);
        final BindingHolder holder = new BindingHolder(binding, (SparseArray<OnClickListener>) adapterModule.listeners.get());
 
        binding.addOnRebindCallback(new OnRebindCallback() {
            @Override
            public boolean onPreBind(ViewDataBinding binding) {
                return recyclerView != null && recyclerView.isComputingLayout();
            }
 
            @Override
            public void onCanceled(ViewDataBinding binding) {
                if (recyclerView == null || recyclerView.isComputingLayout()) {
                    return;
                }
                int position = holder.getAdapterPosition();
                if (position != RecyclerView.NO_POSITION) {
                    notifyItemChanged(position, DATA_INVALIDATION);
                }
            }
        });
        return holder;
    }
 
    @Override
    public void onBindViewHolder(BindingHolder holder, int position) {
        // bindPosition
        onBindBinding(holder.binding, adapterModule.bindPositionVaiableId.get(), position, false);
        onBindBinding(holder.binding, adapterModule.bindingVaiable.get(), adapterModule.list.get(position), true);
    }
 
    private void onBindBinding(ViewDataBinding binding, int bindingVariable, Object item, boolean executePendingBindings) {
        if (bindingVariable != 0) {
            boolean result = binding.setVariable(bindingVariable, item);
            if (!result) {
                throw new IllegalArgumentException("can`t bind variable adapterModule because can`t find the id,is it correct?");
            }
            // refresh
            if (executePendingBindings) {
                binding.executePendingBindings();
            }
        }
    }

 

     onCreateViewHolder 裡不多說了,主要增加了在binging進行時繫結檢測是否recycleView已經在onAttach,在onBindView進行資料繫結第一個繫結的是position,setVariable(bindPosiionVaiableId,position);其中bindpositionVaiableId就是顯示ContentView的position變數值,然後繫結當前position。同時重新繫結我們list中對應position的module變數進行繫結資料重新整理,最後執行  binding.executePendingBindings();進行資料重新整理。這就是一個recycleView繫結的資料過程。

      那麼在哪裡進行繫結OnClickListener?為甚麼這裡進行資料繫結還有要對position進行繫結?其實我們每個點選回撥只需要一個回撥變數函式,因此不用每次都進行繫結只要在ViewHolder裡進行繫結,我們變化的只有Position。

      下面我們再看看ViewHolder程式碼:
      

public static class BindingHolder<T> extends RecyclerView.ViewHolder {
 
        ViewDataBinding binding;
        private SparseArray<OnClickListener> listeners;
 
        public BindingHolder(ViewDataBinding binding, SparseArray<OnClickListener> listeners) {
            super(binding.getRoot());
            this.binding = binding;
            this.listeners = listeners;
            onBindListeners();
        }
 
        public void onBindListeners() {
            if (listeners != null && listeners.size() > 0) {
                for (int i = 0; i < listeners.size(); i++) {
                    binding.setVariable(listeners.keyAt(i), listeners.get(listeners.keyAt(i)));
                }
            }
        }
    }

     最後當我們List進行重新整理的時候需要對我們的Adapter進行重新整理,因此看看OnListChangeListener程式碼:
    

private static class WeakReferenceOnListChangedCallback<T> extends ObservableArrayList.OnListChangedCallback<ObservableArrayList<T>> {
        final WeakReference<BaseRecycleViewAdapter<T>> adapterRef;
 
        WeakReferenceOnListChangedCallback(BaseRecycleViewAdapter<T> adapter) {
            this.adapterRef = new WeakReference<>(adapter);
        }
 
        @Override
        public void onChanged(ObservableArrayList<T> ts) {
            BaseRecycleViewAdapter<T> adapter = adapterRef.get();
            if (adapter == null) {
                return;
            }
            Utils.ensureChangeOnMainThread();
            adapter.notifyDataSetChanged();
        }
 
        ...
    }

      主要是資料更改是對Adapter進行notify。       

      這樣看還是有點抽象,我們再看看怎麼用,就一目瞭然。
      

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
 
       <variable
            name="position"
            type="Integer" />
 
        <variable
            name="user"
            type="com.silver.model.BindingUser" />
 
         <variable
            name="onItemClick"
            type="com.ykse.mvvm.adapter.listener" />
         <variable
            name="onClick"
            type="com.ykse.mvvm.adapter.listener" />
    </data>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:orientation="vertical"
        android:onClick="@{()->onItemClick.onClick(position)}"
        >
 
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"/>
 
        <Button
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_orange_light"
            android:gravity="center"
            android:layout_marginTop="20dp"
            android:onClick="@{()->onClick.onClick(position)}"
            android:text="initList" />
 
        <ImageView
            android:id="@+id/image"
            android:layout_width="150dp"
            android:layout_height="150dp"
            bind:imageUrl="@{user.url}"/>
 
    </LinearLayout>
</layout>

 

     這個就是我們寫的Adapter的ContentView。我們的點選事件,還有變數還有OnItemClick都解決了。

     還有那麼我們的Activity的ContentView是怎麼將AdapterModule和layoutId跟recycleView繫結的?看看XML:
    

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
 
        <variable
            name="layoutId"
            type="Integer" />
 
        <variable
            name="vm"
            type="com.silver.model.AMainVM" />
    </data>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:orientation="vertical"
        >
 
        <android.support.v7.widget.RecyclerView
            android:id="@+id/id_recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            bind:layoutId="@{layoutId}"
            bind:listData="@{vm.adapterModule}"/>
 
    </LinearLayout>
</layout>

     這次@BindingAdapter又發揮作用~
    

public class BindingAdapterUtil {
 
    @BindingAdapter({"layoutId"})
    public static void bindRecycleViewLayoutId(RecyclerView recyclerView, int layoutId) {
        BaseRecycleViewAdapter baseRecycleViewAdapter = new BaseRecycleViewAdapter(layoutId);
        recyclerView.setAdapter(baseRecycleViewAdapter);
    }
 
    @BindingAdapter({"listData"})
    public static <T> void bindRecycleViewAdapterModule(RecyclerView recyclerView, AdapterModule<T> adapterModule) {
        BaseRecycleViewAdapter<T> adapter;
        if (recyclerView.getAdapter() != null) {
            adapter = (BaseRecycleViewAdapter<T>) recyclerView.getAdapter();
            adapter.setAdapterModule(adapterModule);
        } else {
            return;
        }
    }
}

 

      還有更多的雙向繫結~大家可以參考下資料,原本DataBinding也有一些原生的~

      到這裡我們DataBinding的使用篇算是完了,更多的功能需要大家更多發現~謝謝能看到這裡的人,你們都是熱愛技術的人!


相關文章