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;
    }
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_recycler, parent, false);
        return new ViewHolder(view);
    }
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.setData(datas.get(position));
    }
    @Override
    public 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 {
    @Override
    protected 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() {
            @Override
            public 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()));
                }
                // 实现 Callback
                DiffUtil.Callback callback = new DiffUtil.Callback() {
                    @Override
                    public int getOldListSize() {
                        return oldTemp.size();
                    }
                    @Override
                    public int getNewListSize() {
                        return datas.size();
                    }
                    @Override
                    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                        return oldTemp.get(oldItemPosition).id.equals(datas.get(newItemPosition).id);
                    }
                    @Override
                    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                        return oldTemp.get(oldItemPosition).name.equals(datas.get(newItemPosition).name);
                    }
                };
                DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);
                // 把结果应用到 adapter
                diffResult.dispatchUpdatesTo(mAdapter);
            }
        });

效果如下:

DiffUtil

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);
    @Override
    public int getItemCount() {
        return datas.size();
    }
    public void notifyDiff() {
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
            @Override
            public int getOldListSize() {
                return temp.size();
            }
            @Override
            public int getNewListSize() {
                return datas.size();
            }
            // 判断是否是同一个 item
            @Override
            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                return BaseAdapter.this.areItemsTheSame(temp.get(oldItemPosition), datas.get(newItemPosition));
            }
            // 如果是同一个 item 判断内容是否相同
            @Override
            public 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);
    }
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_recycler, parent, false);
        return new ViewHolder(view);
    }
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.setData(datas.get(position));
    }
    @Override
    public boolean areItemsTheSame(Student oldItem, Student newItem) {
        return oldItem.id.equals(newItem.id);
    }
    @Override
    public 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() {
            @Override
            public 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);
    //设置NotificationUri
    cursor.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) {
        //触发NotificationUri
        contentResolver.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) {
        //触发NotificationUri
        contentResolver.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) {
    //触发NotificationUri
    contentResolver.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自动更新是通过观察者模式实现的,整个过程如下图所示

CursorLoader

  • 通过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就会重新加载并显示新的数据。

CursorLoader

在整个过程当中,我要做的就是在改变数据时发出信号,对封装数据的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);
    //设置NotificationUri
    cursor.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);
        }

        @Override
        public 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);
        }

        @Override
        public 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;

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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;
        }
    }

    @Override
    public 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));
    }

    @Override
    public 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;
    }

    @Override
    public 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();
    }

    @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.registerContentObserver(mObserver);
            cursor.setNotificationUri(getContext().getContentResolver(), mNotificationUri);
        }
        return cursor;
    }

    @Override
    public 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();
        }
    }

    @Override
    protected void onStartLoading() {
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor cursor) {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    @Override
    protected 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;

    @Override
    protected 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);
    }

    @Override
    public 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);
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.contact_item_menu, menu);
    }

    @Override
    public 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);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        adapter.changeCursor(data);
    }

    @Override
    public 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;

    @Override
    protected 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);
    }

    @Override
    public 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);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        adapter.changeCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {

    }

    @Override
    public 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);
        }
    }

    @Override
    public 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;

    @Override
    protected 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);
    }

    @Override
    public 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);
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.contact_item_menu, menu);
    }
}

Android视频教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值