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