web-dev-qa-db-fra.com

Android recyclerview-selection Mise en œuvre?

J'essaie actuellement de mettre en œuvre les nouvelles API recyclerview-selection d'Android Support Library 28.0.0-alpha1 et je rencontre quelques problèmes. Mon objectif est d’avoir une RecyclerView, avec la possibilité de sélectionner plusieurs lignes, d’afficher une barre d’action contextuelle et d’exécuter des actions dessus, telles que "supprimer" ou "partager".

J'essaierai de fournir assez de code pour donner une bonne idée de ce qui se passe, mais je peux toujours répondre avec davantage si nécessaire.

Dans ma Fragment qui contient la RecyclerView qui me préoccupe, je lance une SelectionTracker et la mets sur mon RecyclerView.Adapter, comme suit:

private void buildRecyclerView() {
    sheetsAdapter = new SheetsAdapter(getContext(), this, sheets);
    gridManager = new GridLayoutManager(getContext(), getResources().getInteger(R.integer.grid_span_count));

    ItemOffsetDecoration itemDecoration = new ItemOffsetDecoration(getContext(), R.dimen.item_offset);
    sheetsRecycler.addItemDecoration(itemDecoration);
    sheetsRecycler.setLayoutManager(gridManager);
    sheetsRecycler.setAdapter(sheetsAdapter);
    sheetsRecycler.setHasFixedSize(true);

    SelectionTracker selectionTracker = new SelectionTracker.Builder<>("sheet_selection",
                                                        sheetsRecycler,
                                                        new StableIdKeyProvider(sheetsRecycler),
                                                        new SheetDetailsLookup(sheetsRecycler),
                                                        StorageStrategy.createLongStorage())
                                                        .withOnContextClickListener(this)
                                                        .build();

    sheetsAdapter.setSelectionTracker(selectionTracker);
}

Cette Fragment également implements OnContextClickListener, afin d'écouter les clics longs sur les éléments de ma RecyclerView:

@Override
public boolean onContextClick(@NonNull MotionEvent e) {
    if (actionMode != null) {
        return false;
    }

    // Start the CAB using the ActionMode.Callback defined below
    if (getActivity() != null) {
        actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(actionModeCallback);
    }

    return true;
}

Et il devrait montrer mon CAB, comme ceci:

private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.sheets_cab_menu, menu);

        return true;
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
            case  R.id.delete:
                Toast.makeText(getContext(), R.string.sheets_delete, Toast.LENGTH_SHORT).show();
                mode.finish();
                return true;
            default:
                return false;
        }
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
        actionMode = null;
    }
};

Ma SheetDetailsLookup ressemble à ceci:

public class SheetDetailsLookup extends ItemDetailsLookup<Long> {

private RecyclerView recyclerView;

SheetDetailsLookup(RecyclerView recyclerView) {
    super();

    this.recyclerView = recyclerView;
}

@Nullable
@Override
public ItemDetails<Long> getItemDetails(@NonNull MotionEvent e) {
    View view = recyclerView.findChildViewUnder(e.getX(), e.getY());
    if (view != null) {
        RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(view);
        if (holder instanceof SheetsAdapter.SheetViewHolder) {
            return ((SheetsAdapter.SheetViewHolder) holder).getItemDetails();
        }
    }
    return null;
}
}

Et dans ma SheetViewHolder, je mets à jour la vue pour montrer qu'elle a été sélectionnée:

if (selectionTracker.isSelected(sheet.uid)) {
            layout.setBackgroundResource(R.color.md_grey_700);
        } else {
            layout.setBackgroundResource(Android.R.color.transparent);
        }

Aussi bien que:

public SheetItemDetails getItemDetails() {
        return new SheetItemDetails(getAdapterPosition(), mSheets.get(getAdapterPosition()).uid);
    }

SheetItemDetails est simplement:

public class SheetItemDetails extends ItemDetailsLookup.ItemDetails<Long> {

private int position;
private Long key;

SheetItemDetails(int position, Long key) {
    this.position = position;
    this.key = key;
}

@Override
public int getPosition() {
    return position;
}

@Nullable
@Override
public Long getSelectionKey() {
    return key;
}
}

J'ai mis en œuvre tous les éléments mentionnés dans la spécification API , mais je rencontre maintenant des problèmes. Mon CAB n'apparaît pas lorsque je sélectionne un élément ... et l'application se bloque généralement. Des plantages se produisent chaque fois que j'essaie de "revenir en arrière" de sélections, puis de cliquer longuement pour lancer une autre sélection, avec cette trace de pile:

Java.lang.IllegalStateException
    at Android.support.v4.util.Preconditions.checkState(Preconditions.Java:130)
    at Android.support.v4.util.Preconditions.checkState(Preconditions.Java:142)
    at androidx.recyclerview.selection.GestureSelectionHelper.start(GestureSelectionHelper.Java:76)
    at androidx.recyclerview.selection.SelectionTracker$Builder$4.run(SelectionTracker.Java:742)
    at androidx.recyclerview.selection.TouchInputHandler.onLongPress(TouchInputHandler.Java:136)
    at androidx.recyclerview.selection.GestureRouter.onLongPress(GestureRouter.Java:95)
    at Android.view.GestureDetector.dispatchLongPress(GestureDetector.Java:779)
    at Android.view.GestureDetector.access$200(GestureDetector.Java:40)
    at Android.view.GestureDetector$GestureHandler.handleMessage(GestureDetector.Java:293)
    at Android.os.Handler.dispatchMessage(Handler.Java:106)
    at Android.os.Looper.loop(Looper.Java:164)
    at Android.app.ActivityThread.main(ActivityThread.Java:6656)
    at Java.lang.reflect.Method.invoke(Native Method)
    at com.Android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.Java:438)
    at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:823)

De plus, je n’ai plus la possibilité de "cliquer rapidement" sur l’un de mes éléments, de lancer une vue détaillée ... que j’avais fonctionné jusqu’à présent.

Qu'est ce que j'ai mal fait?

5
drinfernoo

J'ai récemment commencé à regarder cette bibliothèque et je me suis retrouvé coincé à la même exception. Le problème se produit lorsque SelectionTracker tente d'obtenir un identifiant de votre sous-classe RecyclerView.Adapter personnalisée. Pour résoudre le problème, appelez d'abord setHasStableIds(true) dans son constructeur. Puis substituez getItemId() pour renvoyer un identifiant pour le paramètre de position donné.

4
Code-Apprentice

La nouvelle bibliothèque semble compliquée pour le moment. J'attendrais la nouvelle version finale avant de commencer à l'implémenter. Certes, vous pouvez expérimenter, mais je vous suggère de ne pas l'utiliser dans votre application pour le moment.

Il n’ya qu’une seule nouvelle fonctionnalité intéressante: la multi-sélection continue en déplaçant le doigt ou la souris.

Cependant, j'ai trouvé ces exemples:

En attendant, je suggère fortement d’utiliser une bibliothèque comme la mienne: FlexibleAdapter qui vient de plus de 3 ans d’expérience de sélection, où elle ne lie PAS l’article lorsque la (dé) sélection est effectuée! La sélection multiple est simple à utiliser avec une ActionModeHelper pour simplifier votre code avec la ActionMode. Lisez la page connexe Wiki .

Actuellement, la sélection est enregistrée dans un ensemble, mais elle pourrait être déléguée à l'élément d'adaptateur lui-même ultérieurement. Cependant, vous pouvez utiliser des "sélections" avec cette extension.

2
Davideas

Deux choses:

1) pour atteindre le clic sur les éléments, vous devez implémenter un écouteur OnItemActivatedListener<K> et lui transmettre une référence sur le générateur de suivi. Après cela, vous pouvez recevoir des "touches" sur un objet.

2) pour afficher une approche différente du menu de la barre d’action contextuelle est nécessaire: vous devez implémenter un SelectionTracker.SelectionObserver et le transmettre au suivi après la création: tracker.addObserver(.... Après cela, vous pouvez recevoir un événement de changement de sélection dans cet observateur (via le rappel onSelectionChanged). Par exemple, lorsque la sélection commence (!tracker.getSelection().isEmpty() => show CAB) et que la sélection se termine (tracker.getSelection (). IsEmpty () hide CAB) . Si vous souhaitez contrôler la sélection d'éléments uniques ou multiples, vous devez compléter l'outil de suivi SelectionTracker.SelectionPredicate instance (via la méthode de construction .withSelectionPredicate(). 

de plus, comme @ Code-Apprentice le suggère, vous devez fournir les ID corrects provenant de getItemId() lors de la création d'un fournisseur ItemDetailsLookup.ItemDetails (ceci élimine une exception).

0
A. Petrov

Le package de sélection est toujours en version alpha, la documentation est assez médiocre et son utilisation n’est pas très claire. J'ai essayé moi-même mais j'ai eu des problèmes similaires, à la fin j'ai utilisé le SmartRecyclerView

0
greywolf82