/* * Copyright (C) 2007 The Android Open Source Project * * 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 android.widget; import android.app.Activity; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.database.DataSetObserver; import android.os.Handler; import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; /** * 将一组{@link Cursor 游标}的数据提供给{@link ExpandableListView 可扩展列表视图} * 的适配器. 顶层{@link Cursor 游标}(由构造函数提供)提供分组的数据.由 * {@link #getChildrenCursor(Cursor)}返回的一系列{@link Cursor 游标} * 用于为分组对应的子条目提供数据.要使该类可用,这些游标必须包含“_id”列. */ public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implements Filterable, CursorFilter.CursorFilterClient { private Context mContext; private Handler mHandler; private boolean mAutoRequery; /** The cursor helper that is used to get the groups */ MyCursorHelper mGroupCursorHelper; /** * The map of a group position to the group's children cursor helper (the * cursor helper that is used to get the children for that group) */ SparseArray<MyCursorHelper> mChildrenCursorHelpers; // Filter related CursorFilter mCursorFilter; FilterQueryProvider mFilterQueryProvider; /** * 构造函数.当数据库的数据发生改变时,适配器将调用{@link Cursor#requery()}, * 重新查询以显示最新的数据. * * @param cursor 为分组提供数据的游标. * @param context 应用程序上下文. */ public CursorTreeAdapter(Cursor cursor, Context context) { init(cursor, context, true); } /** * 构造函数. * * @param cursor 为分组提供数据的游标. * @param context 应用程序上下文. * @param autoRequery 设置为true时,一旦数据库的数据发生变化,适配器会调用 * {@link Cursor#requery()},以保持显示最新的数据. */ public CursorTreeAdapter(Cursor cursor, Context context, boolean autoRequery) { init(cursor, context, autoRequery); } private void init(Cursor cursor, Context context, boolean autoRequery) { mContext = context; mHandler = new Handler(); mAutoRequery = autoRequery; mGroupCursorHelper = new MyCursorHelper(cursor); mChildrenCursorHelpers = new SparseArray<MyCursorHelper>(); } /** * Gets the cursor helper for the children in the given group. * * @param groupPosition The group whose children will be returned * @param requestCursor Whether to request a Cursor via * {@link #getChildrenCursor(Cursor)} (true), or to assume a call * to {@link #setChildrenCursor(int, Cursor)} will happen shortly * (false). * @return The cursor helper for the children of the given group */ synchronized MyCursorHelper getChildrenCursorHelper(int groupPosition, boolean requestCursor) { MyCursorHelper cursorHelper = mChildrenCursorHelpers.get(groupPosition); if (cursorHelper == null) { if (mGroupCursorHelper.moveTo(groupPosition) == null) return null; final Cursor cursor = getChildrenCursor(mGroupCursorHelper.getCursor()); cursorHelper = new MyCursorHelper(cursor); mChildrenCursorHelpers.put(groupPosition, cursorHelper); } return cursorHelper; } /** * 为指定分组的子条目取得游标.子类必须实现这个方法,为指定分组提供子条目的数据. * <p> * 为了避免UI阻塞,可以异步查询提供者,通过返回空,并在查询成功后调用 * {@link #setChildrenCursor(int, Cursor)}即可. * <p> * 你有责任在活动的整个生命周期中管理该游标对象.有个好办法,你可以使用 * {@link Activity#managedQuery}函数,它会为你完成该工作.在某些情况下, * 适配器会使游标停止工作,但该情况不会总是出现,因此请确保有效地管理好游标. * * @param groupCursor 分组游标对象,决定返回哪个分组的子条目用游标. * @return 指定分组的子条目用游标,或者为空. */ abstract protected Cursor getChildrenCursor(Cursor groupCursor); /** * 设置分组游标. * * @param cursor 为分组设置的游标.如果有既存游标,会将其关闭. */ public void setGroupCursor(Cursor cursor) { mGroupCursorHelper.changeCursor(cursor, false); } /** * 设置指定分组的子条目用游标.如果有既存游标,会将其关闭. * <p> * 防止UI阻塞,使用异步查询时使用该方法. * * @param groupPosition 要设置子条目游标的分组. * @param childrenCursor 用于分组子条目的游标. */ public void setChildrenCursor(int groupPosition, Cursor childrenCursor) { /* * Don't request a cursor from the subclass, instead we will be setting * the cursor ourselves. */ MyCursorHelper childrenCursorHelper = getChildrenCursorHelper(groupPosition, false); /* * Don't release any cursor since we know exactly what data is changing * (this cursor, which is still valid). */ childrenCursorHelper.changeCursor(childrenCursor, false); } public Cursor getChild(int groupPosition, int childPosition) { // Return this group's children Cursor pointing to the particular child return getChildrenCursorHelper(groupPosition, true).moveTo(childPosition); } public long getChildId(int groupPosition, int childPosition) { return getChildrenCursorHelper(groupPosition, true).getId(childPosition); } public int getChildrenCount(int groupPosition) { MyCursorHelper helper = getChildrenCursorHelper(groupPosition, true); return (mGroupCursorHelper.isValid() && helper != null) ? helper.getCount() : 0; } public Cursor getGroup(int groupPosition) { // Return the group Cursor pointing to the given group return mGroupCursorHelper.moveTo(groupPosition); } public int getGroupCount() { return mGroupCursorHelper.getCount(); } public long getGroupId(int groupPosition) { return mGroupCursorHelper.getId(groupPosition); } public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { Cursor cursor = mGroupCursorHelper.moveTo(groupPosition); if (cursor == null) { throw new IllegalStateException("this should only be called when the cursor is valid"); } View v; if (convertView == null) { v = newGroupView(mContext, cursor, isExpanded, parent); } else { v = convertView; } bindGroupView(v, mContext, cursor, isExpanded); return v; } /** * 根据游标指向的数据生成新的分组视图. * * @param context 应用程序上下文. * @param cursor 用于取得分组数据的游标.游标已经定位到正确位置. * @param isExpanded 分组是否为展开状态. * @param parent 容纳该新视图的父视图. * @return 新生成的视图. */ protected abstract View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent); /** * 将游标指定的分组数据绑定到既存视图. * * @param view 之前由 newChildView 返回的既存视图. * @param context 应用程序上下文. * @param cursor 用于取得数据的游标.游标已经移到正确位置. * @param isExpanded 分组是否已展开. */ protected abstract void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded); public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { MyCursorHelper cursorHelper = getChildrenCursorHelper(groupPosition, true); Cursor cursor = cursorHelper.moveTo(childPosition); if (cursor == null) { throw new IllegalStateException("this should only be called when the cursor is valid"); } View v; if (convertView == null) { v = newChildView(mContext, cursor, isLastChild, parent); } else { v = convertView; } bindChildView(v, mContext, cursor, isLastChild); return v; } /** * 根据游标指向的数据生成新子视图. * * @param context 应用程序上下文. * @param cursor 用于取得数据的游标.游标已定位于正确位置. * @param isLastChild 该子条目是否是分组的最后一个条目. * @param parent 容纳该新视图的父视图. * @return 新生成的视图. */ protected abstract View newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent); /** * 将游标指定的子数据绑定到既存视图. * * @param view 之前由 newChildView 返回的既存视图. * @param context 应用程序上下文. * @param cursor 用于取得数据的游标.游标已经移到正确位置. * @param isLastChild 视图是否是分组中的最后一个子视图. */ protected abstract void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild); public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } public boolean hasStableIds() { return true; } private synchronized void releaseCursorHelpers() { for (int pos = mChildrenCursorHelpers.size() - 1; pos >= 0; pos--) { mChildrenCursorHelpers.valueAt(pos).deactivate(); } mChildrenCursorHelpers.clear(); } @Override public void notifyDataSetChanged() { notifyDataSetChanged(true); } /** * 通知数据变更,并包含是否是否缓存的游标的选项. * * @param releaseCursors 是否释放并停止缓存的所有游标. */ public void notifyDataSetChanged(boolean releaseCursors) { if (releaseCursors) { releaseCursorHelpers(); } super.notifyDataSetChanged(); } @Override public void notifyDataSetInvalidated() { releaseCursorHelpers(); super.notifyDataSetInvalidated(); } @Override public void onGroupCollapsed(int groupPosition) { deactivateChildrenCursorHelper(groupPosition); } /** * Deactivates the Cursor and removes the helper from cache. * * @param groupPosition The group whose children Cursor and helper should be * deactivated. */ synchronized void deactivateChildrenCursorHelper(int groupPosition) { MyCursorHelper cursorHelper = getChildrenCursorHelper(groupPosition, true); mChildrenCursorHelpers.remove(groupPosition); cursorHelper.deactivate(); } /** * @see CursorAdapter#convertToString(Cursor) */ public String convertToString(Cursor cursor) { return cursor == null ? "" : cursor.toString(); } /** * @see CursorAdapter#runQueryOnBackgroundThread(CharSequence) */ public Cursor runQueryOnBackgroundThread(CharSequence constraint) { if (mFilterQueryProvider != null) { return mFilterQueryProvider.runQuery(constraint); } return mGroupCursorHelper.getCursor(); } public Filter getFilter() { if (mCursorFilter == null) { mCursorFilter = new CursorFilter(this); } return mCursorFilter; } /** * @see CursorAdapter#getFilterQueryProvider() */ public FilterQueryProvider getFilterQueryProvider() { return mFilterQueryProvider; } /** * @see CursorAdapter#setFilterQueryProvider(FilterQueryProvider) */ public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) { mFilterQueryProvider = filterQueryProvider; } /** * @see CursorAdapter#changeCursor(Cursor) */ public void changeCursor(Cursor cursor) { mGroupCursorHelper.changeCursor(cursor, true); } /** * @see CursorAdapter#getCursor() */ public Cursor getCursor() { return mGroupCursorHelper.getCursor(); } /** * Helper class for Cursor management: * <li> Data validity * <li> Funneling the content and data set observers from a Cursor to a * single data set observer for widgets * <li> ID from the Cursor for use in adapter IDs * <li> Swapping cursors but maintaining other metadata */ class MyCursorHelper { private Cursor mCursor; private boolean mDataValid; private int mRowIDColumn; private MyContentObserver mContentObserver; private MyDataSetObserver mDataSetObserver; MyCursorHelper(Cursor cursor) { final boolean cursorPresent = cursor != null; mCursor = cursor; mDataValid = cursorPresent; mRowIDColumn = cursorPresent ? cursor.getColumnIndex("_id") : -1; mContentObserver = new MyContentObserver(); mDataSetObserver = new MyDataSetObserver(); if (cursorPresent) { cursor.registerContentObserver(mContentObserver); cursor.registerDataSetObserver(mDataSetObserver); } } Cursor getCursor() { return mCursor; } int getCount() { if (mDataValid && mCursor != null) { return mCursor.getCount(); } else { return 0; } } long getId(int position) { if (mDataValid && mCursor != null) { if (mCursor.moveToPosition(position)) { return mCursor.getLong(mRowIDColumn); } else { return 0; } } else { return 0; } } Cursor moveTo(int position) { if (mDataValid && (mCursor != null) && mCursor.moveToPosition(position)) { return mCursor; } else { return null; } } void changeCursor(Cursor cursor, boolean releaseCursors) { if (cursor == mCursor) return; deactivate(); mCursor = cursor; if (cursor != null) { cursor.registerContentObserver(mContentObserver); cursor.registerDataSetObserver(mDataSetObserver); mRowIDColumn = cursor.getColumnIndex("_id"); mDataValid = true; // notify the observers about the new cursor notifyDataSetChanged(releaseCursors); } else { mRowIDColumn = -1; mDataValid = false; // notify the observers about the lack of a data set notifyDataSetInvalidated(); } } void deactivate() { if (mCursor == null) { return; } mCursor.unregisterContentObserver(mContentObserver); mCursor.unregisterDataSetObserver(mDataSetObserver); mCursor.close(); mCursor = null; } boolean isValid() { return mDataValid && mCursor != null; } private class MyContentObserver extends ContentObserver { public MyContentObserver() { super(mHandler); } @Override public boolean deliverSelfNotifications() { return true; } @Override public void onChange(boolean selfChange) { if (mAutoRequery && mCursor != null) { if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update"); mDataValid = mCursor.requery(); } } } private class MyDataSetObserver extends DataSetObserver { @Override public void onChanged() { mDataValid = true; notifyDataSetChanged(); } @Override public void onInvalidated() { mDataValid = false; notifyDataSetInvalidated(); } } } }