将 recyclerview 与数据库一起使用
Posted
技术标签:
【中文标题】将 recyclerview 与数据库一起使用【英文标题】:Using the recyclerview with a database 【发布时间】:2014-12-18 12:24:28 【问题描述】:目前没有可用的 RecyclerView.Adapter 的默认实现。
可能随着正式发布,谷歌会添加。
由于目前不支持CursorAdapter
与RecyclerView
,我们如何将RecyclerView
与数据库一起使用?有什么建议 ?
【问题讨论】:
【参考方案1】:如果您使用CursorLoader
运行查询,并且您想要RecyclerView
而不是ListView
。
你可以试试我的CursorRecyclerViewAdapter
:CursorAdapter in RecyclerView
【讨论】:
代码有效,但删除和插入时的动画无效,因为 Cursor 类的方法 registerDataSetObserver 不能准确识别哪个元素发生了变化。所以每次内容提供者改变recyclerview都是完全通过notifyDataSetChanged加载的,这就是RecyclerView的本质。 只通过构造函数将光标传递给适配器并在onBindViewHolder()中使用cursor.moveToPosition()来获取相关数据会有什么缺点? 建议:从构造函数中调用swapCursor(),这样就不用在那儿重复了 @francas Animations 工作正常,如果你设置setHasStableIds(true)
@alders 除了 setHasTableIds 之外,您还进行了其他更改吗?我也试过了,ID确实匹配,每次我交换光标时,列表都会回到顶部。【参考方案2】:
我的解决方案是在我的 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);
【讨论】:
感谢这个解决方案,它对我有用,我只需要在 onBindViewHolder: 'mCursorAdapter.getCursor().moveToPosition(position)' 中添加一行,然后在 mCursorAdapter 上调用 bindView() @nbtk 为什么不用 Cursor 而不是 CursorAdapter?据我了解,您没有使用 CursorAdapter 的任何功能。 @MyDogTom - 我没有光标,因为您需要在完成后清除它,并且将其设置为 NULL 是不够的。 CursorAdapter 完美地处理了这个问题。 @nbtk 我真的很喜欢这个想法,从那以后我就一直在玩它。我尝试将它抽象一点并构建了一个库,这还不是我想要的 100%,但它完成了工作。你有兴趣去看看吗?如果你有 github,我很乐意将灵感归功于你。 github.com/androidessence/RecyclerViewCursorAdapter @nbtk 执行以下操作以使用新数据刷新您的视图:public void changeCursor(Cursor cursor) mCursorAdapter.changeCursor(cursor); notifyDataSetChanged();
【参考方案3】:
由于您的问题是“如何将RecyclerView
与数据库一起使用”,并且您并没有具体说明您是否需要SQLite 或RecyclerView
的其他任何东西,我将为您提供一个高度优化的解决方案。我将使用 Realm 作为数据库,并让您显示 RecyclerView
中的所有数据。它也支持异步查询,无需使用Loaders
或AsyncTask
。
为什么是领域?
第 1 步
为 Realm 添加 gradle 依赖,找到最新版本的依赖here
第 2 步
例如,创建您的模型类,让我们说一些简单的东西,例如 Data
,它有 2 个字段,一个要显示在 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;
第 3 步
为单行在RecyclerView
中的显示方式创建布局。
我们的 Adapter
内单行项的布局如下
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_>
<TextView
android:id="@+id/area"
android:layout_
android:layout_
android:layout_gravity="center"
android:background="@android:color/white"
android:padding="16dp"
android:text="Data"
android:visibility="visible" />
</FrameLayout>
请注意,我将FrameLayout
保留为root,即使我里面有TextView
。我计划在这个布局中添加更多项目,因此现在让它变得灵活:)
对于好奇的人来说,这就是目前单个项目的外观。
第 4 步
创建您的RecyclerView.Adapter
实现。在这种情况下,数据源对象是一个名为RealmResults
的特殊对象,它基本上是一个实时的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);
请注意,我使用删除发生的位置调用 notifyItemRemoved
,但我不调用 notifyItemInserted
或 notifyItemRangeChanged
,因为没有直接的方法可以知道项目插入数据库的哪个位置领域条目不是以有序的方式存储的。每当从数据库中添加、修改或删除新项目时,RealmResults
对象会自动更新,因此我们在添加和插入批量条目时调用 notifyDataSetChanged
。此时,您可能担心不会触发的动画,因为您正在调用 notifyDataSetChanged
代替 notifyXXX
方法。这正是我让getItemId
方法从结果对象返回每一行的时间戳的原因。如果您调用 setHasStableIds(true)
然后覆盖 getItemId
以提供除位置以外的其他内容,则使用 notifyDataSetChanged
分两步实现动画。
第 5 步
让我们将RecyclerView
添加到我们的Activity
或Fragment
。就我而言,我使用的是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_
android:layout_
android:layout_margin="@dimen/text_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
我添加了一个app:layout_behavior
,因为我的RecyclerView
进入了CoordinatorLayout
,为简洁起见,我没有在此答案中发布。
第 6 步
在代码中构造RecyclerView
并提供它需要的数据。在onCreate
中创建并初始化一个Realm 对象并在onDestroy
中关闭它,就像关闭SQLiteOpenHelper
实例一样。最简单的是,Activity
中的 onCreate
看起来像这样。 initUi
方法是所有魔法发生的地方。我在onCreate
中打开了一个Realm 实例。
@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);
请注意,在第一步中,我查询 Realm 以向我提供来自 Data
类的所有对象,这些对象按其名为 data 的变量名以异步方式排序。这给了我一个 RealmResults
对象,主线程上有 0 个项目,我在 Adapter
上设置。我添加了一个RealmChangeListener
,以便在数据从后台线程完成加载时收到通知,我用Adapter
调用notifyDataSetChanged
。我还将setHasStableIds
调用为true,以让RecyclerView.Adapter
实现跟踪添加、删除或修改的项目。我的Activity
的onDestroy
关闭了Realm 实例
@Override
protected void onDestroy()
super.onDestroy();
mRealm.close();
这个方法initUi
可以在Activity
的onCreate
或Fragment
的onCreateView
或onViewCreated
内部调用。请注意以下事项。
第 7 步
砰!您的RecyclerView
内的数据库中有数据异步加载而没有CursorLoader
、CursorAdapter
、SQLiteOpenHelper
和动画。此处显示的 GIF 图像有点滞后,但是当您添加或删除项目时会出现动画。
【讨论】:
感谢您提供全面的示例。我有几个问题。我想将位图存储在领域中是否可能?如果是,列类型是什么以及如何存储和检索它? @Rakesh 从不将图像存储在任何数据库中,始终将其存储在您的文件系统中并将图像的 uri 存储在数据库中 感谢您的快速回复。我正在创建一个应用程序,我想在其中存储联系人的图像。如果我存储在文件系统上,可能会有数百个图像。我将如何管理文件系统上的这些图像? FWIW,包括 Realm 在插入时的性能图表与关于 recyclerviews 的问题(这将不可避免地与检索有关)非常无关 鉴于工具栏上写着 Vivz,这是@slidenerd 解决方案吗?【参考方案4】:您可以自己实现所有必需的方法。我最近通过从 CursorAdapter 复制粘贴代码进行了自己的实现。
public class MyAdapter extends RecyclerView.Adapter<ViewHolder>
protected boolean mDataValid;
protected boolean mAutoRequery;
protected Cursor mCursor;
protected Context mContext;
protected int mRowIDColumn;
protected ChangeObserver mChangeObserver;
protected DataSetObserver mDataSetObserver;
protected FilterQueryProvider mFilterQueryProvider;
public static final int FLAG_AUTO_REQUERY = 0x01;
public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
public Cursor getCursor()
return mCursor;
//Recommended
public MyAdapter(Context context, Cursor c, int flags)
init(context, c, flags);
public MyAdapter(Context context, Cursor c)
init(context, c, FLAG_AUTO_REQUERY);
public MyAdapter(Context context, Cursor c, boolean autoRequery)
init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
void init(Context context, Cursor c, int flags)
if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY)
flags |= FLAG_REGISTER_CONTENT_OBSERVER;
mAutoRequery = true;
else
mAutoRequery = false;
boolean cursorPresent = c != null;
mCursor = c;
mDataValid = cursorPresent;
mContext = context;
mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER)
mChangeObserver = new ChangeObserver();
mDataSetObserver = new MyDataSetObserver();
else
mChangeObserver = null;
mDataSetObserver = null;
if (cursorPresent)
if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
// Create new views (invoked by the layout manager)
@Override
public ViewHolder onCreateViewHolder(final ViewGroup parent,
int viewType)
// create a new view
final View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item, parent, false);
// set the view's size, margins, paddings and layout parameters
ViewHolder vh = new ViewHolder(view, mCursor, new ViewHolder.IMyViewHolderClicks()
@SuppressLint("NewApi")
@Override
public void onClick(Cursor cursor)
Log.e("Item :", cursor.getString(cursor.getColumnIndex(MyDatabaseHelper.MW_NAAM)));
Intent intent = new Intent(TasksListFragment.this.getActivity(), DetailActivity.class);
intent.putExtra(DetailActivity.EXTRA_PARAM_ID, cursor.getLong(cursor.getColumnIndex(MyDatabaseHelper.MW_ID)));
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
TasksListFragment.this.getActivity(),
// Now we provide a list of Pair items which contain the view we can transitioning
// from, and the name of the view it is transitioning to, in the launched activity
new Pair<View, String>(
view.findViewById(R.id.imageview_item),
DetailActivity.VIEW_NAME_HEADER_IMAGE),
new Pair<View, String>(
view.findViewById(R.id.textview_name),
DetailActivity.VIEW_NAME_HEADER_TITLE)
);
// Now we can start the Activity, providing the activity options as a bundle
startActivity(intent, activityOptions.toBundle());
// END_INCLUDE(start_activity)
);
return vh;
// Replace the contents of a view (invoked by the layout manager)
@SuppressLint("NewApi")
@Override
public void onBindViewHolder(ViewHolder holder, int position)
// - get element from your dataset at this position
// - replace the contents of the view with that element
final Cursor cursor = getItem(position);
holder.mTextView.setText(cursor.getString(cursor.getColumnIndex(MyDatabaseHelper.MW_NAAM)));
holder.mImageView.setTransitionName("grid:image:" + cursor.getLong(cursor.getColumnIndex(MyDatabaseHelper.MW_ID)));
holder.mTextView.setTransitionName("grid:name:" + cursor.getLong(cursor.getColumnIndex(MyDatabaseHelper.MW_ID)));
//@Override
// public View getView(int position, View view, ViewGroup viewGroup)
// return view;
//
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount()
return getCount();
public int getCount()
if (mDataValid && mCursor != null)
return mCursor.getCount();
else
return 0;
public Cursor getItem(int position)
if (mDataValid && mCursor != null)
mCursor.moveToPosition(position);
return mCursor;
else
return null;
@Override
public long getItemId(int position)
if (mDataValid && mCursor != null)
if (mCursor.moveToPosition(position))
return mCursor.getLong(mRowIDColumn);
else
return 0;
else
return 0;
public Cursor swapCursor(Cursor newCursor)
if (newCursor == mCursor)
return null;
Cursor oldCursor = mCursor;
if (oldCursor != null)
if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
mCursor = newCursor;
if (newCursor != null)
if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
// notify the observers about the new cursor
notifyDataSetChanged();
else
mRowIDColumn = -1;
mDataValid = false;
// notify the observers about the lack of a data set
notifyDataSetInvalidated();
return oldCursor;
public void changeCursor(Cursor cursor)
Cursor old = swapCursor(cursor);
if (old != null)
old.close();
public CharSequence convertToString(Cursor cursor)
return cursor == null ? "" : cursor.toString();
public Cursor runQueryOnBackgroundThread(CharSequence constraint)
if (mFilterQueryProvider != null)
return mFilterQueryProvider.runQuery(constraint);
return mCursor;
public FilterQueryProvider getFilterQueryProvider()
return mFilterQueryProvider;
public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider)
mFilterQueryProvider = filterQueryProvider;
protected void onContentChanged()
if (mAutoRequery && mCursor != null && !mCursor.isClosed())
if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
mDataValid = mCursor.requery();
private class ChangeObserver extends ContentObserver
public ChangeObserver()
super(new Handler());
@Override
public boolean deliverSelfNotifications()
return true;
@Override
public void onChange(boolean selfChange)
onContentChanged();
private class MyDataSetObserver extends DataSetObserver
@Override
public void onChanged()
mDataValid = true;
notifyDataSetChanged();
@Override
public void onInvalidated()
mDataValid = false;
notifyDataSetInvalidated();
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public void registerDataSetObserver(DataSetObserver observer)
mDataSetObservable.registerObserver(observer);
public void unregisterDataSetObserver(DataSetObserver observer)
mDataSetObservable.unregisterObserver(observer);
public void notifyDataSetInvalidated()
mDataSetObservable.notifyInvalidated();
【讨论】:
那么这个类的Loader在哪里呢? 加载器将使用这个适配器。有关如何使用加载器的示例,请参阅developer.android.com/guide/components/loaders.html。在这个例子中 SimpleCursorAdapter 将是 MyAdapter SimpleCursorAdapter 是什么意思?您正在扩展 RecyclerView.Adapter。 我的意思是,如果您在示例中看到 SimpleCursorAdapter,您可以使用 MyAdapter 代替它。只要有 swapcursor 方法和其他一些方法,你扩展什么适配器都没有关系。 如果与上面@nbtk 的答案相比,似乎有点过度编码。【参考方案5】:我使用SortedList 作为后端创建了一个RecyclerViewCursorAdapter,扩展了RecyclerView.Adapter
可与SQLiteCursor 和Loaders 一起使用
【讨论】:
【参考方案6】:只是另一个答案,因为我不喜欢接受的答案(imo 没有直观的用法)。
以下是我自己的实现,与SimpleCursorAdapter
非常相似(部分灵感来自):
public class RecyclerViewSimpleCursorAdapter extends RecyclerView.Adapter
private int mLayout;
private Cursor mCursor;
private String[] mFrom;
private int[] mTo;
private boolean mAutoRequery;
private ContentObserver mContentObserver;
/**
* Standard constructor.
*
* @param layout resource identifier of a layout file that defines the views for this list item. The layout file should include at least those named views defined in "to"
* @param c The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null if the cursor is not available yet.
* @param to The views that should display column in the "from" parameter. These should all be TextViews and ImageViews. The first N views in this list are given the values of the first N columns in the from parameter. Can be null if the cursor is not available yet.
*/
public RecyclerViewSimpleCursorAdapter(int layout, Cursor c, String[] from, int[] to, boolean autoRequery)
mLayout = layout;
mCursor = c;
mFrom = from;
mTo = to;
mAutoRequery = autoRequery;
if (mAutoRequery)
initializeContentObserver();
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
return new RecyclerView.ViewHolder(
LayoutInflater.from(parent.getContext())
.inflate(mLayout, parent, false)
)
;
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
mCursor.moveToPosition(position);
if (mFrom == null || mTo == null)
return;
for (int i = 0; i < mFrom.length && i < mTo.length; i++)
String from = mFrom[i];
int columnIndex = mCursor.getColumnIndex(from);
String value = mCursor.getString(columnIndex);
View view = holder.itemView.findViewById(mTo[i]);
if (view instanceof TextView)
((TextView) view).setText(value);
else if (view instanceof ImageView)
try
((ImageView) view).setImageResource(Integer.parseInt(value));
catch (NumberFormatException nfe)
((ImageView) view).setImageURI(Uri.parse(value));
else
throw new IllegalStateException(view.getClass().getName() + " is not a view that can be bound by this RecyclerViewSimpleCursorAdapter");
@Override
public int getItemCount()
return mCursor != null ? mCursor.getCount() : 0;
private void initializeContentObserver()
mContentObserver = new ContentObserver(new Handler())
@Override
public boolean deliverSelfNotifications()
return true;
@Override
public void onChange(boolean selfChange)
notifyDataSetChanged();
;
mCursor.registerContentObserver(mContentObserver);
/**
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be closed.
*
* @param cursor The new cursor to be used
*/
public void changeCursor(Cursor cursor)
Cursor oldCursor = mCursor;
if (mAutoRequery)
if (mCursor != null)
mCursor.unregisterContentObserver(mContentObserver);
mContentObserver = new ContentObserver(new Handler())
@Override
public boolean deliverSelfNotifications()
return true;
@Override
public void onChange(boolean selfChange)
notifyDataSetChanged();
;
mCursor = cursor;
if (mCursor != null)
mCursor.registerContentObserver(mContentObserver);
notifyDataSetChanged();
if (oldCursor != null && oldCursor != mCursor)
oldCursor.close();
/**
* Change the cursor and change the column-to-view mappings at the same time.
*
* @param cursor The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null if the cursor is not available yet.
* @param to The views that should display column in the "from" parameter. These should all be TextViews or ImageViews. The first N views in this list are given the values of the first N columns in the from parameter. Can be null if the cursor is not available yet.
*/
public void changeCursorAndColumns(Cursor cursor, String[] from, int[] to)
mFrom = from;
mTo = to;
changeCursor(cursor);
/**
* Returns the cursor.
* @return the cursor
*/
public Cursor getCursor()
return mCursor;
您可以针对其他特定用途对其进行修改,但如果使用光标,它的工作方式与 SimpleCursorAdapter
一样,只是使用 RecyclerView
。
【讨论】:
【参考方案7】:从数据库中检索数据并存储在列表中后,可能应该是这样的:
dbHelper = new BooksDbAdapter(this);
dbHelper.open();
//Clean all data
dbHelper.deleteAllZist();
//Add some data
dbHelper.insertSomeRecords();
List<String> mylist = dbHelper.getArrayColumn(3);
将数据添加到recyclerview
list = new ArrayList<DataObject>();
Integer i=0;
for (String lst : mylist)
list.add(i++, new DataObject(lst,
"The RecyclerView widget is a more advanced and flexible
version of ListView."));
recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setHasFixedSize(true);
myRecAdapter = new RecyclerviewAdapter(list, Zist1ChapterActivity.this);
【讨论】:
【参考方案8】:下面是我为 recyclerview 实现的 cursoradapter。 它支持 OnItemClickListener、OnLongItemClickListener、OnfooterClickListener、Sections 和 footer。 不支持、Header、onHeaderClickListner、快速滚动条、置顶标题或置顶部分。
只需扩展此适配器并创建您自己的适配器。覆盖提供的方法。并从 OnCursorLoadFinished 方法传递光标。如果适配器已经创建,则 swapCursor()
package com.tracker.paisa;
import android.database.Cursor;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* @author Rahul Upadhyay (https://github.com/devDroidRaul)
* Supports footer
* Onitemclicklistener, OnItemLongClickListener, OnFooterClickListener
* Supports Sections.
*
* Does Not support,Header, OnHeaderClickListener, FastScroller, StickySection (this can b done with item decor)
* Pull requests are welcome for improvements.
*
* Override this to give logic to place subheaders between items.
* public abstract boolean onPlaceSubheaderBetweenItems(int position);
*
* create seperate viewHolders for item, subheaders and footer. and return required views.
*
* @Override below methods for individual item type.
* public abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);
* public abstract SH onCreateSubheaderViewHolder(ViewGroup parent, int viewType);
* public abstract FH onCreateFooterViewHolder(ViewGroup parent, int viewType);
*
* Bind your views with data here.
* @Override below methods to bind data to individual item types.
* public abstract void onBindSubHeaderViewHolder(SH holder, Cursor cursor);
* public abstract void onBindItemViewHolder(VH holder, Cursor cursor);
* public abstract void onBindFooterViewHolder(FH holder, int itemPosition);
*
* Item type -1,-2,-3 are reserved, kindly do not pass them in getItemViewType.
*/
public abstract class RecyclerViewCursorAdapter<SH extends RecyclerView.ViewHolder, VH extends RecyclerView.ViewHolder, FH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public static final String TAG = RecyclerViewCursorAdapter.class.getSimpleName();
private static final int TYPE_SECTION_HEADER = -1;
private static final int TYPE_MAIN_HEADER = -2;
private static final int TYPE_FOOTER = -3;
// private int headerLayout=0,viewLayout=0;
boolean createHeader;
private List<Integer> subheaderPositions = new ArrayList<>();
private Cursor mCursor;
private boolean mDataValid,footerRequired=false;
private int mRowIDColumn;
private SparseBooleanArray mSelectedItemsIds;
// public RecyclerViewCursorAdapter()
//constructor
public RecyclerViewCursorAdapter(Cursor c,boolean footerRequired)
setHasStableIds(true);
swapCursor(c);
this.footerRequired = footerRequired;
this.mSelectedItemsIds = new SparseBooleanArray();
// interface for listning click on recycler view;
public interface OnItemClickedListener
void OnItemClick(int id, Object data);
void onItemLongClick(int id);
OnItemClickedListener onItemClickedListener;
public void setOnItemClickedListener(OnItemClickedListener onItemClickedListener)
this.onItemClickedListener = onItemClickedListener;
public interface OnFooterClickedListener
void onFooterClick(Object data);
OnFooterClickedListener onFooterClickedListener;
public void setOnFooterClickedListener( OnFooterClickedListener onFooterClickedListener)
this.onFooterClickedListener = onFooterClickedListener;
public interface OnHeaderClickedListener
void onHeaderClick(Object data);
OnHeaderClickedListener onHeaderClickedListener;
public void setOnHeaderClickedListener( OnHeaderClickedListener onHeaderClickedListener)
this.onHeaderClickedListener = onHeaderClickedListener;
private void initSubheaderPositions()
subheaderPositions.clear();
if(getItemSize() != 0)
//TODO:Handle This please.
//subheaderPositions.add(0);
else
return;
for(int i = 1; i < getItemSize(); i++)
if(onPlaceSubheaderBetweenItems(i - 1))
subheaderPositions.add(i + subheaderPositions.size());
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView)
initSubheaderPositions();
/**
* Called when adapter needs to know whether to place subheader between two neighboring
* items.
*
* @return true if you want to place subheader between two neighboring
* items.
*/
public abstract boolean onPlaceSubheaderBetweenItems(int position);
public abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);
public abstract SH onCreateSubheaderViewHolder(ViewGroup parent, int viewType);
public abstract FH onCreateFooterViewHolder(ViewGroup parent, int viewType);
public abstract void onBindSubHeaderViewHolder(SH holder, Cursor cursor);
public abstract void onBindItemViewHolder(VH holder, Cursor cursor);
public abstract void onBindFooterViewHolder(FH holder, int itemPosition);
public abstract int getItemSize();
/**
* Return the view type of the item at position for the purposes
* of view recycling.
* Don't return -1. It's reserved for subheader view type.
*/
public int getViewType(int position)
return 0;
@Override
public final int getItemViewType(int position)
if(isSubheaderOnPosition(position))
return TYPE_SECTION_HEADER;
if(footerRequired && getCursor().getPosition()==(getCursor().getCount()-1))
return TYPE_FOOTER;
else
return getViewType(position);
public boolean isFooterAdded()
return footerRequired;
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
Log.d("RVCA-OCVH","create viewholder");
if(viewType == TYPE_SECTION_HEADER)
return onCreateSubheaderViewHolder(parent, viewType);
if(footerRequired&&viewType == TYPE_FOOTER)
return onCreateFooterViewHolder(parent, viewType);
else
return onCreateItemViewHolder(parent, viewType);
@SuppressWarnings("unchecked")
@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
Log.d("RVCA-OBVH","bind viewholder");
Log.d("RVCA-OBVH","subheader position:"+isSubheaderOnPosition(position));
if(isSubheaderOnPosition(position))
if (!mDataValid)
throw new IllegalStateException("Cannot bind viewholder when cursor is in invalid state.");
if (!mCursor.moveToPosition(getItemPositionForViewHolder(position)))
throw new IllegalStateException("Could not move cursor to position " + getItemPositionForViewHolder(position) + " when trying to bind viewholder");
onBindSubHeaderViewHolder((SH)holder, mCursor);
if(footerRequired && position==getItemCount()-1)
Log.d("RVCA-OBVH","bind footerHolder");
onBindFooterViewHolder((FH) holder,position);
else
if (!mDataValid)
throw new IllegalStateException("Cannot bind viewholder when cursor is in invalid state.");
if (!mCursor.moveToPosition(getItemPositionForViewHolder(position)))
throw new IllegalStateException("Could not move cursor to position " + getItemPositionForViewHolder(position) + " when trying to bind viewholder");
// if(!mCursor.isAfterLast())
// mCursor.moveToPosition(position);
onBindItemViewHolder((VH)holder, mCursor);
@Override
public final int getItemCount()
return getItemSize() + subheaderPositions.size()+(footerRequired?1:0);
public void notifyDataChanged()
initSubheaderPositions();
notifyDataSetChanged();
public void notifyItemInsertedAtPosition(int itemPosition)
if (itemPosition == 0)
if (getItemCount() == 1 || onPlaceSubheaderBetweenItems(itemPosition))
subheaderPositions.add(0, 0);
increaseSubheaderPositions(1, 2);
notifyItemRangeInserted(0, 2);
else
increaseSubheaderPositions(1, 1);
notifyItemInserted(1);
else if (itemPosition == getItemSize() - 1)
if (onPlaceSubheaderBetweenItems(itemPosition - 1))
subheaderPositions.add(getItemCount() - 1);
notifyItemRangeInserted(getItemCount() - 1, 2);
else
notifyItemInserted(getItemPositionInRecyclerView(itemPosition));
else
if (onPlaceSubheaderBetweenItems(itemPosition - 1) && onPlaceSubheaderBetweenItems(itemPosition))
final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition - 1);
final int countOfSubheadersBeforePosition = getCountOfSubheadersBeforePosition(itemPositionInRv);
subheaderPositions.add(countOfSubheadersBeforePosition, itemPositionInRv + 1);
increaseSubheaderPositions(countOfSubheadersBeforePosition + 1, 2);
notifyItemRangeInserted(itemPositionInRv + 1, 2);
else if (onPlaceSubheaderBetweenItems(itemPosition))
final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition - 1);
increaseSubheaderPositions(getCountOfSubheadersBeforePosition(itemPositionInRv), 1);
notifyItemInserted(itemPositionInRv + 1);
else if (onPlaceSubheaderBetweenItems(itemPosition - 1))
final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition);
increaseSubheaderPositions(getCountOfSubheadersBeforePosition(itemPositionInRv), 1);
notifyItemInserted(itemPositionInRv);
else
final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition);
increaseSubheaderPositions(getCountOfSubheadersBeforePosition(itemPositionInRv), 1);
notifyItemInserted(itemPositionInRv);
public void notifyItemChangedAtPosition(int itemPosition)
final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition);
notifyItemChanged(itemPositionInRv);
public void notifyItemRemovedAtPosition(int itemPosition)
final int itemPositionInRv = getItemPositionInRecyclerView(itemPosition);
for (int i = 1; i < subheaderPositions.size(); i++)
final int subheaderPosition = subheaderPositions.get(i);
if (subheaderPosition > itemPositionInRv)
final int previousSubheaderPosition = subheaderPositions.get(i - 1);
if (subheaderPosition - previousSubheaderPosition == 2)
subheaderPositions.remove(subheaderPositions.indexOf(previousSubheaderPosition));
decreaseSubheaderPositions(subheaderPositions.indexOf(subheaderPosition), 2);
notifyItemRangeRemoved(itemPositionInRv - 1, 2);
else
decreaseSubheaderPositions(subheaderPositions.indexOf(subheaderPosition), 1);
notifyItemRemoved(itemPositionInRv);
return;
final int lastSubheaderPosition = subheaderPositions.get(subheaderPositions.size() - 1);
if (itemPositionInRv - lastSubheaderPosition == 1 && getItemCount() == itemPosition + subheaderPositions.size())
subheaderPositions.remove(subheaderPositions.size() - 1);
notifyItemRangeRemoved(itemPositionInRv - 1, 2);
else
notifyItemRemoved(itemPositionInRv);
public void setGridLayoutManager(final GridLayoutManager gridLayoutManager)
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
@Override
public int getSpanSize(int position)
if(subheaderPositions.contains(position))
return gridLayoutManager.getSpanCount();
else
return 1;
);
public boolean isSubheaderOnPosition(int position)
return subheaderPositions.contains(position);
public int getCountOfSubheadersBeforePosition(int position)
int count = 0;
for(int subheaderPosition : subheaderPositions)
if(subheaderPosition < position)
count++;
return count;
public int getItemPositionInRecyclerView(int position)
int countOfItems = 0;
for (int i = 1; i < subheaderPositions.size(); i++)
final int previousSubheaderPosition = subheaderPositions.get(i - 1);
final int nextSubheaderPosition = subheaderPositions.get(i);
countOfItems += nextSubheaderPosition - previousSubheaderPosition - 1;
if (countOfItems > position)
return position + i;
return position + subheaderPositions.size();
public int getItemPositionForViewHolder(int viewHolderPosition)
return viewHolderPosition - getCountOfSubheadersBeforePosition(viewHolderPosition);
private void decreaseSubheaderPositions(int startSubheaderPosition, int decreaseNum)
for (int i = startSubheaderPosition; i < subheaderPositions.size(); i++)
final int subheaderPosition = subheaderPositions.get(i);
subheaderPositions.set(i, subheaderPosition - decreaseNum);
private void increaseSubheaderPositions(int startSubheaderPosition, int increaseNum)
for (int i = startSubheaderPosition; i < subheaderPositions.size(); i++)
final int subheaderPosition = subheaderPositions.get(i);
subheaderPositions.set(i, subheaderPosition + increaseNum);
private List<Integer> getSubheaderPositions()
return subheaderPositions;
public int getSubheaderCount()
return subheaderPositions.size();
public void swapCursor(Cursor newCursor)
Log.d("RVCA-SC","swap cursor");
if (newCursor == mCursor)
Log.d("RVCA-SC","same cursor doing nothing");
return;
if (newCursor != null)
Log.d("RVCA-SC","swap cursor not null");
mCursor = newCursor;
mRowIDColumn = mCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
// notify the observers about the new cursor
notifyDataChanged();
else
Log.d("RVCA-SC","swap cursor null");
notifyItemRangeRemoved(0, getItemCount());
mCursor = null;
mRowIDColumn = -1;
mDataValid = false;
public Cursor getCursor()
return mCursor ;
@Override
public long getItemId(int position)
if (isSubheaderOnPosition(position))
return position;
else
int cursorPosition = getItemPositionForViewHolder(position);
Cursor cursor = getCursor();
if (hasOpenCursor() && cursor.moveToPosition(cursorPosition))
return cursor.getLong(cursor.getColumnIndex("_id"));
return NO_CURSOR_POSITION;
public static final int NO_CURSOR_POSITION = -99;
protected boolean hasOpenCursor()
Cursor cursor = getCursor();
if (cursor == null || cursor.isClosed())
swapCursor(null);
return false;
return true;
//Methods to do Selection
public void toggleSelection(int position)
selectView(position, !mSelectedItemsIds.get(position));
//Remove selected selections
public void removeSelection()
mSelectedItemsIds = new SparseBooleanArray();
notifyDataSetChanged();
//Put or delete selected position into SparseBooleanArray
public void selectView(int position, boolean value)
if (value)
mSelectedItemsIds.put(position, value);
else
mSelectedItemsIds.delete(position);
// notifyItemChangedAtPosition(position);
notifyDataSetChanged();
//Get total selected count
public int getSelectedCount()
return mSelectedItemsIds.size();
//Return all selected ids
public SparseBooleanArray getSelectedIds()
return mSelectedItemsIds;
public void setSelectedItemIds(SparseBooleanArray selectedItemIds)
this.mSelectedItemsIds=selectedItemIds;
【讨论】:
【参考方案9】:最简单的实现是在适配器中使用游标对象并将游标传递给viewholder构造函数以修改视图并更改onBindViewholder中的游标位置。
如果您使用游标加载器,请调用 onLoadfinished()
中的 setCursor 方法并在 onLoadReset()
中传递 null
public class PetCursorRecyclerViewAdapter extends RecyclerView.Adapter<PetCursorRecyclerViewAdapter.ViewHolder>
Cursor cursor = null;
Context context;
public PetCursorRecyclerViewAdapter(Context context)
this.context = context;
public void setCursor(Cursor cursor)
this.cursor = cursor;
notifyDataSetChanged();
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
View v = LayoutInflater.from(context).inflate(R.layout.catalog_item, parent, false);
return new ViewHolder(v);
@Override
public void onBindViewHolder(ViewHolder holder, int position)
this.cursor.moveToPosition(position);
holder.bindModel(this.cursor);
/* getItemCount() returns the count of videos from the Cursor, or 0 if the
Cursor is null (mimicking the behavior of CursorAdapter, which also treats
a null Cursor as merely being one that has no rows)*/
@Override
public int getItemCount()
if (cursor == null)
return 0;
else
return cursor.getCount();
public static class ViewHolder extends RecyclerView.ViewHolder
private TextView name_tv, breed_tv;
public ViewHolder(View itemView)
super(itemView);
name_tv = (TextView) itemView.findViewById(R.id.name_tv);
breed_tv = (TextView) itemView.findViewById(R.id.breed_tv);
public void bindModel(Cursor cursor)
int name_index = cursor.getColumnIndexOrThrow(PetsContract.PetEntry.COLUMN_PET_NAME);
int breed_index = cursor.getColumnIndexOrThrow(PetsContract.PetEntry.COLUMN_PET_BREED);
name_tv.setText(cursor.getString(name_index));
String breed = cursor.getString(breed_index);
if (TextUtils.isEmpty(breed))
breed = "Unknown Breed";
breed_tv.setText(breed);
【讨论】:
【参考方案10】:最后,我们为数据库/网络实现了 RecyclerView.Adapter。
它是用安卓Architecture Components实现的:
Room 数据库:位于 SQLite 数据库之上的数据库层,负责处理您过去使用 SQLiteOpenHelper
处理的日常任务。用作底层 SQLite 数据库访问点的数据库持有者。 Room 数据库使用 DAO 向 SQLite 数据库发出查询。
ViewModel:向 UI 提供数据。充当存储库和 UI 之间的通信中心。隐藏数据源自 UI 的位置。 ViewModel 实例在 Activity/Fragment 重新创建后仍然存在。
LiveData:可以观察到的数据持有者类。始终保存/缓存最新版本的数据。当数据发生变化时通知其观察者。 LiveData 具有生命周期意识。 UI 组件只是观察相关数据,不会停止或恢复观察。 LiveData 会自动管理所有这些,因为它在观察时知道相关的生命周期状态变化。
【讨论】:
【参考方案11】:您可以使用 SQLite 数据库来存储详细信息。访问数据很容易。 你可以在 github 上查看我的代码。 https://github.com/thiru-wta/ToDo
database = new Database(this);
getSupportActionBar().setTitle("To Do List");
etAddItems = (EditText) findViewById(R.id.etAddItem);
btnAdd = (Button) findViewById(R.id.btnAdd);
mRecyclerView = (RecyclerView) findViewById(R.id.listView);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new RecyclerAdapter(this, listData);
mRecyclerView.setAdapter(adapter);
btnAdd.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
event_name = etAddItems.getText().toString();
String getname="";
database.storeEventDetails(event_name);
getname = database.getEventDetails(event_name);
if (getname.length() != 0)
listData.add(getname);
etAddItems.setText("");
adapter.notifyData(listData);
数据库方法:
public void storeEventDetails(String event_name, long timerStart)
SQLiteDatabase db1 = getWritableDatabase();
db1.execSQL("insert into '"+event_details_tbl+"' values('" + event_name + "')");
db1.close();
获取方法:
public String getEventDetails(String event_name)
SQLiteDatabase db1 = getReadableDatabase();
Cursor cur = db1.rawQuery("select * from '"+event_details_tbl+"' where event_name ='" + event_name + "'", null);
cur.moveToFirst();
String evName = null;
if (cur != null)
do
int eventName = cur.getColumnIndex("event_name");
String ev_name = cur.getString(eventName);
evName = ev_name;
while (cur.moveToNext());
cur.close();
db1.close();
return evName;
【讨论】:
【参考方案12】:您只需按照以下步骤操作:
在回收器view.adapter
中,创建一个初始化为空值的ArrayList 或Iterator。
创建一个类似于前交换光标的方法,例如swapdata(Arraylist<T> data)
。在里面你给数组列表、迭代器或系统可以使用bindview中的整数位置迭代的任何结构提供一个新值。此方法的值在onloaderfinished()
中传递。然后在分配notifydatachange()
后立即打电话;这些将要求RecyclerView
使用 ArrayList 的新数据重绘所有列表。
在您使用 loadercalback 的活动或片段中,创建一个将光标转换为数组列表或迭代器的方法,具体取决于适配器中选择的数据结构。
这样你就可以在不阻塞主线程的情况下使用 loadercalback。
【讨论】:
以上是关于将 recyclerview 与数据库一起使用的主要内容,如果未能解决你的问题,请参考以下文章
如何将 DiffUtil.Callback 与具有页眉和页脚的 RecyclerView 一起使用?
如何将SQLite数据与可重用的RecyclerView适配器一起使用
java 如何将Cardview与RecyclerView + Retrofit一起使用
将 AsyncTask 与 RecyclerView 一起使用:没有结果,但 asynctask 似乎正在工作