/*
* Copyright 2013 Chris Banes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zhaojian.jolly.utils;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Handler;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
public abstract class CursorPagerAdapter extends PagerAdapter {
/**
* This field should be made private, so it is hidden from the SDK. {@hide
*
*
* }
*/
protected boolean mDataValid;
/**
* This field should be made private, so it is hidden from the SDK. {@hide
*
*
* }
*/
protected boolean mAutoRequery;
/**
* This field should be made private, so it is hidden from the SDK. {@hide
*
*
* }
*/
protected Cursor mCursor;
/**
* This field should be made private, so it is hidden from the SDK. {@hide
*
*
* }
*/
protected Context mContext;
/**
* This field should be made private, so it is hidden from the SDK. {@hide
*
*
* }
*/
protected int mRowIDColumn;
/**
* This field should be made private, so it is hidden from the SDK. {@hide
*
*
* }
*/
protected ChangeObserver mChangeObserver;
/**
* This field should be made private, so it is hidden from the SDK. {@hide
*
*
* }
*/
protected DataSetObserver mDataSetObserver;
/**
* If set the adapter will call requery() on the cursor whenever a content change notification is
* delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
*
* @deprecated This option is discouraged, as it results in Cursor queries being performed on the
* application's UI thread and thus can cause poor responsiveness or even Application
* Not Responding errors. As an alternative, use {@link android.app.LoaderManager} with
* a {@link android.content.CursorLoader}.
*/
@Deprecated
public static final int FLAG_AUTO_REQUERY = 0x01;
/**
* If set the adapter will register a content observer on the cursor and will call {@link
* #onContentChanged()} when a notification comes in. Be careful when using this flag: you will
* need to unset the current Cursor from the adapter to avoid leaks due to its registered
* observers. This flag is not needed when using a CursorAdapter with a {@link
* android.content.CursorLoader}.
*/
public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
/**
* Constructor that always enables auto-requery.
*
* @param c The cursor from which to get the data.
* @param context The context
* @deprecated This option is discouraged, as it results in Cursor queries being performed on the
* application's UI thread and thus can cause poor responsiveness or even Application
* Not Responding errors. As an alternative, use {@link android.app.LoaderManager} with
* a {@link android.content.CursorLoader}.
*/
@Deprecated
public CursorPagerAdapter(Context context, Cursor c) {
init(context, c, FLAG_AUTO_REQUERY);
}
/**
* Constructor that allows control over auto-requery. It is recommended you not use this, but
* instead {@link #CursorAdapter(Context, Cursor, int)}. When using this constructor, {@link
* #FLAG_REGISTER_CONTENT_OBSERVER} will always be set.
*
* @param c The cursor from which to get the data.
* @param context The context
* @param autoRequery If true the adapter will call requery() on the cursor whenever it changes so
* the most recent data is always displayed. Using true here is discouraged.
*/
public CursorPagerAdapter(Context context, Cursor c, boolean autoRequery) {
init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
}
/**
* Recommended constructor.
*
* @param c The cursor from which to get the data.
* @param context The context
* @param flags Flags used to determine the behavior of the adapter; may be any combination of
* {@link #FLAG_AUTO_REQUERY} and {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
*/
public CursorPagerAdapter(Context context, Cursor c, int flags) {
init(context, c, flags);
}
/**
* @deprecated Don't use this, use the normal constructor. This will be removed in the future.
*/
@Deprecated
protected void init(Context context, Cursor c, boolean autoRequery) {
init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
}
void init(Context context, Cursor c, int flags) {
if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
flags |= FLAG_REGISTER_CONTENT_OBSERVER;
mAutoRequery = true;
} else {
mAutoRequery = false;
}
boolean cursorPresent = c != null;
mCursor = c;
mDataValid = cursorPresent;
mContext = context;
mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
mChangeObserver = new ChangeObserver();
mDataSetObserver = new MyDataSetObserver();
} else {
mChangeObserver = null;
mDataSetObserver = null;
}
if (cursorPresent) {
if (mChangeObserver != null) {
c.registerContentObserver(mChangeObserver);
}
if (mDataSetObserver != null) {
c.registerDataSetObserver(mDataSetObserver);
}
}
}
/**
* Returns the cursor.
*
* @return the cursor.
*/
public Cursor getCursor() {
return mCursor;
}
/**
* @see android.widget.ListAdapter#getCount()
*/
public int getCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
} else {
return 0;
}
}
/**
* @see android.widget.ListAdapter#getView(int, View, ViewGroup)
*/
@Override
public Object instantiateItem(View container, int position) {
if (!mDataValid) {
throw new IllegalStateException("this should only be called when the cursor is valid");
}
if (!mCursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
View v = newView(mContext, mCursor, (ViewGroup) container);
bindView(v, mContext, mCursor);
((ViewPager) container).addView(v);
return v;
}
@Override
public void destroyItem(View container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
/**
* Makes a new view to hold the data pointed to by cursor.
*
* @param context Interface to application's global information
* @param cursor The cursor from which to get the data. The cursor is already moved to the correct
* position.
* @param parent The parent to which the new view is attached to
* @return the newly created view.
*/
public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
/**
* Bind an existing view to the data pointed to by cursor
*
* @param view Existing view, returned earlier by newView
* @param context Interface to application's global information
* @param cursor The cursor from which to get the data. The cursor is already moved to the correct
* position.
*/
public abstract void bindView(View view, Context context, Cursor cursor);
/**
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be closed.
*
* @param cursor The new cursor to be used
*/
public void changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
old.close();
}
}
/**
* Swap in a new Cursor, returning the old Cursor. Unlike {@link #changeCursor(Cursor)}, the
* returned old Cursor is <em>not</em> closed.
*
* @param newCursor The new cursor to be used.
* @return Returns the previously set Cursor, or null if there was not one. If the given new Cursor
* is the same instance is the previously set Cursor, null is also returned.
*/
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
Cursor oldCursor = mCursor;
if (oldCursor != null) {
if (mChangeObserver != null) {
oldCursor.unregisterContentObserver(mChangeObserver);
}
if (mDataSetObserver != null) {
oldCursor.unregisterDataSetObserver(mDataSetObserver);
}
}
mCursor = newCursor;
if (newCursor != null) {
if (mChangeObserver != null) {
newCursor.registerContentObserver(mChangeObserver);
}
if (mDataSetObserver != null) {
newCursor.registerDataSetObserver(mDataSetObserver);
}
mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
// notify the observers about the new cursor
notifyDataSetChanged();
} else {
mRowIDColumn = -1;
mDataValid = false;
// notify the observers about the lack of a data set
notifyDataSetChanged();
}
return oldCursor;
}
/**
* <p> Converts the cursor into a CharSequence. Subclasses should override this method to convert
* their results. The default implementation returns an empty String for null values or the default
* String representation of the value. </p>
*
* @param cursor the cursor to convert to a CharSequence
* @return a CharSequence representing the value
*/
public CharSequence convertToString(Cursor cursor) {
return cursor == null ? "" : cursor.toString();
}
/**
* Called when the {@link ContentObserver} on the cursor receives a change notification. The
* default implementation provides the auto-requery logic, but may be overridden by sub classes.
*
* @see ContentObserver#onChange(boolean)
*/
@SuppressWarnings("deprecation")
protected void onContentChanged() {
if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
mDataValid = mCursor.requery();
}
}
private class ChangeObserver extends ContentObserver {
public ChangeObserver() {
super(new Handler());
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public void onChange(boolean selfChange) {
onContentChanged();
}
}
private class MyDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
mDataValid = true;
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
mDataValid = false;
notifyDataSetChanged();
}
}
}