14

I've been using SwipeableRecyclerView for my android application for enabling swipes for my recyclerView. RecyclerView contains a list of cardViews.

I was trying to implement undo functionality for cards which will get deleted when swipe to left (first swipe shows undo, next swipe triggers delete)

I am trying the following code (partially working I guess)

SwipeableRecyclerViewTouchListener srvTouchListner = new SwipeableRecyclerViewTouchListener(rvTimerList,
            new SwipeableRecyclerViewTouchListener.SwipeListener(){

                @Override
                public boolean canSwipe(int i) {
                    return true;
                }

                @Override
                public void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] ints) {
                    for(int position : ints){
                        View view = recyclerView.getChildAt(position);
                            if (view.getTag(R.string.card_undo) == null) {
                                if(viewStack == null) {
                                    saveToViewStack(position, view);
                                    final ViewGroup viewGroup = (ViewGroup) view.findViewById(R.id.time_card2);
                                    view.setTag(R.string.card_undo, "true");
                                    viewGroup.addView(view.inflate(TimerSummary.this, R.layout.timeslot_card_undo, null));
                                }
                            } else {
                                Log.d(TAG, "Removing Item");
                                deleteTimeSlot(timerInstanceList.get(position));
                                Toast.makeText(TimerSummary.this, "Deleted!", Toast.LENGTH_SHORT).show();
                                timerInstanceList.remove(position);
                                finalSummaryAdapter.notifyItemRemoved(position);
                            }

                    }
                    finalSummaryAdapter.notifyDataSetChanged();
                }
                @Override
                public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] ints) {
                    for (int position:ints){
                        View view = recyclerView.getChildAt(position);
                        if(view.getTag(R.string.card_undo) != null && view.getTag(R.string.card_undo).equals("true")){
                            viewStack = null;
                            recyclerView.setAdapter(finalSummaryAdapter);
                        }
                    }

                }
            });

when Items are more (needs scrolling)

View view = recyclerView.getChildAt(position);

returns a null reference which causes an app crash.

I doubt that I am using the wrong method for taking view. I should be using something related with viewholder, I am actually kind of confused about how to get the view which you want from viewholder.

If anybody can share anything that helps,, that'll be great! I ll be happy to provide any more infos if somebody wants it,

P-RAD
  • 1,293
  • 2
  • 15
  • 36

3 Answers3

23

You should use findViewHolderForAdapterPosition. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForAdapterPosition(int)

Keep in mind that it will return null if the position is not laid out (e.g. out of bounds or removed).

yigit
  • 37,683
  • 13
  • 72
  • 58
  • 1
    How do I access any views from this viewholder,Can I change the layout and inflate a new one ? – P-RAD Jun 17 '15 at 08:03
  • Anyway I am using a cardview which is hidden at first to show onlly when user tries to delete an item, That way I can escape from the mess created when I try to access the views directly from recyclerView . This one is working fine ,I ll use this concept until I am able to take views prooperly from recyclerView – P-RAD Jun 17 '15 at 14:29
  • @yigit Can you elaborate a bit more? You said you "should use...". Is this the expected behaviour or a bug? (why?). I see RecyclerView as a scrollable view by itself, so this is a bit weird if you ask me. – GMan Aug 27 '15 at 12:25
  • 6
    RecyclerView represents a potentially long list of items with a few number of views (enough to fill the screen). GetChildAt is a ViewGroup method and has nothing to do with adapter positions. So when RV is displaying items 20 to 30 of a 100 item adapter, there are only 10 children in the view group(child at 0 is adapter item 20 etc). This is why RV provides adapter index methods. – yigit Aug 27 '15 at 14:08
  • Thank you, I understand now. – GMan Aug 27 '15 at 14:38
  • RecyclerView has a bit of weirdness to it in this regard. The view group child count actually changes if a view is removed and notifyItemRemoved is called. So if it was 7 before then it would be 8 afterwards. If I understand correctly this ties in with the animation support so views have to stay INVISIBLE for some time until cleaned up. – slott Oct 31 '15 at 08:22
  • Yes. We could implement this differently if we implemented RecyclerView in the core framework. Unfortunately, that would mean no one can use it for 2 years :/. If you are curious about details, I wrote about how it works here: http://www.birbit.com/recyclerview-animations-part-1-how-animations-work/ – yigit Oct 31 '15 at 19:42
  • Helpful answer. In my case `findViewHolderForLayoutPosition()` was appropriate, as I needed to animate a row immediately after calling `notifyDataSetChanged()` on the adapter. `findViewHolderForAdaterPosition()` would indeed return `null` in this case. – PPartisan Nov 01 '15 at 12:06
  • 2
    It would return null because when you call `notifyDataSetChanged`, all views are invalid until another layout calculation happens. – yigit Nov 01 '15 at 16:54
  • im getting same problem as getChild. After get laid out, still both returns null sometimes. – coolcool1994 Aug 24 '20 at 12:03
9

Since I don't have enough reputation points to comment on @yigit's answer, I thought I would give a way to retrieve all viewHolders from a RecyclerView regardless of what is currently in the viewGroup. Even with findViewHolderForAdapterPosition and ForLayoutPosition we still can get a null instance of the ViewHolder in the RecyclerView.

To circumvent this, we can use findViewHolderForItemId[1] and pass the ID of the viewHolder in the adapter using RecyclerView.Adapter#getItemId(int position)[2]

for (int i = 0; i < mAdapter.getItemCount(); i++) {
    RecyclerView.ViewHolder holder = 
            mRecyclerView.findViewHolderForItemId(mAdapter.getItemId(i));
}
  1. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForItemId(int)
  2. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemId(int)
Brandon McAnsh
  • 992
  • 8
  • 18
  • This is an interesting answer. I tried it and found that findViewHolderForItemId(int) just wants an int position, not the Long that getItemId(int) returns. – Gi0rgi0s Oct 12 '15 at 04:58
1

Use findFirstCompletelyVisibleItemPosition() & findLastVisibleItemPosition() methods on LayoutManager of your RecyclerView to find out that the child you are looking for is visible or not. If it is visible then you can use getChild(index) to get the ViewHolder instance of that particular child and it wouldn't be null else it could be null(If the child is not in it). In case your child is not visible you just make changes in your specific position List object, next time whenever it'll be visible will have updated UI.

if (selectedPosition >= linearLayoutManager.findFirstVisibleItemPosition()
                    && selectedPosition <= linearLayoutManager.findLastVisibleItemPosition()){
                    linearLayoutManager.getChildAt(selectedPosition).setBackgroundResource(0);
                }
Shailendra Yadav
  • 1,822
  • 1
  • 12
  • 16