1f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein/* 2f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Copyright (C) 2011 Google Inc. 3f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Licensed to The Android Open Source Project. 4f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * 5f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License"); 6f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * you may not use this file except in compliance with the License. 7f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * You may obtain a copy of the License at 8f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * 9f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * http://www.apache.org/licenses/LICENSE-2.0 10f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * 11f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Unless required by applicable law or agreed to in writing, software 12f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS, 13f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * See the License for the specific language governing permissions and 15f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * limitations under the License. 16f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 17f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 18f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinpackage com.android.ex.photo; 19f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 20f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.content.Context; 21f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.support.v4.view.MotionEventCompat; 22f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.support.v4.view.ViewPager; 23f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.util.AttributeSet; 24f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinimport android.view.MotionEvent; 25d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sappersteinimport android.view.View; 26f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 27f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein/** 28f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * View pager for photo view fragments. Define our own class so we can specify the 29f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * view pager in XML. 30f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 31f77a7eb196d16110c7b1087352b423913821ff61Andrew Sappersteinpublic class PhotoViewPager extends ViewPager { 32f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** 33f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * A type of intercept that should be performed 34f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 35f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public static enum InterceptType { NONE, LEFT, RIGHT, BOTH } 36f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 37f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** 38f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Provides an ability to intercept touch events. 39f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * <p> 40f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * {@link ViewPager} intercepts all touch events and we need to be able to override this 41d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein * behavior. Instead, we could perform a similar function by declaring a custom 42f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * {@link ViewGroup} to contain the pager and intercept touch events at a higher level. 43f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 44f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public static interface OnInterceptTouchListener { 45f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** 46f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Called when a touch intercept is about to occur. 47f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * 48f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * @param origX the raw x coordinate of the initial touch 49f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * @param origY the raw y coordinate of the initial touch 50f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * @return Which type of touch, if any, should should be intercepted. 51f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 52f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public InterceptType onTouchIntercept(float origX, float origY); 53f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 54f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 55f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private static final int INVALID_POINTER = -1; 56f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 57f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private float mLastMotionX; 58f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private int mActivePointerId; 59f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** The x coordinate where the touch originated */ 60f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private float mActivatedX; 61f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** The y coordinate where the touch originated */ 62f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private float mActivatedY; 63f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private OnInterceptTouchListener mListener; 64f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 65f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public PhotoViewPager(Context context) { 66f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein super(context); 67d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein initialize(); 68f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 69f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 70f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public PhotoViewPager(Context context, AttributeSet attrs) { 71f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein super(context, attrs); 72d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein initialize(); 73d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein } 74d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein 75d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein private void initialize() { 76d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein // Set the page transformer to perform the transition animation 77d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein // for each page in the view. 78d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein setPageTransformer(true, new PageTransformer() { 79d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein @Override 80d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein public void transformPage(View page, float position) { 811f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project 821f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // The >= 1 is needed so that the page 831f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // (page A) that transitions behind the newly visible 841f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // page (page B) that comes in from the left does not 851f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // get the touch events because it is still on screen 861f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // (page A is still technically on screen despite being 871f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // invisible). This makes sure that when the transition 881f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // has completely finished, we revert it to its default 891f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // behavior and move it off of the screen. 901f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project if (position < 0 || position >= 1.f) { 91d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setTranslationX(0); 92d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setAlpha(1.f); 93d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setScaleX(1); 94d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setScaleY(1); 95d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein } else { 96d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setTranslationX(-position * page.getWidth()); 97d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setAlpha(Math.max(0,1.f - position)); 98d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein final float scale = Math.max(0, 1.f - position * 0.3f); 99d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setScaleX(scale); 100d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setScaleY(scale); 101d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein } 102d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein } 103d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein }); 104f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 105f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 106f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** 107f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * {@inheritDoc} 108f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * <p> 109f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * We intercept touch event intercepts so we can prevent switching views when the 110f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * current view is internally scrollable. 111f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 112f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein @Override 113f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public boolean onInterceptTouchEvent(MotionEvent ev) { 114f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final InterceptType intercept = (mListener != null) 115f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein ? mListener.onTouchIntercept(mActivatedX, mActivatedY) 116f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein : InterceptType.NONE; 117f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final boolean ignoreScrollLeft = 118f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein (intercept == InterceptType.BOTH || intercept == InterceptType.LEFT); 119f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final boolean ignoreScrollRight = 120f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein (intercept == InterceptType.BOTH || intercept == InterceptType.RIGHT); 121f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 122f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // Only check ability to page if we can't scroll in one / both directions 123f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 124f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 125f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 126f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivePointerId = INVALID_POINTER; 127f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 128f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 129f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein switch (action) { 130f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein case MotionEvent.ACTION_MOVE: { 131f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (ignoreScrollLeft || ignoreScrollRight) { 132f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int activePointerId = mActivePointerId; 133f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (activePointerId == INVALID_POINTER) { 134f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // If we don't have a valid id, the touch down wasn't on content. 135f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein break; 136f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 137f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 138f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int pointerIndex = 139f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein MotionEventCompat.findPointerIndex(ev, activePointerId); 140f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final float x = MotionEventCompat.getX(ev, pointerIndex); 141f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 142f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (ignoreScrollLeft && ignoreScrollRight) { 143f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = x; 144f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein return false; 145f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } else if (ignoreScrollLeft && (x > mLastMotionX)) { 146f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = x; 147f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein return false; 148f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } else if (ignoreScrollRight && (x < mLastMotionX)) { 149f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = x; 150f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein return false; 151f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 152f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 153f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein break; 154f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 155f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 156f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein case MotionEvent.ACTION_DOWN: { 157f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = ev.getX(); 158f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // Use the raw x/y as the children can be located anywhere and there isn't a 159f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // single offset that would be meaningful 160f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivatedX = ev.getRawX(); 161f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivatedY = ev.getRawY(); 162f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 163f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein break; 164f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 165f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 166f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein case MotionEventCompat.ACTION_POINTER_UP: { 167f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int pointerIndex = MotionEventCompat.getActionIndex(ev); 168f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 169f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (pointerId == mActivePointerId) { 170f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // Our active pointer going up; select a new active pointer 171f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 172f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 173f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 174f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 175f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein break; 176f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 177f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 178f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 179f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein return super.onInterceptTouchEvent(ev); 180f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 181f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 182f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** 183f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * sets the intercept touch listener. 184f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 185f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public void setOnInterceptTouchListener(OnInterceptTouchListener l) { 186f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mListener = l; 187f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 188f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein} 189