11
votes

Est-ce que ce cursoradaptateur personnalisé pour une liste de réception est-il correctement codé pour Android?

Je n'ai jamais été heureux avec le code sur mon CursorAdapter personnalisé jusqu'à aujourd'hui j'ai décidé de revoir et corriger un petit problème qui me tracasse depuis longtemps (il est intéressant, aucun des utilisateurs de mon application jamais fait état d'une telle . problème)

Voici une petite description de ma question: p>

Mes remplacements de CursorAdapter personnalisés NewView () code> et Bindview () code> au lieu de getView () code> comme la plupart des exemples que je vois. J'utilise le modèle de ViewHolder entre ces 2 méthodes. Mais mon principal problème était la coutume page que je suis en utilisant pour chaque élément de la liste, il contient un ToggleButton code>. P>

Le problème est que l'état du bouton n'a pas été conservé lorsqu'une liste Voir l'article défilée hors de la vue et scrollbed arrière en vue. Ce problème existait parce que le curseur code> n'a jamais eu connaissance que les données de base de données modifiées lorsque le ToggleButton code> a été pressé et il tirait toujours les mêmes données. J'ai essayé de actualisez le curseur en cliquant sur le ToggleButton code> et qui a résolu le problème, mais il était très lent. P>

J'ai résolu ce problème et je poste la classe ici pour évaluation. Je l'ai commenté le code à fond pour cette question spécifique pour mieux expliquer mes décisions de codage. P>

Est-ce que ce regard bon code pour vous? Souhaitez-vous améliorer / optimiser ou modifier en quelque sorte p>

P.S: Je sais que le CursorLoader est une amélioration évidente mais je n'ai pas le temps de traiter de tels grands réécritures de code pour l'instant. C'est quelque chose que j'ai dans la feuille de route que p>

Voici le code:. P>

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);
    }

}


0 commentaires

3 Réponses :


4
votes

Votre solution est optimale et je vais l'ajouter à mes armes :) Peut-être, je vais essayer d'apporter un peu d'optimisation pour les appels à la base de données.

En effet, à cause des conditions de la tâche, il n'y a que trois Solutions:

  1. Mettez à jour une seule ligne, curseur de demande et redessinez tous les éléments. (Force brute droite).
  2. Mettez à jour la ligne, cache les résultats et utilisez le cache pour dessiner des éléments.
  3. cache les résultats, utilisez le cache pour dessiner des éléments. Et lorsque vous quittez cette activité / fragment, activez les résultats à la base de données.

    pour la 3ème solution Vous pouvez utiliser SPARSARRAY pour rechercher les modifications. xxx

    à nouveau: à partir du démarrage de ce tableau est vide. Lorsque vous basculez la première fois (il y a un changement), vous ajoutez notedata au tableau. Lorsque vous basculez la deuxième fois sur le bouton (il y a une restauration), vous supprimez NoteTata de la matrice. Etc.

    Lorsque vous terminez, demandez simplement la matrice et appuyez sur les modifications dans la base de données.


1 commentaires

J'ai aimé l'idée d'un Sparsarray , ne savait pas à propos de cette classe. Cela ressemble à un moyen plus efficace de mettre en cache le bouton des états au lieu de sauver toutes dans une liste . Mais je n'aime pas l'idée de ne commet que les résultats à la base de données lorsque l'utilisateur quitte l'activité. Cela aurait besoin de code supplémentaire pour gérer cette situation. Donc, à la fin, je suppose que je vais opter par la deuxième solution que vous avez énumérée. Qui est essentiellement ce que je faisais en premier lieu. J'ai toujours aimé votre réponse mais je vais peut-être 1 ou 2 jours de plus :)



1
votes

Qu'est-ce que vous voyez est la vue de l'utilisation de Android. Je ne pense pas que vous faites quelque chose de mal en interrogeant à nouveau le curseur. Juste n'utilisez pas la fonction curseur.Requery ().

Au lieu de cela, définissez la bascule sur false au début toujours, puis demandez le curseur et activez-le si vous devez.

Peut-être que vous faisiez cela et j'ai mal compris quelque chose, mais je ne pense pas que vous devriez avoir des résultats lents.

pseudo-code: xxx


0 commentaires

1
votes

J'attendrais avant d'aller au curseur. Comme il semble que les dérivés de cursorloader ne sont pas maulants avec cursorloader.


0 commentaires