1

I've got a RecylerView that is displaying a list of data. I need to add dividers or spacers that I need to add two Dividers or spacers too that I can add text to as shown in the image below(with the text DEFAULT or OTHER).

*NOTE Headers are in the image below with the text "DEFAULT" and another that says "OTHER".

The CC's shown are drag and drop items but i don't want the "spaces or headers" marked as default and other to be moved.

I've started adding the divider as shown in the code below but I cannot figure out how to add text.

    @Override
   public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
       super.onViewCreated(view, savedInstanceState);

       ButterKnife.bind(this, view);

       LinearLayoutManager mAdapterLayoutManager = new LinearLayoutManager(context);
       recyclerView.setLayoutManager(mAdapterLayoutManager);
       billingMethodRecyclerViewAdapter = new BillingMethodRecyclerViewAdapter(context, billingMethods, paymentService, ListPaymentMethods.this );
       billingMethodRecyclerViewAdapter.setMasterpassService(masterpassService);
       billingMethodRecyclerViewAdapter.setChoosePaymentMode(choosePaymentMode);
       DividerItemDecoration itemDecor = new DividerItemDecoration(context, mAdapterLayoutManager.getOrientation());

       recyclerView.addItemDecoration(itemDecor);
       recyclerView.setAdapter(billingMethodRecyclerViewAdapter);
       billingMethodRecyclerViewAdapter.clear();

       if (!choosePaymentMode) {
           setupSwipeListener(billingMethodRecyclerViewAdapter);
       }

       if (!BuildConfig.SHOW_AVAILABILITY_INRIX) {
           parkmobileProAd.setVisibility(View.GONE);
       }

       if (getArguments() != null) {
           isAddPaymentZoneDetails = getArguments().getBoolean(AddPaymentActivity.ADD_PAYMENT_FROM_ZONE_DETAIL);
       }
   }

and the xml for the recylcerview

<androidx.recyclerview.widget.RecyclerView
           android:id="@+id/recycler_view"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"/>

EDIT

Adapter:

public class BillingMethodRecyclerViewAdapter extends RecyclerView.Adapter<BillingMethodHolder> {

                private static final int PENDING_REMOVAL_TIMEOUT = 3000; // in milliseconds

                private ArrayList<BillingMethod> billingMethods;
                private Handler handler = new Handler(); // handler for running delayed runnables
                private HashMap<BillingMethod, Runnable> pendingRunnables = new HashMap<>(); // map of items to pending runnables, so we can cancel a removal if need be

                private BillingMethodHolder.BillingMethodListener billingMethodListener;
                private PaymentService paymentService;
                private MasterpassService masterpassService;

                private boolean choosePaymentMode = false;
                private Context mContext;

                private int maxPaymentMethods = 6;

                public BillingMethodRecyclerViewAdapter(Context context, ArrayList<BillingMethod> objects, PaymentService paymentService, BillingMethodHolder.BillingMethodListener billingMethodListener) {
                    this.billingMethods = objects;
                    this.billingMethodListener = billingMethodListener;
                    this.mContext = context.getApplicationContext();
                    this.paymentService = paymentService;
                }

                public int getMaxPaymentMethods() {
                    return maxPaymentMethods;
                }

                public void setMaxPaymentMethods(int maxPaymentMethods) {
                    this.maxPaymentMethods = maxPaymentMethods;
                }

                public void setMasterpassService(MasterpassService masterpassService) {
                    this.masterpassService = masterpassService;
                }

                public boolean onItemMove(int fromPosition, int toPosition) {
                    if (fromPosition < toPosition) {
                        for (int i = fromPosition; i < toPosition; i++) {
                            Collections.swap(billingMethods, i, i + 1);
                        }
                    } else {
                        for (int i = fromPosition; i > toPosition; i--) {
                            Collections.swap(billingMethods, i, i - 1);
                        }
                    }
                    notifyItemMoved(fromPosition, toPosition);
                    return true;
                }


                public List<BillingMethod> getBillingMethods() {
                    return billingMethods;
                }

                public void setChoosePaymentMode(boolean choosePaymentMode) {
                    this.choosePaymentMode = choosePaymentMode;
                }

                @NonNull
                @Override
                public BillingMethodHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

                    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_payment_methods, parent, false);
                    return new BillingMethodHolder(v);
                }

                @Override
                public void onBindViewHolder(@NonNull BillingMethodHolder holder, final int position) {
                    holder.bind(getBillingMethods().get(position), billingMethodListener, choosePaymentMode);
                }


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

            public void pendingRemoval(int position) {

                final BillingMethod billingMethod = billingMethods.get(position);
                    billingMethod.setPendingDeletion(true);
                    this.notifyItemChanged(position); // redraw row in "undo" state

                    Runnable pendingRemovalRunnable = () -> {
                        deleteBillingMethod(position);
                    };
                    handler.postDelayed(pendingRemovalRunnable, PENDING_REMOVAL_TIMEOUT);
                    pendingRunnables.put(billingMethod, pendingRemovalRunnable);

            }

            public void savePaymentOrder() {

                List<BillingMethod> newOrder = new ArrayList<>();
                int order = 0;

                for (BillingMethod method : billingMethods) {
                    if (order == 0) {
                        method.setPreferred(true);
                        LocalyticsUtil.getInstance(mContext).setPrimaryPaymentMethod(method);
                    } else {
                        method.setPreferred(false);
                    }
                    method.setSortOrder(order);
                    newOrder.add(method);
                    order++;
                }

                paymentService.setPaymentMethodsOrder(newOrder, new PaymentService.GenericListener() {
                    @Override
                    public void onSuccess() {
                        Log.d("TAG", "Saved");
                    }

                    @Override
                    public void onError(String errorMessage) {
                        Log.d("TAG", "Saved");
                    }
                });
            }

            private void deleteBillingMethod(int position) {

                BillingMethod method = billingMethods.get(position);

                if (method.getBillingType() == BillingType.CREDIT_CARD) {
                    paymentService.deleteCreditCard(method.getCreditCard().getCardStatus(), new PaymentService.GenericListener() {
                        @Override
                        public void onSuccess() {
                            billingMethods.remove(position);
                            notifyItemRemoved(position);
                        }

                        @Override
                        public void onError(String errorMessage) {
                        }
                    });
                } else if (method.getBillingType() == BillingType.PREPAID) {
                    paymentService.deleteWallet(new PaymentService.GenericListener() {
                        @Override
                        public void onSuccess() {
                            billingMethods.remove(position);
                            notifyItemRemoved(position);

                        }

                        @Override
                        public void onError(String errorMessage) {
                        }
                    });
                } else if (method.getBillingType() == BillingType.PAYPAL) {
                    paymentService.deletePaypal(new PaymentService.GenericListener() {
                        @Override
                        public void onSuccess() {
                            billingMethods.remove(position);
                            notifyItemRemoved(position);
                        }

                        @Override
                        public void onError(String errorMessage) {

                        }
                    });

                } else if (method.getBillingType() == BillingType.CHASEPAY) {
                    paymentService.deleteChasepay(new PaymentService.GenericListener() {
                        @Override
                        public void onSuccess() {
                            billingMethods.remove(position);
                            notifyItemRemoved(position);
                        }

                        @Override
                        public void onError(String errorMessage) {
                        }
                    });
                } else if (method.getBillingType() == BillingType.MASTERPASSV7) {
                    masterpassService.deleteMasterpassV7(String.valueOf(method.getBillingMethodId()), new MasterpassService.GenericListener() {
                        @Override
                        public void onSuccess() {
                            billingMethods.remove(position);
                            notifyItemRemoved(position);
                        }

                        @Override
                        public void onError(String errorMessage) {

                        }
                    });

                } else {
                        notifyDataSetChanged();
                }

            }

            public void clear() {
                billingMethods.clear();
                notifyDataSetChanged();
            }

            public void stopRemoval(BillingMethod billingMethod) {
                Runnable pendingRemovalRunnable = pendingRunnables.get(billingMethod);
                pendingRunnables.remove(billingMethod);
                if (pendingRemovalRunnable != null) {
                    handler.removeCallbacks(pendingRemovalRunnable);
                }
                billingMethod.setPendingDeletion(false);
                this.notifyItemChanged(billingMethods.indexOf(billingMethod));
            }
        }
yams
  • 942
  • 6
  • 27
  • 60

3 Answers3

3

I took a bit of time but I was able to do it.

This is the output

Recycler view with draggable items and group by

1) MainActivity.java

public class MainActivity extends AppCompatActivity implements OnStartDragListener{

RecyclerView recyclerView;
private ItemTouchHelper mItemTouchHelper;
private List<Item> itemList = new ArrayList<>();
private RecyclerViewAdapter mAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);

    mAdapter = new RecyclerViewAdapter(itemList, MainActivity.this);
    RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    recyclerView.addItemDecoration(new SeparationDecorator());
    recyclerView.setAdapter(mAdapter);
    ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(mAdapter);
    mItemTouchHelper = new ItemTouchHelper(callback);
    mItemTouchHelper.attachToRecyclerView(recyclerView);
    prepareItemData();
}

private void prepareItemData() {
    Item item = new Item("Apple Pay");
    itemList.add(item);
    item = new Item("1706-XXXX-XXXX-1112");
    itemList.add(item);
    item = new Item("Google pay");
    itemList.add(item);
    mAdapter.notifyDataSetChanged();
}

@Override
public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
    mItemTouchHelper.startDrag(viewHolder);
}
}

2) OnStartDragListener.java

public interface OnStartDragListener {
   void onStartDrag(RecyclerView.ViewHolder viewHolder);
}

3) SimpleItemTouchHelperCallback.java

public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {

private final ItemTouchHelperAdapter mAdapter;

public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
    mAdapter = adapter;
}

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

@Override
public boolean isItemViewSwipeEnabled() {
    return false; // make this true to enable swipe to delete
}

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

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                      RecyclerView.ViewHolder target) {
    mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
    return true;
}

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}

}

4) ItemTouchHelperAdapter.java

public interface ItemTouchHelperAdapter {
    void onItemMove(int fromPosition, int toPosition);
    void onItemDismiss(int position);
}

5) SeparationDecorator.java

public class SeparationDecorator extends RecyclerView.ItemDecoration {
private int textSize = 50;
private int groupSpacing = 100;

private Paint paint = new Paint();
{
    paint.setTextSize(textSize);
}

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    for (int i = 0; i < parent.getChildCount(); i++) {
        View view = parent.getChildAt(i);
        int position = parent.getChildAdapterPosition(view);
        if (position == 0) {
            c.drawText("DEFAULT", view.getLeft(),
                    view.getTop() - groupSpacing / 2 + textSize / 3, paint);
        } else if(position == 1) {
            c.drawText("OTHER", view.getLeft(),
                    view.getTop() - groupSpacing / 2 + textSize / 3, paint);
        }
    }
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    if (parent.getChildAdapterPosition(view) == 0 || parent.getChildAdapterPosition(view) == 1) {
        outRect.set(0, groupSpacing, 0, 0);
    }
}
}

6) RecyclerViewAdapter.java

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> implements ItemTouchHelperAdapter{

private List<Item> itemList;
private Context context;

@Override
public void onItemMove(int fromPosition, int toPosition) {
    Item prev = itemList.remove(fromPosition);
    itemList.add(toPosition > fromPosition ? toPosition - 1 : toPosition, prev);
    notifyItemMoved(fromPosition, toPosition);
}

@Override
public void onItemDismiss(int position) {
    itemList.remove(position);
    notifyItemRemoved(position);
}


public class MyViewHolder extends RecyclerView.ViewHolder {
    public TextView title;

    public MyViewHolder(View view) {
        super(view);
        title = (TextView) view.findViewById(R.id.title);
    }
}


public RecyclerViewAdapter(List<Item> itemList, Context context) {
    this.itemList = itemList;
    this.context = context;
}

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.view_item, parent, false);

    return new MyViewHolder(itemView);
}

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    Item movie = itemList.get(position);
    holder.title.setText(movie.getTitle());
}

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

7) Item.java

public class Item {
private String title;

public Item(String title) {
    this.title  = title;
}

public void setTitle(String title) {
    this.title = title;
}

public String getTitle() {
    return title;
}
}

This is it. This was able to solve your problem.

I am not able to explain you the complete code, I will take some time in future definitely and explain it.

Karthik
  • 1,088
  • 6
  • 17
  • Thank you @Karrthik. I'll implement and give it a go over the weekend. – yams May 10 '19 at 16:56
  • @yams hope this is what you were looking for. – Karthik May 10 '19 at 17:03
  • the method prepareItemData isn't really relavent as that list will always be dynamic. I've got a list called "billingMethods" that gets pushed into a adapter called BillingMethodAdapter. – yams May 13 '19 at 20:16
  • I've also added the adapter I'm currently using @Karrthik please take a look as this is the adapter I need to use. – yams May 13 '19 at 20:31
0
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {

// other methods..

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    // initialize your other views

    if (your condition){
        // display header
    }else{
        // hide header
    }
  }
}

You can do some thing like this by adding header view to the item and displaying for the required set and making it gone for un necessary items.

Karthik
  • 1,088
  • 6
  • 17
  • That's not really what I was looking for. I'm looking for the best way to show what is in the screenshot. – yams May 08 '19 at 18:55
  • https://stackoverflow.com/a/41447045/5815636 you can check this answer. – Karthik May 08 '19 at 19:00
  • Can you please update the question as per the requirements. Yes this might not be the best way but your problem can be definitely solved using this approach. You havent asked for the best way but you asked for a possible solution. – Karthik May 08 '19 at 19:31
  • The headers I want to add or already marked in the question. Its the "DEFAULT" and "OTHER" text in the screenshot – yams May 08 '19 at 19:39
  • Are you trying to say that the items can be mode between the headers? – Karthik May 08 '19 at 19:44
  • I'm saying the default and other header's position is fixed – yams May 08 '19 at 19:46
  • yes, I have understood that. Along with that can the items be moved between the groups DEFAULT and OTHERS? – Karthik May 08 '19 at 19:48
  • Yes they can be moved – yams May 08 '19 at 19:50
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/193052/discussion-between-karrthik-and-yams). – Karthik May 08 '19 at 19:52
0

Simply coming towards what you need, just add an empty view with layout_height="1dp" and background="#bcbcbc" like:

<View
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:background="#bcbcbc"/>

it'll get a line drawn for you, use it as a divider, while for making format difference just use a change text size for texts. Hope it'll help you.