Android 用于 ExpandableListView 的 SimpleCursorTreeAdapter 和 CursorLoader
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10611927/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
SimpleCursorTreeAdapter and CursorLoader for ExpandableListView
提问by toobsco42
I am trying to asynchronously query a provider by using a CursorLoader
with a SimpleCursorTreeAdapter
我正在尝试使用 aCursorLoader
和 a异步查询提供者SimpleCursorTreeAdapter
Here is my Fragment
class which implements the CursorLoader
这是我的Fragment
课程,它实现了CursorLoader
public class GroupsListFragment extends ExpandableListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
private final String DEBUG_TAG = getClass().getSimpleName().toString();
private static final String[] CONTACTS_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME };
private static final String[] GROUPS_SUMMARY_PROJECTION = new String[] {
ContactsContract.Groups.TITLE, ContactsContract.Groups._ID,
ContactsContract.Groups.SUMMARY_COUNT,
ContactsContract.Groups.ACCOUNT_NAME,
ContactsContract.Groups.ACCOUNT_TYPE,
ContactsContract.Groups.DATA_SET };
GroupsAdapter mAdapter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
populateContactList();
getLoaderManager().initLoader(-1, null, this);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created.
Log.d(DEBUG_TAG, "onCreateLoader for loader_id " + id);
CursorLoader cl;
if (id != -1) {
// child cursor
Uri contactsUri = ContactsContract.Data.CONTENT_URI;
String selection = "(("
+ ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
+ " NOTNULL) AND ("
+ ContactsContract.CommonDataKinds.GroupMembership.HAS_PHONE_NUMBER
+ "=1) AND ("
+ ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
+ " != '') AND ("
+ ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID
+ " = ? ))";
String sortOrder = ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
+ " COLLATE LOCALIZED ASC";
String[] selectionArgs = new String[] { String.valueOf(id) };
cl = new CursorLoader(getActivity(), contactsUri,
CONTACTS_PROJECTION, selection, selectionArgs, sortOrder);
} else {
// group cursor
Uri groupsUri = ContactsContract.Groups.CONTENT_SUMMARY_URI;
String selection = "((" + ContactsContract.Groups.TITLE
+ " NOTNULL) AND (" + ContactsContract.Groups.TITLE
+ " != '' ))";
String sortOrder = ContactsContract.Groups.TITLE
+ " COLLATE LOCALIZED ASC";
cl = new CursorLoader(getActivity(), groupsUri,
GROUPS_SUMMARY_PROJECTION, selection, null, sortOrder);
}
return cl;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in.
int id = loader.getId();
Log.d(DEBUG_TAG, "onLoadFinished() for loader_id " + id);
if (id != -1) {
// child cursor
if (!data.isClosed()) {
Log.d(DEBUG_TAG, "data.getCount() " + data.getCount());
try {
mAdapter.setChildrenCursor(id, data);
} catch (NullPointerException e) {
Log.w("DEBUG","Adapter expired, try again on the next query: "
+ e.getMessage());
}
}
} else {
mAdapter.setGroupCursor(data);
}
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// is about to be closed.
int id = loader.getId();
Log.d(DEBUG_TAG, "onLoaderReset() for loader_id " + id);
if (id != -1) {
// child cursor
try {
mAdapter.setChildrenCursor(id, null);
} catch (NullPointerException e) {
Log.w("TAG", "Adapter expired, try again on the next query: "
+ e.getMessage());
}
} else {
mAdapter.setGroupCursor(null);
}
}
/**
* Populate the contact list
*/
private void populateContactList() {
// Set up our adapter
mAdapter = new GroupsAdapter(getActivity(),this,
android.R.layout.simple_expandable_list_item_1,
android.R.layout.simple_expandable_list_item_1,
new String[] { ContactsContract.Groups.TITLE }, // Name for group layouts
new int[] { android.R.id.text1 },
new String[] { ContactsContract.Contacts.DISPLAY_NAME }, // Name for child layouts
new int[] { android.R.id.text1 });
setListAdapter(mAdapter);
}
}
And here is my adapter which subclasses SimpleCursorTreeAdapter
这是我的适配器,它子类化 SimpleCursorTreeAdapter
public class GroupsAdapter extends SimpleCursorTreeAdapter {
private final String DEBUG_TAG = getClass().getSimpleName().toString();
private ContactManager mActivity;
private GroupsListFragment mFragment;
// Note that the constructor does not take a Cursor. This is done to avoid
// querying the database on the main thread.
public GroupsAdapter(Context context, GroupsListFragment glf,
int groupLayout, int childLayout, String[] groupFrom,
int[] groupTo, String[] childrenFrom, int[] childrenTo) {
super(context, null, groupLayout, groupFrom, groupTo, childLayout,
childrenFrom, childrenTo);
mActivity = (ContactManager) context;
mFragment = glf;
}
@Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
// Given the group, we return a cursor for all the children within that group
int groupId = groupCursor.getInt(groupCursor
.getColumnIndex(ContactsContract.Groups._ID));
Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId);
Loader loader = mActivity.getLoaderManager().getLoader(groupId);
if ( loader != null && loader.isReset() ) {
mActivity.getLoaderManager().restartLoader(groupId, null, mFragment);
} else {
mActivity.getLoaderManager().initLoader(groupId, null, mFragment);
}
}
}
The problem is that when i click one of the parent groups one of three things happens in what appears to be a inconsistent fashion.
问题是,当我单击父组之一时,三件事之一会以似乎不一致的方式发生。
1) Either the group opens up and the children appear below it
1)要么打开组,然后孩子出现在它下面
2) The group does not open and the setChildrenCursor()
call throws an NullPointerException
error which gets caught in the try catch block
2) 该组未打开并且该setChildrenCursor()
调用抛出一个NullPointerException
错误,该错误在 try catch 块中被捕获
3) The group does not open and no error is thrown
3)组不打开,不抛出错误
Here is some debugging output in a scenario in which a group is expanded and showing the children:
以下是扩展组并显示子项的场景中的一些调试输出:
When all groups are displayed it ouputs:
当显示所有组时,它输出:
05-20 10:08:22.765: D/GroupsListFragment(22132): onCreateLoader for loader_id -1
05-20 10:08:23.613: D/GroupsListFragment(22132): onLoadFinished() for loader_id -1
-1 is the loader_id of the group cursor
-1 是组游标的 loader_id
Then if i select one group in particular (let's just call it group A) it outputs:
然后,如果我特别选择一组(让我们称其为 A 组),它会输出:
05-20 23:22:31.140: D/GroupsAdapter(13844): getChildrenCursor() for groupId 67
05-20 23:22:31.140: D/GroupsListFragment(13844): onCreateLoader for loader_id 67
05-20 23:22:31.254: D/GroupsListFragment(13844): onLoadFinished() for loader_id 67
05-20 23:22:31.254: D/GroupsListFragment(13844): data.getCount() 4
05-20 23:22:31.254: W/GroupsListFragment(13844): Adapter expired, try again on the next query: null
The group does not expand and the NullPointerException
is caught. Then if i select another group (let's just call it group B) it outputs:
该组不扩展并被NullPointerException
捕获。然后,如果我选择另一个组(让我们称之为组 B),它会输出:
05-20 23:25:38.089: D/GroupsAdapter(13844): getChildrenCursor() for groupId 3
05-20 23:25:38.089: D/GroupsListFragment(13844): onCreateLoader for loader_id 3
05-20 23:25:38.207: D/GroupsListFragment(13844): onLoadFinished() for loader_id 3
05-20 23:25:38.207: D/GroupsListFragment(13844): data.getCount() 6
This time, the NullPointerException
is not thrown. And instead of group B expanding, group A is expanded.
这一次,NullPointerException
没有抛出。而不是扩大B组,而是扩大A组。
Can anyone explain the behavior that the setChildrenCursor()
call is exhibiting?
任何人都可以解释setChildrenCursor()
电话所表现出的行为吗?
I am thinking there is a problem with how the group/child CursorLoaders are instantiated in onCreateLoader()
. For the group CursorLoader
i just want all groups in my phone. The child CursorLoader
should contain all contacts within a group. Does anyone have any ideas what could be the issue?
我认为组/子 CursorLoaders 在onCreateLoader()
. 对于组,CursorLoader
我只想要手机中的所有组。子项CursorLoader
应包含组内的所有联系人。有没有人有任何想法可能是什么问题?
UPDATE
更新
Thanks to @Yam's advice I have now modified the getChildrenCursor()
method. I am now selecting the groupCursor position not the value of ContactsContract.Groups._ID to pass into the initLoader() call. I also changed the logic to call restartLoader() only when loader is not null and loader isReset is false.
感谢@Yam 的建议,我现在修改了getChildrenCursor()
方法。我现在选择 groupCursor 位置而不是 ContactsContract.Groups._ID 的值传递给 initLoader() 调用。我还更改了仅在 loader 不为 null 且 loader isReset 为 false 时调用 restartLoader() 的逻辑。
protected Cursor getChildrenCursor(Cursor groupCursor) {
// Given the group, we return a cursor for all the children within that
// group
int groupPos = groupCursor.getPosition();
Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos);
Loader loader = mActivity.getLoaderManager().getLoader(groupPos);
if (loader != null && !loader.isReset()) {
mActivity.getLoaderManager().restartLoader(groupPos, null, mFragment);
} else {
mActivity.getLoaderManager().initLoader(groupPos, null, mFragment);
}
return null;
}
This definitely makes more sense and does not exhibit some of the erratic behavior of a group expanding sometimes and not other times.
这肯定更有意义,并且不会表现出有时而不是其他时间扩张的群体的某些不稳定行为。
However, there are contacts that are being displayed under a group that they don't belong to. And also some groups that do have contacts in them but it won't show any contacts. So it seems that the getChildrenCursor()
issues may now be resolved.
但是,有些联系人显示在他们不属于的组下。还有一些组中确实有联系人,但不会显示任何联系人。所以getChildrenCursor()
现在问题似乎可以得到解决。
But now it looks to be an issue of how the CursorLoaders are instantiated in the onCreateLoader()
method. Is the CursorLoader
returned in the onCreateLoader()
method for the child cursor being instantiated improperly?
但是现在看起来是如何在onCreateLoader()
方法中实例化 CursorLoaders 的问题。在CursorLoader
返回的onCreateLoader()
方法为孩子光标被不适当实例?
UPDATE
更新
So I have identified one of my issues. In the getChildrenCursor()
method if I pass the groupId into the initLoader()
method then in the onCreateLoader()
method, when the CursorLoader
is created it will get the correct groupid parameter for the query. However, in the onLoadFinished()
the call to setChildrenCursor()
is getting passed the loader id for the first parameter not the groupPosition. I'm guessing i have to map loader ids to group positions in some data structure. But i'm not sure if this is the best approach. Does anyone have any suggestions?
所以我已经确定了我的问题之一。在getChildrenCursor()
方法中,如果我将 groupId 传递到initLoader()
方法中,那么在onCreateLoader()
方法中,CursorLoader
创建时它将获得正确的 groupid 参数以进行查询。但是,在onLoadFinished()
调用中setChildrenCursor()
传递的是第一个参数的加载程序 ID,而不是 groupPosition。我猜我必须将加载程序 ID 映射到某些数据结构中的分组位置。但我不确定这是否是最好的方法。有没有人有什么建议?
采纳答案by toobsco42
So i figured out that I needed to map loaderids to groupPositions and this solved my issue:
所以我发现我需要将 loaderids 映射到 groupPositions,这解决了我的问题:
Here is my Fragment
class which implements the CursorLoader
这是我的Fragment
课程,它实现了CursorLoader
public class GroupsListFragment extends ExpandableListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
private final String DEBUG_TAG = getClass().getSimpleName().toString();
private static final String[] CONTACTS_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME };
private static final String[] GROUPS_PROJECTION = new String[] {
ContactsContract.Groups.TITLE, ContactsContract.Groups._ID };
GroupsAdapter mAdapter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
populateContactList();
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
Loader loader = getLoaderManager().getLoader(-1);
if (loader != null && !loader.isReset()) {
getLoaderManager().restartLoader(-1, null, this);
} else {
getLoaderManager().initLoader(-1, null, this);
}
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created.
Log.d(DEBUG_TAG, "onCreateLoader for loader_id " + id);
CursorLoader cl;
if (id != -1) {
// child cursor
Uri contactsUri = ContactsContract.Data.CONTENT_URI;
String selection = "((" + ContactsContract.Contacts.DISPLAY_NAME
+ " NOTNULL) AND ("
+ ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ ContactsContract.Contacts.DISPLAY_NAME + " != '') AND ("
+ ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID
+ " = ? ))";
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
+ " COLLATE LOCALIZED ASC";
String[] selectionArgs = new String[] { String.valueOf(id) };
cl = new CursorLoader(getActivity(), contactsUri,
CONTACTS_PROJECTION, selection, selectionArgs, sortOrder);
} else {
// group cursor
Uri groupsUri = ContactsContract.Groups.CONTENT_URI;
String selection = "((" + ContactsContract.Groups.TITLE
+ " NOTNULL) AND (" + ContactsContract.Groups.TITLE
+ " != '' ))";
String sortOrder = ContactsContract.Groups.TITLE
+ " COLLATE LOCALIZED ASC";
cl = new CursorLoader(getActivity(), groupsUri,
GROUPS_PROJECTION, selection, null, sortOrder);
}
return cl;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in.
int id = loader.getId();
Log.d(DEBUG_TAG, "onLoadFinished() for loader_id " + id);
if (id != -1) {
// child cursor
if (!data.isClosed()) {
Log.d(DEBUG_TAG, "data.getCount() " + data.getCount());
HashMap<Integer,Integer> groupMap = mAdapter.getGroupMap();
try {
int groupPos = groupMap.get(id);
Log.d(DEBUG_TAG, "onLoadFinished() for groupPos " + groupPos);
mAdapter.setChildrenCursor(groupPos, data);
} catch (NullPointerException e) {
Log.w("DEBUG","Adapter expired, try again on the next query: "
+ e.getMessage());
}
}
} else {
mAdapter.setGroupCursor(data);
}
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// is about to be closed.
int id = loader.getId();
Log.d(DEBUG_TAG, "onLoaderReset() for loader_id " + id);
if (id != -1) {
// child cursor
try {
mAdapter.setChildrenCursor(id, null);
} catch (NullPointerException e) {
Log.w("TAG", "Adapter expired, try again on the next query: "
+ e.getMessage());
}
} else {
mAdapter.setGroupCursor(null);
}
}
/**
* Populate the contact list
*/
private void populateContactList() {
// Set up our adapter
mAdapter = new GroupsAdapter(getActivity(),this,
android.R.layout.simple_expandable_list_item_1,
android.R.layout.simple_expandable_list_item_1,
new String[] { ContactsContract.Groups.TITLE }, // Name for group layouts
new int[] { android.R.id.text1 },
new String[] { ContactsContract.Contacts.DISPLAY_NAME }, // Name for child layouts
new int[] { android.R.id.text1 });
setListAdapter(mAdapter);
}
}
And here is my adapter which subclasses SimpleCursorTreeAdapter
这是我的适配器,它子类化 SimpleCursorTreeAdapter
public class GroupsAdapter extends SimpleCursorTreeAdapter {
private final String DEBUG_TAG = getClass().getSimpleName().toString();
private ContactManager mActivity;
private GroupsListFragment mFragment;
protected final HashMap<Integer, Integer> mGroupMap;
// Note that the constructor does not take a Cursor. This is done to avoid
// querying the database on the main thread.
public GroupsAdapter(Context context, GroupsListFragment glf,
int groupLayout, int childLayout, String[] groupFrom,
int[] groupTo, String[] childrenFrom, int[] childrenTo) {
super(context, null, groupLayout, groupFrom, groupTo, childLayout,
childrenFrom, childrenTo);
mActivity = (ContactManager) context;
mFragment = glf;
mGroupMap = new HashMap<Integer, Integer>();
}
@Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
// Given the group, we return a cursor for all the children within that group
int groupPos = groupCursor.getPosition();
int groupId = groupCursor.getInt(groupCursor
.getColumnIndex(ContactsContract.Groups._ID));
Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos);
Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId);
mGroupMap.put(groupId, groupPos);
Loader loader = mActivity.getLoaderManager().getLoader(groupId);
if ( loader != null && !loader.isReset() ) {
mActivity.getLoaderManager().restartLoader(groupId, null, mFragment);
} else {
mActivity.getLoaderManager().initLoader(groupId, null, mFragment);
}
return null;
}
//Accessor method
public HashMap<Integer, Integer> getGroupMap() {
return mGroupMap;
}
}
回答by Mitsuaki Ishimoto
In my case, I use the first argument of initLoader (or restartLoader) to give the group position for callback and use Bundle to get children data in getChildrenCursor.
就我而言,我使用 initLoader(或 restartLoader)的第一个参数为回调提供组位置,并使用 Bundle 在 getChildrenCursor 中获取子数据。
Like following;
喜欢以下;
public class ExpandableListAdapter extends SimpleCursorTreeAdapter implements LoaderManager.LoaderCallbacks<Cursor> {
private Context mContext;
private LoaderManager mManager;
public ExpandableListAdapter(
Context context, ExpandableListAdapterListener listener, LoaderManager manager, Cursor groupCursor,
int groupLayout, String[] groupFrom, int[] groupTo,
int childLayout, String[] childFrom, int[] childTo) {
super(context, groupCursor, groupLayout, groupFrom, groupTo, childLayout, childFrom, childTo);
mContext = context;
mManager = manager;
}
@Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
final long idGroup = groupCursor.getLong(groupCursor.getColumnIndex("_id"));
Bundle bundle = new Bundle();
bundle.putLong("idGroup", idGroup);
int groupPos = groupCursor.getPosition();
if (mManager.getLoader(groupPos) != null && !mManager.getLoader(groupPos).isReset()) {
mManager.restartLoader(groupPos, bundle, this);
}
else {
mManager.initLoader(groupPos, bundle, this);
}
return null;
}
@Override
public Loader<Cursor> onCreateLoader(int groupPos, Bundle bundle) {
long idGroup = bundle.getLong("idGroup");
return new CursorLoader(
mContext,
Provider.URI,
new String[]{Table.ID, Table.ID_GROUP, Table.TITLE, Table.CONTEXT},
Table.ID_GROUP + " = ?",
new String[]{String.valueOf(idGroup)},
Table.CREATED + " DESC"
);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
setChildrenCursor(loader.getId(), cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}
回答by Yam
I have bad experience with using ExpandableListView. Its behavior in different Android versions are different. If you are not already too deep into it, you may like to consider redesigning your interface.
我使用 ExpandableListView 的经验很差。它在不同的 Android 版本中的行为是不同的。如果您还没有深入了解它,您可能会考虑重新设计您的界面。
Anyway, to your questions, I suggest you review these 3 points.
不管怎样,对于你的问题,我建议你复习一下这3点。
First, in your call to init the children cursor loader
首先,在您调用 init 子游标加载器时
mActivity.getLoaderManager().initLoader(groupId, null, mFragment);
The groupId you passed in is the value of ContactsContract.Groups._ID. Then, you use this id in the setChildrenCursor's first parameter. This is probably wrong. Instead of passing the groupId into the initLoader, try passing in the group cursor position. For example:
你传入的 groupId 就是 ContactsContract.Groups._ID 的值。然后,您在 setChildrenCursor 的第一个参数中使用此 ID。这很可能是错误的。不要将 groupId 传递到 initLoader,而是尝试传递组光标位置。例如:
int iGroupPos = groupCursor.getPosition();
if ( loader != null && !loader.isReset())
mActivity.getLoaderManager().restartLoader(iGroupPos, null, mFragment);
else
mActivity.getLoaderManager().initLoader(iGroupPos, null, mFragment);
Second, you can see that in the code I suggested above, you probably should call restartLoader only when loader is not null and loader isReset is false.
其次,您可以看到,在我上面建议的代码中,您可能应该仅在 loader 不为 null 且 loader isReset 为 false 时才调用 restartLoader。
Third, you need to return a value for the getChildrenCursor call, which I believe should probably be null.
第三,您需要为 getChildrenCursor 调用返回一个值,我认为该值可能应该为 null。