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 423102e82e0187363d453f4bcde00e396ef5420a46Adam Copp * {@link android.view.ViewGroup} to contain the pager and intercept touch events at a higher 433102e82e0187363d453f4bcde00e396ef5420a46Adam Copp * level. 44f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 45f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public static interface OnInterceptTouchListener { 46f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** 47f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * Called when a touch intercept is about to occur. 48f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * 49f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * @param origX the raw x coordinate of the initial touch 50f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * @param origY the raw y coordinate of the initial touch 51f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * @return Which type of touch, if any, should should be intercepted. 52f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 53f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public InterceptType onTouchIntercept(float origX, float origY); 54f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 55f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 56f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private static final int INVALID_POINTER = -1; 57f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 58f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private float mLastMotionX; 59f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private int mActivePointerId; 60f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** The x coordinate where the touch originated */ 61f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private float mActivatedX; 62f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** The y coordinate where the touch originated */ 63f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private float mActivatedY; 64f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein private OnInterceptTouchListener mListener; 65f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 66f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public PhotoViewPager(Context context) { 67f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein super(context); 68d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein initialize(); 69f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 70f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 71f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public PhotoViewPager(Context context, AttributeSet attrs) { 72f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein super(context, attrs); 73d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein initialize(); 74d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein } 75d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein 76d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein private void initialize() { 77d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein // Set the page transformer to perform the transition animation 78d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein // for each page in the view. 79d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein setPageTransformer(true, new PageTransformer() { 80d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein @Override 81d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein public void transformPage(View page, float position) { 821f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project 831f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // The >= 1 is needed so that the page 841f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // (page A) that transitions behind the newly visible 851f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // page (page B) that comes in from the left does not 861f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // get the touch events because it is still on screen 871f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // (page A is still technically on screen despite being 881f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // invisible). This makes sure that when the transition 891f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // has completely finished, we revert it to its default 901f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project // behavior and move it off of the screen. 911f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project if (position < 0 || position >= 1.f) { 92d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setTranslationX(0); 93d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setAlpha(1.f); 94d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setScaleX(1); 95d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setScaleY(1); 96d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein } else { 97d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setTranslationX(-position * page.getWidth()); 98d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setAlpha(Math.max(0,1.f - position)); 99d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein final float scale = Math.max(0, 1.f - position * 0.3f); 100d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setScaleX(scale); 101d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein page.setScaleY(scale); 102d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein } 103d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein } 104d04cec7495069a1a72a7721fab33eca94cd49043Andrew Sapperstein }); 105f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 106f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 107f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** 108f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * {@inheritDoc} 109f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * <p> 110f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * We intercept touch event intercepts so we can prevent switching views when the 111f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * current view is internally scrollable. 112f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 113f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein @Override 114f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public boolean onInterceptTouchEvent(MotionEvent ev) { 115f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final InterceptType intercept = (mListener != null) 116f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein ? mListener.onTouchIntercept(mActivatedX, mActivatedY) 117f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein : InterceptType.NONE; 118f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final boolean ignoreScrollLeft = 119f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein (intercept == InterceptType.BOTH || intercept == InterceptType.LEFT); 120f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final boolean ignoreScrollRight = 121f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein (intercept == InterceptType.BOTH || intercept == InterceptType.RIGHT); 122f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 123f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // Only check ability to page if we can't scroll in one / both directions 124f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 125f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 126f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 127f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivePointerId = INVALID_POINTER; 128f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 129f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 130f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein switch (action) { 131f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein case MotionEvent.ACTION_MOVE: { 132f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (ignoreScrollLeft || ignoreScrollRight) { 133f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int activePointerId = mActivePointerId; 134f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (activePointerId == INVALID_POINTER) { 135f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // If we don't have a valid id, the touch down wasn't on content. 136f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein break; 137f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 138f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 139f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int pointerIndex = 140f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein MotionEventCompat.findPointerIndex(ev, activePointerId); 141f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final float x = MotionEventCompat.getX(ev, pointerIndex); 142f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 143f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (ignoreScrollLeft && ignoreScrollRight) { 144f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = x; 145f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein return false; 146f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } else if (ignoreScrollLeft && (x > mLastMotionX)) { 147f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = x; 148f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein return false; 149f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } else if (ignoreScrollRight && (x < mLastMotionX)) { 150f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = x; 151f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein return false; 152f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 153f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 154f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein break; 155f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 156f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 157f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein case MotionEvent.ACTION_DOWN: { 158f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = ev.getX(); 159f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // Use the raw x/y as the children can be located anywhere and there isn't a 160f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // single offset that would be meaningful 161f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivatedX = ev.getRawX(); 162f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivatedY = ev.getRawY(); 163f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 164f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein break; 165f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 166f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 167f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein case MotionEventCompat.ACTION_POINTER_UP: { 168f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int pointerIndex = MotionEventCompat.getActionIndex(ev); 169f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 170f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein if (pointerId == mActivePointerId) { 171f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein // Our active pointer going up; select a new active pointer 172f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 173f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 174f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 175f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 176f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein break; 177f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 178f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 179f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 180f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein return super.onInterceptTouchEvent(ev); 181f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 182f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein 183f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein /** 184f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein * sets the intercept touch listener. 185f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein */ 186f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein public void setOnInterceptTouchListener(OnInterceptTouchListener l) { 187f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein mListener = l; 188f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein } 189f77a7eb196d16110c7b1087352b423913821ff61Andrew Sapperstein} 190