Nowy rok zaczniemy małą dygresją dotyczącą data bindingu w Android. Można powiedzieć, że powstało coś próbujące wzorować się na kompilowanym data bindingu w XAML. W porównaniu do tego, co było (zupełny brak bindingu) znaczący postęp, jednak w porównaniu do bindingu znanego z XAML (czy choćby Angulara 1 czy 2) wciąż bardziej złożony w użyciu… W każdym razie cieszy wchodzenie podejścia MVVM czy wzorca obserwatora w miejsce mediatora.
RoboBinding – framework MVVM dla Android
http://robobinding.github.io/RoboBinding/about.html
Butter Knife - biblioteka view “injection” dla Android
http://jakewharton.github.io/butterknife/
Android Annotations
Data Binding (Google) – generacja kodu (analiza pliku z layoutem, stworzenie klasy bindingu, stworzenie metod bindingu w pliku klasy), gradle plugin
wsparcie w Android Studio:
1.3 + (141.2117773) [Preferences –> Appearance & Behavior –> System Settings –> Updates]
prosty data binding
Android Plugin for Gradle 1.3.0 +
Data Binding Plugin 1.0-rc1 +
https://developer.android.com/tools/data-binding/guide.html#build_environment
Android SDK
Android Support Repository
Android Support Library
[Preferences –> Apperance & Behavior –> System Settings –> Android SDK]
projekt:
build.gradle
dependencies
classpath ‘com.android.tools.build:gradle:1.3.0’
classpath ‘com.android.databinding.dataBinder:1.0-rc1’
app/build.gradle
dodajemy apply plugin: ‘com.android.databinding’
w dependencies dodajemy apt ‘com.android.databinding:compiler:1.0-rc1’
<layout>
<data>
<variable
name=”dataSource”
type=”com.xxx.yyy.DataSource”/>
</data>
<LinearLayout …>
<TextView
…
android:text=”@{dataSource.message}”/> // public String getMessage(); public String message();
// public String message; inaczej błąd kompilacji
</LinearLayout>
</layout>
MainActivity (extends AppCompatActivity) onCreate
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); //zamiast setContentView
//ActivityMainBinding na podstawie activity_main.xml
binding.setDataSource(dataSource);
view
View view = inflater.inflate(R.layout.fragment_data, root, false);
FragmentDataBinding binding = DataBindingUtil.bind(view); //lub FragmentDataBinding.bind(view);
FragmentDataBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_data, root, false);
FragmentDataBinding binding = FragmentDataBinding.inflate(inflater, root, false);
FragmentDataBinding binding = DataBindingUtil.getBinding(view);
bindowanie fragmentów
podobnie w layoucie
DataFragment extends Fragment, onCreateView:
FragmentDataBinding binding = FragmentDataBinding.inflate(inflater, container, false);
…
binding.setDataSource(dataSource); //nazwa metody od DataSource
return binding.getRoot();
bindowanie załączonego layoutu
<include layout=”@layout/include_view”
bind:dataSource=”@{dataSource}”/>
include_view.xml:
<layout>
<data>
<variable
name=”dataSource”
type=”xxx.yyy.DataSource”/>
</data>
… //binding TextView tak samo
</layout>
bindowanie załączonego własnego layoutu
w XML podobnie
View view = inflater.inflate(R.layout.data_view, root, false);
DataViewBinding binding = DataBindingUtil.bind(view); //DataViewBinding.bind(view);
DataView extends FrameLayout
onFinishInflate:
…
binding = DataViewBinding.bind(this);
onAttachedToWindow:
…
binding.setDataSource(dataSource);
<include layout=”@layout/data_view”/>
bindowanie powtarzanych elementów
RecyclerView
projekt: dependencies:
+ compile ‘com.android.support.recyclerview-v7:22.2.1’
aktywność:
<android.support.v7.widget.RecyclerView android:id=”@+id/list” …/>
item_view.xml – layout itemu, podobnie jak wcześniej
aktywność – klasa: onCreate
…
binding.list.setLayoutManager(new LinearLayoutManager(this));
binding.list.setAdapter(new DataSourceAdapter(getLayoutInflater()));
…
private class DataSourceAdapter extends RecyclerView.Adapter<ViewHolder> {
private LayoutInflater layoutInflater;
public DataSourceAdapter(LayoutInflater layoutInflater) {
this.layoutInflater = layoutInflater;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ItemViewBinding binding = ItemViewBinding.inflate(layoutInflater, parent, false);
return new ViewHolder(binding.getRoot());
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
…
ItemViewBinding binding = DataBindingUtil.getBinding(holder.itemView);
binding.setDataSource(dataSource);
}
@Override
public int getItemCount() {
return 0;
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
}
Możliwość własnej klasy (widoku)
Wiele layoutów
stategie bindingu:
Inflate and Bind
Late Bind
Binding Reference
<data>
<variable name=”name” type=”String” /> //do bindowania atrybutów w layoucie
pobieranie / ustawianie wartości
layoutBinding.getName();
layoutBinding.setName(String name);
domyślne wartości są ustawiane w runtime np. null dla name
<import type=”com.xxx.StringUtils” />
…
<TextView android:text=@{StringUtils.upper(name)}/>
<import type=”com.xxx.DataSource”/>
<import alias=”SqlDataSource” type=”javax.sql.DataSource”/>
<variable name=”data” type=”DataSource”/>
<variable name=”sql” type=”SqlDataSource”/>
<data class=”” /> //domyślny namespace {module}.databinding, możliwość zmiany nazwy generowanej klasy bindingu i docelowego pakietu
<data class=”com.ddd.fff.eee.MainActivityBinding”/>
Layout Expression Language
odczyt i ustawianie propercji
bez new i super
wyliczanie i konwersja wartości
formatowany string
obsługa kolekcji
pobieranie elementu kolekcji na podstawie klucza lub indeksu
warunkowa logika
w strings.xml w <resources> m.in formatowane stringi z % i kolekcja string-array
android:visibility=”@{item.isSpecial ? View.VISIBLE : View:INVISIBLE}”
android:text=”@{@stringArray/sizes[item.index]}”
android:text=”@{item.map[‘xxx’]}”/>
android:text=”@{@string/price(item.map[‘price’])}”
czasami referencje do zasobów w wyrażeniach bindingu różnią się od typowego użycia
http://developer.android.com/tools/data-binding/guide.html
Własny i dynamiczny binding
Custom Binding Adapter
Default Binding Adapter – np. nie cachuje obrazków
Custom Event Listeners
Custom Event Binding
bindowanie typu listenera w layoucie
android:onClick=”@{listeners}”
lub
binding.setListeners(listeners)
Dynamiczny binding
bindowany typ jest ustawiany w runtime
T binding = DataBindingUtil.inflate(…)
custom binding adapter
znajdowanie metody settera w kontrolce na podstawie nazwy atrybutu w bindingu (np. text –> setText, nie zawsze jest tak prosto, źródła Android 6.0 SDK, API 23)
własny adapter
@BindingAdapter(“android:src”)
public static void setImageUri(ImageView view, String imageUri) {
if (imageUri == null)
view.setImageURI((Uri)null);
else
view.setImageURI(Uri.parse(imageUri));
}
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
bindowanie numeru do propercji string, metoda z atrybutem BindingAdapter w aktywności
Event binding
auto
android:onClickListener=”@{listeners}”
public void setOnClickListener(View.OnClickListener l)
public interface OnClickListener {
void onClick(View v);
}
renamed
android:onClick=”@{listeners}”
@BindingMethods({
@BindingMethod(
type = View.class,
attributte =”android:onClick”
method=”setOnClickListener”),…})
public class ViewBindingAdapter { … }
custom
OnAttachStateChangeListener
android:onViewAttachedToWindow=”@{listeners}”
android:onViewDetachedFromWindow=”@{listeners}”
…
handlery w event bindingu
dynamiczny binding
type=”List<Person>” //List<Person>
typ elementu w adapterze dla item-u w RecycleView
@Override
public int getItemViewType(int position) { … }
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { …}
…
onBindViewHolder: binding.setVariable(BR.person, person); //(id, object)
Observables
activity
onCreate:
…
binding.setListeners(this);
public void onCheckChanged(CompoundButton view, boolean isChecked) {
binding.setIsOn(isChecked);
}
<data>
<variable name=”isOn” type=”boolean” />
<variable name=”listeners” type=”com.xxx.yyy.MainActivity”/>
…
<Switch …
android:onCheckedChanged=”@{listeners.onCheckChanged}”/>
możliwość MVVM
po każdej zmianie trzeba na nowo ustawić w bindingu viewModel, obsługa handlerów poza view modelem
można lepiej
public static class ViewModel extends BaseObservable {
@Bindable public final ObservableBoolean isOn = new ObservableBoolean();
…
}
nie trzeba wtedy na nowo ustawiać view modelu po każdej jego zmianie
viewModel.isOn.set(isChecked);
//od 1.0-rc2 +
Typy obserwowalne:
android.database
ContentObservable
android.view
ViewTreeObserver
rx.Observable
interfejs Observable
public interface Observable {
void addOnPropertyChangedCallback(OnPropertyChangedCallback callback);
void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback);
abstract class OnPropertyChangedCallback {
public abstract void onPropertyChanged(Observable sender, int propertyId);
}
}
klasa BaseObservable
public class BaseObservable implements Observable {
…
public synchronized void notifyChange() { … }
public void notifyPropertyChanged(int fieldId) { … }
…
}
prymitywne Observables
ObservableBoolean
ObservableByte
ObservableChar
ObservableDouble
ObservableFloat
ObservableInt
ObservableLong
ObservableShort
Observable<T> //nie implementuje Parcelable
ObservableParcelable<T extends Parcelable> // extends ObservableField<T>
ObservableList<> // konkretna implementacja: ObservableArrayList<>
callback ma kilka metod (onChanged, onItemRangeChanged, onItemRangeInserted, onItemRangeMoved, onItemRangeRemoved)
ListChangeRegistry
ObservableMap<> // –//-: ObservableArrayMap<>
callback: jedna metoda onMapChanged
MapChangeRegistry
własne obiekty obserwowalne: Observable, BaseObservable lub pola ObservableField
public class CustomObservable implements BaseObservable {
private boolean breaker;
@Bindable public boolean getBreaker() {return breaker; }
public void setBreaker(boolean breaker) {
if (this.breaker != breaker) {
this.breaker = breaker;
notifyPropertyChanged(BR.breaker);
}
}
}
adaptery bindingu w osobnej klasie lub w aktywności lub view modelu
Brak komentarzy:
Prześlij komentarz