Setting an event handler in XML using android databinding - android

I am trying to implement a swipe feature for recyclerview. I found this tutorial for that:
Swipe ⇆ Drag ⇅ Bind RecyclerView
The code is available here:
Code - GitHub
First of all I don't understand why is there an interface SwipeHandler which is implemented by MainActivity and another one within the SwipeItemTouchHelperCallback class? SwipeHandler is never used!? Is that a mistake?
Why am I asking this question? Because for my own project I have some trouble to connect the swipe off event with the appropriate callback within my fragment. The onItemSwiped method within the fragment is never called. So I changed the code a bit (I don't have a SwipeHandler...just use the interface within SwipeItemTouchHelper):
public class FragmentFlightRecords extends Fragment implements SwipeToDeleteCallback.OnItemSwipeListener {
#Override
public void onItemSwiped(int position) {
saveAndRemoveItem(position);
showSnackbar("Swiped Left " + position);
}
}
layout.xml:
<variable
name="handler"
type="de.flightlogger.view.adapter.SwipeToDeleteCallback.OnItemSwipeListener" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_flight_records"
android:layout_width="match_parent"
android:layout_height="0dp"
android:adapter="#{adapter}"
android:visibility="#{viewmodel.flightRecords.size() != 0}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/cl_total_flight_time"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
bind:bgColorSwipe="#{#color/primaryDarkColor}"
bind:drawableSwipe="#{#drawable/ic_add_black_24dp}"
bind:onItemSwipe="#{(position) -> handler.onItemSwiped(position)}"
bind:swipeEnabled="#{true}"
tools:listitem="#layout/rv_item_flight_records"/>
SwipeToDeleteCallback:
public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
private Drawable icon;
private ColorDrawable background;
private OnItemSwipeListener onItemSwipeListener;
private boolean swipeEnabled;
private SwipeToDeleteCallback(int dragDirs, int swipeDirs) {
super(dragDirs, swipeDirs);
}
private SwipeToDeleteCallback(Builder builder) {
this(builder.dragDirs, builder.swipeDirs);
background = builder.bgColorSwipe;
icon = builder.drawableSwipe;
swipeEnabled = builder.swipeEnabled;
onItemSwipeListener = builder.onItemSwipeListener;
}
#Override public boolean isItemViewSwipeEnabled() {
return swipeEnabled;
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
onItemSwipeListener.onItemSwiped(position);
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
View itemView = viewHolder.itemView;
int backgroundCornerOffset = 20; //so background is behind the rounded corners of itemView
int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
int iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
int iconBottom = iconTop + icon.getIntrinsicHeight();
if (dX > 0) { // Swiping to the right
int iconLeft = itemView.getLeft() + iconMargin;
int iconRight = itemView.getLeft() + icon.getIntrinsicWidth() + iconMargin;
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom);
background.setBounds(itemView.getLeft(), itemView.getTop(),
itemView.getLeft() + ((int) dX) + backgroundCornerOffset, itemView.getBottom());
} else if (dX < 0) { // Swiping to the left
int iconLeft = itemView.getRight() - iconMargin - icon.getIntrinsicWidth();
int iconRight = itemView.getRight() - iconMargin;
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom);
background.setBounds(itemView.getRight() + ((int) dX) - backgroundCornerOffset,
itemView.getTop(), itemView.getRight(), itemView.getBottom());
} else { // view is unSwiped
background.setBounds(0, 0, 0, 0);
}
background.draw(c);
icon.draw(c);
}
public interface OnItemSwipeListener {
void onItemSwiped(int position);
}
public static final class Builder {
private int dragDirs, swipeDirs;
private Drawable drawableSwipe;
private ColorDrawable bgColorSwipe;
private OnItemSwipeListener onItemSwipeListener;
private boolean swipeEnabled;
public Builder(int dragDirs, int swipeDirs) {
this.dragDirs = dragDirs;
this.swipeDirs = swipeDirs;
}
public Builder drawableSwipe(Drawable val) {
drawableSwipe = val;
return this;
}
public Builder bgColorSwipe(ColorDrawable val) {
bgColorSwipe = val;
return this;
}
public Builder onItemSwipeListener(OnItemSwipeListener val) {
onItemSwipeListener = val;
return this;
}
public Builder setSwipeEnabled(boolean val) {
swipeEnabled = val;
return this;
}
public SwipeToDeleteCallback build() {
return new SwipeToDeleteCallback(this);
}
}
}
Binding Adapter:
#BindingAdapter(value = {"swipeEnabled", "drawableSwipe", "bgColorSwipe", "onItemSwipe"}, requireAll = false)
public static void setItemSwipeToRecyclerView(RecyclerView recyclerView, boolean swipeEnabled, Drawable drawableSwipe, ColorDrawable bgColorSwipe,
SwipeToDeleteCallback.OnItemSwipeListener onItemSwipe) {
ItemTouchHelper.Callback swipeCallback = new SwipeToDeleteCallback
.Builder(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT)
.bgColorSwipe(bgColorSwipe)
.drawableSwipe(drawableSwipe)
.setSwipeEnabled(swipeEnabled)
.onItemSwipeListener(onItemSwipe)
.build();
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(swipeCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
The swiping itself works but it doesn't have a functionallity. The item of the recyclerview is not deleted and debuging tells me that the callback onItemSwiped within the fragment is never called. So what am I missing to hook up the swipe off event to the onItemSwiped callback within the fragment?

I simply forgot to set the "handler" variable of the XML. So adding binding.setHandler(this) to the fragment solved the problem.
But I still don't understand why they have used two interfaces in the tutorial. I am just using the one of SwipeToDeleteCallback and it works fine.

Related

FirestoreRecyclerAdaptor not showing first item added to firestore

I have a fragment called today, in which i get all the tasks with the current day. But, when i add an item to the firestore with the current day, and this is the first task witch is added to the firestore, the task is now showed in the fragment, when I add the second task is showed, and the third the same. But the first task is never showed, do you know why is doing this?
This is today fragment:
public class TodayFragment extends Fragment {
private View rootView;
private TodayAdaptor todayAdaptor;
private ColorDrawable swipeBackgroundRight = new ColorDrawable(Color.parseColor("#FF0000"));
private Drawable iconDelete;
private FirebaseFirestore firestore = FirebaseFirestore.getInstance();
private CollectionReference collectionReference;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
if (getActivity() != null) {
getActivity().setTitle(R.string.today);
}
collectionReference = firestore.collection("Users")
.document(FirebaseAuth.getInstance().getCurrentUser().getUid())
.collection("Tasks");
iconDelete = ContextCompat.getDrawable(getActivity(), R.drawable.ic_delete);
rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_today, container, false);
RecyclerView recyclerViewToday = rootView.findViewById(R.id.recycler_view_today);
/* create the options for query */
Calendar calendar = Calendar.getInstance();
String dateFormatted = java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL).format(calendar.getTime());
Query query = collectionReference.whereEqualTo("date", dateFormatted);
FirestoreRecyclerOptions<Task> options =
new FirestoreRecyclerOptions.Builder<Task>()
.setQuery(query, Task.class)
.build();
todayAdaptor = new TodayAdaptor(options);
recyclerViewToday.setHasFixedSize(true);
recyclerViewToday.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerViewToday.setAdapter(todayAdaptor);
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (direction == ItemTouchHelper.RIGHT) {
todayAdaptor.deleteTask(viewHolder.getAdapterPosition());
Toast.makeText(getActivity(), "Task deleted", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onChildDraw(#NonNull Canvas c, #NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
int iconMargin = (itemView.getHeight() - iconDelete.getIntrinsicHeight()) / 2;
if (dX > 0) {
swipeBackgroundRight.setBounds(itemView.getLeft(), itemView.getTop(), (int) dX, itemView.getBottom());
iconDelete.setBounds(itemView.getLeft() + iconMargin, itemView.getTop() + iconMargin, itemView.getLeft() + iconMargin + iconDelete.getIntrinsicWidth(), itemView.getBottom() - iconMargin);
} else {
swipeBackgroundRight.setBounds(0, 0, 0, 0);
}
c.save();
swipeBackgroundRight.draw(c);
if (dX > 0) {
c.clipRect(itemView.getLeft(), itemView.getTop(), (int) dX, itemView.getBottom());
} else {
c.clipRect(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
}
iconDelete.draw(c);
c.restore();
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}).attachToRecyclerView(recyclerViewToday);
return rootView;
}
#Override
public void onStart() {
super.onStart();
todayAdaptor.startListening();
}
#Override
public void onStop() {
super.onStop();
todayAdaptor.stopListening();
}
}
This is my adaptor:
public class TodayAdaptor extends FirestoreRecyclerAdapter<Task, TodayAdaptor.TodayViewHolder> {
public TodayAdaptor(#NonNull FirestoreRecyclerOptions<Task> options) {
super(options);
}
#Override
protected void onBindViewHolder(#NonNull TodayViewHolder holder, int position, #NonNull Task model) {
holder.todayCheckBox.setChecked(model.isCompleted());
holder.taskDescription.setText(model.getName());
}
#NonNull
#Override
public TodayViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.today_item, parent, false);
return new TodayViewHolder(view);
}
public void deleteTask(int position){
/* save the item to be
restored if the user want's so
*/
getSnapshots().getSnapshot(position).getReference().delete();
}
class TodayViewHolder extends RecyclerView.ViewHolder {
private CheckBox todayCheckBox;
private TextView taskDescription;
public TodayViewHolder(#NonNull View itemView) {
super(itemView);
todayCheckBox = itemView.findViewById(R.id.today_item_check_box);
taskDescription = itemView.findViewById(R.id.today_item_task_description);
}
}
}

The deleted item is empty when invoke UNDO button on snackbar

I'm trying to implement the feature "Swipe to delete" item in recycleview. But the deleted item is can not restore correctly when invoke UNDO action on snackbar.
This is error.
https://i.imgur.com/hPfaBQX.png
I have tried to implement onChildDraw in different ways, but it does not work
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position) {
// backup of removed item
final String deletedItem = adapter.arrIgnoreNumber.get(viewHolder.getAdapterPosition());
final int deletedIndex = viewHolder.getAdapterPosition();
adapter.removeItem(viewHolder.getAdapterPosition());
// showing snack bar with Undo option
Snackbar snackbar = Snackbar.make(coordinatorLayout, "Removed from the list!", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
#Override
public void onClick(View view) {
adapter.restoreItem(deletedItem, deletedIndex);
}
});
snackbar.setActionTextColor(Color.YELLOW);
snackbar.show();
}
}
#Override
public void onChildDraw(#NonNull Canvas c, #NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
Bitmap icon;
Paint p = new Paint();
if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
View itemView = viewHolder.itemView;
float height = (float) itemView.getBottom() - (float) itemView.getTop();
float width = height / 3;
if(dX > 0){
p.setColor(Color.parseColor("#388E3C"));
RectF background = new RectF((float) itemView.getLeft(), (float) itemView.getTop(), dX,(float) itemView.getBottom());
c.drawRect(background,p);
icon = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_delete);
RectF icon_dest = new RectF((float) itemView.getLeft() + width ,(float) itemView.getTop() + width,(float) itemView.getLeft()+ 2*width,(float)itemView.getBottom() - width);
c.drawBitmap(icon,null,icon_dest,p);
} else {
p.setColor(Color.parseColor("#D32F2F"));
RectF background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(),(float) itemView.getRight(), (float) itemView.getBottom());
c.drawRect(background,p);
icon = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_delete);
RectF icon_dest = new RectF((float) itemView.getRight() - 2*width ,(float) itemView.getTop() + width,(float) itemView.getRight() - width,(float)itemView.getBottom() - width);
c.drawBitmap(icon,null,icon_dest,p);
}
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
I expect the deleted item can restore in list
Update: I provide my adapter for more detail:
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.list_ignore_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, int position) {
final int tmpPos = position;
viewHolder.phone.setText(arrIgnoreNumber.get(position));
String name = arrIgnoreNumber.get(position);
if (!name.isEmpty()) {
viewHolder.phone.setVisibility(View.VISIBLE);
viewHolder.name.setText(name);
} else {
viewHolder.phone.setVisibility(View.GONE);
viewHolder.name.setText(arrIgnoreNumber.get(position));
}
viewHolder.icon.setImageBitmap(Utils.getBitmapByContactNumber(mContext, arrIgnoreNumber.get(position), false));
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemCount() {
return arrIgnoreNumber.size();
}
public void removeItem(int position) {
arrIgnoreNumber.remove(position);
notifyItemRemoved(position);
}
public void restoreItem(String item, int position) {
arrIgnoreNumber.add(position, item);
notifyItemInserted(position);
}
The following solution works for me. Refer to https://www.androidhive.info/2017/09/android-recyclerview-swipe-delete-undo-using-itemtouchhelper/
In ItemTouchHelper.SimpleCallback:
#Override
public int getMovementFlags(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(0, ItemTouchHelper.LEFT);
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
listener.onSwiped(viewHolder, direction, viewHolder.getAdapterPosition());
}
#Override
public void onSelectedChanged(#Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
if (viewHolder != null) {
final View foregroundView = ((IgnoreCallAdapter.ViewHolder) viewHolder).foreground;
getDefaultUIUtil().onSelected(foregroundView);
}
}
#Override
public void onChildDrawOver(#NonNull Canvas c, #NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
final View foregroundView = ((IgnoreCallAdapter.ViewHolder) viewHolder).foreground;
getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY,
actionState, isCurrentlyActive);
}
#Override
public void onChildDraw(#NonNull Canvas c, #NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
final View foregroundView = ((IgnoreCallAdapter.ViewHolder) viewHolder).foreground;
getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY,
actionState, isCurrentlyActive);
}
#Override
public void clearView(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
final View foregroundView = ((IgnoreCallAdapter.ViewHolder) viewHolder).foreground;
getDefaultUIUtil().clearView(foregroundView);
}
#Override
public float getSwipeThreshold(#NonNull RecyclerView.ViewHolder viewHolder) {
return 0.7f;
}
#Override
public int convertToAbsoluteDirection(int flags, int layoutDirection) {
return super.convertToAbsoluteDirection(flags, layoutDirection);
}
In item layout xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="#dimen/row_view_height_ignore_list"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="vertical">
<RelativeLayout
android:id="#+id/ignore_item_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/ignore_item_bg">
</RelativeLayout>
<LinearLayout
android:id="#+id/ignore_item_foreground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/bg_ignore_item_foreground"
android:orientation="horizontal"
android:visibility="visible">
</LinearLayout>
</LinearLayout>
</FrameLayout>

How to add dots under horizontal RecyclerView?

I have RecyclerView which show images as i want
RecyclerView detailsRecycleImage = (RecyclerView) view.findViewById(R.id.detailsRcycleImage);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(
getActivity().getApplicationContext(),
LinearLayoutManager.HORIZONTAL,
false
);
detailsRecycleImage.setLayoutManager(mLayoutManager);
ImagesAdapter imgAdapter = new
ImagesAdapter(getActivity(),contactsData.getContactImages());
detailsRecycleImage.setAdapter(imgAdapter);
And my adapter code is
public class ImagesAdapter extends RecyclerView.Adapter<ImagesAdapter.MyViewHolder>{
private Context cnt;
public ArrayList<String> imgsUrls;
public ImagesAdapter(Context cnt, ArrayList<String> imgsUrls) {
this.cnt=cnt;
this.imgsUrls=imgsUrls;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.single_img,parent,false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
String singleImg = imgsUrls.get(position);
Picasso.with(cnt).load(singleImg).into(holder.img);
}
#Override
public int getItemCount() {
return imgsUrls.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
public ImageView img;
public MyViewHolder(View view) {
super(view);
img = (ImageView) view.findViewById(R.id.img);
}
}
}
And my layout is
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/img"
/>
</RelativeLay
I just need to add dots under the recyclerview as an indicator as this image
Is there any way to add something to my code and get this view ?
I slightly change LinePagerIndicatorDecoration to make it DotsIndicatorDecoration and to support both GridLayoutManager and LinearLayoutManager
public class DotsIndicatorDecoration extends RecyclerView.ItemDecoration {
private final int indicatorHeight;
private final int indicatorItemPadding;
private final int radius;
private final Paint inactivePaint = new Paint();
private final Paint activePaint = new Paint();
public DotsIndicatorDecoration(int radius, int padding, int indicatorHeight, #ColorInt int colorInactive, #ColorInt int colorActive) {
float strokeWidth = Resources.getSystem().getDisplayMetrics().density * 1;
this.radius = radius;
inactivePaint.setStrokeCap(Paint.Cap.ROUND);
inactivePaint.setStrokeWidth(strokeWidth);
inactivePaint.setStyle(Paint.Style.STROKE);
inactivePaint.setAntiAlias(true);
inactivePaint.setColor(colorInactive);
activePaint.setStrokeCap(Paint.Cap.ROUND);
activePaint.setStrokeWidth(strokeWidth);
activePaint.setStyle(Paint.Style.FILL);
activePaint.setAntiAlias(true);
activePaint.setColor(colorActive);
this.indicatorItemPadding = padding;
this.indicatorHeight = indicatorHeight;
}
#Override
public void onDrawOver(#NotNull Canvas c, #NotNull RecyclerView parent, #NotNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
final RecyclerView.Adapter adapter = parent.getAdapter();
if (adapter == null) {
return;
}
int itemCount = adapter.getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = this.radius * 2 * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * indicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2f;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - indicatorHeight / 2f;
drawInactiveDots(c, indicatorStartX, indicatorPosY, itemCount);
final int activePosition;
if (parent.getLayoutManager() instanceof GridLayoutManager) {
activePosition = ((GridLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
} else if (parent.getLayoutManager() instanceof LinearLayoutManager) {
activePosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
} else {
// not supported layout manager
return;
}
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page if the user is scrolling
final View activeChild = parent.getLayoutManager().findViewByPosition(activePosition);
if (activeChild == null) {
return;
}
drawActiveDot(c, indicatorStartX, indicatorPosY, activePosition);
}
private void drawInactiveDots(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
// width of item indicator including padding
final float itemWidth = this.radius * 2 + indicatorItemPadding;
float start = indicatorStartX + radius;
for (int i = 0; i < itemCount; i++) {
c.drawCircle(start, indicatorPosY, radius, inactivePaint);
start += itemWidth;
}
}
private void drawActiveDot(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition) {
// width of item indicator including padding
final float itemWidth = this.radius * 2 + indicatorItemPadding;
float highlightStart = indicatorStartX + radius + itemWidth * highlightPosition;
c.drawCircle(highlightStart, indicatorPosY, radius, activePaint);
}
#Override
public void getItemOffsets(#NotNull Rect outRect, #NotNull View view, #NotNull RecyclerView parent, #NotNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = indicatorHeight;
}
}
Usage:
RecyclerView recyclerView = resourceLayout.getSuccessView().findViewById(R.id.cardsRecyclerView);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(manageCardGenericAdapter);
final int radius = getResources().getDimensionPixelSize(R.dimen.radius);
final int dotsHeight = getResources().getDimensionPixelSize(R.dimen.dots_height);
final int color = ContextCompat.getColor(getContext(), R.color.primaryBlue);
recyclerView.addItemDecoration(new DotsIndicatorDecoration(radius, radius * 4, dotsHeight, color, color));
new PagerSnapHelper().attachToRecyclerView(recyclerView);
Check this out:
https://blog.davidmedenjak.com/android/2017/06/24/viewpager-recyclerview.html
Tldr:
Create a LinePagerIndicatorDecoration and add it to our RecyclerView:
// pager indicator
recyclerView.addItemDecoration(new LinePagerIndicatorDecoration());
I've tried decisions with decorator - strange behaviour: my horizontal recyclerview does not stick to edges of screen. check out my decision -
recycler.setAdapter(recyclerViewHorizontalAdapter);
recycler.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
int currentCompletelyVisibleLab = ((LinearLayoutManager)recyclerViewLayoutManager).findFirstCompletelyVisibleItemPosition();
bottomDotsTransaction(currentCompletelyVisibleLab);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}
});
addBottomDots();
setLabsOnTouchListener(commercialLabsNames, availableTests);
}
//create empty linear layout below recyclerview and add dots to it
private void addBottomDots() {
ImageView imageView;
for (int i = 0; i < commercialLabsLogos.size(); i++) {
imageView = new ImageView(getContext());
imageView.setImageResource(R.drawable.empty_dot_4dp);
imageView.setPadding(15, 15, 15, 15);
dotsLinearLayout.addView(imageView);
}
bottomDotsTransaction(0);
}
private void bottomDotsTransaction(int pos) {
for (int i = 0; i < dotsLinearLayout.getChildCount(); i++) {
if (dotsLinearLayout.getChildAt(i) instanceof ImageView) {
((ImageView) dotsLinearLayout.getChildAt(i)).setImageResource(R.drawable.empty_dot_4dp);
}
}
//Set the chosen dot on position
((ImageView) dotsLinearLayout.getChildAt(pos)).setImageResource(R.drawable.fill_dot_6dp);
}
you just need to set OnScrollListener and inside its callback re-define your linearlayout with dots via your layout_manager and methods
.findFirstCompletelyVisibleItemPosition()
or
.findLastVisibleItemPosition()
.findLastCompletelyVisibleItemPosition()

Calculating total price of items in a List of Items in a recyclerview

I want to calculate the price of items in a List and store the value in sharedpreference. Each Item has a price which takes the form of a double.The issue is when i use my method, it only takes into account one item in the list and calculates for that value. I am using a NumberPicker to change the quantity of the items.
Here is my adapter for handling the List of items that will be displayed on the RecyclerView:
public class CartAdapter extends RecyclerView.Adapter<CartAdapter.ViewHolder> {
private List<SingleItem> items;
private SessionManager sessionManager;
private Context context;
private int pos;
public CartAdapter() {
}
public CartAdapter(Context context, List<SingleItem> items) {
this.items = items;
this.context = context;
sessionManager = new SessionManager(context);
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.cartitem, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
final SingleItem singleItem = items.get(position);
holder.tv_title.setText(singleItem.getTitle());
holder.tv_price.setText("Ksh: " + singleItem.getPrice());
Picasso.with(context).load(singleItem.getUrl()).fit().into(holder.imgcart);
holder.numcart.setMinValue(1);
holder.numcart.setMaxValue(15);
holder.numcart.setWrapSelectorWheel(false);
int qty = holder.numcart.getValue();
getTotal(qty, singleItem.getPrice());
}
public double getTotal(int value, double amount){
double totalamount;
double amountall = amount;
int quantity = value;
totalamount = amountall * quantity;
sessionManager.grandtotal("Ksh: " + totalamount);
return totalamount;
}
#Override
public int getItemCount() {
return items.size();
}
public void removeItem(SingleItem item) {
sessionManager.removeitem(context,item);
items.remove(item);
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView tv_price;
TextView tv_title;
NumberPicker numcart;
ImageView imgcart;
public ViewHolder(View view) {
super(view);
tv_price = (TextView) view.findViewById(R.id.titlecart);
tv_title = (TextView) view.findViewById(R.id.pricecart);
numcart = (NumberPicker) view.findViewById(R.id.pickercart);
imgcart = (ImageView) view.findViewById(R.id.imgcart);
}
}
}
Here is how i'm displaying the RecyclerView on the fragment:
public class Details extends Fragment {
private RecyclerView RecyclerDetails;
private TextView CartPrice;
private CheckBox CheckCart;
private List<SingleItem> list;
private CartAdapter adapter;
private boolean add = false;
private Paint p = new Paint();
private SessionManager sessionManager;
private int pos;
public Details() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_details, container, false);
RecyclerDetails = (RecyclerView) view.findViewById(R.id.recyclercart);
CartPrice = (TextView) view.findViewById(R.id.tvcarttotal);
CheckCart = (CheckBox) view.findViewById(R.id.chkcart);
sessionManager = new SessionManager(getContext());
Toasty.info(getContext(),"Swipe to go to Next", Toast.LENGTH_SHORT,true).show();
RecyclerDetails.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
RecyclerDetails.setLayoutManager(layoutManager);
list = sessionManager.getItems(getContext());
HashMap<String,String> map = sessionManager.itemstostring();
String data = map.get(SessionManager.KEY_ITEMS);
Log.i(Constants.TAG,data);
HashMap<String,String> tot = sessionManager.getgrandtotal();
String total = tot.get(SessionManager.KEY_TOTAL);
CartPrice.setText(total);
CheckCart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (((CheckBox) v).isChecked()){
sessionManager.saveditems("true");
} else {
sessionManager.saveditems("false");
}
}
});
RecyclerDetails.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
}
}));
if(list != null){
adapter = new CartAdapter(getContext(),list);
RecyclerDetails.setAdapter(adapter);
}
initswipe();
return view;
}
private void initswipe() {
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (direction == ItemTouchHelper.RIGHT) {
adapter.removeItem(list.get(position));
sessionManager.saveitems(getContext(),list);
}
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
Bitmap icon;
if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
View itemView = viewHolder.itemView;
float height = (float) itemView.getBottom() - (float) itemView.getTop();
float width = height / 3;
if(dX > 0){
p.setColor(Color.parseColor("#14a895"));
RectF background = new RectF((float) itemView.getLeft(), (float) itemView.getTop(), dX,(float) itemView.getBottom());
c.drawRect(background,p);
icon = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_menu_delete);
RectF icon_dest = new RectF((float) itemView.getLeft() + width ,(float) itemView.getTop() + width,(float) itemView.getLeft()+ 2*width,(float)itemView.getBottom() - width);
c.drawBitmap(icon,null,icon_dest,p);
} else {
p.setColor(Color.parseColor("#14a895"));
RectF background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(),(float) itemView.getRight(), (float) itemView.getBottom());
c.drawRect(background,p);
icon = BitmapFactory.decodeResource(getResources(),android.R.drawable.ic_menu_delete);
RectF icon_dest = new RectF((float) itemView.getRight() - 2*width ,(float) itemView.getTop() + width,(float) itemView.getRight() - width,(float)itemView.getBottom() - width);
c.drawBitmap(icon,null,icon_dest,p);
}
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(RecyclerDetails);
}
}
And finally, here is the function i use to store the value of the total of all items in the list
public void grandtotal (String total){
editor.putString(KEY_TOTAL,total);
editor.commit();
}
public HashMap<String, String> getgrandtotal(){
HashMap<String, String> tot = new HashMap<>();
tot.put(KEY_TOTAL,pref.getString(KEY_TOTAL,null));
return tot;
}
Is there a function I haven't included in the fragment where I'm displaying the RecyclerView with the items ?
Is it just a small change needed ?
private int grandTotal(List<SingleItem> items){
int totalPrice = 0;
for(int i = 0 ; i < items.size(); i++) {
totalPrice += items.get(i).getPrice();
}
return totalPrice;
}
this is the method u need to implement to get the grand total price. U can call this method when u r adding an item or removing and item from the list.
This function in kotlin
var total = 0;
for ( i in 0 until myDebt.size){
total += myDebt[i].valueAdp
}
this is the method you need to implement to get the grand total price. can call this method within your activity
 and you can still assign the value to textView.
mtv_my_debt.setText(String.valueOf(total))

Adding a colored background with text/icon under swiped row when using Android's RecyclerView

EDIT: The real problem was that my LinearLayout was wrapped in another layout, which caused the incorrect behavior. The accepted answer by Sanvywell has a better, more complete example of how to draw a color under swiped view than the code snippet I provided in the question.
Now that RecyclerView widget has native support for row swiping with the help of ItemTouchHelper class, I'm attempting to use it in an app where rows will behave similarly to Google's Inbox app. That is, swiping to the left side performs one action and swiping to the right does another.
Implementing the actions themselves was easy using ItemTouchHelper.SimpleCallback's onSwiped method. However, I was unable to find a simple way to set color and icon that should appear under the view that's currently being swiped (like in Google's Inbox app).
To do that, I'm trying to override ItemTouchHelper.SimpleCallback's onChildDraw method like this:
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
RecyclerViewAdapter.ViewHolder vh = (RecyclerViewAdapter.ViewHolder) viewHolder;
LinearLayout ll = vh.linearLayout;
Paint p = new Paint();
if(dX > 0) {
p.setARGB(255, 255, 0, 0);
} else {
p.setARGB(255, 0, 255, 0);
}
c.drawRect(ll.getLeft(), ll.getTop(), ll.getRight(), ll.getBottom(), p);
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
Determining the swipe direction from dX and setting the appropriate color works as intended, but the coordinates I get from the ViewHolder always correspond to the place where the first LinearLayout was inflated.
How do I get the correct coordinates for the LinearLayout that's in the currently swiped row? Is there an easier way (that doesn't require to override onChildDraw) to set the background color and icon?
I was struggling to implement this feature as well, but you steered me in the right direction.
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
// Get RecyclerView item from the ViewHolder
View itemView = viewHolder.itemView;
Paint p = new Paint();
if (dX > 0) {
/* Set your color for positive displacement */
// Draw Rect with varying right side, equal to displacement dX
c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
(float) itemView.getBottom(), p);
} else {
/* Set your color for negative displacement */
// Draw Rect with varying left side, equal to the item's right side plus negative displacement dX
c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),
(float) itemView.getRight(), (float) itemView.getBottom(), p);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
The accepted answer does a great job of coloring the background, but did not address drawing the icon.
This worked for me because it both set the background color and drew the icon, without the icon being stretched during the swipe, or leaving a gap between the previous and next items after the swipe.
public static final float ALPHA_FULL = 1.0f;
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
// Get RecyclerView item from the ViewHolder
View itemView = viewHolder.itemView;
Paint p = new Paint();
Bitmap icon;
if (dX > 0) {
/* Note, ApplicationManager is a helper class I created
myself to get a context outside an Activity class -
feel free to use your own method */
icon = BitmapFactory.decodeResource(
ApplicationManager.getContext().getResources(), R.drawable.myleftdrawable);
/* Set your color for positive displacement */
p.setARGB(255, 255, 0, 0);
// Draw Rect with varying right side, equal to displacement dX
c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
(float) itemView.getBottom(), p);
// Set the image icon for Right swipe
c.drawBitmap(icon,
(float) itemView.getLeft() + convertDpToPx(16),
(float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2,
p);
} else {
icon = BitmapFactory.decodeResource(
ApplicationManager.getContext().getResources(), R.drawable.myrightdrawable);
/* Set your color for negative displacement */
p.setARGB(255, 0, 255, 0);
// Draw Rect with varying left side, equal to the item's right side
// plus negative displacement dX
c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),
(float) itemView.getRight(), (float) itemView.getBottom(), p);
//Set the image icon for Left swipe
c.drawBitmap(icon,
(float) itemView.getRight() - convertDpToPx(16) - icon.getWidth(),
(float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2,
p);
}
// Fade out the view as it is swiped out of the parent's bounds
final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
} else {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
private int convertDpToPx(int dp){
return Math.round(dp * (getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
Here's how I do it without 3rd party library.
The foreground view will be always visible in the recycler view, and
when swipe is performed the background will be visible staying in a
static position.
Create your custom RecyclerView item and add your custom icon, text and background color to the background layout of item. Notice that I put an id to RelativeLayout with id=foreground and id=background.
Here's mine recylerview_item.xml.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<RelativeLayout
android:id="#+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimary"> <!--Add your background color here-->
<ImageView
android:id="#+id/delete_icon"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
app:srcCompat="#drawable/ic_delete"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:layout_toLeftOf="#id/delete_icon"
android:text="Swipe to delete"
android:textColor="#fff"
android:textSize="13dp" />
</RelativeLayout>
<RelativeLayout
android:padding="20dp"
android:id="#+id/foreground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorWhite">
<TextView
android:id="#+id/textView"
android:text="HelloWorld"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</FrameLayout>
and from your ViewHolder define your RelativeLayout foreground and background view and make it public. Also create a method that will remove the item. In my case my ViewHolder is under my RecyclerViewAdapter.class, so...
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
List<Object> listItem;
public RecyclerViewAdapter(...) {
...
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.recyclerview_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
....
}
#Override
public int getItemCount() {
...
}
public class ViewHolder extends RecyclerView.ViewHolder{
public RelativeLayout foreground, background;
public ViewHolder(View itemView) {
super(itemView);
/** define your foreground and background **/
foreground = itemView.findViewById(R.id.foreground);
background = itemView.findViewById(R.id.background);
}
}
/**Call this later to remove the item on swipe**/
public void removeItem(int position){
//remove the item here
listItem.remove(position);
notifyItemRemoved(position);
}
}
And create a class and name it RecyclerItemTouchHelper.class, this is where swipe thing will happen.
public class RecyclerItemTouchHelper extends ItemTouchHelper.SimpleCallback {
private RecyclerItemTouchHelperListener listener;
public RecyclerItemTouchHelper(int dragDirs, int swipeDirs, RecyclerItemTouchHelperListener listener) {
super(dragDirs, swipeDirs);
this.listener = listener;
}
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
return true;
}
#Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (viewHolder != null) {
final View foregroundView = ((RecyclerViewAdapter.ViewHolder) viewHolder).foreground;
getDefaultUIUtil().onSelected(foregroundView);
}
}
#Override
public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
final View foregroundView = ((RecyclerViewAdapter.ViewHolder) viewHolder).foreground;
getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY,
actionState, isCurrentlyActive);
}
#Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
final View foregroundView = ((RecyclerViewAdapter.ViewHolder) viewHolder).foreground;
getDefaultUIUtil().clearView(foregroundView);
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
final View foregroundView = ((RecyclerViewAdapter.ViewHolder) viewHolder).foreground;
getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY,
actionState, isCurrentlyActive);
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
listener.onSwiped(viewHolder, direction, viewHolder.getAdapterPosition());
}
#Override
public int convertToAbsoluteDirection(int flags, int layoutDirection) {
return super.convertToAbsoluteDirection(flags, layoutDirection);
}
public interface RecyclerItemTouchHelperListener {
void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position);
}
}
Now, from your MainActivity.class or wherever your RecyclerView is, attach the RecyclerItemTouchHelper into it. In my case the RecyclerView is in MainActivity.class so I implemented RecyclerItemTouchHelper.RecyclerItemTouchHelperListener into it and override the method onSwiped()...
public class MainActivity extends AppCompatActivity implements RecyclerItemTouchHelper.RecyclerItemTouchHelperListener {
RecyclerView recyclerView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//Configure RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
RecyclerView.LayoutManager mLyoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(mLyoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
adapter = new RecyclerViewAdapter(this);
adapter.setClickListener(this);
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL));
//Attached the ItemTouchHelper
ItemTouchHelper.SimpleCallback itemTouchHelperCallback = new RecyclerItemTouchHelper(0, ItemTouchHelper.LEFT, this);
new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerView);
}
//define the method onSwiped()
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position) {
if (viewHolder instanceof RecyclerViewAdapter.ViewHolder) {
adapter.removeItem(viewHolder.getAdapterPosition()); //remove the item from the adapter
}
}
}
For more information and clarification here is the blog for it.
For people still finding this default, this is the simplest way.
A simple utility class to add a background, an icon and a label to a RecyclerView item while swiping it left or right.
insert to Gradle
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.1'
Override onChildDraw method of ItemTouchHelper class
#Override
public void onChildDraw (Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,float dX, float dY,int actionState, boolean isCurrentlyActive){
new RecyclerViewSwipeDecorator.Builder(MainActivity.this, c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
.addBackgroundColor(ContextCompat.getColor(MainActivity.this, R.color.my_background))
.addActionIcon(R.drawable.my_icon)
.create()
.decorate();
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
for more info -> https://github.com/xabaras/RecyclerViewSwipeDecorator
I'm not sure how these solutions (by #Sanvywell, #HappyKatz and #user2410066) are working for you guys but in my case I needed another check in the onChildDraw method.
Looks like ItemTouchHelper keeps ViewHolders of removed rows in case they need to be restored. It's also calling onChildDraw for those VHs in addition to the VH being swiped. Not sure about memory management implications of this behavior but I needed an additional check in the start of onChildDraw to avoid drawing for "fantom" rows.
if (viewHolder.getAdapterPosition() == -1) {
return;
}
BONUS PART:
I've also wanted to continue drawing as other rows animate to their new positions after a row is swipe deleted, and I couldn't do it within ItemTouchHelper and onChildDraw. In the end I had to add another item decorator to do it. It goes along these lines:
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getItemAnimator().isRunning()) {
// find first child with translationY > 0
// draw from it's top to translationY whatever you want
int top = 0;
int bottom = 0;
int childCount = parent.getLayoutManager().getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getLayoutManager().getChildAt(i);
if (child.getTranslationY() != 0) {
top = child.getTop();
bottom = top + (int) child.getTranslationY();
break;
}
}
// draw whatever you want
super.onDraw(c, parent, state);
}
}
UPDATE: I wrote a blog post on recycler view swipe to delete feature. Someone might find it usefull. No 3rd party lib necessary.
blog post
git repo
HappyKatz solution has a tricky bug. Is there any reason for drawing bitmap when dX==0?? In some cases this causes permanent icon visibility above list item. Also icons become visible above list item when you just touch list item and dX==1. To fix these:
if (dX > rectOffset) {
c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
(float) itemView.getBottom(), leftPaint);
if (dX > iconOffset) {
c.drawBitmap(leftBitmap,
(float) itemView.getLeft() + padding,
(float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - leftBitmap.getHeight()) / 2,
leftPaint);
}
} else if (dX < -rectOffset) {
c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),
(float) itemView.getRight(), (float) itemView.getBottom(), rightPaint);
if (dX < -iconOffset) {
c.drawBitmap(rightBitmap,
(float) itemView.getRight() - padding - rightBitmap.getWidth(),
(float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - rightBitmap.getHeight()) / 2,
rightPaint);
}
}
In order to implement I used the sample code created by Marcin Kitowicz here.
Benefits of this solution:
Uses background view with layout bounds instead of creating a Rectangle which will show on top of any Bitmap or Drawable.
Uses Drawable image opposed to Bitmap which is easier to implement than needing to convert a Drawable into a Bitmap.
The original implementation code can be found here. In order to implement left swipe I used the inverse left and right positioning logic.
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
var icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
var iconLeft = 0
var iconRight = 0
val background: ColorDrawable
val itemView = viewHolder.itemView
val margin = convertDpToPx(32)
val iconWidth = icon!!.intrinsicWidth
val iconHeight = icon.intrinsicHeight
val cellHeight = itemView.bottom - itemView.top
val iconTop = itemView.top + (cellHeight - iconHeight) / 2
val iconBottom = iconTop + iconHeight
// Right swipe.
if (dX > 0) {
icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
background = ColorDrawable(Color.RED)
background.setBounds(0, itemView.getTop(), (itemView.getLeft() + dX).toInt(), itemView.getBottom())
iconLeft = margin
iconRight = margin + iconWidth
} /*Left swipe.*/ else {
icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
background = ColorDrawable(Color.BLUE)
background.setBounds((itemView.right - dX).toInt(), itemView.getTop(), 0, itemView.getBottom())
iconLeft = itemView.right - margin - iconWidth
iconRight = itemView.right - margin
}
background.draw(c)
icon?.setBounds(iconLeft, iconTop, iconRight, iconBottom)
icon?.draw(c)
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}
Corrected Adam Hurwitz code as the left swipe is not working properly:
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
var icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
var iconLeft = 0
var iconRight = 0
val background: ColorDrawable
val itemView = viewHolder.itemView
val margin = convertDpToPx(32)
val iconWidth = icon!!.intrinsicWidth
val iconHeight = icon.intrinsicHeight
val cellHeight = itemView.bottom - itemView.top
val iconTop = itemView.top + (cellHeight - iconHeight) / 2
val iconBottom = iconTop + iconHeight
// Right swipe.
if (dX > 0) {
icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
background = ColorDrawable(Color.RED)
background.setBounds(0, itemView.getTop(), (itemView.getLeft() + dX).toInt(), itemView.getBottom())
iconLeft = margin
iconRight = margin + iconWidth
} /*Left swipe.*/ else {
icon = ContextCompat.getDrawable(context!!, R.drawable.ic_save_24dp)
background = ColorDrawable(Color.BLUE)
background.setBounds((itemView.right + dX).toInt(), itemView.getTop(), itemView.right, itemView.getBottom())
iconLeft = itemView.right - margin - iconWidth
iconRight = itemView.right - margin
}
background.draw(c)
icon?.setBounds(iconLeft, iconTop, iconRight, iconBottom)
icon?.draw(c)
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}

Categories

Resources