Xây dựng 1 abstract BaseAdapter trong RecyclerView

1. Mở đầu

Đã khi nào bạn chán việc phải tạo ra 1 đống các Adapter khác nhau cho mỗi RecyclerView bạn sử dụng chưa ?

Và sau đây mình sẽ giới thiệu cho các bạn cách xây dựng và dùng 1 Base Adapter dùng chung cho tất cả các RecyclerView khi dùng binding data.

2. Xây dựng BaseAdapter và BindingViewHolde

2.1. Xây dựng BaseAdapter

  • Hàm setVariable
  @Override
    public void onBindViewHolder(BindingViewHolder holder, int position) {
        final Object item = mCollection.get(position);
        holder.getBinding().setVariable(BR.viewModel, item);
        holder.getBinding().setVariable(BR.listener, getPresenter());
        holder.getBinding().executePendingBindings();
        if (mDecorator != null) {
            mDecorator.decorator(holder, position, getItemViewType(position));
        }
    }
  • Full code
/*
 * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tuananh.recyclerviewdatabinding.recyclerview;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;

import com.tuananh.recyclerviewdatabinding.BR;

import java.util.List;

/**
 * Base Data Binding RecyclerView Adapter.
 *
 * @author markzhai on 16/8/25
 */
public abstract class BaseViewAdapter<T> extends RecyclerView.Adapter<BindingViewHolder> {
    protected final LayoutInflater mLayoutInflater;
    protected List<T> mCollection;
    protected Presenter mPresenter;
    protected Decorator mDecorator;

    public BaseViewAdapter(Context context) {
        mLayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public void onBindViewHolder(BindingViewHolder holder, int position) {
        final Object item = mCollection.get(position);
        holder.getBinding().setVariable(BR.viewModel, item);
        holder.getBinding().setVariable(BR.listener, getPresenter());
        holder.getBinding().executePendingBindings();
        if (mDecorator != null) {
            mDecorator.decorator(holder, position, getItemViewType(position));
        }
    }

    @Override
    public int getItemCount() {
        return mCollection.size();
    }

    public void remove(int position) {
        mCollection.remove(position);
        notifyItemRemoved(position);
    }

    public void clear() {
        mCollection.clear();
        notifyDataSetChanged();
    }

    public void setDecorator(Decorator decorator) {
        mDecorator = decorator;
    }

    protected Presenter getPresenter() {
        return mPresenter;
    }

    public void setPresenter(Presenter presenter) {
        mPresenter = presenter;
    }

    public interface Presenter {
    }

    public interface Decorator {
        void decorator(BindingViewHolder holder, int position, int viewType);
    }
}

2.2. Xây dựng BindingViewHolder

/*
 * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tuananh.recyclerviewdatabinding.recyclerview;

import android.databinding.ViewDataBinding;
import android.support.v7.widget.RecyclerView;

/**
 * @author markzhai on 16/3/18
 * @version 1.0.0
 */
public class BindingViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
    protected final T mBinding;

    public BindingViewHolder(T binding) {
        super(binding.getRoot());
        mBinding = binding;
    }

    public T getBinding() {
        return mBinding;
    }
}

3. SingleTypeAdapter and MultiTypeAdapter

3.1. SingleTypeAdapter

/*
 * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tuananh.recyclerviewdatabinding.recyclerview;

import android.content.Context;
import android.databinding.DataBindingUtil;
import android.support.annotation.LayoutRes;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * Super simple single-type adapter using data-binding.
 *
 * @author markzhai on 16/8/22
 */
public class SingleTypeAdapter<T> extends BaseViewAdapter<T> {
    protected int mLayoutRes;

    public SingleTypeAdapter(Context context) {
        this(context, 0);
    }

    public SingleTypeAdapter(Context context, int layoutRes) {
        super(context);
        mCollection = new ArrayList<>();
        mLayoutRes = layoutRes;
    }

    @SuppressWarnings("unchecked")
    @Override
    public BindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new BindingViewHolder(
            DataBindingUtil.inflate(mLayoutInflater, getLayoutRes(), parent, false));
    }

    @Override
    public int getItemCount() {
        return mCollection.size();
    }

    public void add(T viewModel) {
        mCollection.add(viewModel);
        notifyDataSetChanged();
    }

    public void add(int position, T viewModel) {
        mCollection.add(position, viewModel);
        notifyDataSetChanged();
    }

    public void set(List<T> viewModels) {
        mCollection.clear();
        addAll(viewModels);
    }

    public void addAll(List<T> viewModels) {
        mCollection.addAll(viewModels);
        notifyDataSetChanged();
    }

    @LayoutRes
    protected int getLayoutRes() {
        return mLayoutRes;
    }

    public interface Presenter<T> extends BaseViewAdapter.Presenter {
        void onItemClick(T t);
    }
}

3.2. MultiTypeAdapter

Dùng để xử lý với nhiều type truyền vào

/*
 * Copyright (C) 2016 MarkZhai (http://zhaiyifan.cn).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tuananh.recyclerviewdatabinding.recyclerview;

import android.content.Context;
import android.databinding.DataBindingUtil;
import android.support.annotation.LayoutRes;
import android.support.v4.util.ArrayMap;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Super simple multi-type adapter using data-binding.
 *
 * @author markzhai on 16/8/23
 */
public class MultiTypeAdapter extends BaseViewAdapter<Object> {
    protected ArrayList<Integer> mCollectionViewType;
    private ArrayMap<Integer, Integer> mItemTypeToLayoutMap = new ArrayMap<>();

    public MultiTypeAdapter(Context context) {
        this(context, null);
    }

    public MultiTypeAdapter(Context context, Map<Integer, Integer> viewTypeToLayoutMap) {
        super(context);
        mCollection = new ArrayList<>();
        mCollectionViewType = new ArrayList<>();
        if (viewTypeToLayoutMap != null && !viewTypeToLayoutMap.isEmpty()) {
            mItemTypeToLayoutMap.putAll(viewTypeToLayoutMap);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public BindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new BindingViewHolder(
            DataBindingUtil.inflate(mLayoutInflater, getLayoutRes(viewType), parent, false));
    }

    public void addViewTypeToLayoutMap(Integer viewType, Integer layoutRes) {
        mItemTypeToLayoutMap.put(viewType, layoutRes);
    }

    @Override
    public int getItemViewType(int position) {
        return mCollectionViewType.get(position);
    }

    public void set(List viewModels, int viewType) {
        mCollection.clear();
        mCollectionViewType.clear();
        if (viewModels == null) {
            add(null, viewType);
        } else {
            addAll(viewModels, viewType);
        }
    }

    public void set(List viewModels, MultiViewTyper viewTyper) {
        mCollection.clear();
        mCollectionViewType.clear();
        addAll(viewModels, viewTyper);
    }

    public void add(Object viewModel, int viewType) {
        mCollection.add(viewModel);
        mCollectionViewType.add(viewType);
        notifyItemInserted(0);
    }

    public void add(int position, Object viewModel, int viewType) {
        mCollection.add(position, viewModel);
        mCollectionViewType.add(position, viewType);
        notifyItemInserted(position);
    }

    public void addAll(List viewModels, int viewType) {
        mCollection.addAll(viewModels);
        for (int i = 0; i < viewModels.size(); ++i) {
            mCollectionViewType.add(viewType);
        }
        notifyDataSetChanged();
    }

    public void addAll(int position, List viewModels, int viewType) {
        mCollection.addAll(position, viewModels);
        for (int i = 0; i < viewModels.size(); i++) {
            mCollectionViewType.add(position + i, viewType);
        }
        notifyItemRangeChanged(position, viewModels.size() - position);
    }

    public void addAll(List viewModels, MultiViewTyper multiViewTyper) {
        mCollection.addAll(viewModels);
        for (int i = 0; i < viewModels.size(); ++i) {
            mCollectionViewType.add(multiViewTyper.getViewType(viewModels.get(i)));
        }
        notifyDataSetChanged();
    }

    public void remove(int position) {
        mCollectionViewType.remove(position);
        super.remove(position);
    }

    public void clear() {
        mCollectionViewType.clear();
        super.clear();
    }

    @LayoutRes
    protected int getLayoutRes(int viewType) {
        return mItemTypeToLayoutMap.get(viewType);
    }

    public interface MultiViewTyper {
        int getViewType(Object item);
    }
}

4. Cách sử dụng SingleTypeAdapter

  • Cách dùng
  private void updateAdapter() {
        SingleTypeAdapter<Contacts> adapter =
            new SingleTypeAdapter<>(this, R.layout.item_recycler_contact);
        // add viewModels
        adapter.addAll(mContactsList);
        // add listener
        adapter.setPresenter(new ItemAdapterClickListener());
        mBinding.setLayoutManager(new LinearLayoutManager(this));
        mBinding.setAdapter(adapter);
    }
  • Khởi tạo Listener
adapter.setPresenter(new ItemAdapterClickListener());
  • Full code:

package com.tuananh.recyclerviewdatabinding.view;

import android.databinding.DataBindingUtil;
import android.databinding.ObservableArrayList;
import android.databinding.ObservableList;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.widget.Toast;

import com.tuananh.recyclerviewdatabinding.R;
import com.tuananh.recyclerviewdatabinding.databinding.ActivityMainBinding;
import com.tuananh.recyclerviewdatabinding.model.Contacts;
import com.tuananh.recyclerviewdatabinding.recyclerview.BaseViewAdapter;
import com.tuananh.recyclerviewdatabinding.recyclerview.SingleTypeAdapter;

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;
    private ObservableList<Contacts> mContactsList = new ObservableArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        loadData();
        initViews();
        updateAdapter();
    }

    private void loadData() {
        for (int i = 0; i < 10; i++) {
            mContactsList
                .add(new Contacts("abc " + i, "Framgia", "0964980253", "[email protected]", "Family"));
        }
    }

    private void updateAdapter() {
        SingleTypeAdapter<Contacts> adapter =
            new SingleTypeAdapter<>(this, R.layout.item_recycler_contact);
        // add viewModels
        adapter.addAll(mContactsList);
        // add listener
        adapter.setPresenter(new ItemAdapterClickListener());
        mBinding.setLayoutManager(new LinearLayoutManager(this));
        mBinding.setAdapter(adapter);
    }

    private void initViews() {
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    }

    public class ItemAdapterClickListener implements BaseViewAdapter.Presenter {
        public void onItemClick(String values) {
            Toast.makeText(getApplicationContext(), values, Toast.LENGTH_SHORT).show();
        }
    }
}

5. ViewBindingAdapter

  • Thiết lập các hàm set cho binding data:
package com.tuananh.recyclerviewdatabinding.binding;

import android.databinding.BindingAdapter;
import android.support.v7.widget.RecyclerView;

public class ViewBindingAdapter {
    @BindingAdapter("layoutManager")
    public static void setLayoutManager(RecyclerView view, RecyclerView.LayoutManager manager) {
        view.setLayoutManager(manager);
    }

    @BindingAdapter("adapter")
    public static void setAdapter(RecyclerView view, RecyclerView.Adapter adapter) {
        view.setAdapter(adapter);
    }
}

6. Xml chứa recycler

  • Tạo file xml : activity_main.xml

  • Cách dùng :

<data>
        <variable
            name="adapter"
            type="android.support.v7.widget.RecyclerView.Adapter"/>

        <variable
            name="layoutManager"
            type="android.support.v7.widget.RecyclerView.LayoutManager"/>
    </data>
<android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:adapter="@{adapter}"
            app:layoutManager="@{layoutManager}"/>

  • Full code :
<?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"
        xmlns:tools="http://schemas.android.com/tools"
        tools:context="com.tuananh.recyclerviewdatabinding.view.MainActivity">

    <data>

        <variable
            name="viewModel"
            type="com.tuananh.recyclerviewdatabinding.view.MainActivity"/>

        <variable
            name="adapter"
            type="android.support.v7.widget.RecyclerView.Adapter"/>

        <variable
            name="layoutManager"
            type="android.support.v7.widget.RecyclerView.LayoutManager"/>
    </data>

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:paddingBottom="@dimen/margin_padding_5"
            android:paddingTop="@dimen/margin_padding_5"
            android:text="@string/text_contact_list"/>

        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:adapter="@{adapter}"
            app:layoutManager="@{layoutManager}"/>
    </LinearLayout>
</layout>

7. Item RecyclerView

  • Tạo file xml : item_recycler_contact.xml
  • Các hàm set : app:content, app:onClickContent, app:onClickTitle, app:title, app:visibility đươc tạo trong item_text_contact.xml
  • Full code:
<?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>

        <import type="android.view.View"/>

        <variable
            name="viewModel"
            type="com.tuananh.recyclerviewdatabinding.model.Contacts"/>

        <variable
            name="listener"
            type="com.tuananh.recyclerviewdatabinding.view.MainActivity.ItemAdapterClickListener"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/margin_padding_10"
        android:background="@drawable/contact_background"
        android:orientation="vertical">

        <include
            layout="@layout/item_text_contact"
            app:content="@{viewModel.name}"
            app:onClickContent="@{() -> listener.onItemClick(viewModel.name)}"
            app:onClickTitle="@{() -> listener.onItemClick(@string/text_name)}"
            app:title="@{@string/text_name}"/>

        <include
            layout="@layout/item_text_contact"
            app:content="@{viewModel.company}"
            app:onClickContent="@{() -> listener.onItemClick(viewModel.company)}"
            app:onClickTitle="@{() -> listener.onItemClick(@string/text_company_name)}"
            app:title="@{@string/text_company_name}"/>

        <include
            layout="@layout/item_text_contact"
            app:content="@{viewModel.mobile}"
            app:onClickContent="@{() -> listener.onItemClick(viewModel.mobile)}"
            app:onClickTitle="@{() -> listener.onItemClick(@string/text_mobile)}"
            app:title="@{@string/text_mobile}"/>

        <include
            layout="@layout/item_text_contact"
            app:content="@{viewModel.email}"
            app:onClickContent="@{() -> listener.onItemClick(viewModel.email)}"
            app:onClickTitle="@{() -> listener.onItemClick(@string/text_email)}"
            app:title="@{@string/text_email}"/>

        <include
            layout="@layout/item_text_contact"
            app:content="@{viewModel.groupName}"
            app:onClickContent="@{() -> listener.onItemClick(viewModel.groupName)}"
            app:onClickTitle="@{() -> listener.onItemClick(@string/text_group_name)}"
            app:title="@{@string/text_group_name}"
            app:visibility="@{View.GONE}"/>

    </LinearLayout>
</layout>

8. ItemTextContact

  • Tạo file item_text_contact.xml

  • Full code :
<?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="title"
            type="String"/>

        <variable
            name="content"
            type="String"/>

        <variable
            name="visibility"
            type="int"/>

        <variable
            name="onClickTitle"
            type="android.view.View.OnClickListener"/>

        <variable
            name="onClickContent"
            type="android.view.View.OnClickListener"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:weightSum="5">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="2"
                android:onClick="@{onClickTitle}"
                android:padding="@dimen/margin_padding_5"
                android:text="@{title}"
                android:textStyle="bold"/>

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:onClick="@{onClickContent}"
                android:padding="@dimen/margin_padding_5"
                android:text="@{content}"/>

        </LinearLayout>

        <include
            layout="@layout/layout_line_break_form_search"
            app:visibility="@{visibility}"/>
    </LinearLayout>
</layout>

9. Model

  • Khởi tạo model : Contacts
  • Full code :
package com.tuananh.recyclerviewdatabinding.model;

/**
 * Created by framgia on 27/12/2016.
 */
public class Contacts {
    private String mName;
    private String mCompany;
    private String mMobile;
    private String mEmail;
    private String mGroupName;

    public Contacts(String name, String company, String mobile, String email,
                    String groupName) {
        mName = name;
        mCompany = company;
        mMobile = mobile;
        mEmail = email;
        mGroupName = groupName;
    }

    public String getName() {
        return mName;
    }

    public String getCompany() {
        return mCompany;
    }

    public String getMobile() {
        return mMobile;
    }

    public String getEmail() {
        return mEmail;
    }

    public String getGroupName() {
        return mGroupName;
    }
}

Kết quả

Screenshot from 2016-12-28 10-11-36.png

Link Code

Link

Nguồn tham khảo

Link