Implemented swipe to dismiss for Your Train card
This commit is contained in:
parent
a27222dbc6
commit
1b7d60561b
@ -39,6 +39,7 @@ import com.actionbarsherlock.view.MenuInflater;
|
|||||||
import com.actionbarsherlock.view.MenuItem;
|
import com.actionbarsherlock.view.MenuItem;
|
||||||
import com.dougkeen.bart.BartRunnerApplication;
|
import com.dougkeen.bart.BartRunnerApplication;
|
||||||
import com.dougkeen.bart.R;
|
import com.dougkeen.bart.R;
|
||||||
|
import com.dougkeen.bart.controls.SwipeDismissTouchListener;
|
||||||
import com.dougkeen.bart.controls.Ticker;
|
import com.dougkeen.bart.controls.Ticker;
|
||||||
import com.dougkeen.bart.controls.YourTrainLayout;
|
import com.dougkeen.bart.controls.YourTrainLayout;
|
||||||
import com.dougkeen.bart.data.DepartureArrayAdapter;
|
import com.dougkeen.bart.data.DepartureArrayAdapter;
|
||||||
@ -186,8 +187,21 @@ public class ViewDeparturesActivity extends SActivity implements
|
|||||||
mMissingDepartureText = findViewById(R.id.missingDepartureText);
|
mMissingDepartureText = findViewById(R.id.missingDepartureText);
|
||||||
mMissingDepartureText.setVisibility(View.VISIBLE);
|
mMissingDepartureText.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
findViewById(R.id.yourTrainSection).setOnClickListener(
|
mYourTrainSection = (YourTrainLayout) findViewById(R.id.yourTrainSection);
|
||||||
mYourTrainSectionClickListener);
|
mYourTrainSection.setOnClickListener(mYourTrainSectionClickListener);
|
||||||
|
mYourTrainSection.setOnTouchListener(new SwipeDismissTouchListener(
|
||||||
|
mYourTrainSection, null,
|
||||||
|
new SwipeDismissTouchListener.OnDismissCallback() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(View view, Object token) {
|
||||||
|
dismissYourTrainSelection();
|
||||||
|
getListView().clearChoices();
|
||||||
|
getListView().requestLayout();
|
||||||
|
if (isYourTrainActionModeActive()) {
|
||||||
|
mActionMode.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
refreshBoardedDeparture();
|
refreshBoardedDeparture();
|
||||||
|
|
||||||
@ -347,6 +361,8 @@ public class ViewDeparturesActivity extends SActivity implements
|
|||||||
|
|
||||||
private View mMissingDepartureText;
|
private View mMissingDepartureText;
|
||||||
|
|
||||||
|
private YourTrainLayout mYourTrainSection;
|
||||||
|
|
||||||
protected DepartureArrayAdapter getListAdapter() {
|
protected DepartureArrayAdapter getListAdapter() {
|
||||||
return mDeparturesAdapter;
|
return mDeparturesAdapter;
|
||||||
}
|
}
|
||||||
@ -448,8 +464,7 @@ public class ViewDeparturesActivity extends SActivity implements
|
|||||||
private void refreshBoardedDeparture() {
|
private void refreshBoardedDeparture() {
|
||||||
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
|
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
|
||||||
.getBoardedDeparture();
|
.getBoardedDeparture();
|
||||||
final YourTrainLayout yourTrainSection = (YourTrainLayout) findViewById(R.id.yourTrainSection);
|
int currentVisibility = mYourTrainSection.getVisibility();
|
||||||
int currentVisibility = yourTrainSection.getVisibility();
|
|
||||||
|
|
||||||
final boolean boardedDepartureDoesNotApply = boardedDeparture == null
|
final boolean boardedDepartureDoesNotApply = boardedDeparture == null
|
||||||
|| boardedDeparture.getStationPair() == null
|
|| boardedDeparture.getStationPair() == null
|
||||||
@ -462,10 +477,10 @@ public class ViewDeparturesActivity extends SActivity implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
yourTrainSection.updateFromDeparture(boardedDeparture);
|
mYourTrainSection.updateFromDeparture(boardedDeparture);
|
||||||
|
|
||||||
if (currentVisibility != View.VISIBLE) {
|
if (currentVisibility != View.VISIBLE) {
|
||||||
showYourTrainSection(yourTrainSection);
|
showYourTrainSection(mYourTrainSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mActionMode == null) {
|
if (mActionMode == null) {
|
||||||
@ -656,11 +671,7 @@ public class ViewDeparturesActivity extends SActivity implements
|
|||||||
startService(intent);
|
startService(intent);
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.delete) {
|
} else if (itemId == R.id.delete) {
|
||||||
Intent intent = new Intent(ViewDeparturesActivity.this,
|
dismissYourTrainSelection();
|
||||||
BoardedDepartureService.class);
|
|
||||||
intent.putExtra("clearBoardedDeparture", true);
|
|
||||||
startService(intent);
|
|
||||||
hideYourTrainSection();
|
|
||||||
mode.finish();
|
mode.finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -690,6 +701,14 @@ public class ViewDeparturesActivity extends SActivity implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dismissYourTrainSelection() {
|
||||||
|
Intent intent = new Intent(ViewDeparturesActivity.this,
|
||||||
|
BoardedDepartureService.class);
|
||||||
|
intent.putExtra("clearBoardedDeparture", true);
|
||||||
|
startService(intent);
|
||||||
|
hideYourTrainSection();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onETDChanged(final List<Departure> departures) {
|
public void onETDChanged(final List<Departure> departures) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
|
265
src/com/dougkeen/bart/controls/SwipeDismissTouchListener.java
Normal file
265
src/com/dougkeen/bart/controls/SwipeDismissTouchListener.java
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Roman Nurik
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* ADAPTED FROM https://github.com/JakeWharton/SwipeToDismissNOA
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.dougkeen.bart.controls;
|
||||||
|
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.VelocityTracker;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import com.nineoldandroids.animation.Animator;
|
||||||
|
import com.nineoldandroids.animation.AnimatorListenerAdapter;
|
||||||
|
import com.nineoldandroids.animation.ValueAnimator;
|
||||||
|
|
||||||
|
import static com.nineoldandroids.view.ViewHelper.setAlpha;
|
||||||
|
import static com.nineoldandroids.view.ViewHelper.setTranslationX;
|
||||||
|
import static com.nineoldandroids.view.ViewPropertyAnimator.animate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link android.view.View.OnTouchListener} that makes any {@link View}
|
||||||
|
* dismissable when the user swipes (drags her finger) horizontally across the
|
||||||
|
* view.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <em>For {@link android.widget.ListView} list items that don't manage their own touch events
|
||||||
|
* (i.e. you're using
|
||||||
|
* {@link android.widget.ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)}
|
||||||
|
* or an equivalent listener on {@link android.app.ListActivity} or
|
||||||
|
* {@link android.app.ListFragment}, use {@link SwipeDismissListViewTouchListener} instead.</em>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Example usage:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* view.setOnTouchListener(new SwipeDismissTouchListener(view, null, // Optional
|
||||||
|
* // token/cookie
|
||||||
|
* // object
|
||||||
|
* new SwipeDismissTouchListener.OnDismissCallback() {
|
||||||
|
* public void onDismiss(View view, Object token) {
|
||||||
|
* parent.removeView(view);
|
||||||
|
* }
|
||||||
|
* }));
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This class Requires API level 12 or later due to use of
|
||||||
|
* {@link android.view.ViewPropertyAnimator}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see SwipeDismissListViewTouchListener
|
||||||
|
*/
|
||||||
|
public class SwipeDismissTouchListener implements View.OnTouchListener {
|
||||||
|
// Cached ViewConfiguration and system-wide constant values
|
||||||
|
private int mSlop;
|
||||||
|
private int mMinFlingVelocity;
|
||||||
|
private int mMaxFlingVelocity;
|
||||||
|
private long mAnimationTime;
|
||||||
|
|
||||||
|
// Fixed properties
|
||||||
|
private View mView;
|
||||||
|
private OnDismissCallback mCallback;
|
||||||
|
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
|
||||||
|
|
||||||
|
// Transient properties
|
||||||
|
private float mDownX;
|
||||||
|
private boolean mSwiping;
|
||||||
|
private Object mToken;
|
||||||
|
private VelocityTracker mVelocityTracker;
|
||||||
|
private float mTranslationX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The callback interface used by {@link SwipeDismissTouchListener} to
|
||||||
|
* inform its client about a successful dismissal of the view for which it
|
||||||
|
* was created.
|
||||||
|
*/
|
||||||
|
public interface OnDismissCallback {
|
||||||
|
/**
|
||||||
|
* Called when the user has indicated they she would like to dismiss the
|
||||||
|
* view.
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
* The originating {@link View} to be dismissed.
|
||||||
|
* @param token
|
||||||
|
* The optional token passed to this object's constructor.
|
||||||
|
*/
|
||||||
|
void onDismiss(View view, Object token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new swipe-to-dismiss touch listener for the given view.
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
* The view to make dismissable.
|
||||||
|
* @param token
|
||||||
|
* An optional token/cookie object to be passed through to the
|
||||||
|
* callback.
|
||||||
|
* @param callback
|
||||||
|
* The callback to trigger when the user has indicated that she
|
||||||
|
* would like to dismiss this view.
|
||||||
|
*/
|
||||||
|
public SwipeDismissTouchListener(View view, Object token,
|
||||||
|
OnDismissCallback callback) {
|
||||||
|
ViewConfiguration vc = ViewConfiguration.get(view.getContext());
|
||||||
|
mSlop = vc.getScaledTouchSlop();
|
||||||
|
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
|
||||||
|
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
|
||||||
|
mAnimationTime = view.getContext().getResources()
|
||||||
|
.getInteger(android.R.integer.config_shortAnimTime);
|
||||||
|
mView = view;
|
||||||
|
mToken = token;
|
||||||
|
mCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||||
|
// offset because the view is translated during swipe
|
||||||
|
motionEvent.offsetLocation(mTranslationX, 0);
|
||||||
|
|
||||||
|
if (mViewWidth < 2) {
|
||||||
|
mViewWidth = mView.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (motionEvent.getActionMasked()) {
|
||||||
|
case MotionEvent.ACTION_DOWN: {
|
||||||
|
// TODO: ensure this is a finger, and set a flag
|
||||||
|
mDownX = motionEvent.getRawX();
|
||||||
|
mVelocityTracker = VelocityTracker.obtain();
|
||||||
|
mVelocityTracker.addMovement(motionEvent);
|
||||||
|
view.onTouchEvent(motionEvent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_UP: {
|
||||||
|
if (mVelocityTracker == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
float deltaX = motionEvent.getRawX() - mDownX;
|
||||||
|
mVelocityTracker.addMovement(motionEvent);
|
||||||
|
mVelocityTracker.computeCurrentVelocity(1000);
|
||||||
|
float velocityX = Math.abs(mVelocityTracker.getXVelocity());
|
||||||
|
float velocityY = Math.abs(mVelocityTracker.getYVelocity());
|
||||||
|
boolean dismiss = false;
|
||||||
|
boolean dismissRight = false;
|
||||||
|
if (Math.abs(deltaX) > mViewWidth / 2) {
|
||||||
|
dismiss = true;
|
||||||
|
dismissRight = deltaX > 0;
|
||||||
|
} else if (mMinFlingVelocity <= velocityX
|
||||||
|
&& velocityX <= mMaxFlingVelocity && velocityY < velocityX) {
|
||||||
|
dismiss = true;
|
||||||
|
dismissRight = mVelocityTracker.getXVelocity() > 0;
|
||||||
|
}
|
||||||
|
if (dismiss) {
|
||||||
|
// dismiss
|
||||||
|
animate(mView)
|
||||||
|
.translationX(dismissRight ? mViewWidth : -mViewWidth)
|
||||||
|
.alpha(0).setDuration(mAnimationTime)
|
||||||
|
.setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
performDismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// cancel
|
||||||
|
animate(mView).translationX(0).alpha(1)
|
||||||
|
.setDuration(mAnimationTime).setListener(null);
|
||||||
|
}
|
||||||
|
mVelocityTracker = null;
|
||||||
|
mTranslationX = 0;
|
||||||
|
mDownX = 0;
|
||||||
|
mSwiping = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_MOVE: {
|
||||||
|
if (mVelocityTracker == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mVelocityTracker.addMovement(motionEvent);
|
||||||
|
float deltaX = motionEvent.getRawX() - mDownX;
|
||||||
|
if (Math.abs(deltaX) > mSlop) {
|
||||||
|
mSwiping = true;
|
||||||
|
mView.getParent().requestDisallowInterceptTouchEvent(true);
|
||||||
|
|
||||||
|
// Cancel listview's touch
|
||||||
|
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
|
||||||
|
cancelEvent
|
||||||
|
.setAction(MotionEvent.ACTION_CANCEL
|
||||||
|
| (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
|
||||||
|
mView.onTouchEvent(cancelEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSwiping) {
|
||||||
|
mTranslationX = deltaX;
|
||||||
|
setTranslationX(mView, deltaX);
|
||||||
|
// TODO: use an ease-out interpolator or such
|
||||||
|
setAlpha(
|
||||||
|
mView,
|
||||||
|
Math.max(
|
||||||
|
0f,
|
||||||
|
Math.min(1f, 1f - 2f * Math.abs(deltaX)
|
||||||
|
/ mViewWidth)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performDismiss() {
|
||||||
|
// Animate the dismissed view to zero-height and then fire the dismiss
|
||||||
|
// callback.
|
||||||
|
// This triggers layout on each animation frame; in the future we may
|
||||||
|
// want to do something
|
||||||
|
// smarter and more performant.
|
||||||
|
|
||||||
|
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
|
||||||
|
final int originalHeight = mView.getHeight();
|
||||||
|
|
||||||
|
ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1)
|
||||||
|
.setDuration(mAnimationTime);
|
||||||
|
|
||||||
|
animator.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
mCallback.onDismiss(mView, mToken);
|
||||||
|
// Reset view presentation
|
||||||
|
setAlpha(mView, 1f);
|
||||||
|
setTranslationX(mView, 0);
|
||||||
|
lp.height = originalHeight;
|
||||||
|
mView.setLayoutParams(lp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||||
|
lp.height = (Integer) valueAnimator.getAnimatedValue();
|
||||||
|
mView.setLayoutParams(lp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
animator.start();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user