본문
180413(금) - Data Binding Library
Data Binding Library
- data binding libary를 사용해서 declarative layouts를 사용
- application logic 및 layout binding에서 필요한 glue code를 minimize
- support library이므로 Android 2.1 (API level 7+) 이상에서 사용 가능
- Android Plugin for Gradle 1.5.0-alpha1 or higher 필요
Build Environment
- Support library에 포함
- data binding을 사용하는 library를 사용하고 있어도 data binding enabled 필요
- Android studio 1.3 이상이면 databinding 지원
android {
....
dataBinding {
enabled = true
}
}
Data Binding Compiler V2
- Android Gradle Plugin 3.1.0 Canary 6 이상이면 new compiler를 optional하게 사용 가능
- gradle.properties에 다음과 같이 include
android.databinding.enableV2=true
- ViewBinding class는 java compiler 이전에 Android Gradle Plugin에 의해서 generated 된다.
-> java compiler가 data binding과 관련없는 이유로 fail되면, 너무많은 false positive error를 getting하는것을 피할 수 있다.
- V1에서 binding classes는 app이 compiled될 때 (share generated code and access BR and R files) regenerated된다.
-> V2 에서는 generated binding classes 뿐만 아니라 multi-module projects의 data binding performance를 크게 향상시키는 mapper information을 유지한다.
- v1 cannot used by v2 or vice versa
- V2는 거의 사용하지 않는 functionality를 삭제
1. V1에서 app은 dependencies를 override할 수 있는 binding adapter를 제공
-> V2에서는 only effect in your own module / application and dependencies
2. 이전에는 2개 이상의 different resource configurations의 layout files에 동일한 id 이지만, 다른 class인 경우, Data Binding은 최상위 parent class를 찾았다.
-> V2는 configuration이 일치하지 않으면 항상 default view
3. V2에서 Data Binding은 binding mapper class를 생성하기 위해서 해당 package name을 사용하기 때문에 다른 module은 manifest 에서 same package name을 사용 할 수 없다.
Data Binding Layout Files
- Writing your first set of data binding expressions
- data-binding layout file은 기존 xml과 약간 다르다
- layout root tag + data element + view root element
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
- data describes안의 variable user는 layout 안에서 사용할 수 있는 property
<variable name="user" type="com.example.User"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
- data binding의 관점에서 이 두개의 classes는 같다.
- TextView의 android:text attribute에 사용된 @{user.firstNmae}은 위에서 firstName, 아래에선 getFirstName() method에 access한다.
- 해당 method가 있으면 firstName()도 가능
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 this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
- Binding Data
- Binding class는 layout file의 이름을 기반으로 Pascal case로 변환되며, suffixing으로 "Binding"이 붙는다.
- main_activity.xml -> MainAcitivtyBinding
- layout properties에서 layout's View까지의 모든 binding을 보유하고, binding expressions에 values를 assign하는 방법을 알고 있다.
- 가장 쉬운 방법은 inflating하면서 binding하는 방법
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
-> KakaoT에서 BaseActivity()를 상속받는 모든 activity는 해당 방법 사용 불가.
-> val binding = MainActivityBinding.bind(...) 이런식으로 사용해야 한다.
- ListView or RecyclerView를 사용하는 경우 아래와 같이 사용
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
- Event Handling
- onClick과 같은 event handling expression도 allows
- event attribute name는 few exceptions를 제외하고 listener method의 이름에 의해서 governed된다.
- ex) View.OnLongClickListener has onLongClick() -> android:onLongClick
- two way to handle event
<요약>
- expressions에서 listener method의 signature를 conform하는 method를 reference할 수 있다.
- expression이 method reference로 판단되면, Data Binding은 method reference and owner object를 listener에 wraps하고, 해당 listener를 target view에 sets한다.
- expressions가 null이면 Data Binding은 listener를 만들지 않고 null listener를 sets한다.
<상세>
- android:onClick를 Activity의 method에 직접 bound할 수 있는 방식과 비슷하게, event를 handler method에 직접 bound할 수 있다.
- View#onClick attribute와 비교해 볼때 한가지 장점은 expression이 compile time에 processed
- 그래서 method not exist or signature not correct이면 compile time에 error가 발생
- listener bindings와 주요한 차이점은 event가 triggered될 때가 아니라 data binding될 때 실제 listener implementation is create
- event가 happens되었을 때 expression evaluate되기를 원한다면 listener binding을 써야 한다.
- handler에 event를 assign하려면 call할 method name으로 normal binding expression을 사용
- expression의 signature는 listener object method signature와 정확히 일치해야 한다.
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
<요약>
- event happens일 때 evaluated되는 lambda expressions
- Data Binding은 항상 listener를 만들고 view에 sets
- event is dispatched, listener evaluates the lambda expression
<상세>
- evnet happens시 run되는 binding expressions
- method reference와 비슷하지만 arbitrary data binding expressions를 실행 가능
- Android Gradle Plugin for Gradle version 2.0 and later에서 사용 가능
- method reference에서 method parameter의 개수는 event listener의 parameters 개수와 must match해야 한다.
- listener binding에서는 return value만 listner의 expected return value와 일치하면 된다. (void가 예상되는 경우는 제외)
public class Presenter {
public void onSaveClick(Task task){}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
- listener는 expressions의 root elements로 allowed 되는 lambda expressions로 표현된다.
- expression에 callback이 사용되면 data binding은 자동으로 필요한 listener create and event registers
- view가 event를 발생시키면 data binding은 expression을 evaluates
- regular binding expressions와 마찬가지로 listener expressions가 evaluated되는 동안 null and thread safety 가 유지된다.
- listener parameters
1. either ignore all parameters to method
2. all name을 지정 가능
expression에서 name 지정 가능
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
public class Presenter {
public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
public class Presenter {
public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
- if expression이 null object때문에 evaluate 할 수 없으면 data binding은 Java value type을 return
- reference type -> null
- int -> 0
- boolean -> false
- ternary predicate를 사용해야 한다면 void symbol을 사용 할 수 있다.
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
Class | Listener Setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
Layout Details
- import
- data element 내부에서 zero or more import elements used
- allow easy reference java classes
- Android Studio IDE 에선 아직 imported variables에 대해서 autocomplete가 되지 않는다.
<data>
<import type="android.view.View"/>
</data>
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- Variables
- data element에서 any number of variable elements를 사용 가능
- 각 variable elements는 layout file에서 binding expressions에 설정할 수 있는 property를 describes
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
- variable types는 compile time에 inspected되므로 android.databinding.Observable or obsavable collection인 경우 해당 type에 reflected되어야 한다.
- variavle이 Observable* interface 를 implement하지 않는 base class or interface인 경우 not be observed!
- various configurations (landscape or portrait)인 different layout files이 있는 경우 variables will be combined
- 이런 layout files 간에는 must not be conflicting variable definitions
- generated binding class는 각 described variables에 대한 setter and getter가 있다.
- variables는 setter가 called될 때까지
- null for reference types
- 0 for int
- false for boolean
- etc...
- needed에 따라서 binding expressions에 사용하기 위해 context라는 special variable이 생긴다.
- context value는 root View's getContext()
- context variable는 explicit variable declarations에 의해서 overridden된다.
- Custom Binding Class Names
- 생성된 Binding class는 module package under의 databinding package에 배치된다.
- ex) contact_item.xml -> ContactItemBinding
- ex) com.example.my.app under com.example.my.app.databinding
- data element의 attribute를 조정하여 binding class를 renamed or different package에 배치 가능
<data class="ContactItem">
...
</data>
<data class=".ContactItem"> // module package에 directly created
...
</data>
<data class="com.example.ContactItem"> // full package가 provided되면 any package can be used.
...
</data>
- include
- variables는 application namespace and attribute variable name을 사용해서 included layout으로 binding 가능하다.
- name and contact layout에는 user variable이 반드시 있어야 한다.
<?xml version="1.0" encoding="utf-8"?>
<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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
- data binding not support merge element의 include를 지원하지 않는다.
<?xml version="1.0" encoding="utf-8"?>
<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>
<merge>
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>
- Expression Language
- Common Features
- Java expression과 비슷
- 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:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
this
super
new
- Explicit generic invocation
- Null Coalescing Operator
- null coalescing operator는 null이 아닐경우 left operand, null일 경우 right operand
android:text="@{user.displayName ?? user.lastName}"
android:text="@{user.displayName != null ? user.displayName : user.lastName}" // Ternary operator사용
- class property expression references는 getter, fields and ObservableField에 대해서 동일한 format을 사용
android:text="@{user.lastName}"
- Avoiding NullPointerException
- generated data binding code는 automatically checks null and avoid null point exceptions
- ex) @{user.name} 에서 만약 user가 null이면 user.name은 default value(null) assigned
- ex) @{user.age} 에서 age가 int이면 default 는 0
- Collections
- common collections (array, lists, sparse lists, maps)는 [] operator로 access가능
<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]}"
- String Literals
- attribute에 single quotes (')를 사용하면 expression에서 double quotes (")를 사용하기 쉬워진다.
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
Type | Normal Reference | Expression Reference |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
Data objects
- POJO는 data binding에 사용될 수 있겠지만, POJO를 수정해도 UI update가 되는건 아니다.
- data binding의 real power는 object data가 변경될 때 이를 notify 하도록 ability하는것.
- three different data change notification mechanisms
- implementing android.databinding.Observable interface
- binding to attach single listener to bound object
- insten for change of all properties on that object
- listener를 add and remove하는 mechanism이 있지만 notifying은 developer가 결정
- listenr registration mechanism을 implement하기 위해 android.databinding.BaseObservable 이라는 base class가 있다.
- data class implementer는 properties가 언제 변경되는지 notifying해야하는 책임이 있다.
- 이 작업은 android.databinding.Bindable annotation을 getter에 assigning하고 setter에서 notifying하는 식으로 수행된다.
- android.databinding.Bindable annotaton은 compilation동안에 BR class file에 entry generates
- BR class file은 module package에서 생성된다.
- data class의 base class를 변경할 수 없는경우, android.databinding.Observable interface는 convenient android.databinding.PropertyChangeRegistry를 사용하여 listeners를 효율적으로 store and notify 하도록 implement 가능하다.
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
- 작업해야할 내용은 대부분 android.databinding.Observable class make이므로 save time or have few properties이라면 android.databinding.ObservableField and siblings를 사용
- android.databinding.ObservableBoolean
- android.databinding.ObservableByte
- android.databinding.ObservableChar
- android.databinding.ObservableShort
- android.databinding.ObservableInt
- android.databinding.ObservableLong
- android.databinding.ObservableFloat
- android.databinding.ObservableDouble
- android.databinding.ObservableParcelable.ObservableFields
- 위 siblings들은 single field를 가지고 있는 self-contained observable objects 이다.
- primitive versions는 access작업 중 boxing and unboxing을 avoid.
- 사용하려면 data class에 public final field를 만든다.
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
user.firstName.set("Google");
int age = user.age.get();
- some applications use more dynamic structures to hold data
- observable collection은 이러한 data objects에 대한 access key allow
- android.databinding.ObservableArrayMap은 string key reference type에 유용
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
android:text='@{user["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- android.databinding.ObservableArrayList는 integer key reference에 유용
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView
android:text='@{user[Fields.LAST_NAME]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Generated Binding
- generated binding class links layout variables with layout Views
- generated binding classes all extend android.databinding.ViewDataBinding
- Creating
- Views with expressions within the layout에 binding하기 전에 View hierarchy가 disturbed하지 않도록 binding should be created soon after inflation
- layout에 binding하는 몇가지 방법이 있다.
- inflate method는 View hierarchy inflate and binds all one step
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
- inflated와 bind 분리
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:id="@+id/firstName"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:id="@+id/lastName"/>
</LinearLayout>
</layout>
public final TextView firstName;
public final TextView lastName;
- Variables
- each variable은 accessor methods가 제공
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);
- ViewStubs
- ViewStub는 normal Views와는 약간 다르다.
- start off invisible
- visible or explicitly inflate하게되면 replace themselves in the layout by inflating another layout
- essentially disappears from View hierarchy이기 때문에 binding object View도 collection을 allow하기위해 사라져야 한다.
- View는 final이므로 android.databinding.ViewStubProxy object가 ViewStub대신에 사용되어 devleoper가 ViewStub에 access가능하며, ViewStub가 inflated되었을 때 inflated View hierarchy에 대한 access 권한도 제공
- inflating another layout일 때 새로운 layout을 위한 binding이 established되어야 한다.
- ViewStubProxy는 must listen to the ViewStub's ViewStub.OnInflateListener and 해당 time에 establish the binding
- only one can exist, ViewStubProxy는 developer call establishing binding 후에 set an OnInflateListener
- Advanced Binding
- Dynamic Variables
- specific binding class를 알수 없는 상황이 있다. (ex: RecyclerView.Adapter)
- it still must assign binding value during the onBindViewHolder(VH, int)
.
- BindingHolder는 android.databinding.ViewDataBinding을 return하는 getBinding method가 있다.
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
- collection이 아니라면 background thread에서 data model을 변경 가능하다.
- data binding은 evaluating to avoid any concurrency issues 동안에 each variable / field localized.
Attribute Setters
- bound value changes될 때마다 generated binding class는 binding expression이 있는 View에서 setter method를 call해야한다.
- data binding framework에는 value를 set하기 위해 call할 method를 customize 하는 방법이 있다.
- Automatic Setters
- attribute의 경우 data binding은 setAttribute method를 찾으려고 시도
- attribute의 namespace는 중요하지 않고 attribute name자체가 중요
- ex) TextView attribute인 android:text와 연결된 expression으로 setText(String)을 찾는다.
- expression이 int를 return하면 data binding은 setText(int)를 찾는다.
- casting if necessary, expression return correct type하도록 주의할것
- no attribute exists given name일때도 data binding이 work
- data binding을 사용하여 any setter attributes를 쉽게 create가능
- ex) DrawerLayout support에는 attribute는 없지만 많은 setters가 있다. automatic setter를 사용하여 이것들 중 하나를 사용 가능
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
- Renamed Setters
- some attributes는 don't match by name인 setter가 있다
- 이러한 method의 경우 android.databinding.BindingMethod를 통해서 attribute associated setter
- must associated class이여야 하고, each renamed method마다 android.databinding.BindingMethod annotation이 붙어야 한다.
- ex) android:tint attribute는 setTint가 아니라 setImageTintList(ColorStateList) 와 연관
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
- android framework attributes already been implemented이기 때문에 developers가 setter rename할 필요는 없을것으로 보인다.
- Custom Setters
- some attribute need custom binding logic
- ex) android:paddingLeft 관련 setter가 없다. 대신 setPadding(left, top, right, bottom)이 있다.
- android.databinding.BindingAdatper annotation이 있는 static binding adapter method는 attribute에 대한 setter가 called되는 방법을 customize할수 있도록 한다.
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
- Binding adapter는 other types customization에 useful
- ex) custom loader는 can be called off-thread to load image
- custom adapter는 conflict발생시 data binding default adapter override!
- multiple parameters receive adapters도 가능
- imageUrl and error가 ImageView에 사용되고, imageUrl is string and error is drawable인 경우 adapter가 called
- custom namespace are ignored during matching
- android namespace용 adapters write가능
@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>
- binding adapter method는 handler에서 optionally take old values
- old and new values take method는 attributes에 대한 all old values가 먼저 와야하며 그 다음에 new values followed
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
- event handler only used interface or abstract classes with one abstract method
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue);
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue);
}
}
}
- listener가 multiple methods를 가지고 있으면, must be split multiple listeners
- ex) View.OnAttachStateChangeListener -> onViewAttachedToWindow() and onViewDetachedFromWindow()
- attributes and handler를 구분하기 위해 two interfaces must create
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
- 하나의 listener를 변경하면 다른 listener에도 영향을 미치기 때문에, one for each attribute and for both 설정해야 하는 must have three different binding adapter가 있어야 한다.
- 아래 ex는 add and remove listener를 하기 때문에 약간 복잡하다.
- android.databinding.adapters.ListenerUtil class는 previous listeners가 Binding Adapter에서 removed될 수 있도록 keep tracking
- @TargetApi(VERSION_CODES.HONEYCOMB_MR1) annotation에 의해서 Honeycomb MR1 and new devices 이상에서만 동작해야함을 알고 있다.
@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
setListener(view, null, attached);
}
@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
setListener(view, detached, null);
}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
final OnViewAttachedToWindow attach) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
final OnAttachStateChangeListener newListener;
if (detach == null && attach == null) {
newListener = null;
} else {
newListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (attach != null) {
attach.onViewAttachedToWindow(v);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
if (detach != null) {
detach.onViewDetachedFromWindow(v);
}
}
};
}
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
newListener, R.id.onAttachStateChangeListener);
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener);
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener);
}
}
}
Converters
- Object Conversions
- binding expressions에서 Object returned, setter는 automatic, renamed, and custom setter중에서 선택된다.
- Object는 선택된 setter의 parameter type으로 casting 된다.
- ObservableMaps를 사용해서 data hold일 경우 편리하다.
- userMap은 return Object and Object will be setter setText(CharSequence) automatically cast parameter type
- parameter type에 confusion이 있을경우 need cast expression
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- Custom Conversions
- 때로는 specific types간에 automatic conversions 되어야 한다.
- background는 Drawable을 사용하지만, color is an integer.
- Drawable expected and integer returned, int should converted to ColorDrawable (int -> ColorDrawable)
- 이러한 conversion은 BindingConversion annotation이 있는 static method에서 수행된다.
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
- conversions는 setter level에서만 일어나므로, not allowed mix types
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Android Studio Support for Data Binding
- 여러가지 data binding code에 대해서 code editing features 지원
- Preview pane displays default values for data binding expressions
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=PLACEHOLDER}"/>
- 물론 tools도 사용 가능하다.
'Architecture > MVVM' 카테고리의 다른 글
180402(월) - Android Architecture Components (Saving UI States) (0) | 2018.04.02 |
---|---|
180402(월) - Android Architecture Components (ViewModel) (0) | 2018.04.02 |
180328(목) - Android Architecture Components (LiveData) (0) | 2018.03.29 |
180328(수) - Android Architecture Components (Handling Lifecycles) (0) | 2018.03.28 |
180326(화) - Android Architecture Components (Adding components to Project) (0) | 2018.03.27 |
댓글