RecyclerView.Adapter:全能notify解决方案
原文鏈接: https://loshine.me/2016/08/25/a-universal-solution-of-recyclerview-adapter-notify/
在之前我們用 ListView 或者 GridView 的時候,通知適配器刷新是這樣的:
adapter.notifyDataSetChanged();但是當我們使用了更強大的 RecyclerView 之后,如果直接這樣通知適配器刷新將不會顯示動畫效果。它會直接將所有的 item 重新繪制。
我們需要使用如下的方法來通知適配器刷新,這樣 RecyclerView 才會顯示對應的動畫效果:
adapter.notifyItemInserted(); adapter.notifyItemChanged(); adapter.notifyItemMoved(); adapter.notifyItemRemoved(); adapter.notifyItemRangeChanged(); adapter.notifyItemRangeInserted(); adapter.notifyItemRangeRemoved();在這次更新的 Support Library 24.2.0 中添加了一個新的工具類,可以用來方便快捷的處理 RecyclerView.Adapter 的通知刷新。
DiffUtil
DifUtil 就是這次引入的工具類,它會找出 Adapter 中每一個 Item 對應發生的變化,然后對每一個變化給予對應的刷新。
最重要的就是如下的兩個重載方法
DifUtil.calculateDiff(Callback cb, boolean detectMoves); DifUtil.calculateDiff(Callback cb);其中DifUtil.calculateDiff(Callback cb);實際上就是DifUtil.calculateDiff(callback, true);所以我們著重研究第一個方法即可。
該方法會接收兩個參數,其中第二個參數是一個 boolean 值,查看源碼注釋我們知道這個參數有如下作用:
True if DiffUtil should try to detect moved items, false otherwise.
如果 DiffUtil 嘗試檢測移動的項目就設為 true,否則設為 false。
這個參數實際上是指定是否需要項目移動的檢測,如果設為 false ,那么一個項目移動了會先判定為 remove,再判定為 insert。
而Callback是一個抽象類,它有四個方法需要實現:
public abstract static class Callback {/*** 舊的數據源的大小*/public abstract int getOldListSize();/*** 新的數據源的大小*/public abstract int getNewListSize();/*** 該方法用于判斷兩個 Object 是否是相同的 Item,比如有唯一標識的時候應該比較唯一標識是否相等*/public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);/*** 當 areItemsTheSame 返回 true 時調用該方法,返回顯示的 Item 的內容是否一致*/public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition); }如上所述,我們四個需要實現的方法的作用都在注釋中寫出了。前兩個方法都很好理解,需要重點說明的是后兩個
areItemsTheSame:這個方法用來判斷兩個 Object 是否是相同的 Item,此處最好不要簡單的用equals方法判斷,我們可以根據 Object 的唯一標識或者自己指定一個規則來判斷兩個 Object 是否是展示的相同的 Item。
areContentsTheSame:該方法只有在areItemsTheSame返回true之后才會被調用,我們在重寫該方法的時候,只需要判斷兩個 Object 顯示的元素是否一致即可。如我們有兩個 Object,它們可能擁有很多屬性,但是其中只有兩個屬性需要被顯示出來,那只要這兩個屬性一致我們這個方法就要返回true。
使用 DiffUtils 通知刷新
下面我們寫一個簡單的例子來學習使用 DiffUtil
首先我們來一個 Item 對應的數據類:
public class Student {public String id; // 學號是唯一的public String name; // 名字可能重復public Student(String id, String name) {this.id = id;this.name = name;} }然后寫一個 Adapter:
class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {private final List<Student> datas;public MyAdapter(List<Student> datas) {this.datas = datas;}@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false);return new ViewHolder(view);}@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {holder.setData(datas.get(position));}@Overridepublic int getItemCount() {return datas.size();}class ViewHolder extends RecyclerView.ViewHolder {public ViewHolder(View itemView) {super(itemView);}public void setData(Student student) {TextView textView = (TextView) this.itemView.findViewById(R.id.text);textView.setText(student.name);}} }其對應的布局文件就是一個簡單的 TextView:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/text"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:orientation="vertical"android:padding="10dp"tools:text="content"/>然后我們在 Activity 里使用它們并顯示出來:
class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {// ...mRandom = new Random();datas = new ArrayList<>();for (int i = 0; i < 10; i++) {datas.add(new Student(mRandom.nextInt(3000) + "", "Students: " + i));}mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);mRecyclerView.setLayoutManager(new LinearLayoutManager(this));mAdapter = new MyAdapter(datas);mRecyclerView.setAdapter(mAdapter);// ...} }這樣我們就獲得了一個簡單的展示學生數據的 RecyclerView 了。
然后我們對 Adapter 的數據源進行更改,并通知刷新:
mFab.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {// 創建一個原來的 List 的副本final ArrayList<Student> oldTemp = new ArrayList<>(datas);// 更改原數據源datas.remove(mRandom.nextInt(mAdapter.getItemCount()));for (int i = 0; i < mRandom.nextInt(3); i++) {datas.add(mRandom.nextInt(mAdapter.getItemCount() - 1),new Student(mRandom.nextInt(3000) + "", "Students: " + mRandom.nextDouble()));}// 實現 CallbackDiffUtil.Callback callback = new DiffUtil.Callback() {@Overridepublic int getOldListSize() {return oldTemp.size();}@Overridepublic int getNewListSize() {return datas.size();}@Overridepublic boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {return oldTemp.get(oldItemPosition).id.equals(datas.get(newItemPosition).id);}@Overridepublic boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {return oldTemp.get(oldItemPosition).name.equals(datas.get(newItemPosition).name);}};DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);// 把結果應用到 adapterdiffResult.dispatchUpdatesTo(mAdapter);}});效果如下:
DiffUtil 的使用就是這樣,根據 DiffUtil.Callback 計算出 Result,然后應用更新到 Adapter。
封裝
有的人可能說了,這樣其實并不好用啊,我們原來數據的改變就直接使用對應的方法就可以了,你這里每次還要寫得這么麻煩。那么我們就使用 DiffUtil 和 Adapter 結合再進行一次封裝吧。
我們抽取一個 BaseAdapter 出來:
public abstract class BaseAdapter<T, V extends RecyclerView.ViewHolder>extends RecyclerView.Adapter<V>{protected final List<T> temp; // 用于保存修改之前的數據源的副本protected final List<T> datas; // 數據源public BaseAdapter(List<T> datas) {this.datas = datas;temp = new ArrayList<>(datas);}protected abstract boolean areItemsTheSame(T oldItem, T newItem);protected abstract boolean areContentsTheSame(T oldItem, T newItem);@Overridepublic int getItemCount() {return datas.size();}public void notifyDiff() {DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {@Overridepublic int getOldListSize() {return temp.size();}@Overridepublic int getNewListSize() {return datas.size();}// 判斷是否是同一個 item@Overridepublic boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {return BaseAdapter.this.areItemsTheSame(temp.get(oldItemPosition), datas.get(newItemPosition));}// 如果是同一個 item 判斷內容是否相同@Overridepublic boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {return BaseAdapter.this.areContentsTheSame(temp.get(oldItemPosition), datas.get(newItemPosition));}});diffResult.dispatchUpdatesTo(this);// 通知刷新了之后,要更新副本數據到最新temp.clear();temp.addAll(datas);} }然后我們只需要令 Adapter 實現 BaseAdapter即可:
class MyAdapter extends BaseAdapter<Student, MyAdapter.ViewHolder> {public MyAdapter(List<Student> datas) {super(datas);}@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false);return new ViewHolder(view);}@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {holder.setData(datas.get(position));}@Overridepublic boolean areItemsTheSame(Student oldItem, Student newItem) {return oldItem.id.equals(newItem.id);}@Overridepublic boolean areContentsTheSame(Student oldItem, Student newItem) {return oldItem.name.equals(newItem.name);}class ViewHolder extends RecyclerView.ViewHolder {public ViewHolder(View itemView) {super(itemView);}public void setData(Student student) {TextView textView = (TextView) this.itemView.findViewById(R.id.text);textView.setText(student.name);}} }之后我們如果數據源 List 中的數據有任何改動,我們只需要調用notifyDiff()就可以了:
mFab.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {datas.remove(mRandom.nextInt(mAdapter.getItemCount()));for (int i = 0; i < mRandom.nextInt(3); i++) {datas.add(mRandom.nextInt(mAdapter.getItemCount() - 1),new Student(mRandom.nextInt(3000) + "", "Students: " + mRandom.nextDouble()));}mAdapter.notifyDiff();}});總結
最新 Support 包中的 DiffUtil 類給我們帶來了一個對 RecyclerView 的不同數據變化的統一處理方案,可以對所有數據變化之后的通知刷新簡化,非常好用,強烈推薦使用。
參考
Android開發學習之路-DiffUtil使用教程
Android 7.0帶來的新工具類:DiffUtil
RecyclerView:使用DiffUtil刷新錯位
【Android】你可能不知道的Support(一) 0步自動定向刷新:SortedList
原文鏈接:http://www.sxrczx.com/pages/kohoh1992.github.io/cursor-auto-sync/index_1431878338570.html
在Android日常開發中,時常會請求數據到Cursor,然后再通過Cursor獲取數據。像SQLiteDatabase和ContentProvider都使用了Cursor。在這些應用中,往往希望當數據發生改變時,Cursor也會自動的更新數據。這篇文章,我就會向你闡述如何通過Android自身的API實現Cursor的自動更新。另外我還將向你闡述這背后的原理。通過這些原理你可以舉一反三的實現更為廣泛的自動跟新。
文章中的代碼
可以在https://github.com/KOHOH1992/CursorSyncDemo中找到文章中出現的代碼
該項目共有4個分支。use_provider分支介紹了使用ContentProvider實現Cursor同步更新的方法。use_database分支介紹了不使用ContentProvider實現Cursor同步更新的方法。use_adapter分支介紹了不使用Loader實現Cursor同步更新的方法。
Cursor自動更新的實現
前提
首先假設項目使用了如下的前提
- 數據存儲在SqliteDataBase當中
- 通對ContentProvider的請求,獲取封裝了數據的Cursor
- 使用CursorLoader加載數據
- 使用AdapterView和CursorAdapter顯示數據
定義同步標志
static final Uri SYNC_SIGNAL_URI = Uri.parse("content://com.kohoh.cursorsyncdemo/SYNC_SIGNAL");在ContentProvider的query中設置NotificationUri
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder) {SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(ContactContract.CONTACT_TABLE, projection,selection,selectionArgs, null, null, sortOrder);//設置NotificationUricursor.setNotificationUri(contentResolver, ContactContract.SYNC_SIGNAL_URI);return cursor; }在ContentProvider的insert,update,delete中觸發NotificationUri
@Override public Uri insert(Uri uri, ContentValues values) {SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();long id = database.insert(ContactContract.CONTACT_TABLE, null, values);if (id >= 0) {//觸發NotificationUricontentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return uri.withAppendedPath(ContactContract.CONTACT_URI, String.valueOf(id)); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();int result = database.update(ContactContract.CONTACT_TABLE, values, selection, selectionArgs);if (result > 0) {//觸發NotificationUricontentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return result; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) {SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();int result = database.delete(ContactContract.CONTACT_TABLE, selection, selectionArgs);if (result > 0) {//觸發NotificationUricontentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return result; }CursorLoader
ForceLoadContentObserver mObserver;public Cursor loadInBackground() {...try {//不過多解釋,耗時的查詢操作Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,mSelectionArgs, mSortOrder, mCancellationSignal);if (cursor != null) {try {// Ensure the cursor window is filled.cursor.getCount();//給Cursor設置觀察者;ContentProvider通知Cursor的觀察者數據發生了改變,//Cursor通知CursorLoader的觀察者數據發生了改變,CursorLoader通過ContentProvider重新加載新的數據cursor.registerContentObserver(mObserver);//cursor.setNotificationUri(getContext().getContentResolver(), otificationUri);//給Cursor設置要觀察的URI} catch (RuntimeException ex) {cursor.close();throw ex;}}return cursor;} }Cursor的實現原理
Android的Cursor自動更新是通過觀察者模式實現的,整個過程如下圖所示
- 通過ContentPorvider和ContentResolver使得數據發生了改變
- ContentProvider通知Cursor的觀察者數據發生了改變
- Cursor通知CursorLoader的觀察者數據發生了改變
- CursorLoader通過ContentProvider加載新的數據
- ContentPovider向DataBase請求新的數據
- CursorLoader調用CursorAdapter# changeCursor,用封裝了新數據的Cursor替換舊的Cursor
- CursorAdapter告知AdapterView的觀察者有新的數據
- AdapterView重新加載并顯示數據
在Android的android.database包下,有一個ContentObserver。Android正是通過他來實現觀察者模式的。當數據改變之后,觀察者會將數據改變的消息通知相應的對象,進而做出反饋。在代碼中,當數據改變之后,我會調用ContentResolver# notifyChange,發出ContactContract.SYNC_SIGNAL_URI信號,通知數據發生了改變。而在此之前,從ContentProvider# query中獲得的Cursor已經通過Cursor# setNotificationUri對ContactContract.SYNC_SIGNAL_URI信號進行了監視。當該信號出現,Cursor就會將信息改變的消息告訴CursorLoader的觀察者(在此之前CursorLoader已經對該Cursor設立了觀察者)。CursorLoader會開始重新開始加載數據。當數據加載成功,CursorLoader會通過CursorAdapter# changeCursor設置封裝了新數據的Cursor。而后CursorAdapter又會通知AdapterView的觀察者數據發生了改變(在此之前AdapterView已經對CursorAdapter設立了觀察者)。最后AdapterView就會重新加載并顯示新的數據。
在整個過程當中,我要做的就是在改變數據時發出信號,對封裝數據的Cursor設置需要監視的信號。具體的說就是在query中調用Cursor# setNotificationUri,在insert、update、delete中調用ContentResolver# notifyChange。這里需要補充的是Cursor和ContentResolver的信號機制同樣是通過觀察者模式實現的。
其他的實現方式
這里要介紹的其他的實現方式,依舊是通過觀察者模式實現的。區別在于是否使用ContentProvider和CursorLoader
不使用ContentProvider
在開發過程中,如果數據不用于應用之間的共享,使用ContentProvider似乎有一些多余。然而Android提供的CursorLoader的API必須通過ContentProvider才能實現數據加載和數據同步更新。但是你任然可以在不使用ContentProvider的情況下實現Cursor的自動更新。你需要做的只是在你的Loader中加入下面的代碼
// 實例化一個全局的ForceLoadContentObserver ForceLoadContentObserver mObserver = new ForceLoadContentObserver(); @Override public Cursor loadInBackground() {SQLiteDatabase database = mSqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(mTable, mColumns, mSelection, mSelectionArgs, mGroupBy,mHaving, mOrderBy);if (cursor != null) {cursor.getCount();// 對Cursor設立觀察者cursor.registerContentObserver(mObserver);// 設置Cursor的觀察信號cursor.setNotificationUri(getContext().getContentResolver(), mNotificationUri);}return cursor; }ForceLoadContentObserver是Loader的內部類。當觀察到數據發生變化之后,該類會調用Loader# forceLoad,進而開始重新加載數據。另外你也可以直接使用我項目中的DatabaseLoader。該類是我參照CursorLoader編寫的一個工具,通過它你可以繞過ContentProvider,直接請求Database。
不使用Loader
如果你不想要使用Loader(我非常不贊成你這么做),你可以通過如下的代碼實現Cursor的同步更新。
// 使用CursorAdapter.FLAG_AUTO_REQUERY標志 adapter = new SimpleCursorAdapter(this, R.layout.contact_item, null, from, to,CursorAdapter.FLAG_AUTO_REQUERY); private void loadData() {SQLiteOpenHelper sqliteOpenHelper = ContactContract.getSqliteOpenHelper(this);SQLiteDatabase database = sqliteOpenHelper.getReadableDatabase();String[] columns = {ContactContract._ID, ContactContract.NAME, ContactContract.PHONE};Cursor cursor = database.query(ContactContract.CONTACT_TABLE, columns, null, null, null,null, null);//設置NotificationUricursor.setNotificationUri(this.getContentResolver(), ContactContract.SYNC_SIGNAL_URI);adapter.changeCursor(cursor); }這里的關鍵在于,在實例化CursorAdapter時使用了CursorAdapter.FLAGAUTOREQUERY標志。當使用該標志后,每當收到數據更新的消息,CursorAdapter就會自己調用CursorAdapter# requery重新加載數據。然而整個加載過程會再UI線程中發生,這很有可能會使得程序運行部流暢。正是因為這個原因該方法以及被Android設置為Deprecated了。因此如果有可能,我還是推薦你使用Loader。
ContactContract.java
package com.kohoh.cursorsyncdemo;import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.provider.BaseColumns;import com.google.common.base.Preconditions; import com.google.common.base.Strings;/*** Created by kohoh on 14-11-3.*/ public class ContactContract implements BaseColumns {static final int DATABSE_VERSION = 1;static final String DATABASE_NAME = "contact.db";static final String CONTACT_TABLE = "contact";static final String NAME = "name";static final String PHONE = "phone";static final String AUTHORITY = "com.kohoh.cursorsyncdemo";static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);static final Uri CONTACT_URI = Uri.withAppendedPath(BASE_URI, "contact");static final Uri SYNC_SIGNAL_URI = Uri.withAppendedPath(BASE_URI, "SYNC_SIGNAL_URI");static public ContactDatabaseHelper getSqliteOpenHelper(Context context) {return new ContactDatabaseHelper(context);}static class ContactDatabaseHelper extends SQLiteOpenHelper {public ContactDatabaseHelper(Context context) {super(context, DATABASE_NAME, null, DATABSE_VERSION);}static public long addContact(SQLiteDatabase database, String name, int phone) {Preconditions.checkNotNull(database);Preconditions.checkNotNull(phone);Preconditions.checkArgument(!Strings.isNullOrEmpty(name));ContentValues contentValues = new ContentValues();contentValues.put(NAME, name);contentValues.put(PHONE, phone);return database.insert(CONTACT_TABLE, null, contentValues);}static public void deleteContact(Context context, long id) {Preconditions.checkNotNull(context);Preconditions.checkArgument(id >= 0);ContactContract.ContactDatabaseHelper databaseHelper = ContactContract.getSqliteOpenHelper(context);SQLiteDatabase databasea = databaseHelper.getWritableDatabase();String where = ContactContract._ID + " = ?";String[] whereArgs = {String.valueOf(id)};databasea.delete(ContactContract.CONTACT_TABLE, where, whereArgs);context.getContentResolver().notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL("CREATE TABLE " + CONTACT_TABLE + "( " +_ID + " INTEGER PRIMARY KEY," +NAME + " TEXT," +PHONE + " INTERGER)");addContact(db, "aaa", 111);addContact(db, "bbb", 222);addContact(db, "ccc", 333);addContact(db, "ddd", 444);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}} }ContactProvider.java
package com.kohoh.cursorsyncdemo;import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri;/*** Created by kohoh on 14-11-3.*/ public class ContactProvider extends ContentProvider {private SQLiteOpenHelper sqLiteOpenHelper;private ContentResolver contentResolver;private UriMatcher uriMatcher;final private int DIR = 0;final private int ITEM = 1;@Overridepublic boolean onCreate() {sqLiteOpenHelper = ContactContract.getSqliteOpenHelper(getContext());contentResolver = getContext().getContentResolver();uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);uriMatcher.addURI(ContactContract.AUTHORITY, "contact", DIR);uriMatcher.addURI(ContactContract.AUTHORITY, "contact/#", ITEM);return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder) {if (uriMatcher.match(uri) == ITEM) {return null;}SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(ContactContract.CONTACT_TABLE, projection, selection,selectionArgs, null, null, sortOrder);cursor.setNotificationUri(contentResolver, ContactContract.SYNC_SIGNAL_URI);return cursor;}@Overridepublic String getType(Uri uri) {switch (uriMatcher.match(uri)) {case ITEM:return "vnd.android.cursor.item/vnd.con.kohoh.cursorsyncdemo";case DIR:return "vnd.android.cursor.dir/vnd.con.kohoh.cursorsyncdemo";default:return null;}}@Overridepublic Uri insert(Uri uri, ContentValues values) {if (uriMatcher.match(uri) == ITEM) {return null;}SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();long id = database.insert(ContactContract.CONTACT_TABLE, null, values);if (id >= 0) {contentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return uri.withAppendedPath(ContactContract.CONTACT_URI, String.valueOf(id));}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {if (uriMatcher.match(uri) == ITEM) {return 0;}SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();int result = database.delete(ContactContract.CONTACT_TABLE, selection, selectionArgs);if (result > 0) {contentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return result;}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {if (uriMatcher.match(uri) == ITEM) {return 0;}SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();int result = database.update(ContactContract.CONTACT_TABLE, values, selection, selectionArgs);if (result > 0) {contentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return result;} }DatabaseLoader.java
package com.kohoh.cursorsyncdemo;import android.content.AsyncTaskLoader; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri;/*** Created by kohoh on 14-11-3.*/ public class DatabaseLoader extends AsyncTaskLoader<Cursor> {final ForceLoadContentObserver mObserver;Uri mNotificationUri;String mTable;String[] mColumns;String mSelection;String[] mSelectionArgs;String mGroupBy;String mHaving;String mOrderBy;SQLiteOpenHelper mSqLiteOpenHelper;Cursor mCursor;public DatabaseLoader(Context context) {super(context);this.mObserver = new ForceLoadContentObserver();}public DatabaseLoader(Context context, SQLiteOpenHelper sqLiteOpenHelper, Uri mNotificationUri,String mTable, String[] mColumns, String mSelection, String[] mSelectionArgs,String mGroupBy, String mHaving, String mOrderBy) {super(context);this.mNotificationUri = mNotificationUri;this.mTable = mTable;this.mColumns = mColumns;this.mSelection = mSelection;this.mSelectionArgs = mSelectionArgs;this.mGroupBy = mGroupBy;this.mHaving = mHaving;this.mOrderBy = mOrderBy;this.mSqLiteOpenHelper = sqLiteOpenHelper;this.mObserver = new ForceLoadContentObserver();}@Overridepublic Cursor loadInBackground() {SQLiteDatabase database = mSqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(mTable, mColumns, mSelection, mSelectionArgs, mGroupBy,mHaving, mOrderBy);if (cursor != null) {cursor.getCount();cursor.registerContentObserver(mObserver);cursor.setNotificationUri(getContext().getContentResolver(), mNotificationUri);}return cursor;}@Overridepublic void deliverResult(Cursor cursor) {if (isReset()) {if (cursor != null) {cursor.close();}return;}Cursor oldCursor = mCursor;mCursor = cursor;if (isStarted()) {super.deliverResult(cursor);}if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {oldCursor.close();}}@Overrideprotected void onStartLoading() {if (mCursor != null) {deliverResult(mCursor);}if (takeContentChanged() || mCursor == null) {forceLoad();}}@Overrideprotected void onStopLoading() {// Attempt to cancel the current load task if possible.cancelLoad();}@Overridepublic void onCanceled(Cursor cursor) {if (cursor != null && !cursor.isClosed()) {cursor.close();}}@Overrideprotected void onReset() {super.onReset();onStopLoading();if (mCursor != null && !mCursor.isClosed()) {mCursor.close();}mCursor = null;}public SQLiteOpenHelper getSqLiteOpenHelper() {return mSqLiteOpenHelper;}public void setSqLiteOpenHelper(SQLiteOpenHelper mSqLiteOpenHelper) {this.mSqLiteOpenHelper = mSqLiteOpenHelper;}public Uri getNotificationUri() {return mNotificationUri;}public void setNotificationUri(Uri mNotificationUri) {this.mNotificationUri = mNotificationUri;}public String getTable() {return mTable;}public void setTable(String mTable) {this.mTable = mTable;}public String[] getColumns() {return mColumns;}public void setColumns(String[] mColumns) {this.mColumns = mColumns;}public String getSelection() {return mSelection;}public void setSelection(String mSelection) {this.mSelection = mSelection;}public String[] getSelectionArgs() {return mSelectionArgs;}public void setSelectionArgs(String[] mSelectionArgs) {this.mSelectionArgs = mSelectionArgs;}public String getGroupBy() {return mGroupBy;}public void setGroupBy(String mGroupBy) {this.mGroupBy = mGroupBy;}public String getHaving() {return mHaving;}public void setHaving(String mHaving) {this.mHaving = mHaving;}public String getOrderBy() {return mOrderBy;}public void setOrderBy(String mOrderBy) {this.mOrderBy = mOrderBy;} }use provider
package com.kohoh.cursorsyncdemo;import android.app.Activity; import android.app.LoaderManager; import android.content.CursorLoader; import android.content.Loader; import android.database.Cursor; import android.os.Bundle; import android.view.ContextMenu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.CursorAdapter; import android.widget.ListView; import android.widget.SimpleCursorAdapter;public class CursorSyncDemo extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {private ListView listView;private SimpleCursorAdapter adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_cursor_sync_demo);String[] from = {ContactContract.NAME, ContactContract.PHONE};int[] to = {R.id.name, R.id.phone};listView = (ListView) findViewById(R.id.lv);adapter = new SimpleCursorAdapter(this, R.layout.contact_item, null, from, to,CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);listView.setAdapter(adapter);getLoaderManager().initLoader(0, null, this);registerForContextMenu(listView);}@Overridepublic boolean onContextItemSelected(MenuItem item) {switch (item.getItemId()) {case R.id.delete:AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();ContactContract.ContactDatabaseHelper.deleteContact(this, menuInfo.id);return true;default:return super.onContextItemSelected(item);}}@Overridepublic void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.contact_item_menu, menu);}@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {String[] projection = {ContactContract._ID, ContactContract.NAME, ContactContract.PHONE};return new CursorLoader(this, ContactContract.CONTACT_URI, projection, null, null, null);}@Overridepublic void onLoadFinished(Loader<Cursor> loader, Cursor data) {adapter.changeCursor(data);}@Overridepublic void onLoaderReset(Loader<Cursor> loader) {} }use database
package com.kohoh.cursorsyncdemo;import android.app.Activity; import android.app.LoaderManager; import android.content.Loader; import android.database.Cursor; import android.os.Bundle; import android.view.ContextMenu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.CursorAdapter; import android.widget.ListView; import android.widget.SimpleCursorAdapter;public class CursorSyncDemo extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {private ListView listView;private SimpleCursorAdapter adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_cursor_sync_demo);String[] from = {ContactContract.NAME, ContactContract.PHONE};int[] to = {R.id.name, R.id.phone};listView = (ListView) findViewById(R.id.lv);adapter = new SimpleCursorAdapter(this, R.layout.contact_item, null, from, to,CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);listView.setAdapter(adapter);getLoaderManager().initLoader(0, null, this);registerForContextMenu(listView);}@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {String[] columns = {ContactContract._ID, ContactContract.NAME, ContactContract.PHONE};return new DatabaseLoader(this,ContactContract.getSqliteOpenHelper(this),ContactContract.SYNC_SIGNAL_URI,ContactContract.CONTACT_TABLE,columns,null,null,null,null,null);}@Overridepublic void onLoadFinished(Loader<Cursor> loader, Cursor data) {adapter.changeCursor(data);}@Overridepublic void onLoaderReset(Loader<Cursor> loader) {}@Overridepublic boolean onContextItemSelected(MenuItem item) {switch (item.getItemId()) {case R.id.delete:AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();ContactContract.ContactDatabaseHelper.deleteContact(this, menuInfo.id);return true;default:return super.onContextItemSelected(item);}}@Overridepublic void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.contact_item_menu, menu);} }use adapter
package com.kohoh.cursorsyncdemo;import android.app.Activity; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Bundle; import android.view.ContextMenu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.CursorAdapter; import android.widget.ListView; import android.widget.SimpleCursorAdapter;public class CursorSyncDemo extends Activity {private ListView listView;private SimpleCursorAdapter adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_cursor_sync_demo);String[] from = {ContactContract.NAME, ContactContract.PHONE};int[] to = {R.id.name, R.id.phone};listView = (ListView) findViewById(R.id.lv);adapter = new SimpleCursorAdapter(this, R.layout.contact_item, null, from, to,CursorAdapter.FLAG_AUTO_REQUERY);listView.setAdapter(adapter);loadData();registerForContextMenu(listView);}private void loadData() {SQLiteOpenHelper sqliteOpenHelper = ContactContract.getSqliteOpenHelper(this);SQLiteDatabase database = sqliteOpenHelper.getReadableDatabase();String[] columns = {ContactContract._ID, ContactContract.NAME, ContactContract.PHONE};Cursor cursor = database.query(ContactContract.CONTACT_TABLE, columns, null, null, null,null, null);cursor.setNotificationUri(this.getContentResolver(), ContactContract.SYNC_SIGNAL_URI);adapter.changeCursor(cursor);}@Overridepublic boolean onContextItemSelected(MenuItem item) {switch (item.getItemId()) {case R.id.delete:AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();ContactContract.ContactDatabaseHelper.deleteContact(this, menuInfo.id);return true;default:return super.onContextItemSelected(item);}}@Overridepublic void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.contact_item_menu, menu);} }總結
以上是生活随笔為你收集整理的RecyclerView.Adapter:全能notify解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ViewPager刷新问题详解
- 下一篇: 你应该更新的Java知识