Reputation: 1817
I have to finish Activity
when user offer a right swipe anywhere in the screen. I have tried with GestureDetector
and that is works fine if there is neither ScrollView
nor RescyclerView
exists in the Activity
and in addition views that have onClickListener
also doesn't allow to detect swipe over them. So I had tried a different way by overlaying a view into the layout at the top of all them programmatically then tried to detect the swipe event over it.
private void swipeOverToExit(ViewGroup rootView) {
OverlayLayout child = new OverlayLayout(this);
ViewGroup.LayoutParams layoutParams =
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
child.setLayoutParams(layoutParams);
rootView.addView(child);
}
OverlayLayout
public class OverlayLayout extends RelativeLayout {
private float x1, x2;
private final int MIN_DISTANCE = 150;
public OverlayLayout(Context context) {
super(context);
}
public OverlayLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public OverlayLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public OverlayLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onTouchEvent will be called and we do the actual
* logic there.
*/
final int action = MotionEventCompat.getActionMasked(event);
Logger.logD("Intercept===", action + "");
// Always handle the case of the touch gesture being complete.
if (action == MotionEvent.ACTION_DOWN) {
return true; // Intercept touch event, let the parent handle swipe
}
Logger.logD("===", "Out side" + action + "");
// In general, we don't want to intercept touch events. They should be
// handled by the child view.
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
break;
case MotionEvent.ACTION_UP:
x2 = event.getX();
float deltaX = x2 - x1;
if (Math.abs(deltaX) > MIN_DISTANCE) {
Logger.logD("Swipe Right===", MIN_DISTANCE + "");
return true;
} else {
Logger.logD("Tap===", "Tap===");
return super.onTouchEvent(event);
}
}
return true;
}
}
The logic is to intercept touch event to other view if swipe action performs over the OverlayLayout
then further end up the Activity
. However, now I can detect the swipe event on OverlayLayout
but other views couldn't respond even though I had return return super.onTouchEvent(event);
in else condition of onTouchEvent
as u can figure out there in my code. Any one please help me to make it . I'm pinned here and super excited to learn the trick :)
Upvotes: 26
Views: 12230
Reputation: 498
Kotlin with SwipeDismissBehavior
. Refresh and swipe back to close.
Using androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01
for refresh. Enjoy!
class SwipeActivity : AppCompatActivity() {
private lateinit var binding: ActivitySilentSalesmenBinding
private lateinit var swipe: SwipeDismissBehavior<View>
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
swipe = SwipeDismissBehavior()
swipe.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END)
swipe.listener = object : SwipeDismissBehavior.OnDismissListener {
override fun onDismiss(view: View) {
//......
}
override fun onDragStateChanged(state: Int) {
finish()
}
}
binding = ActivitySwipeBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding.swiperefresh.setOnRefreshListener {
Log.v(TAG, "Refreshing...")
Timer().schedule(1000) {
binding.swiperefresh.isRefreshing = false
}
}
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
}
companion object {
const val TAG = "SwipeActivity"
}
Upvotes: 0
Reputation: 1818
I have faced the same problem regarding the Activity Overlay. This worked for me
1) Define you SwipeListener
public class SwipeListener implements View.OnTouchListener {
private SwipeListenerInterface activity;
private float downX, downY, upX, upY;
public SwipeListener(SwipeListenerInterface activity) {
this.activity = activity;
}
public void onRightToLeftSwipe(View v) {
Log.i(logTag, "RightToLeftSwipe!");
activity.onRightToLeftSwipe(v);
}
public void onLeftToRightSwipe(View v) {
Log.i(logTag, "LeftToRightSwipe!");
activity.onLeftToRightSwipe(v);
}
public void onTopToBottomSwipe(View v) {
Log.i(logTag, "TopToBottomSwipe!");
activity.onTopToBottomSwipe(v);
}
public void onBottomToTopSwipe(View v) {
Log.i(logTag, "BottomToTopSwipe!");
activity.onBottomToTopSwipe(v);
}
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
downX = event.getX();
downY = event.getY();
return true;
}
case MotionEvent.ACTION_UP: {
upX = event.getX();
upY = event.getY();
float deltaX = downX - upX;
float deltaY = downY - upY;
if (deltaX < 0 ) {
this.onLeftToRightSwipe(v);
return true;
}
if (deltaX > 0 ) {
this.onRightToLeftSwipe(v);
return true;
}
if (deltaY < 0) {
this.onTopToBottomSwipe(v);
return true;
}
if (deltaY > 0) {
this.onBottomToTopSwipe(v);
return true;
}
}
}
return false;
}
public void setSwipeRestrictions(int swipeRestrictionX, int swipeRestrictionY) {
this.swipeRestrictionX = swipeRestrictionX;
this.swipeRestrictionY = swipeRestrictionY;
}
2) With the following referred interface
public interface SwipeListenerInterface {
void onRightToLeftSwipe(View v);
void onLeftToRightSwipe(View v);
void onTopToBottomSwipe(View v);
void onBottomToTopSwipe(View v);
}
3) Create the object and bind it to your overlayView (make sure to adjust the interface to the overLay view so it can receive the callbacks)
sl = new SwipeListener(this);
overlayView.setOnTouchListener(sl);
Upvotes: 1
Reputation: 45110
What you are trying to do is basically default behavior in Android Wear and its is consider as standard practices in Android Watches
to exit an app.
In Android wear DismissOverlayView does all heavy lifting for you.
Smartphones have back button while Wear rely on long press or swipe dismiss pattern for to exit screen. You should dismiss Activity on back press, mixing wear pattern in Android Smartphones will make user confused. At Least show a warning dialog to avoid accidental exit.
Solution
As I see this question is tagged with Android Activity
I would suggest you to make an Base Activity which will take care of swipe gesture and finish()
itself on left to right swipe.
The base activity class should look like this :-
public abstract class SwipeDismissBaseActivity extends AppCompatActivity {
private static final int SWIPE_MIN_DISTANCE = 120;
private static final int SWIPE_MAX_OFF_PATH = 250;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
private GestureDetector gestureDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gestureDetector = new GestureDetector(new SwipeDetector());
}
private class SwipeDetector extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// Check movement along the Y-axis. If it exceeds SWIPE_MAX_OFF_PATH,
// then dismiss the swipe.
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
return false;
// Swipe from left to right.
// The swipe needs to exceed a certain distance (SWIPE_MIN_DISTANCE)
// and a certain velocity (SWIPE_THRESHOLD_VELOCITY).
if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
finish();
return true;
}
return false;
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TouchEvent dispatcher.
if (gestureDetector != null) {
if (gestureDetector.onTouchEvent(ev))
// If the gestureDetector handles the event, a swipe has been
// executed and no more needs to be done.
return true;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
}
Now you can make other activities extend this base Activity and they will
Inheritance
will automatically make them adopt swipe to dismiss behavior .
public class SomeActivity extends SwipeDismissBaseActivity {
Advantages of this way
Upvotes: 18
Reputation: 10549
SwipeBack is an android library is for Activities
to do pretty the same as the android "back-button" will do, but in a really intuitive way by using a swipe gesture.
Get it from maven central
compile 'com.hannesdorfmann:swipeback:1.0.4'
Create a base activity and write down the method
public void initDrawerSwipe(int layoutId) {
SwipeBack.attach(this, Position.LEFT)
.setContentView(layoutId)
.setSwipeBackView(R.layout.layout_swipe_back)
.setSwipeBackTransformer(new SlideSwipeBackTransformer() {
@Override
public void onSwipeBackCompleted(SwipeBack swipeBack, Activity activity) {
supportFinishAfterTransition();
}
});
}
Thereafter pass your layout id to the method that placed in your base activity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initDrawerSwipe(R.layout.activity_stylists);
}
This works well in all scenario that you had point out in your query.
Upvotes: 0
Reputation: 10971
I believe the issue you have with the RecyclerView and ScrollView has to do with the child elements obtaining the focus before the parents. you can try to set the android:descendantFocusability="beforeDescendants"
for the Recycler/Scroll views.
Upvotes: 1
Reputation: 4182
Try this I am using Function for swipe like that
View to swipe...
yourview.setOnTouchListener(new SimpleGestureFilter(this)); // yourview is layout or container to swipe
SimpleGestureFilter class
public class SimpleGestureFilter implements View.OnTouchListener {
static final String logTag = "ActivitySwipeDetector";
private Context activity;
static final int MIN_DISTANCE = 100;// TODO change this runtime based on screen resolution. for 1920x1080 is to small the 100 distance
private float downX, downY, upX, upY;
// private NDAAgreementActivity mMainActivity;
public SimpleGestureFilter(Context mainActivity) {
activity = mainActivity;
}
public void onRightToLeftSwipe() {
//do your code to right to left
}
public void onLeftToRightSwipe() {
//do your code to left to right
}
public void onTopToBottomSwipe() {
}
public void onBottomToTopSwipe() {
}
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
downX = event.getX();
downY = event.getY();
return true;
}
case MotionEvent.ACTION_UP: {
upX = event.getX();
upY = event.getY();
float deltaX = downX - upX;
float deltaY = downY - upY;
// swipe horizontal?
if (Math.abs(deltaX) > MIN_DISTANCE) {
// left or right
if (deltaX < 0) {
this.onLeftToRightSwipe();
return true;
}
if (deltaX > 0) {
this.onRightToLeftSwipe();
return true;
}
} else {
Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long horizontally, need at least " + MIN_DISTANCE);
// return false; // We don't consume the event
}
// swipe vertical?
if (Math.abs(deltaY) > MIN_DISTANCE) {
// top or down
if (deltaY < 0) {
this.onTopToBottomSwipe();
return true;
}
if (deltaY > 0) {
this.onBottomToTopSwipe();
return true;
}
} else {
Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long vertically, need at least " + MIN_DISTANCE);
// return false; // We don't consume the event
}
return false; // no swipe horizontally and no swipe vertically
}// case MotionEvent.ACTION_UP:
}
return false;
}
}
Upvotes: 2