Enlace de datos de Android en RecyclerView – Pantalla de perfil
En mi artículo anterior, aprendimos los conceptos básicos del enlace de datos. Hoy vamos a poner en práctica lo básico mediante la implementación de una pantalla de perfil con conceptos de enlace de datos. La pantalla de perfil tiene detalles de perfil en la parte superior y la siguiente sección tiene imágenes de publicaciones en formato de trama. La cuadrícula se crea utilizando un RecyclerView que implementa el enlace de datos en la clase del adaptador.
El uso de DataBinding en una clase de adaptador mantiene el código al mínimo, ya que muchas cosas se tienen en cuenta en el diseño en sí.
1. Requisito
Este ejemplo requiere un conocimiento básico del enlace de datos de Android. Comience con DataBinding leyendo el tutorial a continuación.
Leer: Android funciona con enlace de datos
2. Crea un nuevo proyecto
1. Crea un nuevo proyecto en Android Studio Archivo ⇒ nuevo proyecto y seleccione Actividad basica de plantillas.
2. Activar DataBiding en app / build.gradle. También agregue las dependencias RecyclerView y Glide y Sincronizar el proyecto.
android dataBinding enabled = true dependencies //... implementation 'com.github.bumptech.glide:glide:4.6.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1' implementation 'com.android.support:recyclerview-v7:27.1.0'
3. Agregar INTERNET Permiso en AndroidManifest.xml ya que las imágenes deben cargarse desde una URL.
<uses-permission android:name="android.permission.INTERNET" />
Cuarto. Descarga res.zip y agrégalo a tus proyectos res Carpeta. Estas carpetas de caracteres contienen el símbolo más requerido para FAB.
5. Agregue los siguientes recursos a cada uno string.xml, dimension.xml y Colors.xml
<resources> <string name="app_name">Data Binding</string> <string name="action_settings">Settings</string> <string name="toolbar_profile">Profile</string> <string name="posts">POSTS</string> <string name="followers">FOLLOWERS</string> <string name="following">FOLLOWING</string> </resources>
<resources> <dimen name="fab_margin">16dp</dimen> <dimen name="activity_margin">16dp</dimen> <dimen name="dimen_8dp">8dp</dimen> <dimen name="profile_image">100dp</dimen> <dimen name="fab_profile">30dp</dimen> <dimen name="profile_name">15dp</dimen> <dimen name="profile_about">13dp</dimen> <dimen name="profile_meta">24dp</dimen> <dimen name="profile_meta_label">10dp</dimen> </resources>
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#222222</color> <color name="colorPrimaryDark">#111111</color> <color name="colorAccent">#fecb2f</color> <color name="profile_meta">#333</color> </resources>
Sexto. Crea tres paquetes con el nombre modelo, Utils y vista. Una vez creado, mueva el Actividad principal a vista Paquete.
A continuación se muestra la estructura final del proyecto y los archivos requeridos.
Séptimo. Crear usuario Clase bajo modelo Paquete. Extienda la clase de para hacer que esta clase sea observable BaseObservable.
Ambos para demostración Observable y Campo observable se utilizan en la misma clase.
- Para nombres de variables, correo electrónico, profileImage y aproximadamente., @Bindable Se utiliza nota y notificarPropertyChanged se llama cuando se establecen nuevos datos
- variables Numero de publicaciones, Numero de seguidores, numberOfFollowing se declaran como Campos observables
- @BindingAdapter se usa para atar foto de perfil a Vista de imagen para cargar la imagen desde la URL usando la biblioteca Glide.
import android.databinding.BaseObservable; import android.databinding.Bindable; import android.databinding.BindingAdapter; import android.databinding.ObservableField; import android.widget.ImageView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import info.androidhive.databinding.BR; public class User extends BaseObservable String name; String email; String profileImage; String about; // profile meta fields are ObservableField, will update the UI // whenever a new value is set public ObservableField<Long> numberOfFollowers = new ObservableField<>(); public ObservableField<Long> numberOfPosts = new ObservableField<>(); public ObservableField<Long> numberOfFollowing = new ObservableField<>(); public User() @Bindable public String getName() return name; public void setName(String name) this.name = name; notifyPropertyChanged(BR.name); @Bindable public String getEmail() return email; public void setEmail(String email) this.email = email; notifyPropertyChanged(BR.email); @BindingAdapter("profileImage") public static void loadImage(ImageView view, String imageUrl) Glide.with(view.getContext()) .load(imageUrl) .apply(RequestOptions.circleCropTransform()) .into(view); // If you consider Picasso, follow the below // Picasso.with(view.getContext()).load(imageUrl).placeholder(R.drawable.placeholder).into(view); @Bindable public String getProfileImage() return profileImage; public void setProfileImage(String profileImage) this.profileImage = profileImage; notifyPropertyChanged(BR.profileImage); @Bindable public String getAbout() return about; public void setAbout(String about) this.about = about; notifyPropertyChanged(BR.about); public ObservableField<Long> getNumberOfFollowers() return numberOfFollowers; public ObservableField<Long> getNumberOfPosts() return numberOfPosts; public ObservableField<Long> getNumberOfFollowing() return numberOfFollowing;
Octavo. Crea otra clase llamada Post.java debajo modelo Paquete. Esta clase de modelo proporciona datos RecyclerView.
import android.databinding.BindingAdapter; import android.widget.ImageView; import com.bumptech.glide.Glide; public class Post String imageUrl; @BindingAdapter("imageUrl") public static void loadImage(ImageView view, String imageUrl) Glide.with(view.getContext()) .load(imageUrl) .into(view); public String getImageUrl() return imageUrl; public void setImageUrl(String imageUrl) this.imageUrl = imageUrl;
9. Debajo Utils Paquete, crea dos clases con el nombre BindingUtils.java y GridSpacingItemDecoration.java
- convertToSuffix () El método convierte un número en un formato legible por humanos. Por ejemplo, 5500L se convertirá como 5,5 km y 5050890L se convertirá como 5,1 metros.
- Vinculamos esta función a TextViews para mostrar las publicaciones, los seguidores y los seguidores en un formato legible por humanos.
package info.androidhive.databinding.utils; public class BindingUtils // https://stackoverflow.com/questions/9769554/how-to-convert-number-into-k-thousands-m-million-and-b-billion-suffix-in-jsp // Converts the number to K, M suffix // Ex: 5500 will be displayed as 5.5k public static String convertToSuffix(long count) if (count < 1000) return "" + count; int exp = (int) (Math.log(count) / Math.log(1000)); return String.format("%.1f%c", count / Math.pow(1000, exp), "kmgtpe".charAt(exp - 1));
GridSpacingItemDecoration Proporciona espacio entre los elementos de la cuadrícula de RecyclerView.
package info.androidhive.databinding.utils; import android.graphics.Rect; import android.support.v7.widget.RecyclerView; import android.view.View; public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration private int spanCount; private int spacing; private boolean includeEdge; public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) this.spanCount = spanCount; this.spacing = spacing; this.includeEdge = includeEdge; @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) int position = parent.getChildAdapterPosition(view); int column = position % spanCount; if (includeEdge) outRect.left = spacing - column * spacing / spanCount; outRect.right = (column + 1) * spacing / spanCount; if (position < spanCount) outRect.top = spacing; outRect.bottom = spacing; else outRect.left = column * spacing / spanCount; outRect.right = spacing - (column + 1) * spacing / spanCount; if (position >= spanCount) outRect.top = spacing;
2.1 Enlace de datos en RecyclerView
La vinculación de un diseño RecyclerView es similar a la vinculación normal, excepto que se han realizado pocos cambios en los métodos onCreateViewHolder y onBindViewHolder.
10. Crea un diseño llamado post_row_item.xml. Este diseño incluye un Vista de imagen para renderizar la imagen en RecyclerView.
- En este diseño, el enlace de datos se habilita manteniendo el elemento raíz como . La correo Modelo vinculado a este diseño con Etiqueta.
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:bind="http://schemas.android.com/apk/res/android"> <data> <variable name="post" type="info.androidhive.databinding.model.Post" /> </data> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/thumbnail" android:layout_width="0dp" android:layout_height="0dp" android:scaleType="centerCrop" bind:imageUrl="@post.imageUrl" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="H,1:1" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> </layout>
11. Crea una clase llamada PostsAdapter.java debajo vista Paquete.
- Cuál es el nombre del diseño post_row_item.xmlserá la clase de enlace generada PostRowItemBinding.
- en el onCreateViewHolder () Método, el diseño de post_row_item se infla con la ayuda de PostRowItemBinding Clase.
- holder.binding.setPost () une el correo Modelo para cada fila.
package info.androidhive.databinding.view; import android.databinding.DataBindingUtil; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.List; import info.androidhive.databinding.R; import info.androidhive.databinding.databinding.PostRowItemBinding; import info.androidhive.databinding.model.Post; public class PostsAdapter extends RecyclerView.Adapter<PostsAdapter.MyViewHolder> { private List<Post> postList; private LayoutInflater layoutInflater; private PostsAdapterListener listener; public class MyViewHolder extends RecyclerView.ViewHolder private final PostRowItemBinding binding; public MyViewHolder(final PostRowItemBinding itemBinding) super(itemBinding.getRoot()); this.binding = itemBinding; public PostsAdapter(List<Post> postList, PostsAdapterListener listener) this.postList = postList; this.listener = listener; @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext()); PostRowItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.post_row_item, parent, false); return new MyViewHolder(binding); @Override public void onBindViewHolder(MyViewHolder holder, final int position) holder.binding.setPost(postList.get(position)); holder.binding.thumbnail.setOnClickListener(new View.OnClickListener() @Override public void onClick(View v) if (listener != null) listener.onPostClicked(postList.get(position)); ); @Override public int getItemCount() return postList.size(); public interface PostsAdapterListener void onPostClicked(Post post); }
2.2 Creación de la pantalla de perfil
Ahora tenemos todos los archivos en su lugar. Comencemos con la construcción de la interfaz principal.
12º. Abra los archivos de diseño de actividad principal, es decir activity_main.xml y content_main.xml y activar el enlace de datos agregando , y Palabras clave.
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:bind="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="info.androidhive.databinding.model.User" /> </data> <android.support.design.widget.CoordinatorLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".view.MainActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:elevation="0dp" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include android:id="@+id/content" layout="@layout/content_main" bind:user="@user" /> </android.support.design.widget.CoordinatorLayout> </layout>
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:bind="http://schemas.android.com/apk/res/android"> <data> <import type="info.androidhive.databinding.utils.BindingUtils" /> <variable name="user" type="info.androidhive.databinding.model.User" /> <variable name="handlers" type="info.androidhive.databinding.view.MainActivity.MyClickHandlers" /> </data> <android.support.v4.widget.NestedScrollView 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" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:focusableInTouchMode="true" android:orientation="vertical" tools:context=".view.MainActivity" tools:showIn="@layout/activity_main"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorPrimary" android:orientation="vertical" android:paddingBottom="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin"> <RelativeLayout android:layout_width="@dimen/profile_image" android:layout_height="@dimen/profile_image" android:layout_gravity="center_horizontal"> <ImageView android:id="@+id/profile_image" android:layout_width="@dimen/profile_image" android:layout_height="@dimen/profile_image" android:layout_centerHorizontal="true" android:onLongClick="@handlers::onProfileImageLongPressed" bind:profileImage="@user.profileImage" /> <android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:onClick="@handlers::onProfileFabClicked" android:src="@drawable/ic_add_white_24dp" app:fabCustomSize="@dimen/fab_profile" /> </RelativeLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="@dimen/dimen_8dp" android:fontFamily="sans-serif" android:letterSpacing="0.1" android:text="@user.name" android:textColor="@android:color/white" android:textSize="@dimen/profile_name" android:textStyle="bold" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:fontFamily="sans-serif" android:letterSpacing="0.1" android:text="@user.about" android:textColor="@android:color/white" android:textSize="@dimen/profile_about" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/activity_margin" android:layout_marginTop="@dimen/fab_margin" android:orientation="horizontal" android:weightSum="3"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:onClick="@handlers::onPostsClicked" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="sans-serif-condensed" android:text="@BindingUtils.convertToSuffix(user.numberOfPosts)" android:textColor="@color/profile_meta" android:textSize="24dp" android:textStyle="normal" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/posts" android:textSize="@dimen/profile_meta_label" /> </LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:onClick="@handlers::onFollowersClicked" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="sans-serif-condensed" android:text="@BindingUtils.convertToSuffix(user.numberOfFollowers)" android:textColor="@color/profile_meta" android:textSize="24dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/followers" android:textSize="@dimen/profile_meta_label" /> </LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:onClick="@handlers::onFollowingClicked" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="sans-serif-condensed" android:text="@BindingUtils.convertToSuffix(user.numberOfFollowing)" android:textColor="@color/profile_meta" android:textSize="@dimen/profile_meta" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/following" android:textSize="@dimen/profile_meta_label" /> </LinearLayout> </LinearLayout> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> </android.support.v4.widget.NestedScrollView> </layout>
13. Finalmente abierto MainActivity.java y realice los siguientes cambios.
- ¿Cuál es el nombre del diseño de la actividad principal? activity_mainserá la clase de enlace generada ActivityMainBinding.
- renderProfile () muestra la información del usuario como nombre, descripción, publicaciones, seguidores y el siguiente número.
- initRecyclerView () Inicializa RecyclerView con datos de imagen de muestra.
- MyClickHandlers maneja los eventos de clic de los elementos de la interfaz de usuario. Aquí, toda la vinculación de eventos de clic se lleva a cabo solo a través del diseño XML. No asignamos nada explícitamente al código de actividad.
package info.androidhive.databinding.view; import android.content.Context; import android.content.res.Resources; import android.databinding.DataBindingUtil; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.TypedValue; import android.view.View; import android.widget.Toast; import java.util.ArrayList; import info.androidhive.databinding.R; import info.androidhive.databinding.databinding.ActivityMainBinding; import info.androidhive.databinding.model.Post; import info.androidhive.databinding.model.User; import info.androidhive.databinding.utils.GridSpacingItemDecoration; public class MainActivity extends AppCompatActivity implements PostsAdapter.PostsAdapterListener private MyClickHandlers handlers; private PostsAdapter mAdapter; private RecyclerView recyclerView; private ActivityMainBinding binding; private User user; @Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); Toolbar toolbar = binding.toolbar; setSupportActionBar(toolbar); getSupportActionBar().setTitle(R.string.toolbar_profile); getSupportActionBar().setDisplayHomeAsUpEnabled(true); handlers = new MyClickHandlers(this); renderProfile(); initRecyclerView(); /** * Renders RecyclerView with Grid Images in 3 columns */ private void initRecyclerView() recyclerView = binding.content.recyclerView; recyclerView.setLayoutManager(new GridLayoutManager(this, 3)); recyclerView.addItemDecoration(new GridSpacingItemDecoration(3, dpToPx(4), true)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setNestedScrollingEnabled(false); mAdapter = new PostsAdapter(getPosts(), this); recyclerView.setAdapter(mAdapter); /** * Renders user profile data */ private void renderProfile() user = new User(); user.setName("David Attenborough"); user.setEmail("[email protected]"); user.setProfileImage("https://api.androidhive.info/images/nature/david.jpg"); user.setAbout("Naturalist"); // ObservableField doesn't have setter method, instead will // be called using set() method user.numberOfPosts.set(3400L); user.numberOfFollowers.set(3050890L); user.numberOfFollowing.set(150L); // display user binding.setUser(user); // assign click handlers binding.content.setHandlers(handlers); private ArrayList<Post> getPosts() ArrayList<Post> posts = new ArrayList<>(); for (int i = 1; i < 10; i++) Post post = new Post(); post.setImageUrl("https://api.androidhive.info/images/nature/" + i + ".jpg"); posts.add(post); return posts; @Override public void onPostClicked(Post post) Toast.makeText(getApplicationContext(), "Post clicked! " + post.getImageUrl(), Toast.LENGTH_SHORT).show(); public class MyClickHandlers Context context; public MyClickHandlers(Context context) this.context = context; /** * Demonstrating updating bind data * Profile name, number of posts and profile image * will be updated on Fab click */ public void onProfileFabClicked(View view) user.setName("Sir David Attenborough"); user.setProfileImage("https://api.androidhive.info/images/nature/david1.jpg"); // updating ObservableField user.numberOfPosts.set(5500L); user.numberOfFollowers.set(5050890L); user.numberOfFollowing.set(180L); public boolean onProfileImageLongPressed(View view) Toast.makeText(getApplicationContext(), "Profile image long pressed!", Toast.LENGTH_LONG).show(); return false; public void onFollowersClicked(View view) Toast.makeText(context, "Followers is clicked!", Toast.LENGTH_SHORT).show(); public void onFollowingClicked(View view) Toast.makeText(context, "Following is clicked!", Toast.LENGTH_SHORT).show(); public void onPostsClicked(View view) Toast.makeText(context, "Posts is clicked!", Toast.LENGTH_SHORT).show(); /** * Converting dp to pixel */ private int dpToPx(int dp) Resources r = getResources(); return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()));
Ejecute la aplicación una vez y pruébela. Asegúrese de que el dispositivo tenga una conexión a Internet ya que las imágenes se descargan de la red.
Si tiene alguna pregunta, publíquela en la sección siguiente.