Відповіді:
Якщо ви запускаєте запит з a CursorLoader
і хочете RecyclerView
замість цього ListView
.
Ви можете спробувати мій CursorRecyclerViewAdapter
: CursorAdapter в RecyclerView
setHasStableIds(true)
Моє рішення полягало в тому, щоб утримувати член CursorAdapter в моїй реалізації recilerView.Adapter. Потім передайте все керування створенням нового перегляду та прив’язування його до адаптера курсору, приблизно так:
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);
}
}
public void changeCursor(Cursor cursor){ mCursorAdapter.changeCursor(cursor); notifyDataSetChanged(); }
Оскільки у вашому запитанні написано "Як користуватися RecyclerView
базою даних", і ви не конкретно визначаєте, чи хочете ви SQLite чи що-небудь ще з цим RecyclerView
, я дам вам оптимальне рішення. Я буду використовувати Realm як базу даних і дозволю вам відображати всі дані всередині вашого RecyclerView
. Він також підтримує асинхронний запит без використання Loaders
або AsyncTask
.
Крок 1
Додайте залежність градусів для Realm, тут можна знайти залежність для останньої версії
Крок 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_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>
Зауважте, що я зберігав FrameLayout
як корінь, навіть якщо я маю 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
через те, що немає прямого способу дізнатися, яку позицію елемент було вставлено в базу даних, оскільки записи Realm не зберігаються впорядкованому порядку. RealmResults
Об'єкт автоматично оновлюється кожного разу , коли новий елемент додається, змінений або видалений з бази даних , тому ми називаємо в notifyDataSetChanged
той час як додавання і вставкою записів сипучими. На даний момент ви, мабуть, стурбовані анімацією, яка не буде запущена, тому що ви викликаєте notifyDataSetChanged
замість notifyXXX
методів. Саме тому я маю getItemId
метод повернути часову позначку для кожного рядка з об’єкта результатів. Анімація досягається в 2 етапи, notifyDataSetChanged
якщо ви зателефонуєте setHasStableIds(true)
та переадресуєтеgetItemId
забезпечити щось інше, ніж просто посаду.
Крок 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_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/text_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
Я додав, 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);
}
Зауважте, що на першому кроці я запитую Realm, щоб дати мені всі об'єкти з Data
класу, відсортовані за їхньою назвою змінної, звані даними асинхронно. Це дає мені RealmResults
об'єкт із 0 елементами на головній нитці, яку я встановлюю на Adapter
. Я додав RealmChangeListener
сповіщення, коли дані закінчуються з фонового потоку, куди я дзвоню notifyDataSetChanged
зі своїм Adapter
. Я також закликав setHasStableIds
до true, щоб RecyclerView.Adapter
впровадження відстежувало елементи, які додаються, видаляються чи змінюються. Функція onDestroy
for my Activity
закриває екземпляр Realm
@Override
protected void onDestroy() {
super.onDestroy();
mRealm.close();
}
Цей метод initUi
може бути викликаний всередині onCreate
з ваших Activity
або onCreateView
або onViewCreated
з ваших Fragment
. Зауважте такі речі.
Крок 7
БАМ! Існують дані з бази даних всередині вашої RecyclerView
asynchonously завантажені без CursorLoader
, CursorAdapter
, SQLiteOpenHelper
з анімацією. Зображення GIF, показане тут, начебто мляве, але анімація відбувається, коли ви додаєте елементи чи видаляєте їх.
Ви можете реалізувати всі необхідні методи самостійно. Нещодавно я зробив власну реалізацію, просто скопіювавши код вставлення з 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();
}
}
Я зробив RecyclerViewCursorAdapter, використовуючи SortedList як бекенд, розширивши RecyclerView.Adapter
Можна використовувати з SQLiteCursor та навантажувачами
Ще одна відповідь, оскільки мені не сподобався прийнятий (який 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
.
Після отримання даних із БД та збереження у списку, можливо, це має бути щось подібне:
dbHelper = new BooksDbAdapter(this);
dbHelper.open();
//Clean all data
dbHelper.deleteAllZist();
//Add some data
dbHelper.insertSomeRecords();
List<String> mylist = dbHelper.getArrayColumn(3);
додати дані до перегляду рециркуляторів
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, розділи та колонтитул. Не підтримує Header, onHeaderClickListner, швидку прокрутку, липкий заголовок або липкі розділи.
Просто продовжте цей адаптер і створіть свій власний адаптер. Перевизначте запропоновані методи. і передайте курсор від OnCursorLoadFinished method.і якщо адаптер вже створений, тоді 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;
}
}
Найпростіша реалізація полягає у використанні об'єкта курсору в адаптері та передачі курсору конструктору переглядача для зміни представлень та зміни положення курсору в onBindViewholder.
Якщо ви використовуєте навантажувач курсору, зателефонуйте до методу setCursor onLoadfinished()
та ввівши нуль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);
}
}
}
Нарешті, у нас є реалізація RecyclerView.Adapter для бази даних / мережі.
Це реалізовано за допомогою компонентів архітектури Android :
Кімнатна база даних: рівень баз даних поверх бази даних SQLite, який забезпечує повсякденні завдання, з якими ви працювали SQLiteOpenHelper
. Власник бази даних, який служить точкою доступу до базової бази даних SQLite. База даних кімнат використовує DAO для видачі запитів до бази даних SQLite.
ViewModel: надає дані в інтерфейс користувача. Діє як центр зв'язку між сховищем та інтерфейсом користувача. Приховує, звідки дані походять з інтерфейсу користувача. Вияви ViewModel виживають активність / фрагменти відпочинку.
LiveData: клас власника даних, який можна спостерігати. Завжди зберігає / зберігає останню версію даних. Повідомляє своїх спостерігачів, коли дані змінилися. LiveData знає життєвий цикл. Компоненти інтерфейсу просто спостерігають за відповідними даними та не зупиняють і не продовжують спостереження. LiveData автоматично керує всім цим, оскільки знає про відповідні зміни стану життєвого циклу під час спостереження.
Ви можете використовувати базу даних 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;
}
Ви просто повинні виконати наступні кроки:
view.adapter
створіть ArrayList або ітератор, ініціалізований з нульовим значенням.swapdata(Arraylist<T> data)
. Всередині ви даєте нові значення списку масивів, ітератору або будь-якій структурі, яку система може повторити, використовуючи цілу позицію int в bindview. Значення цього методу передаються в onloaderfinished()
. Потім зателефонуйте відразу після присвоєння notifydatachange()
; вони попросять RecyclerView
перемалювати весь список новими даними ArrayList.У діяльності або фрагменті, де ви використовуєте завантажувач зворотного зв'язку, створіть метод, який перетворює курсор на масив чи ітератор, залежно від структури даних, обраної у адаптері.
Таким чином, ви завжди можете використовувати зворотний навантажувач, не блокуючи основну нитку.