原文链接: 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解决方案相关推荐

  1. RecyclerView Adapter 优雅封装搞定所有列表

    转载自: 依然范特稀西 RecycleView加载列表,封装Adapter,快速高效的添加一个列表(包括单 Item 列表和多item列表). 理念 1, 构造一个通用的Adapter模版,避免每添加 ...

  2. RecyclerView的全能适配器,带有header和bottom

    概述 现在我们基本上不怎么使用ListView,而是使用RecyclerView代替了,关于如何引入和基本使用RecyclerView这里就不分享了,我们主要分享的是如何打造一个全能的适配器给Recy ...

  3. android activity调用Adapter方法刷新列表UI,RecyclerView.Adapter

    在adapter中创建被调用方法cleckAll public class JYfkleixinAdapter extends RecyclerView.Adapter<JYfkleixinAd ...

  4. RecyclerView.Adapter的封装(RecyclerAdapter)

    思路 ListView中已经自带了添加头布局和添加底部布局的方法,但是在RecyclerView中,却没有默认实现,这导致在实现一些特殊布局中不是那么的方便. 在实现RecyclerView.Adap ...

  5. RecyclerView.Adapter notifyDataSetChanged 不起作用

    最近项目里要添加个聊天功能,我们使用的是环信SDK. 如果应用启动,不在聊天界面,接收到消息后就弹出通知栏消息通知用户,点击进入聊天界面. 如果用户已经在聊天界面,就要将接收到的数据添加到adapte ...

  6. kotlin中RecyclerView.Adapter通用适配器

    文章目录 结合databinding,livedata BaseViewHolder BaseAdapter recycleView通用adapter 使用 数据bean ViewModel 绑定it ...

  7. kiel4.7下载_使用Kiel构建更好,干净的RecyclerView.Adapter

    kiel4.7下载 Some years ago, we had only ListView and its Adapter. It had performance issues and these ...

  8. Android 封装RecyclerView.Adapter,省其ViewHolder

    RecyclerView大家都使用过的话会有些不方便,现在对其封装一下,让其可以: 1.可以像ListView一样方便使用OnItemClickListener: 2.可省去ViewHolder,快速 ...

  9. RecyclerView.Adapter通用基类

    RecyclerView.Adapter通用基类 一般来说,我们在使用RecyclerView的时候,需要自定义一个Adapter,用来适配RecyclerView和data.这里主要来编写这个Ada ...

最新文章

  1. 如何提升研发人员的非技术才能
  2. JavaWeb黑马旅游网-学习笔记04【BaseServlet抽取】
  3. UE4学习-4.25版本Possess无法继承、UNavigationSystem命名空间找不到的解决方法
  4. mysql log all sql_记录一次mysqlbinlog恢复过程
  5. 编程语言开发编程语言_D编程语言是开发的绝佳选择的5个理由
  6. thinkphp在nginx下pathinfo支持
  7. getParameter和getAttribute区别(超详细分析)
  8. [JSOI2017]原力(分块+map(hash))
  9. CMP SUB 区别
  10. 我的世界pe服务器坐标怎么显示,我的世界手机版坐标怎么看 PE版没有MOD怎么查看坐标...
  11. Word文档中styles分析
  12. JAVA TOOL-【1】配置
  13. Excel如何批量修改行距和列距
  14. 树莓派开发—“树莓派开发“ 可口的派应该如何登陆呢?
  15. Spring Boot idel 实现热部署
  16. 图神经网络推荐方向--论文代码读记
  17. Blender 物理属性 (二)布料和碰撞
  18. ESP32 ESP-IDF TFT-LCD(ST7735 128x160) LVGL演示
  19. 链路追踪-Google-Dapper论文翻译总结
  20. 解决 error: failed to push some refs to ‘https://github.com/mxp520/test-spring-cloud-base.git‘

热门文章

  1. [转]代码分析工具FxCop1.36之一:介绍与使用
  2. 【C++】C++0x :: Introduction to some amazing features
  3. IPSEC ×××实验六:ASA SSL ×××
  4. 2011年3月华章新书书讯:ASP.NET本质论、Erlang编程指南、SNS网站构建
  5. Runtime's DiskExplorer手工填写分区表
  6. PAT_B_1074 宇宙无敌加法器
  7. python xml模块
  8. 【C++】源自指针的报错
  9. 构建之法4、17章观后感
  10. MongoDB对文档的操作