光标更新后,Android CursorTreeAdapter 不更新组光标/折叠组

Posted

技术标签:

【中文标题】光标更新后,Android CursorTreeAdapter 不更新组光标/折叠组【英文标题】:Android CursorTreeAdapter does not update group cursor / collapses group after Cursor update 【发布时间】:2021-11-23 19:27:12 【问题描述】:

我希望 ExpandableListView 在进行 SQLite 数据库更改时更新(重新绑定?)组项和子项。我已经实现了我自己的 CursorTreeAdapter 子类。更新数据库时,需要重新查询支持游标(子游标和组)并刷新视图,从而保留组的展开/折叠状态。但是,我无法实现这种行为。

期望的行为:组游标和子游标都被更新。将添加一个子项,并且当前展开的所有组都将保持展开状态。

场景 #1 - 更新使用 CursorTreeAdapter.notifyDataSetChanged()

发生数据库更改时,我在适配器上调用此方法,添加子项,组保持原样展开,但组项文本未更新

在这种情况下,组视图显示的数据不会更改以反映子数据。我可以看到调用这个方法的组游标没有更新,并且包含了陈旧的数据。

场景 #2 - 更新使用 CursorTreeAdapter.getCursor().requery()

当发生数据库更改时,我在 Adapter 上调用此方法时,将添加子项,具有新子项的组自动折叠,并更新组项文本。

在这种情况下,我可以看到组游标已经更新并且组视图中的数据是正确的,但是添加一个子项后组不应该崩溃。我不明白为什么不保留展开状态,但应该保留。

适用的代码如下...

如果有任何后果,这里是 build.gradle 相关设置,为简洁起见省略了打包选项和依赖项:

apply plugin: 'com.android.application'

android 
    compileSdkVersion 29
    buildToolsVersion '30.0.2'

    defaultConfig 
        applicationId "com.conceptualsystems.kitting"
        minSdkVersion 24
        targetSdkVersion 29
        versionCode 92
        versionName "2.29"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
    
    buildTypes 
        release 
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        
    

处理程序:

/**
 * a Handler which will be invoked when a database change has occurred.
 * I have tried several possibilities to achieve the desired behavior to no avail.
 */
public android.os.Handler mRefreshHandler = new Handler() 
    @Override
    public void handleMessage(android.os.Message msg) 
        new SummaryTask().execute();
        //mShipKitExpandableListAdapter.notifyDataSetChanged();
        //mShipKitExpandableListAdapter.setGroupCursor(
        //      DbSingleton.getInstance().getDatabase().rawQuery(
        //              getGroupQuery(), null
        //      ));
        mShipKitExpandableListAdapter.getCursor().requery();
        //mShipKitExpandableListView.invalidateViews();
    
;

ShipKitExpandableAdapter:

public class ShipKitExpandableAdapter extends CursorTreeAdapter 
    public ShipKitExpandableAdapter(Cursor cursor, Context context) 
        super(cursor, context, true);
    

    @Override
    protected Cursor getChildrenCursor(Cursor groupCursor) 
        String productName = groupCursor.getString(
                groupCursor.getColumnIndex(DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_FIN_ID));

        return DbSingleton.getInstance().getDatabase().rawQuery(
                getChildQuery(productName), null
        );
    

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) 
        View groupView = super.getGroupView(
                groupPosition,
                isExpanded,
                convertView,
                parent);
        if(groupView != null) 
            ColorLayout.setTheme(groupView);
        

        return groupView;
    

    @Override
    protected View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) 
        return mInflater.inflate(R.layout.ship_list_group, parent, false);
    

    @Override
    protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) 
        try 
            String count = cursor.getString(cursor.getColumnIndex("count"));
            ((TextView)view.findViewById(R.id.group_pieces)).setText(count);
            String netUnits = cursor.getString(cursor.getColumnIndex(DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_NET));
            ((TextView)view.findViewById(R.id.group_net_units)).setText(netUnits);
            ((TextView)view.findViewById(R.id.group_net_uom)).setText(" lbs");
            String productName = cursor.getString(cursor.getColumnIndex(DbSchemaKitting.KitProductSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitProductSchema.COLUMN_NAME));
            ((TextView)view.findViewById(R.id.group_product_name)).setText(productName);
            String grossUnits = cursor.getString(cursor.getColumnIndex(DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_GROSS));
            ((TextView)view.findViewById(R.id.group_gross_units)).setText(grossUnits);
            ((TextView)view.findViewById(R.id.group_gross_uom)).setText(" lbs");
            ColorLayout.setTheme(view);
         catch(Exception e) 
            mLogger.info("error getting a field: ", e);
            e.printStackTrace();
        
    

    @Override
    public View getChildView(final int groupPosition,
                             final int childPosition, boolean isLastChild, View convertView,
                             ViewGroup parent) 
        final View childView = super.getChildView(
                groupPosition,
                childPosition,
                isLastChild,
                convertView,
                parent);
        if(childView != null) 
            ColorLayout.setTheme(childView);

            childView.setOnLongClickListener(new View.OnLongClickListener() 
                @Override
                public boolean onLongClick(View v) 
                    //long press deletes a shipment record.
                    //mShipKitAdapter.getCursor().moveToPosition(position);
                    //String kit_id = kitCursor.getString(kitCursor.getColumnIndex(DbSchema.KitSchema.TABLE_NAME+DbSchema.KitSchema.COLUMN_ID));
                    String kit_id = ((TextView)childView.findViewById(R.id.kit_id)).getText().toString();
                    removeDialog(DIALOG_CONFIRM_DELETE);

                    Bundle b = new Bundle();
                    if(kit_id!=null) 
                        b.putInt("kit_id", Integer.valueOf(kit_id));
                    

                    showDialog(DIALOG_CONFIRM_DELETE, b);

                    return true;
                
            );
        

        return childView;
    

    @Override
    protected View newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent) 
        return mInflater.inflate(R.layout.ship_list_item, parent, false);
    

    @Override
    protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) 
        try 
            ((TextView)view.findViewById(R.id.kit_id)).setText(cursor.getString(cursor.getColumnIndex(DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_ID)));
            ((TextView)view.findViewById(R.id.net_units)).setText(cursor.getString(cursor.getColumnIndex(DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_NET)));
            ((TextView)view.findViewById(R.id.net_uom)).setText(" lbs");
            ((TextView)view.findViewById(R.id.product_name)).setText(cursor.getString(cursor.getColumnIndex(DbSchemaKitting.KitProductSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitProductSchema.COLUMN_NAME)));
            ((TextView)view.findViewById(R.id.gross_units)).setText(cursor.getString(cursor.getColumnIndex(DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_GROSS)));
            ((TextView)view.findViewById(R.id.gross_uom)).setText(" lbs");
            ColorLayout.setTheme(view);
         catch(Exception e) 
            mLogger.info("error getting a field: ", e);
            e.printStackTrace();
        
    

设置适配器:

mShipKitExpandableListAdapter = new ShipKitExpandableAdapter(
    DbSingleton.getInstance().getDatabase().rawQuery(
            getGroupQuery(), null
    ),
    ShipActivity.this
);
mShipKitExpandableListView.setAdapter(mShipKitExpandableListAdapter);
mShipKitExpandableListView.setGroupIndicator(null);

数据库查询:

public String getGroupQuery() 
    return "SELECT " +
            "COUNT(*) AS count, " +
            DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema._ID +
            " AS _id, " +
            DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_ID +
            " AS " + DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_ID + ", " +
            DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_FIN_ID +
            " AS " + DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_FIN_ID + ", " +
            "SUM(" +
                DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_GROSS +
            ")" +
            " AS " + DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_GROSS + ", " +
            "SUM(" +
                DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_NET +
            ")" +
            " AS " + DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_NET + ", " +
            DbSchemaKitting.KitProductSchema.TABLE_NAME + "." + DbSchemaKitting.KitProductSchema.COLUMN_NAME +
            " AS " + DbSchemaKitting.KitProductSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitProductSchema.COLUMN_NAME +
            " FROM " + DbSchemaKitting.KitSchema.TABLE_NAME +
            " LEFT OUTER JOIN " + DbSchemaKitting.KitProductSchema.TABLE_NAME +
            " ON " + DbSchemaKitting.KitSchema.TABLE_NAME + "." +  DbSchemaKitting.KitSchema.COLUMN_FIN_ID + "=" + DbSchemaKitting.KitProductSchema.TABLE_NAME + "." +  DbSchemaKitting.KitProductSchema.COLUMN_ID +
            " WHERE " + DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_SHIP_ID + "=" + mShipmentID +
            " GROUP BY " + DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_FIN_ID;


public String getChildQuery(String productName) 
    return "SELECT " +
            DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema._ID +
            " AS _id, " +
            DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_ID +
            " AS " + DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_ID + ", " +
            DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_FIN_ID +
            " AS " + DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_FIN_ID + ", " +
            DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_GROSS +
            " AS " + DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_GROSS + ", " +
            DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_NET +
            " AS " + DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_NET + ", " +
            DbSchemaKitting.KitProductSchema.TABLE_NAME + "." + DbSchemaKitting.KitProductSchema.COLUMN_NAME +
            " AS " + DbSchemaKitting.KitProductSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitProductSchema.COLUMN_NAME +
            " FROM " + DbSchemaKitting.KitSchema.TABLE_NAME +
            " LEFT OUTER JOIN " + DbSchemaKitting.KitProductSchema.TABLE_NAME +
            " ON " + DbSchemaKitting.KitSchema.TABLE_NAME + "." +  DbSchemaKitting.KitSchema.COLUMN_FIN_ID + "=" + DbSchemaKitting.KitProductSchema.TABLE_NAME + "." +  DbSchemaKitting.KitProductSchema.COLUMN_ID +
            " WHERE " + DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_SHIP_ID + "=" + mShipmentID + " AND " +
            DbSchemaKitting.KitSchema.TABLE_NAME + "." + DbSchemaKitting.KitSchema.COLUMN_FIN_ID + "='" + productName + "'";

【问题讨论】:

【参考方案1】:

我最终通过手动跟踪适配器中的所有扩展状态并在我的适配器实现中创建一个requery() 调用来解决这个问题,该调用在请求Cursor 时重新创建扩展状态。我觉得这是用CursorTreeAdapter 更新解决这个相当奇怪的怪癖的最优雅的解决方案。

我在CursorTreeAdapter 实现中添加了以下代码:

public void requery() 
    getCursor().requery();
    for(int i = 0; i < getCursor().getCount(); i++) 
        Cursor groupCursor = getGroup(i);
        String productID = groupCursor.getString(groupCursor.getColumnIndex(DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_FIN_ID));
        if(expansionStates.containsKey(productID)) 
            if(expansionStates.get(productID))
                mShipKitExpandableListView.expandGroup(i);
        
    


@Override
public void onGroupCollapsed(int groupPosition) 
    Cursor groupCursor = getGroup(groupPosition);
    String productID = groupCursor.getString(groupCursor.getColumnIndex(DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_FIN_ID));
    expansionStates.put(productID, false);


@Override
public void onGroupExpanded(int groupPosition) 
    Cursor groupCursor = getGroup(groupPosition);
    String productID = groupCursor.getString(groupCursor.getColumnIndex(DbSchemaKitting.KitSchema.TABLE_NAME + "dot" + DbSchemaKitting.KitSchema.COLUMN_FIN_ID));
    expansionStates.put(productID, true);

从这里开始,只需调用ShipKitExpandableAdapter.requery() 方法而不是底层Cursor 的requery 方法,而且,你瞧,在每次requery 时,扩展状态都会被保留。请注意,您当然可以通过更改 Map.Entry 类型参数(如有必要)并在 getColumnIndex() 中填写您自己的列名,使用光标结果中的任何标识符来保留展开状态。

处理程序:

/**
 * a Handler which will be invoked when a database change has occurred.
 * I have tried several possibilities to achieve the desired behavior to no avail.
 */
public android.os.Handler mRefreshHandler = new Handler() 
    @Override
    public void handleMessage(android.os.Message msg) 
        new SummaryTask().execute();
        mShipKitExpandableListAdapter.requery();
    
;

【讨论】:

以上是关于光标更新后,Android CursorTreeAdapter 不更新组光标/折叠组的主要内容,如果未能解决你的问题,请参考以下文章

android.database.CursorWindowAllocationException:即使在关闭光标后,2048 kb 的光标窗口分配也失败

用户更新后从 Matlab 编辑 uicontrol 中删除光标?

ContentEditable div - 更新内部 html 后设置光标位置

Android - 关闭数据库后可以使用 SQLite 光标吗?

android edittext输入完成后让光标消失

Android Studio:打开其他类或在不同类中粘贴文本后,文本光标消失/消失