Reputation: 803
First of all, I saw this question: Adding a colored background with text/icon under swiped row when using Android's RecyclerView
However, even though the title states "with text/icon", the answer only covers using Canvas
object to draw a rectangle.
Now I have drawing a green rectangle implemented; however I want to make it more obvious what is going to happen by adding "done" button, just like on the following screenshot.
I created a custom view:
public class ChoosePlanView extends LinearLayout{
public ChoosePlanView(Context context) {
super(context);
init();
}
public ChoosePlanView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ChoosePlanView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init(){
View v = inflate(getContext(), R.layout.choose_plan_view, null);
addView(v);
}
}
And the layout is very simple, consisting of green background and the icon:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/choose_green">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/doneIcon"
android:layout_gravity="center_horizontal|right"
android:src="@mipmap/done"/>
</LinearLayout>
I tried overriding method OnChildDraw
:
// ChoosePlanView choosePlanView; <- that was declared and initialized earlier, so I don't have to create a new ChoosePlanView everytime in OnChildDraw();
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
/*
onMove, onSwiped omitted
*/
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
if(dX < 0){
choosePlanView.invalidate();
//choosePlanView.setBackgroundResource(R.color.delete_red);
choosePlanView.measure(itemView.getWidth(), itemView.getHeight());
choosePlanView.layout(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
c.save();
c.translate(choosePlanView.getRight() + (int) dX, viewHolder.getAdapterPosition()*itemView.getHeight());
choosePlanView.draw(c);
c.restore();
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
And it kinda works (draws my ChoosePlanView
), but there are two problems.
First of all it draws only a small green square wrapping my "done" icon (the red part of the view seen on the screenshot below comes from commented line choosePlanView.setBackgroundResource(R.color.delete_red);
). The view has the correct width though, when checked in debugger.
Second thing is where the icon is placed. I specified that I want it to be centered horizontally and always "sticked" to the right side of the view. However it moves, when the RecyclerView
item is is swiped, and having red background shows it isn't placed in the center either.
I tried adding line choosePlanView.invalidate()
, because I thought that happens, because the view was created earlier, but it seems redrawing changes nothing.
Upvotes: 15
Views: 28158
Reputation: 415
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.2.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
Upvotes: 4
Reputation: 654
Using the ItemTouchUiUtil interface provides a robust solution to this. It allows you to have a foreground and background view in your ViewHolder and delegate all the swipe handling to the foreground view. Here is an example that I use for swipe right to remove.
In your ItemTouchHelper.Callback :
public class ClipItemTouchHelper extends ItemTouchHelper.SimpleCallback {
...
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (viewHolder != null){
final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;
getDefaultUIUtil().onSelected(foregroundView);
}
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;
drawBackground(viewHolder, dX, actionState);
getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY,
actionState, isCurrentlyActive);
}
@Override
public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;
drawBackground(viewHolder, dX, actionState);
getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY,
actionState, isCurrentlyActive);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
final View backgroundView = ((ClipViewHolder) viewHolder).clipBackground;
final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;
// TODO: should animate out instead. how?
backgroundView.setRight(0);
getDefaultUIUtil().clearView(foregroundView);
}
private static void drawBackground(RecyclerView.ViewHolder viewHolder, float dX, int actionState) {
final View backgroundView = ((ClipViewHolder) viewHolder).clipBackground;
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
//noinspection NumericCastThatLosesPrecision
backgroundView.setRight((int) Math.max(dX, 0));
}
}
}
And in your layout file, define the foreground and background views:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/clipRow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View style="@style/Divider"/>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/row_selector"
>
<RelativeLayout
android:id="@+id/clipBackground"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@color/swipe_bg"
tools:layout_width="match_parent">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:focusable="false"
android:src="@drawable/ic_delete_24dp"
tools:ignore="ContentDescription"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/clipForeground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/clip_vertical_margin"
android:paddingLeft="@dimen/clip_horizontal_margin"
android:paddingRight="@dimen/clip_horizontal_margin"
android:paddingTop="@dimen/clip_vertical_margin" >
<CheckBox
android:id="@+id/favCheckBox"
android:layout_width="@dimen/view_image_size"
android:layout_height="@dimen/view_image_size"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:background="@android:color/transparent"
android:button="@drawable/ic_star_outline_24dp"
android:clickable="true"
android:contentDescription="@string/content_favorite"
/>
...
</RelativeLayout>
</FrameLayout>
</LinearLayout>
Upvotes: 21
Reputation: 399
I managed to get this to work with the color and an image by extending the answer provided by Sanvywell at : Adding a colored background with text/icon under swiped row when using Android's RecyclerView
I won't recreate the full post (you can follow the link). In brief, I did not create a full view to render during the swipe, but only added a bitmap to the canvas, appropriately positioned to the RecyclerView item.
Upvotes: 0
Reputation: 4958
I was investigating the same issue. What i ended up doing was, in the layout that contained the recyclerView, I added a simple FrameLayout
called 'swipe_bg' of the same height as one of my RecyclerView.ViewHolder
items. I set its visibility to "gone", and placed it under the RecyclerView
Then in my activity where i set the ItemTouchHelper
, I override the onChildDraw like so..
final ItemTouchHelper.SimpleCallback swipeCallback = new ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT){
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
swipe_bg.setY(itemView.getTop());
if(isCurrentlyActive) {
swipe_bg.setVisibility(View.VISIBLE);
}else{
swipe_bg.setVisibility(View.GONE);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
};
Don't know if that is the best way, but seemed like the simplest way.
Upvotes: -2
Reputation: 8211
Instead of draw over, I believe you should use viewType
Implements getItemViewType(int position)
in adapter, returning different types, e.g. 0 for normal, 1 for swipped
and change onCreateViewHolder etc to return different layout on different viewType
Upvotes: 4