与数据库一起使用recyclerview
目前没有可用的RecyclerView.Adapter的默认实现。
可能随着官方发布,Google会join它。
由于目前还没有对CursorAdapter与recyclerview的支持,我们如何使用数据库的recyclerview? 有什么build议么 ?
如果您正在使用CursorLoader运行查询,而您想要RecyclerView而不是ListView。
您可以尝试我的CursorRecyclerViewAdapter: 在RecyclerView中的CursorAdapter
我的解决scheme是在我的recyclerView.Adapter实现中持有一个CursorAdapter成员。 然后传递创build新视图的所有处理并将其绑定到游标适配器,如下所示:
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); } }
由于你的问题是“如何使用RecyclerView和数据库”,而且你不需要SQLite或者其他的RecyclerView,我会给你一个高度优化的解决scheme。 我将使用Realm作为数据库,并让您显示RecyclerView中的所有数据。 它具有asynchronous查询支持,而不使用Loaders或AsyncTask 。
为什么要领域?
步骤1
为Realm添加gradle依赖项,在这里find最新版本的依赖项
第2步
例如,创build你的模型类,让我们说一些简单的数据 ,例如有两个字段的数据 ,一个在RecyclerView行内显示的string以及一个用作itemId的时间戳,以允许RecyclerViewanimation项目。 请注意,我扩展了下面的RealmObject ,因为它的Data类将作为一个表存储,并且所有的属性都将存储为表Data的列。 我已经把数据文本标记为主键,因为我不想多次添加一个string。 但是如果你喜欢重复的话,那么把时间戳记作为@PrimaryKey。 你可以有一个没有主键的表,但是如果你在创build它之后尝试更新这个行,会导致问题。 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步
创build您的布局,以便如何在RecyclerView中显示单个行。 适配器内单行项目的布局如下
<?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>
请注意,即使我有一个TextView里面,我已经保持一个FrameLayout作为根。 我打算在这个布局中添加更多的项目,因此使它现在变得灵活:)
对于那些好奇的人来说,这就是目前单个项目的外观。
步骤4
创build您的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); } } }
请注意,我正在调用notifyItemRemoved的移除发生的位置,但我不会调用notifyItemInserted或notifyItemRangeChanged,因为没有直接的方法知道该项目插入到数据库中的哪个位置,因为Realm项目没有以有序的方式存储。 RealmResults对象在每次添加,修改或从数据库中删除新项目时自动更新,所以我们在添加和插入批量条目时调用notifyDataSetChanged 。 此时,您可能会担心由于您正在调用notifyDataSetChanged而不是notifyXXX方法而不会触发的animation。 这正是我为什么getItemId方法返回结果对象中每一行的时间戳的原因。 如果调用setHasStableIds (true),则通过notifyDataSetChanged实现animation,然后重写getItemId以提供除位置之外的其他内容。
第5步
让我们将RecyclerView添加到我们的Activity或Fragment中。 在我的情况下,我正在使用一个活动。 包含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" />
我添加了一个应用程序:layout_behavior,因为我的RecyclerView进入了一个CoordinatorLayout,为了简洁,我没有在这个答案中发布。
第6步
在代码中构buildRecyclerView并提供所需的数据。 在onCreate中创build并初始化一个Realm对象,并在onDestroy里closures它就像closures一个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,以asynchronous的方式向我提供Data类中的所有对象,这些对象按其名称为data的variables名sorting。 这给了我一个RealmResults对象,在主线程上有0个项目,我在适配器上设置。 我添加了一个RealmChangeListener,当数据完成加载后,我通过我的Adapter调用notifyDataSetChanged后台线程通知。 我还调用了setHasStableIds为true,让RecyclerView.Adapter实现跟踪添加,删除或修改的项目。 我的Activity的onDestroyclosures了Realm实例
@Override protected void onDestroy() { super.onDestroy(); mRealm.close(); }
initUi这个方法可以在你的Activity的onCreate里面调用,或者在你的Fragment的onCreateView或onViewCreated里面调用。 注意下面的事情。
第七步
BAM! 有从您的RecyclerViewasynchronous加载没有CursorLoader,CursorAdapter,SQLiteOpenHelperanimation的数据库中的数据。 这里显示的GIF图像比较滞后,但添加项目或删除项目时会出现animation。
你可以自己实现所有需要的方法。 我最近通过从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(); } }
我做了一个使用SortedList作为后端的RecyclerViewCursorAdapter ,扩展了RecyclerView.Adapter
可以与SQLiteCursor和Loaders一起使用
只是另一个答案,因为我不喜欢接受的(这是一个非常直观的用法)。
以下是我自己的实现,它与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
一起使用。
从数据库中检索数据并存储在列表中,也许它应该是这样的:
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);
下面是我的回收视图的cursoradapter的实现。 它支持OnItemClickListener,OnLongItemClickListener,OnfooterClickListener,部分和页脚。 不支持 ,标题,onHeaderClickListner,快速滚动,粘滞标题或粘滞部分。
只需扩展此适配器并创build您自己的适配器。 覆盖所提供的方法。 并从OnCursorLoadFinished方法传递光标。如果适配器已经创build,则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; } }
Simplest implementation is to use cursor object in the adapter and pass cursor to viewholder constructor for modifying views and change the cursor position in onBindViewholder.
If you are using cursor Loader call the setCursor method in onLoadfinished()
and passing null in onLoadReset()
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); } }
}
You can use SQLite database to store the details. It is easy to access the data. You can check my code on 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); }
Database methods:
public void storeEventDetails(String event_name, long timerStart) { SQLiteDatabase db1 = getWritableDatabase(); db1.execSQL("insert into '"+event_details_tbl+"' values('" + event_name + "')"); db1.close(); }
get method:
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; }
You have simply to follow these steps:
- In the recycler
view.adapter
, create an ArrayList or an Iterator initialized with a null value. - Create a method like the ex swapcursor, for example
swapdata(Arraylist<T> data)
. Inside you give a new values to the array list, iterator or any structure that system can iterate using the integer position int the bindview. The values of this method are passed in theonloaderfinished()
. Then call in just after assignementnotifydatachange()
; these will ask theRecyclerView
to redraw all the list with new data of the ArrayList.
In the activity or fragment where you are using the loadercalback, create a method that transforms the cursor to an arraylist, or iterator, depending on the data structure chosen in the adapter.
This way you can always use the loadercalback with out blocking the main thread.