将recyclerview与数据库一起使用

Los*_*ppy 126 android simplecursoradapter android-recyclerview

目前没有可用的RecyclerView.Adapter的默认实现.

可能正式发布,谷歌将添加它.

由于不存在用于支持CursorAdapterRecyclerView当前,如何才能使用RecyclerView同一个数据库?有什么建议 ?

小智 100

如果你正在运行一个查询CursorLoader,你想RecyclerView的不是ListView.

您可以在RecyclerView中尝试我的CursorRecyclerViewAdapter:CursorAdapter

  • 删除和插入时代码可以工作但不能动画,因为Cursor类的方法registerDataSetObserver无法识别哪个元素已经完全更改.因此,每次内容提供者更改时,recyclerview都会通过[notifyDataSetChanged](http://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#notifyDataSetChanged())完全加载,这样是RecyclerView的精髓. (12认同)
  • 建议:从构造函数中调用swapCursor(),这样你就不必在那里重复了一遍 (2认同)
  • @francas动画如果设置`setHasStableIds(true)`就可以正常工作 (2认同)
  • @alders除了setHasTableIds之外还有其他任何变化吗?我也试过,ID也匹配,每次交换光标时列表都会回到顶部. (2认同)

nbt*_*btk 86

我的解决方案是在recyclerView.Adapter实现中保存CursorAdapter成员.然后传递创建新视图的所有处理并将其绑定到游标适配器,如下所示:

public class MyRecyclerAdapter extends Adapter<MyRecyclerAdapter.ViewHolder> {

    // Because RecyclerView.Adapter in its current form doesn't natively 
    // support cursors, we wrap a CursorAdapter that will do all the job
    // for us.
    CursorAdapter mCursorAdapter;

    Context mContext;

    public MyRecyclerAdapter(Context context, Cursor c) {

        mContext = context;

        mCursorAdapter = new CursorAdapter(mContext, c, 0) {

            @Override
            public View newView(Context context, Cursor cursor, ViewGroup parent) {
                // Inflate the view here
            }

            @Override
            public void bindView(View view, Context context, Cursor cursor) {
                // Binding operations
            }
        };
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        View v1;

        public ViewHolder(View itemView) {
            super(itemView);
            v1 = itemView.findViewById(R.id.v1);
        }
    }

    @Override
    public int getItemCount() {
        return mCursorAdapter.getCount();
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // Passing the binding operation to cursor loader
        mCursorAdapter.getCursor().moveToPosition(position); //EDITED: added this line as suggested in the comments below, thanks :)
        mCursorAdapter.bindView(holder.itemView, mContext, mCursorAdapter.getCursor());

    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // Passing the inflater job to the cursor-adapter
        View v = mCursorAdapter.newView(mContext, mCursorAdapter.getCursor(), parent);
        return new ViewHolder(v);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 感谢这个解决方案,它适用于我,我只需要在onBindViewHolder上添加一行:'mCursorAdapter.getCursor().moveToPosition(position)',然后在mCursorAdapter上调用bindView() (16认同)
  • @MyDogTom - 我没有一个Cursor,因为你需要在你完成时清除它,并将它设置为NULL是不够的.CursorAdapter完美地处理了这个问题. (13认同)
  • @nbtk为什么不保持Cursor而不是CursorAdapter?据我了解,您不使用CursorAdapter的任何功能. (8认同)
  • @nbtk执行以下操作以使用新数据刷新视图:`public void changeCursor(Cursor cursor){mCursorAdapter.changeCursor(cursor); notifyDataSetChanged(); }` (5认同)
  • @nbtk我真的很喜欢这个想法,从那时起我一直在玩它.我尝试抽象一点并构建了一个库,这不是100%我想要它的地方,但它完成了工作.你有兴趣检查一下吗?如果你有一个github,我很乐意为你提供灵感.https://github.com/androidessence/RecyclerViewCursorAdapter (3认同)
  • @Dale 我今天毫无问题地实施了它。 (2认同)

Pir*_*App 49

既然你的问题是"如何使用RecyclerView数据库"而你并不具体说明你是否想要SQLite或其他任何东西RecyclerView,我会给你一个非常优化的解决方案.我将使用Realm作为数据库,让你显示你的内部的所有数据RecyclerView.它也具有异步查询支持,无需使用LoadersAsyncTask.

为什么境界? realm.io性能Android

步骤1

为Realm添加gradle依赖项,可在此处找到最新版本的依赖项

第2步

创建你的模型类,例如,让我们说一些简单的东西,比如Data有两个字段,RecyclerView一行显示在行内,一个时间戳将用作itemId,允许RecyclerView动画项目.请注意,我RealmObject在下面进行了扩展,因为您的Data类将作为表存储,并且所有属性都将存储为该表的列Data.我已将数据文本标记为我的主键,因为我不希望多次添加字符串.但如果你喜欢重复,那么将时间戳作为@PrimaryKey.您可以拥有一个没有主键的表,但如果您在创建后尝试更新该行,则会导致问题.Realm不支持撰写此答案时的复合主键.

import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;

public class Data extends RealmObject {
@PrimaryKey
private String data;

//The time when this item was added to the database
private long timestamp;

public String getData() {
    return data;
}

public void setData(String data) {
    this.data = data;
}

public long getTimestamp() {
    return timestamp;
}

public void setTimestamp(long timestamp) {
    this.timestamp = timestamp;
}
}
Run Code Online (Sandbox Code Playgroud)

第3步

创建一个单行应该如何出现的布局RecyclerView.我们内部单个行项目的布局Adapter如下

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<TextView
    android:id="@+id/area"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:background="@android:color/white"
    android:padding="16dp"
    android:text="Data"
    android:visibility="visible" />

</FrameLayout>
Run Code Online (Sandbox Code Playgroud)

请注意,FrameLayout即使我有TextView内部,我也保持了root用户身份.我计划在这个布局中添加更多项目,因此现在变得灵活:)

对于那里的好奇人来说,这就是目前单个项目的外观. RecyclerView中的单项行布局

第4步

创建您的RecyclerView.Adapter实现.在这种情况下,数据源对象是一个特殊的对象RealmResults,它基本上是一个LIVE ArrayList,换句话说,当你的表中添加或删除项时,这个RealmResults对象会自动更新.

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

import io.realm.Realm;
import io.realm.RealmResults;
import slidenerd.vivz.realmrecycler.R;
import slidenerd.vivz.realmrecycler.model.Data;

public class DataAdapter extends RecyclerView.Adapter<DataAdapter.DataHolder> {
private LayoutInflater mInflater;
private Realm mRealm;
private RealmResults<Data> mResults;

public DataAdapter(Context context, Realm realm, RealmResults<Data> results) {
    mRealm = realm;
    mInflater = LayoutInflater.from(context);
    setResults(results);
}

public Data getItem(int position) {
    return mResults.get(position);
}

@Override
public DataHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = mInflater.inflate(R.layout.row_data, parent, false);
    DataHolder dataHolder = new DataHolder(view);
    return dataHolder;
}

@Override
public void onBindViewHolder(DataHolder holder, int position) {
    Data data = mResults.get(position);
    holder.setData(data.getData());
}

public void setResults(RealmResults<Data> results) {
    mResults = results;
    notifyDataSetChanged();
}

@Override
public long getItemId(int position) {
    return mResults.get(position).getTimestamp();
}

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

public void add(String text) {

    //Create a new object that contains the data we want to add
    Data data = new Data();
    data.setData(text);

    //Set the timestamp of creation of this object as the current time
    data.setTimestamp(System.currentTimeMillis());

    //Start a transaction
    mRealm.beginTransaction();

    //Copy or update the object if it already exists, update is possible only if your table has a primary key
    mRealm.copyToRealmOrUpdate(data);

    //Commit the transaction
    mRealm.commitTransaction();

    //Tell the Adapter to update what it shows.
    notifyDataSetChanged();
}

public void remove(int position) {

    //Start a transaction
    mRealm.beginTransaction();

    //Remove the item from the desired position
    mResults.remove(position);

    //Commit the transaction
    mRealm.commitTransaction();

    //Tell the Adapter to update what it shows
    notifyItemRemoved(position);
}

public static class DataHolder extends RecyclerView.ViewHolder {
    TextView area;

    public DataHolder(View itemView) {
        super(itemView);
        area = (TextView) itemView.findViewById(R.id.area);
    }

    public void setData(String text) {
        area.setText(text);
    }
}
}
Run Code Online (Sandbox Code Playgroud)

请注意,我正在调用notifyItemRemoved删除发生的位置但是我没有调用,notifyItemInserted或者notifyItemRangeChanged因为没有直接的方法知道项目被插入到数据库中的位置,因为Realm条目没有以有序的方式存储.RealmResults每当从数据库添加,修改或删除新项目时,对象都会自动更新,因此我们notifyDataSetChanged在添加和插入批量条目时进行调用.在这一点上,你可能担心,因为你拨打的不会被触发动画notifyDataSetChanged到位的notifyXXX方法.这正是我让getItemId方法从结果对象返回每一行的时间戳的原因.notifyDataSetChanged如果您调用setHasStableIds(true)然后覆盖getItemId以提供除位置之外的其他内容,则动画分两步实现.

第5步

让我们添加RecyclerView到我们ActivityFragment.就我而言,我正在使用Activity.包含它的布局文件RecyclerView非常简单,看起来像这样.

<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/text_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
Run Code Online (Sandbox Code Playgroud)

我已经添加了一个,app:layout_behavior因为我RecyclerView进入了一个CoordinatorLayout我没有在这个答案中发布的简洁.

第6步

构造RecyclerView代码并提供所需的数据.在里面创建并初始化一个Realm对象onCreate并将其关闭,onDestroy就像关闭一个SQLiteOpenHelper实例一样.在最简单的onCreate内部,Activity看起来像这样.这种initUi方法是所有魔法发生的地方.我在里面打开一个Realm实例onCreate.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mRealm = Realm.getInstance(this);
    initUi();
}

private void initUi() {

    //Asynchronous query
    RealmResults<Data> mResults = mRealm.where(Data.class).findAllSortedAsync("data");

    //Tell me when the results are loaded so that I can tell my Adapter to update what it shows
    mResults.addChangeListener(new RealmChangeListener() {
        @Override
        public void onChange() {
            mAdapter.notifyDataSetChanged();
            Toast.makeText(ActivityMain.this, "onChange triggered", Toast.LENGTH_SHORT).show();
        }
    });
    mRecycler = (RecyclerView) findViewById(R.id.recycler);
    mRecycler.setLayoutManager(new LinearLayoutManager(this));
    mAdapter = new DataAdapter(this, mRealm, mResults);

    //Set the Adapter to use timestamp as the item id for each row from our database
    mAdapter.setHasStableIds(true);
    mRecycler.setAdapter(mAdapter);
}
Run Code Online (Sandbox Code Playgroud)

请注意,在第一步中,我查询Realm Data,以异步方式向我提供类中的所有对象,这些对象按其变量名称data data排序.这给了我一个RealmResults在我设置的主线程上有0项的对象Adapter.我添加了一个RealmChangeListener通知,当数据从后台线程完成加载时我会通知notifyDataSetChangedAdapter.我还调用setHasStableIds了true,让RecyclerView.Adapter实现跟踪添加,删除或修改的项目.在onDestroyActivity关闭的领域实例

@Override
protected void onDestroy() {
    super.onDestroy();
    mRealm.close();
}
Run Code Online (Sandbox Code Playgroud)

这个方法initUi里面可以叫onCreate你的Activity或者onCreateViewonViewCreated你的Fragment.请注意以下事项.

第7步

BAM!里面你从数据库中有数据RecyclerViewasynchonously不装CursorLoader,CursorAdapter,SQLiteOpenHelper用动画.此处显示的GIF图像有点滞后,但是当您添加项目或删除它们时会发生动画.

来自RecyclerView内部数据库的数据

  • FWIW,包括Realm在插入时的性能图表与Recyclerviews的问题非常无关(这将不可避免地与检索有关) (9认同)