11

カスタム CursorAdapter のコードに満足したことはありませんでしたが、今日それを見直して、長い間私を悩ませていた小さな問題を修正することにしました (興味深いことに、私のアプリのユーザーは誰もそのような問題を報告していません)。

ここに私の質問の簡単な説明があります:

私のカスタム CursorAdapterは、私が目にするほとんどの例の代わりにオーバーライドnewView()します。これら 2 つのメソッドの間で ViewHolder パターンを使用します。しかし、私の主な問題は、各リスト項目に使用しているカスタム レイアウトにありました。bindView()getView()ToggleButton

問題は、リスト アイテム ビューがビューの外にスクロールされ、その後スクロールしてビューに戻ると、ボタンの状態が保持されないことでした。この問題cursorは、 が押されたときにデータベース データが変更されたことを認識せず、ToggleButton常に同じデータを取得していたため発生していました。をクリックしたときにカーソルを再クエリしようとしましたToggleButtonが、問題は解決しましたが、非常に遅かったです。

私はこの問題を解決し、レビューのためにクラス全体をここに投稿しています。コーディングの決定をよりよく説明するために、この特定の質問についてコードに徹底的にコメントしました。

このコードはあなたに似合いますか?何らかの方法で改善/最適化または変更しますか?

PS: CursorLoader が明らかに改善されていることは知っていますが、当分の間、そのような大規模なコードの書き直しに対処する時間がありません。それは私がロードマップに持っているものです。

コードは次のとおりです。

public class NotesListAdapter extends CursorAdapter implements OnClickListener {

    private static class ViewHolder {
        ImageView icon;
        TextView title;
        TextView description;
        ToggleButton visibility;
    }

    private static class NoteData {
        long id;
        int iconId;
        String title;
        String description;
        int position;
    }

    private LayoutInflater mInflater;

    private NotificationHelper mNotificationHelper;
    private AgendaNotesAdapter mAgendaAdapter;

    /*
     * This is used to store the state of the toggle buttons for each item in the list
     */
    private List<Boolean> mToggleState;

    private int mColumnRowId;
    private int mColumnTitle;
    private int mColumnDescription;
    private int mColumnIconName;
    private int mColumnVisibility;

    public NotesListAdapter(Context context, Cursor cursor, NotificationHelper helper, AgendaNotesAdapter adapter) {
        super(context, cursor);

        mInflater = LayoutInflater.from(context);

        /*
         * Helper class to post notifications to the status bar and database adapter class to update
         * the database data when the user presses the toggle button in any of items in the list
         */
        mNotificationHelper = helper;
        mAgendaAdapter = adapter;

        /*
         * There's no need to keep getting the column indexes every time in bindView() (as I see in
         * a few examples) so I do it once and save the indexes in instance variables
         */
        findColumnIndexes(cursor);

        /*
         * Populate the toggle button states for each item in the list with the corresponding value
         * from each record in the database, but isn't this a slow operation?
         */
        for(mToggleState = new ArrayList<Boolean>(); !cursor.isAfterLast(); cursor.moveToNext()) {
            mToggleState.add(cursor.getInt(mColumnVisibility) != 0);
        }
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View view = mInflater.inflate(R.layout.list_item_note, null);

        /*
         * The ViewHolder pattern is here only used to prevent calling findViewById() all the time
         * in bindView(), we only need to find all the views once
         */
        ViewHolder viewHolder = new ViewHolder();

        viewHolder.icon = (ImageView)view.findViewById(R.id.imageview_icon);
        viewHolder.title = (TextView)view.findViewById(R.id.textview_title);
        viewHolder.description = (TextView)view.findViewById(R.id.textview_description);
        viewHolder.visibility = (ToggleButton)view.findViewById(R.id.togglebutton_visibility);

        /*
         * I also use newView() to set the toggle button click listener for each item in the list
         */
        viewHolder.visibility.setOnClickListener(this);

        view.setTag(viewHolder);

        return view;
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        Resources resources = context.getResources();

        int iconId = resources.getIdentifier(cursor.getString(mColumnIconName),
                "drawable", context.getPackageName());

        String title = cursor.getString(mColumnTitle);
        String description = cursor.getString(mColumnDescription);

        /*
         * This is similar to the ViewHolder pattern and it's need to access the note data when the
         * onClick() method is fired
         */
        NoteData noteData = new NoteData();

        /*
         * This data is needed to post a notification when the onClick() method is fired
         */
        noteData.id = cursor.getLong(mColumnRowId);
        noteData.iconId = iconId;
        noteData.title = title;
        noteData.description = description;

        /*
         * This data is needed to update mToggleState[POS] when the onClick() method is fired
         */
        noteData.position = cursor.getPosition();

        /*
         * Get our ViewHolder with all the view IDs found in newView()
         */
        ViewHolder viewHolder = (ViewHolder)view.getTag();

        /*
         * The Html.fromHtml is needed but the code relevant to that was stripped
         */
        viewHolder.icon.setImageResource(iconId);
        viewHolder.title.setText(Html.fromHtml(title));
        viewHolder.description.setText(Html.fromHtml(description));

        /*
         * Set the toggle button state for this list item from the value in mToggleState[POS]
         * instead of getting it from the database with 'cursor.getInt(mColumnVisibility) != 0'
         * otherwise the state will be incorrect if it was changed between the item view scrolling
         * out of view and scrolling back into view
         */
        viewHolder.visibility.setChecked(mToggleState.get(noteData.position));

        /*
         * Again, save the note data to be accessed when onClick() gets fired
         */
        viewHolder.visibility.setTag(noteData);
    }

    @Override
    public void onClick(View view) {
        /*
         * Get the new state directly from the toggle button state 
         */
        boolean visibility = ((ToggleButton)view).isChecked();

        /*
         * Get all our note data needed to post (or remove) a notification 
         */
        NoteData noteData = (NoteData)view.getTag();

        /*
         * The toggle button state changed, update mToggleState[POS] to reflect that new change
         */
        mToggleState.set(noteData.position, visibility);

        /*
         * Post the notification or remove it from the status bar depending on toggle button state
         */
        if(visibility) {
            mNotificationHelper.postNotification(
                    noteData.id, noteData.iconId, noteData.title, noteData.description);
        } else {
            mNotificationHelper.cancelNotification(noteData.id);
        }

        /*
         * Update the database note item with the new toggle button state, without the need to
         * requery the cursor (which is slow, I've tested it) to reflect the new toggle button state
         * in the list because the value was saved in mToggleState[POS] a few lines above
         */
        mAgendaAdapter.updateNote(noteData.id, null, null, null, null, visibility);
    }

    private void findColumnIndexes(Cursor cursor) {
        mColumnRowId = cursor.getColumnIndex(AgendaNotesAdapter.KEY_ROW_ID);
        mColumnTitle = cursor.getColumnIndex(AgendaNotesAdapter.KEY_TITLE);
        mColumnDescription = cursor.getColumnIndex(AgendaNotesAdapter.KEY_DESCRIPTION);
        mColumnIconName = cursor.getColumnIndex(AgendaNotesAdapter.KEY_ICON_NAME);
        mColumnVisibility = cursor.getColumnIndex(AgendaNotesAdapter.KEY_VISIBILITY);
    }

}
4

3 に答える 3

4

あなたのソリューションは最適であり、武器に追加します:) 多分、データベースへの呼び出しを少し最適化しようとします。

実際、タスクの条件により、解決策は 3 つしかありません。

  1. 1 行のみを更新し、カーソルを再クエリして、すべての項目を再描画します。(率直に、力ずくで)。
  2. 行を更新し、結果をキャッシュして、アイテムを描画するためにキャッシュを使用します。
  3. 結果をキャッシュし、アイテムの描画にキャッシュを使用します。そして、このアクティビティ/フラグメントを離れると、結果がデータベースにコミットされます。

3番目のソリューションでは、変更を探すために SparseArray を使用できます。

private SparseArray<NoteData> mArrayViewHolders;

public void onClick(View view) {
     //here your logic with NoteData. 
     //start of my improve
     if (mArrayViewHolders.get(selectedPosition) == null) {
        // put the change into array
        mArrayViewHolders.put(selectedPosition, noteData);
     } else {
        // rollback the change
        mArrayViewHolders.delete(selectedPosition);
     }
     //end of my improve
     //we don't commit the changes to database.
}

繰り返しますが、最初からこの配列は空です。最初にボタンを切り替える (変更がある) ときは、配列に NoteData を追加します。ボタンを 2 回目に切り替えると (ロールバックがあります)、配列から NoteData が削除されます。等々。

終了したら、配列をリクエストして、変更をデータベースにプッシュします。

于 2012-02-29T07:47:04.477 に答える
1

あなたが見ているのは、Androidのビューの再利用です。カーソルをもう一度クエリして、何か問題が発生しているとは思いません。cursor.requery()関数は使用しないでください。

代わりに、最初は常にトグルをfalseに設定してから、カーソルを確認し、必要に応じてオンに切り替えます。

たぶんあなたはそれをやっていて、私は何かを誤解しました、しかし私はあなたがそれをするのに遅い結果を持つべきではないと思います。

擬似コード:

getView(){
setToggleFalse();
boolean value = Boolean.valueOf(cursor.getString('my column'));
if (value){
   setToggleTrue();
}
}
于 2012-03-01T18:42:39.423 に答える
1

CursorLoaderに行く前に待っていました。CursorLoader の派生物は CursorLoader と連携しないようです。

于 2012-06-18T12:02:31.327 に答える