DataBinding(一) 一、應用 DataBinding + LiveData 雙向監聽 二、原理分析

Android知識總結

一、應用 DataBinding + LiveData 雙向監聽

  • 1.1、在build.gradle中配置
    dataBinding {
        enabled = true
    }
  • 1.2、model 類
public class Student {
    public final ObservableField<String> name = new ObservableField<>();

    public void setText(String text) {
        name.set(text);
    }
}
  • 1.3、ViewModel 類
public class MyViewModule extends ViewModel {
    public MutableLiveData<Student> mStudent = new MutableLiveData<>();

    public MyViewModule() {
        mStudent.setValue(new Student());
        mStudent.getValue().setText("ssss");
    }
    

    public void onStudentClick(View view){
        mStudent.getValue().setText("aaaaa");
    }
}
  • 1.4、佈局類
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data class="DataBindingActivityBinging">
        <variable
            name="viewModel"
            type="com.example.designdemo.jetpack.databinding.MyViewModule" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".jetpack.databinding.DataBindingActivity">

        <TextView
            android:id="@+id/name_tv"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center"
            android:text="@{viewModel.mStudent.name}"
            android:textSize="25sp"
            android:layout_marginTop="30dp"
            android:background="#EA0707"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/name_et"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center"
            android:text="@={viewModel.mStudent.name}"
            android:textSize="25sp"
            android:layout_marginTop="30dp"
            android:background="#31EA07"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/name_tv" />


        <Button
            android:id="@+id/stu_btn"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center"
            android:text="點擊"
            android:textSize="25sp"
            android:onClick="@{(v)->viewModel.onStudentClick(v)}"
            android:layout_marginTop="30dp"
            android:background="#31EA07"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/name_et" />

        <ImageView
            android:id="@+id/stu_img"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_marginTop="30dp"
            android:background="#31EA07"
            android:gravity="center"
            android:text="點擊"
            android:textSize="25sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/stu_btn"
            app:setImageView="@{R.drawable.circle_blue}" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  • 1.5、實現類
public class DataBindingActivity extends AppCompatActivity {

    private static final String TAG = "DataBindingActivity";

    public static void startDataBindingActivity(Activity activity){
        Intent intent = new Intent(activity, DataBindingActivity.class);
        activity.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DataBindingActivityBinging binging = DataBindingUtil.setContentView(this,
                R.layout.activity_data_binding);


        MyViewModule myViewModule = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory())
                .get(MyViewModule.class);
        //綁定 ViewModel
        binging.setViewModel(myViewModule);
        //綁定生命週期  Lifecycle,感應變化
        binging.setLifecycleOwner(this);

        myViewModule.mStudent.observe(this, new Observer<Student>() {
            @Override
            public void onChanged(Student student) {
                Log.i(TAG, student.name.get());
            }
        });
    }
}
  • BindingAdapter 的使用
public class MyDataBindAdapter {
    //這裏 requireAll = false 表示我們可以使用這兩個兩個屬性中的任一個或同時使用,
    //如果requireAll = true 則兩個屬性必須同時使用,不然報錯。默認爲 true。
    @BindingAdapter(value = {"setImageView"}, requireAll = false)
    public static void setImageView(ImageView view, int id){
        view.setBackgroundResource(id);
    }
}

二、原理分析

2.1、佈局文件分析

假如我們XML的佈局是下面的樣式:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    //是給 databinding 用的
    <data class="JetpackActivityBinding">
        <variable
            name="student"
            type="com.example.designdemo.jetpack.databinding.Student" />

    </data>
    //給Activity 用的
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".jetpack.JetpackActivity">

        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">

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

                <Button
                    android:id="@+id/lifecycle"
                    android:layout_width="match_parent"
                    android:layout_height="60dp"
                    android:layout_marginTop="20dp"
                    android:text="Lifecycle"
                    android:textAllCaps="false"
                    android:textSize="18sp"
                    app:layout_constraintLeft_toLeftOf="parent"
                    app:layout_constraintRight_toRightOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />


                <Button
                    android:id="@+id/data_bind_btn"
                    android:layout_width="match_parent"
                    android:layout_height="60dp"
                    android:layout_marginTop="20dp"
                    android:text="dataBinding"
                    android:textAllCaps="false"
                    android:textSize="18sp"
                    app:layout_constraintLeft_toLeftOf="parent"
                    app:layout_constraintRight_toRightOf="parent"
                    app:layout_constraintTop_toBottomOf="@id/lifecycle" />

            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.core.widget.NestedScrollView>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

會在app\build\intermediates\data_binding_layout_info_type_merge\debug\out目錄中生產對應XML

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout bindingClass="JetpackActivityBinding" directory="layout"
    filePath="app\src\main\res\layout\activity_jetpack.xml" isBindingData="true"
    isMerge="false" layout="activity_jetpack" modulePackage="com.example.designdemo"
    rootNodeType="androidx.constraintlayout.widget.ConstraintLayout">
    <ClassNameLocation endLine="5" endOffset="38" startLine="5" startOffset="17" />
    <Variables name="student" declared="true"
        type="com.example.designdemo.jetpack.databinding.Student">
        <location endLine="8" endOffset="71" startLine="6" startOffset="8" />
    </Variables>
    <Targets>
        <Target tag="layout/activity_jetpack_0"
            view="androidx.constraintlayout.widget.ConstraintLayout">
            <Expressions />
            <location endLine="53" endOffset="55" startLine="12" startOffset="4" />
        </Target>
        <Target id="@+id/lifecycle" view="Button">
            <Expressions />
            <location endLine="35" endOffset="63" startLine="25" startOffset="16" />
        </Target>
        <Target id="@+id/data_bind_btn" view="Button">
            <Expressions />
            <location endLine="48" endOffset="73" startLine="38" startOffset="16" />
        </Target>
    </Targets>
</Layout>

可以看到,這裏定義了多個Target標籤,這些Target的定義,其實就是定義對應的tag,將tag與自己的佈局中的對應的View的id對應起來經過DataBinding變化後的佈局,會多出tag。

最後會在app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml中生成共我們Activity用的佈局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:tag="layout/activity_jetpack_0"
    tools:context=".jetpack.JetpackActivity">

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

            <Button
                android:id="@+id/lifecycle"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:layout_marginTop="20dp"
                android:text="Lifecycle"
                android:textAllCaps="false"
                android:textSize="18sp"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent" />


            <Button
                android:id="@+id/data_bind_btn"
                android:layout_width="match_parent"
                android:layout_height="60dp"
                android:layout_marginTop="20dp"
                android:text="dataBinding"
                android:textAllCaps="false"
                android:textSize="18sp"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@id/lifecycle" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.core.widget.NestedScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>

2.2、代碼分析

首先有此處爲入口

DataBindingActivityBinging binging = DataBindingUtil.setContentView(this,R.layout.activity_data_binding);`

進入DataBindingUtil.setContentView,其實DataBindingUtil的setContentView()方法,主要就是調用activity的setContentView設置佈局,並且綁定添加對應的View

    public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId) {
        return setContentView(activity, layoutId, sDefaultComponent);
    }

    public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId, @Nullable DataBindingComponent bindingComponent) {
        // 調用Activity的setContentView設置佈局
        activity.setContentView(layoutId);
        View decorView = activity.getWindow().getDecorView();
        // 獲取 DecorView 中的 content (根佈局)
        ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
    }

DataBindingUtil.bindToAddedViews()

    private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
            ViewGroup parent, int startChildren, int layoutId) {
        final int endChildren = parent.getChildCount();
        final int childrenAdded = endChildren - startChildren;
        // 如果childrenAdded==1,則就只有一個子View
        // 如果不等於1,則有多個
        if (childrenAdded == 1) {
            final View childView = parent.getChildAt(endChildren - 1);
            return bind(component, childView, layoutId);
        } else {
            final View[] children = new View[childrenAdded];
            //遍歷每個佈局
            for (int i = 0; i < childrenAdded; i++) {
                children[i] = parent.getChildAt(i + startChildren);
            }
            return bind(component, children, layoutId);
        }
    }

然後到DataBindingUtil.bind()

    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    }

    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }

這裏的sMapper是一個DataBinderMapper對象,其實現類是
DataBinderMapperImpl,DataBinderMapperImpl是通過apt註解處理器生成的。這裏的sMapper.getDataBinder()其實就是調用的MergedDataBinderMapper的getDataBinder()方法而 sMapper中的數據,其實就是DataBinderMapperImpl的構造器中調用其父類。

MergedDataBinderMapperaddMapper()方法添加的對象

public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
    addMapper(new com.example.designdemo.DataBinderMapperImpl());
  }
}

在DataBinding中有兩·個DataBinderMapperImpl·類,一個是上面這個在androidx.databinding包下,繼承了MergedDataBinderMapper的,另一個是在com.example.databindingdemo應用包下,直接繼承DataBinderMapper。其實MergedDataBinderMapper也是繼承自DataBinderMapper

  • 調用MergedDataBinderMapper.getDataBinder()
    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
            int layoutId) {
        // mMappers集合中的數據就是來源於androidx.databinding.DataBinderMapperImpl
        // 的構造器中調用addMapper方法傳入的對象添加的
        // 所以這裏的mapper就是com.example.databindingdemo.DataBinderMapperImpl對象
        for(DataBinderMapper mapper : mMappers) {
            ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
            if (result != null) {
                return result;
            }
        }
        if (loadFeatures()) {
            return getDataBinder(bindingComponent, view, layoutId);
        }
        return null;
    }

我們可以看上面getDataBinder明顯少了tag的解析。實際執行的是build\generated\ap_generated_sources\debug\out中的DataBinderMapperImpl.getDataBinder
這裏要注意兩點,就是如果是佈局的頂層View,比如tag爲layout/activity_main_0,那麼就會new一個ActivityMainBindingImpl對象。

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = view.getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
        case  LAYOUT_ACTIVITYDATABINDING: {
          if ("layout/activity_data_binding_0".equals(tag)) {
            return new DataBindingActivityBingingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_data_binding is invalid. Received: " + tag);
        }
        case  LAYOUT_ACTIVITYJETPACK: {
          if ("layout/activity_jetpack_0".equals(tag)) {
            return new JetpackActivityBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_jetpack is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View[] views, int layoutId) {
    if(views == null || views.length == 0) {
      return null;
    }
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = views[0].getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
      }
    }
    return null;
  }

我們看JetpackActivityBindingImpl構造器的方法,進行一些View的綁定操作,將通過tag取出的View與JetpackActivityBindingImpl中對應的View屬性進行綁定。

    public JetpackActivityBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
    }
    private JetpackActivityBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0
            , (android.widget.Button) bindings[2]
            , (android.widget.Button) bindings[1]
            );
        this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
        this.mboundView0.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

在這裏,會調用了一個mapBindings方法,第三個參數是一個3,這個3的意思是activity_main.xml佈局文件中有3個節點。

ViewDataBinding.mapBinding()這裏的主要工作,就是將佈局中的View保存在對應的bindings數組中,然後取出這個數組中的數據賦值給ActivityMainBindingImpl中的View。

    //mapBindings就會返回一個Object[] bindings數組
    private static void mapBindings(DataBindingComponent bindingComponent, View view,
                                    Object[] bindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds,
                                    boolean isRoot) {
        final int indexInIncludes;
        // 判斷View是否已經存在綁定,如果已經綁定,則直接return
        final ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding != null) {
            return;
        }
        // 獲取View的tag標籤
        Object objTag = view.getTag();
        final String tag = (objTag instanceof String) ? (String) objTag : null;
        boolean isBound = false;
        // 如果tag是根佈局,並且是以layout開頭的tag
        if (isRoot && tag != null && tag.startsWith("layout")) {
            final int underscoreIndex = tag.lastIndexOf('_');
            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
                final int index = parseTagInt(tag, underscoreIndex + 1);
                // 將根佈局標籤對應的View放在bindings數組中
                if (bindings[index] == null) {
                    bindings[index] = view;
                }
                indexInIncludes = includes == null ? -1 : index;
                isBound = true;
            } else {
                indexInIncludes = -1;
            }
        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
            if (bindings[tagIndex] == null) {
                bindings[tagIndex] = view;
            }
            isBound = true;
            indexInIncludes = includes == null ? -1 : tagIndex;
        } else {
            // Not a bound view
            indexInIncludes = -1;
        }
        if (!isBound) {
            final int id = view.getId();
            if (id > 0) {
                int index;
                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
                        bindings[index] == null) {
                    bindings[index] = view;
                }
            }
        }

        if (view instanceof ViewGroup) {
            final ViewGroup viewGroup = (ViewGroup) view;
            final int count = viewGroup.getChildCount();
            int minInclude = 0;
            for (int i = 0; i < count; i++) {
                final View child = viewGroup.getChildAt(i);
                boolean isInclude = false;
                if (indexInIncludes >= 0 && child.getTag() instanceof String) {
                    String childTag = (String) child.getTag();
                    if (childTag.endsWith("_0") &&
                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
                        // This *could* be an include. Test against the expected includes.
                        int includeIndex = findIncludeIndex(childTag, minInclude,
                                includes, indexInIncludes);
                        if (includeIndex >= 0) {
                            isInclude = true;
                            minInclude = includeIndex + 1;
                            final int index = includes.indexes[indexInIncludes][includeIndex];
                            final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                            int lastMatchingIndex = findLastMatching(viewGroup, i);
                            if (lastMatchingIndex == i) {
                                bindings[index] = DataBindingUtil.bind(bindingComponent, child,
                                        layoutId);
                            } else {
                                final int includeCount =  lastMatchingIndex - i + 1;
                                final View[] included = new View[includeCount];
                                for (int j = 0; j < includeCount; j++) {
                                    included[j] = viewGroup.getChildAt(i + j);
                                }
                                bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                        layoutId);
                                i += includeCount - 1;
                            }
                        }
                    }
                }
                if (!isInclude) {
                    mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                }
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章