12/08/2018, 18:11

Android DataBinding in RecyclerView – Phần 2

Mời các bạn đọc phần 1 tại link này Video demo Bước 1. Tạo project mới: File ⇒ New Project và chọn Basic Activity từ templates. Bước 2. Enable DataBiding trong app/build.gradle. Ngoài ra, thêm dependencies của RecyclerView và Glide sau đó, đồng bộ hóa dự án. android { ...

Mời các bạn đọc phần 1 tại link này

Video demo

Bước 1.

Tạo project mới: File ⇒ New Project và chọn Basic Activity từ templates.

Bước 2.

Enable DataBiding trong app/build.gradle. Ngoài ra, thêm dependencies của RecyclerView và Glide sau đó, đồng bộ hóa dự án.

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'
}

Bước 3.

Thêm quyền truy cập INTERNET trong AndroidManifest.xml vì hình ảnh cần được tải từ URL.

<uses-permission android:name="android.permission.INTERNET" />

Bước 4.

Download res.zip sau đó thêm vào thư mục res dự án của bạn. Các thư mục drawable chứa biểu tượng dấu cộng cần thiết cho FAB.

Bước 5.

Thêm các resources bên dưới vào strings.xml, dimens.xml và colors.xml tương ứng

strings.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>
dimens.xml
<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>
colors.xml
<?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>

Bước 6.

Tạo ba packages có tên là model, utils và view. Sau khi tạo, di chuyển đến MainActivity để xem packages.

Dưới đây là cấu trúc dự án và các tệp được yêu cầu.

Bước 7.

Tạo class User theo model package. Tạo class Observable bằng cách extend từ class BaseObservable.

Để demo, cả Observable và ObservableField đều được sử dụng trong cùng một lớp.

Đối với tên biến email, profileImage và about., Annotation @Bindable được sử dụng và notifyPropertyChanged() được gọi khi thiết lập dữ liệu mới Biến numberOfPosts, numberOfFollowers, numberOfFollowing được khai báo như ObservableFields @BindingAdapter được sử dụng để liên kết profileImage với ImageView để tải hình ảnh từ URL bằng cách sử dụng thư viện Glide.

User.java
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;
    }
}

Bước 8.

Tạo một lớp khác có tên là Post.java trong model package. Model class này cung cấp dữ liệu cho RecyclerView.

Post.java
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;
    }
}

Bước 9.

Trong gói utils, tạo hai classes có tên là BindingUtils.java và GridSpacingItemDecoration.java

Phương thức convertToSuffix () chuyển đổi một số sang định dạng mà con người có thể đọc được. Ví dụ, 5500L sẽ được chuyển đổi thành 5.5k và 5050890L sẽ được chuyển đổi thành 5.1m. Chúng ta liên kết chức năng này với TextViews để hiển thị các bài đăng, người theo dõi và theo dõi ở định dạng con người có thể đọc được của con người.

BindingUtils.java
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 cung cấp khoảng cách giữa các phần tử lưới RecyclerView.

GridSpacingItemDecoration.java
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;
            }
        }
    }
}

Binding layout RecyclerView tương tự như binding bình thường ngoại trừ vài thay đổi trong phương thức onCreateViewHolder và onBindViewHolder.

Bước 10.

Tạo layout có tên post_row_item.xml. Layout này chứa một ImageView để hiển thị hình ảnh trong RecyclerView.

Trong layout này, data binding được bật bằng cách giữ nguyên phần tử gốc dưới dạng <layout>. Post mode trong ràng buộc với layout này bằng cách sử dụng thẻ <variable>.

post_row_item.xml
<?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_awidth="match_parent"
        android:layout_height="wrap_content">
 
        <ImageView
            android:id="@+id/thumbnail"
            android:layout_awidth="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>

Bước 11.

Create a class named PostsAdapter.java under view package.

As the layout name is post_row_item.xml, the generated binding class will be PostRowItemBinding. In onCreateViewHolder() method, post_row_item layout is inflated with the help of PostRowItemBinding class. holder.binding.setPost() binds the Post model to each row.

Tạo một class có tên PostsAdapter.java trong view package.

Layout post_row_item.xml, thì class ràng buộc được tạo sẽ là PostRowItemBinding. Trong phương thức onCreateViewHolder (), layout post_row_item được inflated với sự trợ giúp của class PostRowItemBinding. holder.binding.setPost () liên kết Post model với mỗi hàng.

PostsAdapter.java
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);
    }
}

Bây giờ chúng ta có tất cả các files. Bây giờ chúng ta cùng bắt đầu xây dựng giao diện chính.

Bước 12.

Mở file layout activity_main.xml và content_main.xml và enable data-binding bằng cách add <layout>, <data> and <variable> tags.

activity_main.xml
<?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_awidth="match_parent"
        android:layout_height="match_parent"
        tools:context=".view.MainActivity">
 
        <android.support.design.widget.AppBarLayout
            android:layout_awidth="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_awidth="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>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:
            
            
            
         
0