m trying to build android new inbox style listview with swipe left and right as shown in this image , i have tried 47deg swipelistview but its not that stable , is there any
As I mentioned previously, I took the same approach and it seems to work as expected. I have added 3 layers to a RelativeLayout. Top layer is what you want to show. Second layer is a plain background with delete icon at the left. Third layer is another plain background with share icon at the right. I implemented a swipe detector class which extends View.OnTouchListener
.
public class SwipeDetector implements View.OnTouchListener {
private static final int MIN_DISTANCE = 300;
private static final int MIN_LOCK_DISTANCE = 30; // disallow motion intercept
private boolean motionInterceptDisallowed = false;
private float downX, upX;
private ObjectHolder holder;
private int position;
public SwipeDetector(ObjectHolder h, int pos) {
holder = h;
position = pos;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
downX = event.getX();
return true; // allow other events like Click to be processed
}
case MotionEvent.ACTION_MOVE: {
upX = event.getX();
float deltaX = downX - upX;
if (Math.abs(deltaX) > MIN_LOCK_DISTANCE && listView != null && !motionInterceptDisallowed) {
listView.requestDisallowInterceptTouchEvent(true);
motionInterceptDisallowed = true;
}
if (deltaX > 0) {
holder.deleteView.setVisibility(View.GONE);
} else {
// if first swiped left and then swiped right
holder.deleteView.setVisibility(View.VISIBLE);
}
swipe(-(int) deltaX);
return true;
}
case MotionEvent.ACTION_UP:
upX = event.getX();
float deltaX = upX - downX;
if (Math.abs(deltaX) > MIN_DISTANCE) {
// left or right
swipeRemove();
} else {
swipe(0);
}
if (listView != null) {
listView.requestDisallowInterceptTouchEvent(false);
motionInterceptDisallowed = false;
}
holder.deleteView.setVisibility(View.VISIBLE);
return true;
case MotionEvent.ACTION_CANCEL:
holder.deleteView.setVisibility(View.VISIBLE);
return false;
}
return true;
}
private void swipe(int distance) {
View animationView = holder.mainView;
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) animationView.getLayoutParams();
params.rightMargin = -distance;
params.leftMargin = distance;
animationView.setLayoutParams(params);
}
private void swipeRemove() {
remove(getItem(position));
notifyDataSetChanged();
}
}
public static class ObjectHolder {
public LinearLayout mainView;
public RelativeLayout deleteView;
public RelativeLayout shareView;
/* other views here */
}
I have also added requestDisallowInterceptTouchEvent
so that ListView (which is parent) doesn't intercept the touch event when there's some amount of vertical scrolling involved.
I have written a blogpost about it which you can find it here. I have also added a Youtube video for demo.
I implemented one of these myself, but it's a bit different. I use just touch instead of swiping. Touch to open, touch to close. Here's youtube demo.
I created custom ArrayAdapter. To set the layout, I created a custom layout like this.
<RelativeLayout>
<RelativeLayout>
<Stuff that you want at the back of your list/>
</RelativeLayout>
<RelativeLayout>
<Stuff that you want at the front of your list/>
</RelativeLayout>
</RelativeLayout>
Using RelativeLayout
, I am putting the top view over the bottom view. Both have same sizes. You can use different layouts for inner layouts.
In Custom ArrayAdapter,
@Override
public view getView(int position, View convertView, ViewGroup parent) {
// get holder and entry
// set each element based on entry preferences
holder.topView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (entry.isSwiped()) {
swipeWithAnimationValue(holder.topView, 1);
entry.setSwiped(false);
} else {
closeOtherSwipes(entry); // if you want to keep only one entry open at a time
swipeWithAnimationValue(holder.topView, 0);
entry.setSwiped(true);
}
}
});
}
Normal Animation would not work as it just shifts the view, but it's still there so if you try to click, the click still occurs on the top view. Hence I have used valueAnimator and actually shifted those lists.
public void swipeWithAnimationValue(final View view, final int direction) {
final int width = view.getWidth();
Log.i(TAG, "view width = " + String.valueOf(width));
ValueAnimator animationSwipe;
int duration = 300;
if (direction == 0) {
animationSwipe = ValueAnimator.ofInt(0, view.getWidth() - 200);
} else {
animationSwipe = ValueAnimator.ofInt(view.getWidth() - 200, 0);
}
animationSwipe.setDuration(duration);
AnimatorUpdateListener maringUpdater = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
params.rightMargin = -(Integer)animation.getAnimatedValue();
params.leftMargin = (Integer)animation.getAnimatedValue();
view.setLayoutParams(params);
}
};
animationSwipe.addUpdateListener(maringUpdater);
animationSwipe.setRepeatCount(0);
animationSwipe.start();
}
Instead of using a custom ListView you can simply support "swipe" gesture on list items onTouch, like the following:
private static final int DEFAULT_THRESHOLD = 128;
row.setOnTouchListener(new View.OnTouchListener() {
int initialX = 0;
final float slop = ViewConfiguration.get(context).getScaledTouchSlop();
public boolean onTouch(final View view, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
initialX = (int) event.getX();
view.setPadding(0, 0, 0, 0);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
int currentX = (int) event.getX();
int offset = currentX - initialX;
if (Math.abs(offset) > slop) {
view.setPadding(offset, 0, 0, 0);
if (offset > DEFAULT_THRESHOLD) {
// TODO :: Do Right to Left action! And do nothing on action_up.
} else if (offset < -DEFAULT_THRESHOLD) {
// TODO :: Do Left to Right action! And do nothing on action_up.
}
}
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
// Animate back if no action was performed.
ValueAnimator animator = ValueAnimator.ofInt(view.getPaddingLeft(), 0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
view.setPadding((Integer) valueAnimator.getAnimatedValue(), 0, 0, 0);
}
});
animator.setDuration(150);
animator.start();
}
};
I also use reverse animation if no action was performed.
This solution is lightweight so you should not experience any lags.
It's a great library that does exactly what you're asking for. It allows Swipe in both directions with an underlying Layout
or Color
. It's easy to implement and looks nice!