Implemented swipe to dismiss for Your Train card

This commit is contained in:
Doug Keen 2012-10-03 13:07:19 -07:00
parent a27222dbc6
commit 1b7d60561b
2 changed files with 295 additions and 11 deletions

View File

@ -39,6 +39,7 @@ import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.SwipeDismissTouchListener;
import com.dougkeen.bart.controls.Ticker;
import com.dougkeen.bart.controls.YourTrainLayout;
import com.dougkeen.bart.data.DepartureArrayAdapter;
@ -186,8 +187,21 @@ public class ViewDeparturesActivity extends SActivity implements
mMissingDepartureText = findViewById(R.id.missingDepartureText);
mMissingDepartureText.setVisibility(View.VISIBLE);
findViewById(R.id.yourTrainSection).setOnClickListener(
mYourTrainSectionClickListener);
mYourTrainSection = (YourTrainLayout) findViewById(R.id.yourTrainSection);
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();
@ -347,6 +361,8 @@ public class ViewDeparturesActivity extends SActivity implements
private View mMissingDepartureText;
private YourTrainLayout mYourTrainSection;
protected DepartureArrayAdapter getListAdapter() {
return mDeparturesAdapter;
}
@ -448,8 +464,7 @@ public class ViewDeparturesActivity extends SActivity implements
private void refreshBoardedDeparture() {
final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture();
final YourTrainLayout yourTrainSection = (YourTrainLayout) findViewById(R.id.yourTrainSection);
int currentVisibility = yourTrainSection.getVisibility();
int currentVisibility = mYourTrainSection.getVisibility();
final boolean boardedDepartureDoesNotApply = boardedDeparture == null
|| boardedDeparture.getStationPair() == null
@ -462,10 +477,10 @@ public class ViewDeparturesActivity extends SActivity implements
return;
}
yourTrainSection.updateFromDeparture(boardedDeparture);
mYourTrainSection.updateFromDeparture(boardedDeparture);
if (currentVisibility != View.VISIBLE) {
showYourTrainSection(yourTrainSection);
showYourTrainSection(mYourTrainSection);
}
if (mActionMode == null) {
@ -656,11 +671,7 @@ public class ViewDeparturesActivity extends SActivity implements
startService(intent);
return true;
} else if (itemId == R.id.delete) {
Intent intent = new Intent(ViewDeparturesActivity.this,
BoardedDepartureService.class);
intent.putExtra("clearBoardedDeparture", true);
startService(intent);
hideYourTrainSection();
dismissYourTrainSelection();
mode.finish();
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
public void onETDChanged(final List<Departure> departures) {
runOnUiThread(new Runnable() {

View 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();
}
}