⬆️ ⬇️

Android Data Binding in RecyclerView





On Google IO 2015 announced a new library Data Binding Library. Its main task is to render the interaction between the model and the View into xml-files. It greatly simplifies writing code and eliminates the need to use the methods findByViewId (), adding references to the view-elements inside the Activity / Fragment. It also allows you to use custom attributes, binding them to static methods. Since the article is just enough for Data Binding, but for its use in the RecycleView just a little, let's fill this gap.



Customization


To get started, go to the file build.gradle, which lies in the root directory of the project. In the block dependencies expose:



buildscript { repositories { jcenter() } dependencies { classpath "com.android.tools.build:gradle:1.3.0" classpath "com.android.databinding:dataBinder:1.0-rc1" } } allprojects { repositories { jcenter() } } 


')

Next, connect the Data Binding plugin to the project. To do this, add a line with the plugin to build.gradle. We also check that compileSdkVersion is 23.



 apply plugin: 'com.android.application' apply plugin: 'com.android.databinding' 




Binding


Let's proceed to the creation of the xml-file. As usual, it is created in the res / layoyt package. We use layout as the root tag. Android Studio may highlight it in red or offer to set the width and height, but we ignore it.



 <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> </data> <!--    layout --> </layout> 




To create a Binder class, which will bind the model to the view, you need to bind xml to the model. To do this, inside the tag specify the name and path to our model. As an example, a list of movies will be displayed.



 public class Movie { public boolean isWatched; public String image; public String description; public String title; public Movie(boolean isWatched, String image, String description, String title) { this.isWatched = isWatched; this.image = image; this.description = description; this.title = title; } } 




 <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="movie" type="com.example.databinding.Movie" /> </data> <!--    layout --> </layout> 




It remains to add your layout and attach a model to it. Let each movie have a picture, a title and a short description. To indicate that the field will be read from the model, use “@ {* which field from the model to use *}”.



 <?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"> <data> <variable name="movie" type="com.example.databinding.Movie" /> </data> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:layout_margin="8dp"> <RelativeLayout android:id="@+id/relativeLayout" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/imageView" ... app:imageUrl="@{movie.image}"/> <TextView android:id="@+id/textView" ... android:text="@{movie.title}" android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView android:id="@+id/textView2" ... android:text="@{movie.description}" android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </android.support.v7.widget.CardView> </layout> 




With android: text = "@ {movie.title}" and android: text = "@ {movie.description}" everything is clear - the corresponding field will be shown as text, but what about the app: imageUrl = "@ movie .image} "? This is where the real Data Binding magic begins. You can add as many custom attributes as you like and not even write them in atts.xml, and the @BindingAdapter () annotation will help you process them. The following will show how to handle such annotations.



Let's go to the adapter. Let's write a simple RecyclerView.Adapter. Let's start with the ViewHolder. What he looked like before:



 public static class MovieItemViewHolder extends RecyclerView.ViewHolder { private TextView title, description; private ImageView image; public ViewHolder(View v) { super(v); title = (TextView) v.findViewById(R.id.textView); description = (TextView) v.findViewById(R.id.textView2); image = (ImageView) v.findViewById(R.id.imageView); } } 




What he looked like after the Butter Knife:



 public static class MovieItemViewHolder extends RecyclerView.ViewHolder { @Bind(R.id.textView) TextView title; @Bind(R.id.textView2) TextView description; @Bind(R.id.imageView) ImageView image; public ViewHolder(View v) { super(v); ButterKnife.bind(v); } } 




How it looks after databinding:



 public class MovieItemViewHolder extends RecyclerView.ViewHolder { MovieItemBinding binding; public MovieItemViewHolder(View v) { super(v); binding = DataBindingUtil.bind(v); } } 




Next, we are interested in two main adapter methods: onCreateViewHolder and onBindViewHolder. MovieItemBinding will be engaged in creation and bindigom. It is generated by the name xml, which we wrote above. In this case, the xml file was called movie_item.xml.



 @Override public MovieItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); MovieItemBinding binding = MovieItemBinding.inflate(inflater, parent, false); return new MovieItemViewHolder(binding.getRoot()); } 




We now turn to onBindViewHolder, as it looked before:



 @Override public void onBindViewHolder(MovieItemViewHolder holder, int position) { Movie movie = Movie.ITEMS[position]; holder.title.setText(movie.title); holder.description.setText(movie.description); Picasso.with(holder.image.getContext()).load(movie.image).into(holder.image); } 




How it looks now:



 @Override public void onBindViewHolder(MovieItemViewHolder holder, int position) { Movie movie = Movie.ITEMS[position]; holder.binding.setMovie(movie); } 




But this is not all, what about the custom app: imageUrl = "@ {movie.image}"? .. Again, everything is simple: inside the adapter we make a static method annotated with @BindingAdapter. Inside the annotation we pass our attribute. As a result, we get



 @BindingAdapter("bind:imageUrl") public static void loadImage(ImageView imageView, String v) { Picasso.with(imageView.getContext()).load(v).into(imageView); } 




ImageView will come to the input and what will transfer the model as an image. Now everything will work.



The rest of the utility


In the Movie model, the variable wasWatched. Suppose we want the new and watched movies to have different handlers per click. With DataBinding, this is now easier than ever. Let's write a click handler for the movie.



 public interface MovieClickHandler{ void onNewClick(View view); void onWatchedClick(View view); } 




Add it to the xml file in the data tag.



 ... <data> ... <variable name="click" type="com.example.databinding.MovieClickHandler" /> </data> ... <ImageView ... android:onClick="@{movie.isWatched ? click.onWatchedClick : click.onNewClick}"/> ... 




Now on the adapter's onBindViewHolder method you can see our tweeter. As in the case with a binder, the method name is generated by the appropriate name of the variable in the xml file.



 public void onBindViewHolder(MovieItemViewHolder holder, int position) { Movie movie = Movie.ITEMS[position]; holder.binding.setMovie(movie); holder.binding.setClick(new MovieClickHandler() { @Override public void onWatchedClick(View view) { } @Override public void onOldClick(View view) { } }); } 




Let on loading the picture at the watched films will be black-and-white. To convert a picture, add a new attribute.



 <ImageView ... app:filter='@{movie.isWatched ? "grey" : null}' .../> 




In the adapter through @BindingAdapter we implement processing



 @BindingAdapter("bind:filter") public static void applyFilter(ImageView imageView, String v) { imageView.setColorFilter(null); if("grey".equals(v)){ ColorMatrix matrix = new ColorMatrix(); matrix.setSaturation(0); ColorMatrixColorFilter cf = new ColorMatrixColorFilter(matrix); imageView.setColorFilter(cf); } } 




It is also very convenient to use stub values ​​if one of the fields is empty.



 <TextView ... android:text='@{movie.title ?? "unknown"}' ... /> 




It is also worth noting that the MovieItemBinding contains links to all the views that have an ID in the xml file.







Total


The library greatly simplifies working with RecycleView, the amount of code when writing is now reduced by several times, with no if / else for callbacks and data. With JavaRX, you can further simplify data updates, while the truth is that it works only in one direction: when you change data, the UI is updated, but not vice versa.



Useful links :



Test project.

Official documentation.

Quick start Data Binding in Android.

Source: https://habr.com/ru/post/267735/



All Articles