/*
* Copyright 2013 Google Inc.
*
* 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.
*/
// The animation code comes from SwipeDismissListViewTouchListener.java from DashClock
// https://code.google.com/p/dashclock/
package com.wigwamlabs.veckify;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import com.android.systemui.SwipeHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SwipeDismissListView extends ListView implements SwipeHelper.Callback, AbsListView.OnScrollListener {
private final SwipeHelper mSwipeHelper;
private final int mAnimationTime;
private final List<PendingDismissData> mPendingDismisses = new ArrayList<PendingDismissData>();
private final Rect mScratchRect = new Rect();
private final int[] mScratchCoords = new int[2];
private Callback mCallback;
private int mDismissAnimationRefCount = 0;
private int mDragPosition = -1;
private int mScrollState = SCROLL_STATE_IDLE;
public SwipeDismissListView(Context context, AttributeSet attrs) {
super(context, attrs);
final float densityScale = getResources().getDisplayMetrics().density;
final float touchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, touchSlop);
mAnimationTime = context.getResources().getInteger(android.R.integer.config_shortAnimTime);
setOnScrollListener(this);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
final float densityScale = getResources().getDisplayMetrics().density;
mSwipeHelper.setDensityScale(densityScale);
final float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
}
void setCallback(Callback callback) {
mCallback = callback;
}
@Override
public boolean onInterceptTouchEvent(@NonNull MotionEvent ev) {
// prioritize scrolling over swipe
boolean intercepted = super.onInterceptTouchEvent(ev);
if (!intercepted && mScrollState == SCROLL_STATE_IDLE) {
intercepted = mSwipeHelper.onInterceptTouchEvent(ev);
}
return intercepted;
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent ev) {
// if we're not scrolling we're most likely swiping, so prioritize swipe helper
boolean handled = false;
if (mScrollState == SCROLL_STATE_IDLE) {
handled = mSwipeHelper.onTouchEvent(ev);
}
if (!handled) {
handled = super.onTouchEvent(ev);
}
return handled;
}
@Override
public View getChildAtPosition(MotionEvent ev) {
final int childCount = getChildCount();
getLocationOnScreen(mScratchCoords);
final int x = (int) ev.getRawX() - mScratchCoords[0];
final int y = (int) ev.getRawY() - mScratchCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = getChildAt(i);
child.getHitRect(mScratchRect);
if (mScratchRect.contains(x, y)) {
return child;
}
}
return null;
}
@Override
public View getChildContentView(View v) {
return v;
}
@Override
public boolean canChildBeDismissed(View v) {
if (mDismissAnimationRefCount > 0) { // prevent until all current anims have finished
return false;
}
if (v.getParent() == null) {
// if there is no parent something is wrong, don't allow dismiss
return false;
}
final int pos = getPositionForView(v);
mDragPosition = pos;
return mCallback.canDismiss(pos);
}
@Override
public void onBeginDrag(View v) {
// We need to prevent the surrounding ListView from intercepting us now;
// the scroll position will be locked while we swipe
requestDisallowInterceptTouchEvent(true);
}
@Override
public void onChildDismissStart(View view) {
mDismissAnimationRefCount++;
}
@Override
public void onChildDismissed(final View dismissView) {
final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
final int originalHeight = dismissView.getHeight();
final ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingDismisses);
final int[] dismissPositions = new int[mPendingDismisses.size()];
for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
dismissPositions[i] = mPendingDismisses.get(i).position;
}
mCallback.onDismiss(SwipeDismissListView.this, dismissPositions);
ViewGroup.LayoutParams lp;
for (PendingDismissData pendingDismiss : mPendingDismisses) {
// Reset view presentation
pendingDismiss.view.setAlpha(1f);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = pendingDismiss.originalHeight;
pendingDismiss.view.setLayoutParams(lp);
}
mPendingDismisses.clear();
}
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = ((Integer) valueAnimator.getAnimatedValue()).intValue();
dismissView.setLayoutParams(lp);
}
});
mPendingDismisses.add(new PendingDismissData(mDragPosition, dismissView, originalHeight));
animator.start();
mDragPosition = -1;
}
@Override
public void onDragCancelled(View v) {
mDragPosition = -1;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mScrollState = scrollState;
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
interface Callback {
boolean canDismiss(int position);
void onDismiss(SwipeDismissListView swipeDismissListView, int[] reverseSortedPositions);
}
private static class PendingDismissData implements Comparable<PendingDismissData> {
final int position;
final View view;
final int originalHeight;
public PendingDismissData(int position, View view, int originalHeight) {
this.position = position;
this.view = view;
this.originalHeight = originalHeight;
}
@Override
public int compareTo(@NonNull PendingDismissData other) {
// Sort by descending position
return other.position - position;
}
}
}