/*
* Copyright (C) 2010 mAPPn.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.
*/
package com.mappn.gfan.common.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Scroller;
/**
* @author andrew.wang
* @date 2010-10-8
* @since Version 0.4.0
*/
public class ScreenShotGallery extends LinearLayout {
private static final int INVALID_SCREEN = -1;
private static final int TOUCH_STATE_REST = 0;
private static final int TOUCH_STATE_SCROLLING = 1;
/**
* The velocity at which a fling gesture will cause us to snap to the next screen
*/
private static final int SNAP_VELOCITY = 1000;
private Scroller mScroller;
// Distance a touch can wander before we think the user is scrolling in pixels
private int mTouchSlop;
// Minimum velocity to initiate a fling, as measured in pixels per second.
// private int mMinimumVelocity;
// Maximum velocity to initiate a fling, as measured in pixels per second.
private int mMaximumVelocity;
private float mLastMotionX;
private float mLastMotionY;
private int mCurrentScreen;
private int mNextScreen = INVALID_SCREEN;
private VelocityTracker mVelocityTracker;
private int mTouchState = TOUCH_STATE_REST;
private boolean mAllowLongPress;
private GestureDetector mGestureDetector;
public ScreenShotGallery(Context context) {
this(context, null);
requestDisallowInterceptTouchEvent(true);
}
public ScreenShotGallery(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
Context localContext = getContext();
this.mScroller = new Scroller(localContext);
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(localContext);
mTouchSlop = configuration.getScaledTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mGestureDetector = new GestureDetector(localContext, new SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float absX = Math.abs(velocityX);
float absY = Math.abs(velocityY);
if (absX > absY && absX > 400) {
if (velocityX > 0) {
ScreenShotGallery.this.scrollLeft();
} else {
ScreenShotGallery.this.scrollRight();
}
}
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.UNSPECIFIED
|| heightSpecMode == MeasureSpec.UNSPECIFIED) {
return;
}
final int count = getChildCount();
if (count > 0) {
final int width = widthSpecSize - getPaddingLeft() - getPaddingRight();
final int height = heightSpecSize - getPaddingTop() - getPaddingBottom();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
int childheightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
child.measure(childWidthSpec, childheightSpec);
}
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
super.scrollTo(mScroller.getCurrX(), getScrollY());
postInvalidate();
} else if (mNextScreen != INVALID_SCREEN) {
mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
mNextScreen = INVALID_SCREEN;
}
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(true);
}
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onTouchEvent will be called and we do the actual
* scrolling there.
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* Shortcut the most recurring case: the user is in the dragging
* state and he is moving his finger. We want to intercept this
* motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
return true;
}
final float x = ev.getX();
final float y = ev.getY();
switch (action) {
case MotionEvent.ACTION_MOVE:
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionX is set to the y value
* of the down event.
*/
final int xDiff = (int) Math.abs(x - mLastMotionX);
final int yDiff = (int) Math.abs(y - mLastMotionY);
final int touchSlop = mTouchSlop;
boolean xMoved = xDiff > touchSlop;
boolean yMoved = yDiff > touchSlop;
if (xMoved || yMoved) {
if (xMoved && (xDiff > yDiff)) {
// Scroll if the user moved far enough along the X axis
mTouchState = TOUCH_STATE_SCROLLING;
}
// else if(yMoved) {
// // Do not scroll if the user moved far enough along the Y axis
// mTouchState = TOUCH_STATE_REST;
// }
// else {
// mTouchState = TOUCH_STATE_REST;
// }
// Either way, cancel any pending longpress
if (mAllowLongPress) {
mAllowLongPress = false;
// Try canceling the long press. It could also have been scheduled
// by a distant descendant, so use the mAllowLongPress flag to block
// everything
final View currentScreen = getChildAt(mCurrentScreen);
currentScreen.cancelLongPress();
}
}
break;
case MotionEvent.ACTION_DOWN:
// mHandler.sendEmptyMessage(HomeActivity.PAUSE_AUTO_FLOW);
// Remember location of down touch
mLastMotionX = x;
mLastMotionY = y;
mAllowLongPress = true;
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// Release the drag
mTouchState = TOUCH_STATE_REST;
mAllowLongPress = false;
break;
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mTouchState != TOUCH_STATE_REST;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Give everything to the gesture detector
boolean retValue = mGestureDetector.onTouchEvent(event);
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
final int action = event.getAction();
final float x = event.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionX = x;
break;
case MotionEvent.ACTION_MOVE:
if (mTouchState == TOUCH_STATE_SCROLLING) {
// Scroll to follow the motion event
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
if (deltaX < 0) {
if (getScrollX() > 0) {
scrollBy(Math.max(-getScrollX(), deltaX), 0);
}
} else if (deltaX > 0) {
final int availableToScroll = getWidth() * getChildCount() -
getScrollX() - getWidth();
if (availableToScroll > 0) {
scrollBy(Math.min(availableToScroll, deltaX), 0);
}
}
}
mTouchState = TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityX = (int) velocityTracker.getXVelocity();
if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
// Fling hard enough to move left
snapToScreen(mCurrentScreen - 1);
} else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
// Fling hard enough to move right
snapToScreen(mCurrentScreen + 1);
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
break;
}
return retValue;
}
private void snapToDestination() {
final int screenWidth = getWidth();
final int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth;
snapToScreen(whichScreen);
}
public void snapToScreen(int whichScreen) {
if (!mScroller.isFinished()) return;
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
boolean changingScreens = whichScreen != mCurrentScreen;
mNextScreen = whichScreen;
if(mScrollListener != null) {
mScrollListener.onChanged(mNextScreen);
}
View focusedChild = getFocusedChild();
if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
focusedChild.clearFocus();
}
final int newX = whichScreen * getWidth();
final int delta = newX - getScrollX();
mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
invalidate();
}
public void scrollLeft() {
if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
snapToScreen(mCurrentScreen - 1);
}
}
public void scrollRight() {
if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
mScroller.isFinished()) {
snapToScreen(mCurrentScreen + 1);
}
}
public void snapToNextScreen() {
int next = mCurrentScreen + 1;
if (next != INVALID_SCREEN && next < getChildCount() && mScroller.isFinished()) {
snapToScreen(next);
} else {
snapToScreen(0);
}
}
public void addChild(LinearLayout child) {
addView(child);
}
private PageScrollListener mScrollListener;
public void setPageScrollListener(PageScrollListener listener) {
mScrollListener = listener;
}
public interface PageScrollListener {
void onChanged(int pageIndex);
}
public void clear() {
final int length = getChildCount();
for (int i = 0; i < length; i++) {
LinearLayout parent = (LinearLayout) getChildAt(i);
final int imageViewLength = parent.getChildCount();
for (int j = 0; j < imageViewLength; j++) {
ImageView v = (ImageView) parent.getChildAt(j);
Drawable d = v.getDrawable();
if (d != null) {
Bitmap bmp = ((BitmapDrawable) d).getBitmap();
if (bmp != null) {
bmp.recycle();
}
d.setCallback(null);
}
v.setImageDrawable(null);
}
}
removeAllViews();
}
}