Михаил Анохин "data binding 2.0"
TRANSCRIPT
Data Binding
● представлен на Google I/O 2015
● официальная библиотека от Google
● генерирует биндинг во время компиляции
● позволяет реализовать двусторонний биндинг
● доступна стабильная версия 2.1
2
public class FindViewByIdFragment extends Fragment { private TextView firstName; private TextView lastName;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(
R.layout.fragment_simple_as_is, container, false); firstName = (TextView) view.findViewById(R.id.first_name); lastName = (TextView) view.findViewById(R.id.last_name); return view; }
@Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); User user = User.getDefault(); firstName.setText(user.firstName); lastName.setText(user.lastName); }
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<TextView android:id="@+id/first_name" android:layout_width="match_parent" android:layout_height="wrap_content" />
<TextView android:id="@+id/last_name" android:layout_width="match_parent" android:layout_height="wrap_content" />
</LinearLayout>
findViewById
3
public class ButterKnifeFragment extends Fragment { @Bind(R.id.first_name) TextView firstName; @Bind(R.id.last_name) TextView lastName;
@Override public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(
R.layout.fragment_simple_as_is, container, false); ButterKnife.bind(this, view); return view; }
@Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); User user = User.getDefault(); firstName.setText(user.firstName); lastName.setText(user.lastName); }
@Override public void onDestroyView() { super.onDestroyView(); ButterKnife.unbind(this); }
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<TextView android:id="@+id/first_name" android:layout_width="match_parent" android:layout_height="wrap_content" />
<TextView android:id="@+id/last_name" android:layout_width="match_parent" android:layout_height="wrap_content" />
</LinearLayout>
ButterKnife
4
Binding (Model)<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data> <variable name="user" type="com.anokmik.databinding.model.User" /> </data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{user.firstName}"/>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
public class BindingModelFragment extends Fragment {
@Override public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(
R.layout.fragment_binding_model, container, false); FragmentBindingModelBinding binding
= FragmentBindingModelBinding.bind(view); binding.setUser(User.getDefault()); return view; }
}
5
Binding (Ids)
public class BindingIdsFragment extends Fragment { private TextView firstName; private TextView lastName;
@Override public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(
R.layout.fragment_binding_ids, container, false); FragmentBindingIdsBinding binding
= FragmentBindingIdsBinding.bind(view); firstName = binding.firstName; lastName = binding.lastName; return view; }
@Override public void onViewCreated(View view,
Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); User user = User.getDefault(); firstName.setText(user.firstName); lastName.setText(user.lastName); }
}
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<TextView android:id="@+id/first_name" android:layout_width="match_parent" android:layout_height="wrap_content"/>
<TextView android:id="@+id/last_name" android:layout_width="match_parent" android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
6
Data Model
public class User { private final String firstName; private final String lastName;
public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; } }
public class User { public final String firstName; public final String lastName;
public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }
}
7
Configuration
android {
...
buildToolsVersion "23.0.2"
...
dataBinding {enabled = true
}
...
}
buildscript {
repositories { jcenter() }
dependencies { classpath 'com.android.tools.build:gradle:2.1.0' }
}
8
Compile-time Generation
View view = inflater.inflate(R.layout.fragment_main, container, false);FragmentMainBinding binding = FragmentMainBinding.bind(view);
<data class="FragmentMainBinding"> ...</data>
<data class=".FragmentMainBinding"> ...</data>
<data class="com.example.FragmentMainBinding"> ...</data>
9
Generated classes location: /build/intermediates/classes/{$buildType}/{$applicationId}/databinding
Variables and Imports
public abstract User getUser();public abstract void setUser(User user);public abstract Drawable getImage();public abstract void setImage(Drawable image);public abstract String getText();public abstract void setText(String text);
<data> <import type="android.graphics.drawable.Drawable" /> <import type="com.anokmik.databinding.model.User" /> <variable name="user" type="User" /> <variable name="image" type="Drawable" /> <variable name="text" type="String" /></data>
10
Variables and Imports
<data> <import type="android.util.SparseArray"/> <import type="java.util.Map"/> <import type="java.util.List"/> <variable name="array" type="String[]" /> <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>
<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{titles[0]}" />
<data> <import type="android.app.Fragment"/> <import type="android.support.v4.app.Fragment" alias="SupportFragment"/></data>
11
Data Objects
public class NotifyGreeting extends BaseObservable {
private String name;
@Bindable public String getName() { return name; }
public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); }
}
public class ObservableGreeting {
public ObservableString name = new ObservableString();
}
12
Include and Merge
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto">
<data> <variable name="user" type="com.example.User" /> </data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<include layout="@layout/name" bind:user="@{user}" /> </LinearLayout> </layout>
<layout xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.example.User"/> </data> <merge> <include layout="@layout/name" bind:user="@{user}"/> </merge> </layout>
14
Expressions
● mathematical +, -, /, *, %● string concatenation +● logical &&, ||● binary &, |, ^● unary +, -, !, ~● shift >>, >>>, <<● comparison ==, >, <, >=, <=● instanceof
● grouping ()● literals - character, String, numeric, null● cast● method calls● field access● array access []● ternary operator ?:
android:enabled="@{communicator.isLoginValid & communicator.isPasswordValid}"
15
Null
android:text="@{user.firstName ?? user.lastName}"
android:text="@{user.firstName != null ? user.firstName : user.lastName}"
android:visibility="@{communicator.greeting.name.length > 0 ? View.VISIBLE : View.GONE}"
17
Setters and Binding Adapters
bind:layoutManager="@{communicator.layoutManager}"
18
android:text="@{user.firstName}"
@BindingAdapter("android:text")public static void setText(TextView view, CharSequence text) { ...}
android:onTextChanged="@{communicator.onLoginTextChanged}"
@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged", "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
public static void setTextWatcher(TextView view, final BeforeTextChanged before, final OnTextChanged on, final AfterTextChanged after, final InverseBindingListener textAttrChanged) {...
}
bind:adapter="@{communicator.adapter}"
Data Binding Component
19
public class MainDataBindingComponent {
@BindingAdapter(value = {"defaultColor", "pressedColor"}) public void setButtonStateListBackground(Button button, int defaultColor, int pressedColor) { button.setBackground(getButtonStateListDrawable(defaultColor, pressedColor)); }
...
} public class DataBindingComponentProvider implements DataBindingComponent {
@Override public MainDataBindingComponent getMainDataBindingComponent() { return new MainDataBindingComponent(); }
...
}
DataBindingUtil.setDefaultComponent(new DataBindingComponentProvider());
DataBindingUtil.setContentView(this, R.layout.activity_main, new DataBindingComponentProvider());
DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false, new DataBindingComponentProvider());
Binding Methods
20
@BindingMethods({ @BindingMethod(type = View.class, attribute = "android:onDrag", method = "setOnDragListener"), @BindingMethod(type = View.class, attribute = "android:onClick", method = "setOnClickListener"), @BindingMethod(type = View.class, attribute = "android:onTouch", method = "setOnTouchListener"),})public class ViewBindingAdapter {
...}
@BindingMethods({ @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"), @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType")})public class TextViewBindingAdapter {
...}
Binding Converters
public class Converters {
@BindingConversion public static String convertObservableToString(ObservableString observableString) { return observableString.get(); }
}
21
Two Way Binding
22
android:text="@={communicator.editTextValue}"
AdapterView android:selectedItemPosition
CalendarView android:date
CompoundButton android:checked
DatePicker android:year / android:month / android:day
NumberPicker android:value
RadioGroup android:checkedButton
RatingBar android:rating
SeekBar android:progress
TabHost android:currentTab
TextView android:text
TimePicker android:hour / android:minute
app:color="@={communicator.color}"
Two Way Binding
23
@InverseBindingMethods({ @InverseBindingMethod(type = MaterialDesignPrimaryPaletteView.class, attribute = "color")})public class MaterialDesignPrimaryPaletteView extends GridLayout implements View.OnClickListener {
private OnColorChangeListener listener;
private int color;
...
public int getColor() { return color; }
public void setColor(int color) { this.color = color; }
public void setOnColorChangeListener(OnColorChangeListener listener) { this.listener = listener; }
public interface OnColorChangeListener {
void onColorChange(MaterialDesignPrimaryPaletteView view, int color);
}
}
Two Way Binding
24
public class TwoWayDataBindingComponent {
@BindingAdapter(value = {"onColorChange", "colorAttrChanged"}, requireAll = false) public void setColorListener(MaterialDesignPrimaryPaletteView view,
final OnColorChangeListener listener, final InverseBindingListener colorChange) { if (colorChange == null) { view.setOnColorChangeListener(listener); } else { view.setOnColorChangeListener(new OnColorChangeListener() { @Override public void onColorChange(MaterialDesignPrimaryPaletteView view, int color) { if (listener != null) { listener.onColorChange(view, color); } colorChange.onChange(); } }); } }
@BindingAdapter("color") public void setColor(MaterialDesignPrimaryPaletteView view, int color) { if (color != view.getColor()) { view.setColor(color); } }
}
Lambda Expressions
25
android:onClick="@{() -> communicator.showToast()}"
android:onClick="@{() -> communicator.switchState()}"
android:onClick="@{(v) -> communicator.replaceFragment(v.id)}"
Binding Executor
26
private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;
...
protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
... if (USE_CHOREOGRAPHER) { mChoreographer = Choreographer.getInstance(); mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { mRebindRunnable.run(); } }; } else { mFrameCallback = null; mUIThreadHandler = new Handler(Looper.myLooper()); }}
Binding Execution
27
protected void requestRebind() { ... if (USE_CHOREOGRAPHER) { mChoreographer.postFrameCallback(mFrameCallback); } else { mUIThreadHandler.post(mRebindRunnable); }}
private final Runnable mRebindRunnable = new Runnable() { @Override public void run() { ... executePendingBindings(); }};
public void executePendingBindings() { ... if (!mRebindHalted) { executeBindings(); ... } ...}
Generated Binding
28
public class FragmentBindingModelBinding extends android.databinding.ViewDataBinding {
... @Override protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } java.lang.String firstNameUser = null; java.lang.String lastNameUser = null; com.anokmik.databinding.model.User user = mUser; if ((dirtyFlags & 0x3L) != 0) { user = user; if (user != null) { firstNameUser = user.firstName; lastNameUser = user.lastName; } } if ((dirtyFlags & 0x3L) != 0) { this.mboundView1.setText(firstNameUser); this.mboundView2.setText(lastNameUser); } } ...}
References
● http://developer.android.com/tools/data-binding/guide.html
● https://realm.io/news/data-binding-android-boyar-mount/
● https://speakerdeck.com/rciovati/binding-data-with-android-databinding
● https://medium.com/@fabioCollini/android-data-binding-f9f9d3afc761
● https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/
● https://www.youtube.com/watch?v=ssayKH0tudk
● https://www.youtube.com/watch?v=WdUbXWztKNY
● https://www.youtube.com/watch?v=DAmMN7m3wLU
29
Thanks for your attention!
source: https://github.com/anokmik/data-binding
mail: [email protected]
skype: anokmik