如何在由数据库游标适配器支持的 Android ListView 中的布局中隐藏一个视图

How to hide one view within a layout in an Android ListView backed by database cursor adapter

示例应用程序的目标是显示 SQLite 数据库中的项目,但如果数据库记录的隐藏标志处于活动状态,则隐藏第二个文本视图(否则显示第二个文本视图)。

问题是它没有隐藏正确的东西。当滚动操作导致项目离开视图并返回视图时,第二个文本视图被隐藏并以不稳定的方式显示在各种列表项上。

已在项目 5、10、15、20 上设置隐藏标志,这是它出现的方式: 向下滚动,隐藏着各种奇怪的物品,而且每次看起来都不一样。例如,条目 14、条目 16 是隐藏的。

滚动到顶部后,我们看到第一组项目不再有相同的隐藏第二行。

然后隐藏了一组全新的条目来回滚动。不是很随机,但莫名其妙。亲眼所见才会相信。

此示例所基于的 'real' 应用程序(此处未显示)实际上正在尝试显示和隐藏 ImageView,但同样的问题围绕着隐藏 TextView,所以这就是我的'已经显示在这里。

下面是应用程序。 你需要的一切都应该包含包括样本数据),如果你想运行这个疯狂的东西。或者您可以在 github 上找到它:https://github.com/sengsational/LvCaApp

LvCaActivity.java:

public class LvCaActivity extends AppCompatActivity {

    private SimpleCursorAdapter dataAdapter;
    private DbAdapter dbHelper;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lv_ca);

        dbHelper = new DbAdapter(this);
        dbHelper.open();
        dbHelper.deleteAll();
        dbHelper.insertSome();

        Cursor bCursor = dbHelper.fetchAll(DbAdapter.bColumns);

        dataAdapter = new MySimpleCursorAdapter(
                this, R.layout.b_item,
                bCursor,
                DbAdapter.bColumns,
                ViewHolder.viewsArray,
                0);

        ListView listView = (ListView) findViewById(R.id.listView1);

        listView.setAdapter(dataAdapter);

    }
}

activity_lv_ca.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent" android:layout_height="fill_parent"
              android:orientation="vertical">

    <ListView android:id="@+id/listView1" android:layout_width="fill_parent"
              android:layout_height="fill_parent" />

</LinearLayout>

b_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:padding="6dip"
                android:id="@+id/b_item_layout">

        <TextView
            android:id="@+id/bName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:textAppearance="?android:attr/textAppearanceListItem"
            android:ellipsize="end"
            android:singleLine="true"
            android:paddingTop="30dp"/>

        <TextView
            android:id="@+id/bSecondLine"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/bName"
            android:textAppearance="?android:attr/textAppearanceSmall" />

        <TextView
            android:id="@+id/bDbItem"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:visibility="gone"
            />
        <TextView
            android:id="@+id/bHidden"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:visibility="gone"
            />

</RelativeLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.company.cpp.lvcaapp"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".LvCaActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

DbAdapter.java:

public class DbAdapter {

    private static final String TAG = "DbAdapter";
    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;

    private static final String DATABASE_NAME = "adbname";
    private static final String SQLITE_TABLE = "atablename";
    private static final int DATABASE_VERSION = 1;

    private final Context mCtx;

    public static final String[] bColumns = new String[] {
            "_id",
            "NAME",
            "SECOND_LINE",
            "HIDDEN",
    };

    private static final String DATABASE_CREATE =
            "CREATE TABLE if not exists " + SQLITE_TABLE + " (" +
                    "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    "NAME TEXT, " +
                    "SECOND_LINE, " +
                    "HIDDEN" +
                    ");";

    private static class DatabaseHelper extends SQLiteOpenHelper {
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            Log.w(TAG, DATABASE_CREATE);
            db.execSQL(DATABASE_CREATE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
                    + newVersion + ", which will destroy all old data");
            db.execSQL("DROP TABLE IF EXISTS " + SQLITE_TABLE);
            onCreate(db);
        }
    }

    public DbAdapter(Context ctx) {
        this.mCtx = ctx;
    }

    public DbAdapter open() throws SQLException {
        mDbHelper = new DatabaseHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
        return this;
    }

    public void close() {
        if (mDbHelper != null) {
            mDbHelper.close();
        }
    }
    public Cursor fetchAll(String[] fields) {
        Cursor mCursor = mDb.query(SQLITE_TABLE, fields, null, null, null, null, null);
        if (mCursor != null) {
            mCursor.moveToFirst();
        }
        return mCursor;
    }

    public void insertSome() {
        AListItem.getInstance();
        String sampleData = "[{\"name\":\"Entry 1\",\"second_line\":\"Second Line 1\",\"hidden\":\"F\"},{\"name\":\"Entry 2\",\"second_line\":\"Second Line 2\",\"hidden\":\"F\"},{\"name\":\"Entry 3\",\"second_line\":\"Second Line 3\",\"hidden\":\"F\"},{\"name\":\"Entry 4\",\"second_line\":\"Second Line 4\",\"hidden\":\"F\"},{\"name\":\"EntryH 5\",\"second_line\":\"Second Line 5\",\"hidden\":\"T\"},{\"name\":\"Entry 6\",\"second_line\":\"Second Line 6\",\"hidden\":\"F\"},{\"name\":\"Entry 7\",\"second_line\":\"Second Line 7\",\"hidden\":\"F\"},{\"name\":\"Entry 8\",\"second_line\":\"Second Line 8\",\"hidden\":\"F\"},{\"name\":\"Entry 9\",\"second_line\":\"Second Line 9\",\"hidden\":\"F\"},{\"name\":\"EntryH 10\",\"second_line\":\"Second Line 10\",\"hidden\":\"T\"},{\"name\":\"Entry 11\",\"second_line\":\"Second Line 11\",\"hidden\":\"F\"},{\"name\":\"Entry 12\",\"second_line\":\"Second Line 12\",\"hidden\":\"F\"},{\"name\":\"Entry 13\",\"second_line\":\"Second Line 13\",\"hidden\":\"F\"},{\"name\":\"Entry 14\",\"second_line\":\"Second Line 14\",\"hidden\":\"F\"},{\"name\":\"EntryH 15\",\"second_line\":\"Second Line 15\",\"hidden\":\"T\"},{\"name\":\"Entry 16\",\"second_line\":\"Second Line 16\",\"hidden\":\"F\"},{\"name\":\"Entry 17\",\"second_line\":\"Second Line 17\",\"hidden\":\"F\"},{\"name\":\"Entry 18\",\"second_line\":\"Second Line 18\",\"hidden\":\"F\"},{\"name\":\"Entry 19\",\"second_line\":\"Second Line 19\",\"hidden\":\"F\"},{\"name\":\"EntryH 20\",\"second_line\":\"Second Line 20\",\"hidden\":\"T\"},{\"name\":\"Entry 21\",\"second_line\":\"Second Line 21\",\"hidden\":\"F\"},{\"name\":\"Entry 22\",\"second_line\":\"Second Line 22\",\"hidden\":\"F\"},{\"name\":\"Entry 23\",\"second_line\":\"Second Line 23\",\"hidden\":\"F\"}]";
        String[] items = sampleData.split("\},\{");
        for(String item: items){
            AListItem.clear();
            AListItem.load(item);
            if(AListItem.getName().contains("Hide")){
                AListItem.setHidden("T");
            }

            mDb.insert(SQLITE_TABLE, null, AListItem.getContentValues());

            ContentValues values = AListItem.getContentValues();
            Log.v(TAG, "values.toString()" + values.toString());
        }
    }

    public boolean deleteAll() {
        int doneDelete = 0;
        doneDelete = mDb.delete(SQLITE_TABLE, null , null);
        Log.w(TAG, Integer.toString(doneDelete));
        return doneDelete > 0;
    }
}

AListItem.java:

public class AListItem {

    static String rawInputString;

    static String name;
    static String second_line;
    static String hidden;

    static AListItem aListItem;

    private AListItem() {
    }

    public static AListItem getInstance(){
        if (aListItem == null) {
            aListItem = new AListItem();
        }
        return aListItem;
    }

    public static void clear() {
        rawInputString = null;
        name = null;
        second_line = null;
        hidden = null;
    }

    public static ContentValues getContentValues() {
        ContentValues values = new ContentValues();
        values.put("NAME", name);
        values.put("SECOND_LINE", second_line);
        values.put("HIDDEN",  hidden);
        return values;
    }


    public static void load(String string) {
        StringBuffer buf = new StringBuffer(string);
        if (buf.substring(0,2).equals("[{")){
            buf.delete(0,2);
        }
        rawInputString = buf.toString();
        parse();
    }

    public static void parse() {

        if (rawInputString == null) {
            System.out.println("nothing to parse");
            return;
        }

        rawInputString = rawInputString.replaceAll("\"\:null,", "\"\:\"null\",");
        String[] nvpa = rawInputString.split("\",\"");
        for (String nvpString : nvpa) {
            String[] nvpItem = nvpString.split("\":\"");
            if (nvpItem.length < 2) continue;
            String identifier = nvpItem[0].replaceAll("\"", "");
            String content = nvpItem[1].replaceAll("\"", "");

            switch (identifier) {
                case "name":
                    setName(content);
                    break;
                case "second_line":
                    setSecond_line(content);
                    break;
                case "hidden":
                    setHidden(content);
                    break;
                default:
                    System.out.println("nowhere to put [" + nvpItem[0] + "] " + nvpString + " raw: " + rawInputString);
                    break;
            }
        }
    }

    public static String getName() {
        return name;
    }

    public static void setName(String name) {  AListItem.name = name; }

    public static void setSecond_line(String second_line) {
        AListItem.second_line = second_line;
    }

    public static String getSecond_line() {
        return second_line;
    }

    public static void setHidden(String hidden) {
        AListItem.hidden = hidden;
    }

    public static String getHidden() {
        return hidden;
    }

    public String toString() {
        return getName() + ", " +
                getSecond_line() + ", " +
                getHidden();
    }

}

MySimpleCursorAdapter.java:

public class MySimpleCursorAdapter extends SimpleCursorAdapter {

    Context context;
    Cursor cursor;
    public static final String TAG = "MySimpleCursorAdapter";

    public MySimpleCursorAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to, int flags) {
        super(context, layout, cursor, from, to, flags);
        this.context = context;
        this.cursor = cursor;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Log.v(TAG,"getView() >>>>>>STARTING");

        ViewHolder viewHolder;
        LayoutInflater inflater = LayoutInflater.from(context);
        if (null == convertView || null == convertView.getTag()) {
            convertView = inflater.inflate(R.layout.b_item, null);
            viewHolder = new ViewHolder(convertView);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        for (int i = 0; i < cursor.getColumnCount(); i++) {
            Log.v(TAG, "getView cursor " + i + ": " + cursor.getString(i));
        }

        String hidden = cursor.getString(ViewHolder.HIDDEN);
        if (hidden == null) hidden = "F";
        Log.v(TAG,"Hidden State: " + hidden);
        switch (hidden) {
            case "F":
               viewHolder.showSecondLine(); // DRS 20160827 - Added line suggested by aiwiguna
                break;
            case "T":
                Log.v(TAG,">>>>>Hidden was TRUE<<<<<<<: " + cursor.getString(ViewHolder.NAME));
                viewHolder.hideSecondLine();
                break;
        }

        convertView.setTag(viewHolder);
        View returnView = super.getView(position, convertView, parent);

        Log.v(TAG,"getView() ENDING<<<<<<<<<");

        return returnView;
    }
}

ViewHolder.java:

class ViewHolder {

    public static final String TAG = "ViewHolder";

    public static final int DB_ITEM = 0;
    public static final int NAME = 1;
    public static final int SECOND_LINE = 2;
    public static final int HIDDEN = 3;

    public static final int[] viewsArray = new int[] {
            R.id.bDbItem,
            R.id.bName,
            R.id.bSecondLine,
            R.id.bHidden,
    };

    public static final TextView[] textViewArray = new TextView[viewsArray.length];

    public ViewHolder( final View root ) {
        Log.v(TAG, "ViewHolder constructor");
        for (int i = 0; i < viewsArray.length; i++) {
            textViewArray[i] = (TextView) root.findViewById(viewsArray[i]);
            Log.v(TAG, "             textViewArray[" + i + "]: " + textViewArray[i]);
        }
    }

    public void hideSecondLine() {
        textViewArray[SECOND_LINE].setVisibility(View.INVISIBLE);
    }

    //DRS 20160827 - Addition recommended by aiwiguna
    public void showSecondLine() {
        textViewArray[SECOND_LINE].setVisibility(View.VISIBLE);
    }

}

在您当前的解决方案中,您需要 "unhide" 循环视图中的第二行。最简单的方法是在 switch 语句中。 FWIW,您可以将 "hidden" 标志作为整数值存储在 SQLite table 中。这将使比较更容易,并且可能会稍微快一些。

另一种可能的解决方案是使用两种不同的布局,一种用于正常情况(hidden 为假),另一种用于 "hidden" 情况(hidden 为真)。在 MySimpleCursorAdapter.getView() 中,if 语句决定了要扩充哪个布局。当回收视图时,您仍然会遇到同样的问题:在重新使用之前检查以确保回收的视图是正确的类型。

switch (hidden) {
            case "F":
                viewHolder.showSecondLine();
                break;
            case "T":
                Log.v(TAG,">>>>>Hidden was TRUE<<<<<<<: " + cursor.getString(ViewHolder.NAME));
                viewHolder.hideSecondLine();
                break;
        }

为了让这个应用程序正常工作,

  1. ListView必须换成RecyclerView,加上
  2. SimpleCursorAdapter 实现需要替换为 RecyclerCursorAdapter 实现。

MyRecyclerCursorAdapter,示例中的新 class,扩展 RecyclerView.Adapter:

//DRS 20160829 - Added class.  Replaces MySimpleCursorAdapter
public class MyRecyclerCursorAdapter extends RecyclerView.Adapter{

    private Cursor cursor;
    private Context context;
    private static final String TAG = MyRecyclerCursorAdapter.class.getSimpleName();

    public MyRecyclerCursorAdapter(Context context, Cursor cursor) {
        this.cursor = cursor;
        this.context = context;
    }

    //DRS 20160829 - Critical method within new class
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Log.v(TAG, "onCreateViewHolder ");
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);

        View itemView = inflater.inflate(R.layout.b_item, parent, false);

        ViewHolder viewHolder = new ViewHolder(itemView, cursor);
        return viewHolder;
    }

    //DRS 20160829 - Critical method within new class
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        cursor.moveToPosition(position);
        ((ViewHolder)holder).bindFields(cursor);
    }

    @Override
    public int getItemCount() {
        return cursor.getCount();
    }
}

请注意,此 class 带有一个 Cursor 对象,即 link 到将填充列表的 SQLite 数据库条目。

此外,为了获得对 Recycler View 的访问权限,必须将依赖项添加到 build.gradle:

// DRS 20160829 - Added recyclerview
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.2.0'
    compile 'com.android.support:recyclerview-v7:24.2.0'
}

b_item.xml 无需更改。
activity_lv_ca.xml 需要 RecyclerView 代替旧的 ListView:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent" android:layout_height="fill_parent"
          android:orientation="vertical">

    <!-- DRS 20160829 - Commented ListView, Added RecyclerView
    <ListView android:id="@+id/listView1" android:layout_width="fill_parent"
          android:layout_height="fill_parent" / -->
    <android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical" />

</LinearLayout>

ViewHolder class 现在扩展 Recycler.ViewHolder。除了标准的 ViewHolder 实现之外,这个自定义的 ViewHolder 还有一个 Cursor,用于设置出现在列表每一行的 TextViews 的文本。这是管理可见性的地方(在我称为 bindFields():

的方法中
public class ViewHolder extends RecyclerView.ViewHolder {
    public static final String TAG = "ViewHolder";
    private final Cursor cursor;

    public TextView bDbItem;
    public TextView bName;
    public TextView bSecondLine;
    public TextView bHidden;

    public static final int DB_ITEM = 0;
    public static final int NAME = 1;
    public static final int SECOND_LINE = 2;
    public static final int HIDDEN = 3;

    public ViewHolder(View root, Cursor cursor ) {
        super(root);
        this.cursor = cursor;
        Log.v(TAG, "ViewHolder constructor");
        bDbItem = (TextView) itemView.findViewById(R.id.bDbItem);
        bName = (TextView) itemView.findViewById(R.id.bName);
        bSecondLine = (TextView) itemView.findViewById(R.id.bSecondLine);
        bHidden = (TextView) itemView.findViewById(R.id.bHidden);
    }

    public void bindFields(Cursor cursor) {

        bDbItem.setText("" + cursor.getInt(DB_ITEM));
        bName.setText(cursor.getString(NAME));
        bSecondLine.setText(cursor.getString(SECOND_LINE));

        String hidden = cursor.getString(HIDDEN);
        bHidden.setText(hidden);
        if ("F".equals(hidden)) {
            bSecondLine.setVisibility(View.VISIBLE);
        } else {
            bSecondLine.setVisibility(View.INVISIBLE);
        }
    }
}

AListItem.java 无需更改。
DBAdapter.java 无需更改。

可以在 github 上找到工作应用程序:RecyclerViewSqlite