Jak prawidłowo wyróżnić wybrany element w RecyclerView?

146

Próbuję użyć a RecyclerViewjako poziomego ListView. Próbuję wymyślić, jak wyróżnić wybrany element. Kiedy klikam na jeden z elementów, zostaje on zaznaczony i odpowiednio podświetlony, ale kiedy kliknę na inny, drugi zostaje podświetlony ze starszym.

Oto moja funkcja onClick:

@Override
public void onClick(View view) {

    if(selectedListItem!=null){
        Log.d(TAG, "selectedListItem " + getPosition() + " " + item);
        selectedListItem.setBackgroundColor(Color.RED);
    }
    Log.d(TAG, "onClick " + getPosition() + " " + item);
    viewHolderListener.onIndexChanged(getPosition());
    selectedPosition = getPosition();
    view.setBackgroundColor(Color.CYAN); 
    selectedListItem = view;
}

Oto onBindViewHolder:

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {   
    viewHolder.setItem(fruitsData[position]);
    if(selectedPosition == position)
        viewHolder.itemView.setBackgroundColor(Color.CYAN);    
    else
        viewHolder.itemView.setBackgroundColor(Color.RED);

}
user65721
źródło
Korzystanie z widoków, na które można ustawić fokus, nie jest dobrym pomysłem do śledzenia wybranego elementu. Sprawdź moją odpowiedź na kompletne rozwiązanie
Greg Ennis
może b ta pomoc u amolsawant88.blogspot.in/2015/08/…
Amol Sawant 96 Kuli
Wybór elementu widoku recyklera: stackoverflow.com/a/38501676/2648035
Alok Omkar
Trudno to śledzić, gdy nic już nie działa , co jest złe, ponieważ odpowiedzi zawierają znaczniki i nie określają zbyt wiele, co się dzieje.
FirstOne

Odpowiedzi:

61

Napisałem podstawową klasę adaptera, aby automatycznie obsługiwać wybór elementów za pomocą RecyclerView. Po prostu wyprowadź z niego swój adapter i użyj rysowalnych list stanów z state_selected, tak jak w przypadku widoku listy.

Mam tutaj post na blogu o tym, ale oto kod:

public abstract class TrackSelectionAdapter<VH extends TrackSelectionAdapter.ViewHolder> extends RecyclerView.Adapter<VH> {
    // Start with first item selected
    private int focusedItem = 0;

    @Override
    public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);

        // Handle key up and key down and attempt to move selection
        recyclerView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();

                // Return false if scrolled to the bounds and allow focus to move off the list
                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                        return tryMoveSelection(lm, 1);
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                        return tryMoveSelection(lm, -1);
                    }
                }

                return false;
            }
        });
    }

    private boolean tryMoveSelection(RecyclerView.LayoutManager lm, int direction) {
        int tryFocusItem = focusedItem + direction;

        // If still within valid bounds, move the selection, notify to redraw, and scroll
        if (tryFocusItem >= 0 && tryFocusItem < getItemCount()) {
            notifyItemChanged(focusedItem);
            focusedItem = tryFocusItem;
            notifyItemChanged(focusedItem);
            lm.scrollToPosition(focusedItem);
            return true;
        }

        return false;
    }

    @Override
    public void onBindViewHolder(VH viewHolder, int i) {
        // Set selected state; use a state list drawable to style the view
        viewHolder.itemView.setSelected(focusedItem == i);
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View itemView) {
            super(itemView);

            // Handle item click and set the selection
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Redraw the old selection and the new
                    notifyItemChanged(focusedItem);
                    focusedItem = getLayoutPosition();
                    notifyItemChanged(focusedItem);
                }
            });
        }
    }
} 
Greg Ennis
źródło
mRecyclerViewnie jest nigdzie zadeklarowany. Czy powinniśmy po prostu przekazać go jako parametr w konstruktorze i przechowywać go w polu?
Pedro
1
Przepraszam za to. Mój adapter jest wewnętrzną klasą mojego fragmentu, więc ma dostęp do pola widoku recyklera. W przeciwnym razie tak, możesz podać to jako parametr. Lub jeszcze lepiej, obsłuż go onRecyclerViewAttachedi zapisz tam w zmiennej składowej.
Greg Ennis
1
Dobra odpowiedź, ale po co używać getChildPosition ()? Jest inna metoda developer.android.com/reference/android/support/v7/widget/ ...
skyfishjy
Przepraszam, czy masz na myśli, że muszę tylko zaimportować twoją TrackSelectionAdapterklasę i użyć jej na mojej liście? Jak „wyprowadzić adapter z Twojej klasy”? Czy mógłbyś odpowiedzieć na moje pytanie? Utknąłem tak głęboko: stackoverflow.com/questions/29695811/…
Cristiano Colacillo
2
Próbowałem tego i stwierdziłem, że dwa wywołania back-to-back do NotifyItemChanged () zabiły wszelkie pozory płynnego przewijania na wolniejszym sprzęcie. Był szczególnie zły w Fire TV przed aktualizacją Lollipop
Redshirt
158

To bardzo prosty sposób na zrobienie tego.

Have a private int selectedPos = RecyclerView.NO_POSITION;in the RecyclerView Adapter class, and under onBindViewHolder method try:

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {   
    viewHolder.itemView.setSelected(selectedPos == position);

}

A w zdarzeniu OnClick zmodyfikuj:

@Override
public void onClick(View view) {
     notifyItemChanged(selectedPos);
     selectedPos = getLayoutPosition();
     notifyItemChanged(selectedPos); 
}

Działa jak urok dla Navigtional Drawer i innych adapterów przedmiotów RecyclerView.

Uwaga: pamiętaj, aby użyć koloru tła w swoim układzie za pomocą selektora, takiego jak wyjaśniono w przypadku współpracy:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:drawable="@color/pressed_color" android:state_pressed="true"/>
  <item android:drawable="@color/selected_color" android:state_selected="true"/>
  <item android:drawable="@color/focused_color" android:state_focused="true"/>
</selector>

W przeciwnym razie setSelected (..) nic nie zrobi, przez co to rozwiązanie stanie się bezużyteczne.

zIronManBox
źródło
12
Użyłem tego rozwiązania z tłem do rysowania / selektora ustawionym w moim widoku wiersza: <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/pressed_color" android:state_pressed="true"/> <item android:drawable="@color/selected_color" android:state_selected="true"/> <item android:drawable="@color/focused_color" android:state_focused="true"/> </selector>
colabug
1
@zIronManBox: ładny, prosty sposób! Czy „selectedPosition” w metodzie onClick ma być „selectedPos”?
AJW
3
Funkcja getLayoutPosition () nie jest dostępna dla mnie.
ka3ak
3
Nie używaj tylko -1, użyj RecyclerView.NO_POSITION; (czyli -1)
Martin Marconcini
8
@ ka3ak: getLayoutPosition to metoda klasy ViewHolder, której obiekt jest przekazywany jako pierwszy parametr w metodzie widoku powiązania. Można więc uzyskać do niego dostępvieHolder.getLayoutPosition
Tushar Kathuria
129

AKTUALIZACJA [26 lipca 2017 r.]:

Jak Pawan wspomniał w komentarzu o tym ostrzeżeniu IDE o nie używaniu tej ustalonej pozycji, właśnie zmodyfikowałem mój kod, jak poniżej. Odbiornik kliknięć jest przenoszony do ViewHolderi tam otrzymuję pozycję za pomocą getAdapterPosition()metody

int selected_position = 0; // You have to set this globally in the Adapter class

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    Item item = items.get(position);

    // Here I am just highlighting the background
    holder.itemView.setBackgroundColor(selected_position == position ? Color.GREEN : Color.TRANSPARENT);
}

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    public ViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        // Below line is just like a safety check, because sometimes holder could be null,
        // in that case, getAdapterPosition() will return RecyclerView.NO_POSITION
        if (getAdapterPosition() == RecyclerView.NO_POSITION) return;

        // Updating old as well as new positions
        notifyItemChanged(selected_position);
        selected_position = getAdapterPosition();
        notifyItemChanged(selected_position);

        // Do your another stuff for your onClick
    }
}

mam nadzieję, że to pomoże.

Poznaj Vorę
źródło
To cudownie! Jestem jednak trochę zdezorientowany, czy możesz mi pomóc wdrożyć sposób, rozszerzając tę ​​odpowiedź, jak zrobić cokolwiek robisz w instrukcji else, gdy klikniesz element, który jest już wybrany, aby go odznaczyć: else { holder.itemView.setBackgroundColor (Color.TRANSPARENT); }
iBobb,
Oczywiście, że tak. @iBobb. Dla pozycji NIE WYBRANYCH, tj. Których pozycja nie jest równa naszej zmiennej „selected_position”, będzie musiała BEZ tła. Użyłem tego, ponieważ RecyclerView, jak sugeruje nazwa, przetwarza każdy element, więc ustawia ZIELONE tło dla losowych wierszy, gdy przewijamy, dlatego umieściłem tę ELSE część. (Możesz po prostu wypróbować, komentując tę ​​część ELSE, a następnie przewinąć widok Recyklera)
Poznaj Vorę
Myślę, że nie zrozumiałeś mojego pytania. W każdej aplikacji wiesz, że po wybraniu czegoś zostanie to podświetlone. Jeśli przytrzymasz go ponownie, zostanie odznaczony. Jak byśmy to zaimplementowali? W tej chwili możemy wybrać tylko za pomocą Twojego kodu, a dotknięcie i przytrzymanie tego samego elementu nic nie robi.
iBobb
Może to być możliwe, ale w tym celu musisz zastąpić onLongClickListener zamiast onClickListener, w tej chwili jestem zajęty, więc nie mogę podać pełnego kodu, ale zasadniczo powinieneś to zrobić w ten sposób.
Spotkaj się z Vorą
Z jakiegoś powodu podświetlenie po kliknięciu zajmuje trochę czasu, około 100 do 200 milisekund. Każdy pomysł, dlaczego? Doświadczyłem tego na emulatorze i moim telefonie Lollipop
suku
14

Twoja implementacja prawdopodobnie zadziała, jeśli przewiniesz treść poza widok, a następnie z powrotem do widoku. Miałem podobny problem, kiedy trafiłem na twoje pytanie.

Poniższy fragment pliku działa dla mnie. Moja implementacja miała na celu wielokrotny wybór, ale wrzuciłem tam hack, aby wymusić pojedynczy wybór. (* 1)

// an array of selected items (Integer indices) 
private final ArrayList<Integer> selected = new ArrayList<>();

// items coming into view
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
    // each time an item comes into view, its position is checked
    // against "selected" indices
    if (!selected.contains(position)){
        // view not selected
        holder.parent.setBackgroundColor(Color.LTGRAY);
    }
    else
        // view is selected
        holder.parent.setBackgroundColor(Color.CYAN);
}

// selecting items
@Override
public boolean onLongClick(View v) {

        // set color immediately.
        v.setBackgroundColor(Color.CYAN);

        // (*1)
        // forcing single selection here
        if (selected.isEmpty()){
            selected.add(position);
        }else {
            int oldSelected = selected.get(0);
            selected.clear();
            selected.add(position);
            // we do not notify that an item has been selected
            // because that work is done here.  we instead send
            // notifications for items to be deselected
            notifyItemChanged(oldSelected);
        }
        return false;
}

Jak wspomniano w tym połączonym pytaniu , ustawianie detektorów dla viewHolders powinno być wykonywane w onCreateViewHolder. Zapomniałem o tym wcześniej wspomnieć.

Dustin Charles
źródło
6

Myślę, że znalazłem najlepszy samouczek dotyczący korzystania z RecyclerView ze wszystkimi podstawowymi funkcjami, których potrzebujemy (pojedynczy + multiselekcja, podświetlanie, tętnienie, klikanie i usuwanie w przypadku wielokrotnego wyboru itp.).

Oto jest -> http://enoent.fr/blog/2015/01/18/recyclerview-basics/

Na tej podstawie mogłem stworzyć bibliotekę „FlexibleAdapter”, która rozszerza SelectableAdapter. Myślę, że to musi leżeć w gestii Adaptera, właściwie nie musisz za każdym razem przepisywać podstawowych funkcjonalności Adaptera, niech zrobi to biblioteka, więc możesz po prostu ponownie użyć tej samej implementacji.

Ten adapter jest bardzo szybki, działa od razu po wyjęciu z pudełka (nie trzeba go przedłużać); dostosowujesz elementy do każdego potrzebnego typu widoku; ViewHolder są predefiniowane: zaimplementowano już typowe zdarzenia: pojedyncze i długie kliknięcie; utrzymuje stan po rotacji i wiele więcej .

Zapraszamy do obejrzenia i wdrożenia go w swoich projektach.

https://github.com/davideas/F flexibleAdapter

Dostępna jest również Wiki.

Davideas
źródło
Świetna robota podczas pisania tego adaptera, wydaje się bardzo przydatna. Jedyną rzeczą jest to, że naprawdę potrzebuje kilku podstawowych przykładów i dokumentacji, uważam to za trochę zagmatwane, aby nawet go uruchomić. Potencjalnie świetnie!
Voy
Tak, nie znalazłem tam wystarczających informacji, aby rozpocząć. Nie można znaleźć odniesienia do interfejsu API, mimo że kod wydaje się być skomentowany, mając to na uwadze. Przykładowa aplikacja wydaje się - choć obszerna i zawiera wiele informacji - bardzo trudna do zrozumienia bez wcześniejszej znajomości biblioteki. Wszystkie przypadki użycia są ze sobą powiązane, niewiele wskazuje na to, co demonstruje, klasy są ponownie wykorzystywane w różnych scenariuszach, co powoduje, że są przeładowane informacjami. Utworzyłem nowy problem z tymi sugestiami tutaj: github.com/davideas/F flexibleAdapter
Voy
Wiki została całkowicie przepisana i jest na dobrej drodze.
Davideas
6

Spójrz na moje rozwiązanie. Przypuszczam, że powinieneś ustawić wybraną pozycję w uchwycie i przekazać ją jako Tag of View. Widok należy ustawić w metodzie onCreateViewHolder (...). Istnieje również prawidłowe miejsce do ustawienia odbiornika dla widoku, takie jak OnClickListener lub LongClickListener.

Proszę spojrzeć na poniższy przykład i przeczytać komentarze do kodu.

public class MyListAdapter extends RecyclerView.Adapter<MyListAdapter.ViewHolder> {
    //Here is current selection position
    private int mSelectedPosition = 0;
    private OnMyListItemClick mOnMainMenuClickListener = OnMyListItemClick.NULL;

    ...

    // constructor, method which allow to set list yourObjectList

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //here you prepare your view 
        // inflate it
        // set listener for it
        final ViewHolder result = new ViewHolder(view);
        final View view =  LayoutInflater.from(parent.getContext()).inflate(R.layout.your_view_layout, parent, false);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //here you set your current position from holder of clicked view
                mSelectedPosition = result.getAdapterPosition();

                //here you pass object from your list - item value which you clicked
                mOnMainMenuClickListener.onMyListItemClick(yourObjectList.get(mSelectedPosition));

                //here you inform view that something was change - view will be invalidated
                notifyDataSetChanged();
            }
        });
        return result;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final YourObject yourObject = yourObjectList.get(position);

        holder.bind(yourObject);
        if(mSelectedPosition == position)
            holder.itemView.setBackgroundColor(Color.CYAN);
        else
            holder.itemView.setBackgroundColor(Color.RED);
    }

    // you can create your own listener which you set for adapter
    public void setOnMainMenuClickListener(OnMyListItemClick onMyListItemClick) {
        mOnMainMenuClickListener = onMyListItemClick == null ? OnMyListItemClick.NULL : onMyListItemClick;
    }

    static class ViewHolder extends RecyclerView.ViewHolder {


        ViewHolder(View view) {
            super(view);
        }

        private void bind(YourObject object){
            //bind view with yourObject
        }
    }

    public interface OnMyListItemClick {
        OnMyListItemClick NULL = new OnMyListItemClick() {
            @Override
            public void onMyListItemClick(YourObject item) {

            }
        };

        void onMyListItemClick(YourObject item);
    }
}
Konrad Krakowiak
źródło
Czy uważasz, że mSelectedPosition można zadeklarować jako statyczną, aby zachować zmiany konfiguracji?
Sathesh
Nie! źle jest robić to tak statycznie, to bardzo źle
Konrad Krakowiak
Tak. to jest niebezpieczne. Ale aby zachować zmianę konfiguracji (zwłaszcza obrót ekranu), możemy zadeklarować tę zmienną w aktywności i pobrać ją stamtąd, prawda?
Sathesh
Rozwiązań jest wiele ... Możesz użyć gettera i settera, możesz stworzyć własną metodę saveInstanceState dla adaptera i wywołać ją w saveInstanceState z Activity / Fragment
Konrad Krakowiak
4

nie ma selektora w RecyclerView, takiego jak ListView i GridView, ale spróbuj poniżej tego, co zadziałało dla mnie

utwórz selektor do rysowania, jak poniżej

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
<item android:state_pressed="true">
   <shape>
         <solid android:color="@color/blue" />
   </shape>
</item>

<item android:state_pressed="false">
    <shape>
       <solid android:color="@android:color/transparent" />
    </shape>
</item>
</selector>

następnie ustaw to do rysowania jako tło układu wierszy RecyclerView jako

android:background="@drawable/selector"
amodkanthe
źródło
2
Nie robi to nic pożytecznego. Po prostu zmieni kolor w miarę upływu czasu.
Leo Droidcoder
4

Decyzja z interfejsami i wywołaniami zwrotnymi. Utwórz interfejs ze stanami zaznaczania i usuwania zaznaczenia:

public interface ItemTouchHelperViewHolder {
    /**
     * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
     * Implementations should update the item view to indicate it's active state.
     */
    void onItemSelected();


    /**
     * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item
     * state should be cleared.
     */
    void onItemClear();
}

Zaimplementuj interfejs w ViewHolder:

   public static class ItemViewHolder extends RecyclerView.ViewHolder implements
            ItemTouchHelperViewHolder {

        public LinearLayout container;
        public PositionCardView content;

        public ItemViewHolder(View itemView) {
            super(itemView);
            container = (LinearLayout) itemView;
            content = (PositionCardView) itemView.findViewById(R.id.content);

        }

               @Override
    public void onItemSelected() {
        /**
         * Here change of item
         */
        container.setBackgroundColor(Color.LTGRAY);
    }

    @Override
    public void onItemClear() {
        /**
         * Here change of item
         */
        container.setBackgroundColor(Color.WHITE);
    }
}

Uruchom zmianę stanu przy oddzwanianiu:

public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private final ItemTouchHelperAdapter mAdapter;

    public ItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        this.mAdapter = adapter;
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return true;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        ...
    }

    @Override
    public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) {
        ...
    }

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            if (viewHolder instanceof ItemTouchHelperViewHolder) {
                ItemTouchHelperViewHolder itemViewHolder =
                        (ItemTouchHelperViewHolder) viewHolder;
                itemViewHolder.onItemSelected();
            }
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        if (viewHolder instanceof ItemTouchHelperViewHolder) {
            ItemTouchHelperViewHolder itemViewHolder =
                    (ItemTouchHelperViewHolder) viewHolder;
            itemViewHolder.onItemClear();
        }
    }   
}

Utwórz RecyclerView z wywołaniem zwrotnym (przykład):

mAdapter = new BuyItemsRecyclerListAdapter(MainActivity.this, positionsList, new ArrayList<BuyItem>());
positionsList.setAdapter(mAdapter);
positionsList.setLayoutManager(new LinearLayoutManager(this));
ItemTouchHelper.Callback callback = new ItemTouchHelperCallback(mAdapter);
mItemTouchHelper = new ItemTouchHelper(callback);
mItemTouchHelper.attachToRecyclerView(positionsList);

Zobacz więcej w artykule iPaulPro: https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd#.6gh29uaaz

Mykoła Tychyna
źródło
3

to jest moje rozwiązanie, możesz ustawić na elemencie (lub grupie) i odznaczyć go kolejnym kliknięciem:

 private final ArrayList<Integer> seleccionados = new ArrayList<>();
@Override
    public void onBindViewHolder(final ViewHolder viewHolder, final int i) {
        viewHolder.san.setText(android_versions.get(i).getAndroid_version_name());
        if (!seleccionados.contains(i)){ 
            viewHolder.inside.setCardBackgroundColor(Color.LTGRAY);
        }
        else {
            viewHolder.inside.setCardBackgroundColor(Color.BLUE);
        }
        viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (seleccionados.contains(i)){
                    seleccionados.remove(seleccionados.indexOf(i)); 
                    viewHolder.inside.setCardBackgroundColor(Color.LTGRAY);
                } else { 
                    seleccionados.add(i);
                    viewHolder.inside.setCardBackgroundColor(Color.BLUE);
                }
            }
        });
    }
Santiago Castellano
źródło
2

Miałem ten sam problem i rozwiązuję go w następujący sposób:

Plik xml, który jest używany do tworzenia wiersza wewnątrz createViewholder, po prostu dodaj poniższy wiersz:

 android:clickable="true"
 android:focusableInTouchMode="true"
 android:background="?attr/selectableItemBackgroundBorderless"

LUB Jeśli używasz frameLayout jako elementu nadrzędnego elementu wiersza, to:

android:clickable="true"
android:focusableInTouchMode="true"
android:foreground="?attr/selectableItemBackgroundBorderless"

W kodzie java wewnątrz uchwytu widoku, w którym dodano odbiornik kliknięć:

@Override
   public void onClick(View v) {

    //ur other code here
    v.setPressed(true);
 }
Vikas Tiwari
źródło
1

Nie mogłem znaleźć w sieci dobrego rozwiązania tego problemu i rozwiązałem go samodzielnie. Wiele osób cierpi na ten problem. Dlatego chcę udostępnić tutaj moje rozwiązanie.

Podczas przewijania wiersze są poddawane recyklingowi. Dlatego zaznaczone pola wyboru i wyróżnione wiersze nie działają poprawnie. Rozwiązałem ten problem, pisząc poniższą klasę adaptera.

Realizuję również pełny projekt. W tym projekcie możesz zaznaczyć wiele pól wyboru. Wiersze zawierające zaznaczone pola wyboru są podświetlone. Co ważniejsze, nie są one tracone podczas przewijania. Możesz go pobrać z linku:

https://www.dropbox.com/s/ssm58w62gw32i29/recyclerView_checkbox_highlight.zip?dl=0

    public class RV_Adapter extends RecyclerView.Adapter<RV_Adapter.ViewHolder> {
        public ArrayList<String> list;
        boolean[] checkBoxState;
        MainActivity mainActivity;
        MyFragment myFragment;
        View firstview;

        private Context context;

        FrameLayout framelayout;

        public RV_Adapter() {

      }

        public RV_Adapter(Context context, MyFragment m, ArrayList<String> list ) {
          this.list = list;
          myFragment = m;
          this.context = context;
          mainActivity = (MainActivity) context;
          checkBoxState = new boolean[list.size()];
          // relativeLayoutState = new boolean[list.size()];
        }

        public class ViewHolder extends RecyclerView.ViewHolder  {
            public TextView textView;
            public CheckBox checkBox;
            RelativeLayout relativeLayout;
            MainActivity mainActivity;
            MyFragment myFragment;
            public ViewHolder(View v,MainActivity mainActivity,MyFragment m) {
                super(v);
                textView = (TextView) v.findViewById(R.id.tv_foodname);
                /**/
                checkBox= (CheckBox) v.findViewById(R.id.checkBox);
                relativeLayout = (RelativeLayout)v.findViewById(R.id.relativelayout);
                this.mainActivity = mainActivity;
                this.myFragment = m;
                framelayout = (FrameLayout) v.findViewById(R.id.framelayout);
                framelayout.setOnLongClickListener(m);
            }

        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            firstview = inflater.inflate(R.layout.row, parent, false);
            return new ViewHolder(firstview,mainActivity, myFragment);
        }

        @Override
        public void onBindViewHolder( final ViewHolder holder,  final int position) {

            holder.textView.setText(list.get(position));

            holder.itemView.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {

              }
            });

            // When action mode is active, checkboxes are displayed on each row, handle views(move icons) on each row are disappered.
            if(!myFragment.is_in_action_mode)
            {

              holder.checkBox.setVisibility(View.GONE);
            }
            else
            {
              holder.checkBox.setVisibility(View.VISIBLE);
              holder.checkBox.setChecked(false);
            }

              holder.checkBox.setTag(position);

              holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
                @Override
                public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                  if(compoundButton.isPressed()) // ekrandan kaybolan checkbox'lar otomatik olarak state degistiriyordu ve bu listener method cagiriliyordu, bunu onlemek icin isPressed() method'u ile kullanici mi basmis diye kontrol ediyorum.
                  {
                    int getPosition = (Integer) compoundButton.getTag();  // Here we get the position that we have set for the checkbox using setTag.
                    checkBoxState[getPosition] = compoundButton.isChecked(); // Set the value of checkbox to maintain its state.
                    //relativeLayoutState[getPosition] = compoundButton.isChecked();

                  if(checkBoxState[getPosition] && getPosition == position )
                    holder.relativeLayout.setBackgroundResource(R.color.food_selected); /** Change background color of the selected items in list view  **/
                  else
                    holder.relativeLayout.setBackgroundResource(R.color.food_unselected); /** Change background color of the selected items in list view  **/
                    myFragment.prepareselection(compoundButton, getPosition, holder.relativeLayout);

                  }
                }
              });
              holder.checkBox.setChecked(checkBoxState[position]);

              if(checkBoxState[position]  )
                holder.relativeLayout.setBackgroundResource(R.color.food_selected); /** Change background color of the selected items in list view  **/
              else
                holder.relativeLayout.setBackgroundResource(R.color.food_unselected);
        }



        @Override
        public int getItemCount() {
            return list.size();
        }

        public void updateList(ArrayList<String> newList){
          this.list = newList;
          checkBoxState = new boolean[list.size()+1];
        }

      public void resetCheckBoxState(){
        checkBoxState = null;
        checkBoxState = new boolean[list.size()];
      }

    }

Zrzuty ekranu aplikacji:

screen1 ekran2 ekran3 ekran4

oiyio
źródło
-1

Ustaw, private int selected_position = -1;aby zapobiec wybraniu dowolnego elementu na początku.

 @Override
 public void onBindViewHolder(final OrdersHolder holder, final int position) {
    final Order order = orders.get(position);
    holder.bind(order);
    if(selected_position == position){
        //changes background color of selected item in RecyclerView
        holder.itemView.setBackgroundColor(Color.GREEN);
    } else {
        holder.itemView.setBackgroundColor(Color.TRANSPARENT);
        //this updated an order property by status in DB
        order.setProductStatus("0");
    }
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //status switch and DB update
            if (order.getProductStatus().equals("0")) {
                order.setProductStatus("1");
                notifyItemChanged(selected_position);
                selected_position = position;
                notifyItemChanged(selected_position);
             } else {
                if (order.getProductStatus().equals("1")){
                    //calls for interface implementation in
                    //MainActivity which opens a new fragment with 
                    //selected item details 
                    listener.onOrderSelected(order);
                }
             }
         }
     });
}
LeonD
źródło
-1

Samo dodawanie android:background="?attr/selectableItemBackgroundBorderless"powinno działać, jeśli nie masz koloru tła, ale nie zapomnij użyć metody setSelected. Jeśli masz inny kolor tła, właśnie tego użyłem (używam wiązania danych);

Ustaw funkcję isSelected at onClick

b.setIsSelected(true);

I dodaj to do xml;

android:background="@{ isSelected ? @color/{color selected} : @color/{color not selected} }"
Mohamed
źródło
-1

Wybierz selektor:

 <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/Green_10" android:state_activated="true" />
    <item android:drawable="@color/Transparent" />
</selector>

Ustaw go jako tło w układzie elementu listy

   <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:background="@drawable/selector_attentions_list_item"
        android:layout_width="match_parent"
        android:layout_height="64dp">

W swoim adapterze dodaj OnClickListener do widoku (metoda onBind)

 @Suppress("UNCHECKED_CAST")
    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bindItems(item: T) {
            initItemView(itemView, item)
            itemView.tag = item
            if (isClickable) {
                itemView.setOnClickListener(onClickListener)
            }
        }
    }

W zdarzeniu onClick aktywuj widok:

 fun onItemClicked(view: View){
        view.isActivated = true
    }
i_tanova
źródło
2
Ma to 2 problemy: 1) Nie podano żadnego sposobu na cofnięcie aktywacji innych elementów w recyklerze. 2) Widoki są ponownie wykorzystywane. Jest więc możliwe, że ten sam aktywowany widok będzie widoczny w kolejności po przewinięciu.
Anup Sharma