图片浏览(点击放大缩小支持多张图片浏览)

大神写的,我就参考参考啦!

从主布局开始了

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ff171b19"
    android:orientation="vertical" >

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1" >

        <com.app.widget.MyViewPager
            android:id="@+id/viewPager"
            android:background="#ff171b19"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></com.app.widget.MyViewPager>
    </RelativeLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="80dip"
        android:paddingLeft="15dip"
        android:paddingRight="15dip" >

        <ImageView
            android:id="@+id/imagebrowser_iv_download"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:src="@drawable/ic_topbar_save"
            android:visibility="gone" />

        <TextView
            android:id="@+id/imagebrowser_ptv_page"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:gravity="right|center_vertical"
            android:textColor="#ffcdcdcd"
            android:textSize="34sp" />
    </LinearLayout>

</LinearLayo

根据界面,下面是代码

@ContentView(R.layout.activity_imagebrowser)
public class ImageBrowserActivity extends BaseAppActivity implements OnPageChangeListener, OnClickListener {
    @ViewInject(R.id.viewPager)
    private MyViewPager viewPager;//一、自定义的viewpager
    @ViewInject(R.id.imagebrowser_ptv_page)
    private TextView mPtvPage;
    @ViewInject(R.id.imagebrowser_iv_download)
    private ImageView mIvDownload;

    private ImageBrowserAdapter mAdapter;
    private String mType;
    private int mPosition;
    private int mTotal;
    private List<String> list;

    public static final String PATH_TYPE = "pathType";//1全路径
    public static final int TYPE_FULL = 1;//1全路径
    public static final int TYPE_PART = 0;//0部分路径
    public static final String IMAGE_TYPE = "image_type";
    public static final String TYPE_ALBUM = "image_album";
    public static final String TYPE_PHOTO = "image_photo";
    private int pathType;

    @Override
    protected void init() {
        viewPager.setOnPageChangeListener(this);
        mIvDownload.setOnClickListener(this);
        Bundle bundle =getIntent().getExtras();//二、是从其他类中传过来的
        if (bundle != null) {
            mType = bundle.getString(IMAGE_TYPE);
            pathType=bundle.getInt(PATH_TYPE,0);//默认是部分路径
        }

        if (TYPE_ALBUM.equals(mType)) {
            mPosition = bundle.getInt("position", 0);
            list = (ArrayList)bundle.getSerializable("images");
            mTotal = list.size();
            if (mPosition > mTotal) {
                mPosition = mTotal - 1;
            }
            if (mTotal > 1) {
                mPosition += 1000 * mTotal;
                mPtvPage.setText((mPosition % mTotal) + 1 + "/" + mTotal);
                final ImageView []mImageView=new ImageView[list.size()];
                PhotoAdapter adapter=new PhotoAdapter(list,this);
                viewPager.setAdapter(adapter);
            }
        } else if (TYPE_PHOTO.equals(mType)) {
            final String path = bundle.getString("path");
            list = new ArrayList<>();
            list.add(path);
            mPtvPage.setText("1/1");
            PhotoAdapter adapter=new PhotoAdapter(list,this);
            viewPager.setAdapter(adapter);

        }
    }

    @Override
    public void onPageScrollStateChanged(int arg0) {

    }

    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {

    }

    @Override
    public void onPageSelected(int arg0) {
        mPosition = arg0;
        mPtvPage.setText((mPosition % mTotal) + 1 + "/" + mTotal);
    }

    @Override
    public void onClick(View arg0) {
        showCustomToast("图片已保存到本地");
    }
//adapter
    class PhotoAdapter extends PagerAdapter{
        private List<String> datas;
        private List<View> views;
        private Context context;
        private PhotoAdapter(List<String>datas,Context context){
            this.datas=datas;
            this.context=context;
            views=new ArrayList<>();
            if (datas!=null){
                for (Object o:datas){
                    View view=View.inflate(context,R.layout.vp,null);//三、图片自定义view
                    views.add(view);
                }
            }
        }
        @Override
        public int getCount() {
            return views==null?0:views.size();
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            View view=views.get(position);
            PhotoView photoView= (PhotoView)view.findViewById(R.id.photoView);//
            ImageLoader.getInstance().displayImage(UrlConfig.BASE_IMAGE_URL+datas.get(position),photoView);
            container.addView(view);
            return view;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView(views.get(position));
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view==object;
        }
    }

}

从一到三我们开始吧!

一、MyViewPager

public class MyViewPager extends ViewPager {
    public MyViewPager(Context context) {
        super(context);
    }

    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        try {
            return super.onTouchEvent(ev);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        try {
            return super.onInterceptTouchEvent(ev);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }
        return false;
    }

}
二、是从其他类中传过来的
我们看怎么传的
//list为取得数据list
 final JSONObject object = (JSONObject) list.get(i);
        pathlist = new ArrayList<>();
        for (int j = 0; j < list.size(); j++) {
            JSONObject object1 = (JSONObject) list.get(j);
            pathlist.add(object1.getString("path"));
        }
        ImageUtil.ShowIamge(holder.pic, UrlConfig.BASE_IMAGE_URL + object.getString("path"));
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bundle bundle = new Bundle();
                if (list.size() == 1) {
                    String str = object.getString("path").toString();
                    bundle.putString(ImageBrowserActivity.IMAGE_TYPE, ImageBrowserActivity.TYPE_PHOTO);
                    bundle.putString("path", str);
                } else {
                    bundle.putString(ImageBrowserActivity.IMAGE_TYPE, ImageBrowserActivity.TYPE_ALBUM);
                    bundle.putInt("position", i);
                    bundle.putSerializable("images", (Serializable) pathlist);
                }
                startActivity(ImageBrowserActivity.class, "图片浏览", bundle);
            }
        });
//三、图片自定义view R.layout.vp

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical">
 6 
 7     <com.app.widget.photoview.PhotoView//自定义View
 8         android:id="@+id/photoView"
 9         android:layout_width="match_parent"
10         android:layout_height="match_parent" />
11 
12 </LinearLayout>

现在开始写PhotoView啦!

  1 public class PhotoView extends ImageView implements IPhotoView {
  2 
  3     private final PhotoViewAttacher mAttacher;
  4 
  5     private ScaleType mPendingScaleType;
  6 
  7     public PhotoView(Context context) {
  8         this(context, null);
  9     }
 10 
 11     public PhotoView(Context context, AttributeSet attr) {
 12         this(context, attr, 0);
 13     }
 14     
 15     public PhotoView(Context context, AttributeSet attr, int defStyle) {
 16         super(context, attr, defStyle);
 17         super.setScaleType(ScaleType.MATRIX);
 18         mAttacher = new PhotoViewAttacher(this);
 19 
 20         if (null != mPendingScaleType) {
 21             setScaleType(mPendingScaleType);
 22             mPendingScaleType = null;
 23         }
 24     }
 25 
 26     @Override
 27     public boolean canZoom() {
 28         return mAttacher.canZoom();
 29     }
 30 
 31     @Override
 32     public RectF getDisplayRect() {
 33         return mAttacher.getDisplayRect();
 34     }
 35 
 36     @Override
 37     public float getMinScale() {
 38         return mAttacher.getMinScale();
 39     }
 40 
 41     @Override
 42     public float getMidScale() {
 43         return mAttacher.getMidScale();
 44     }
 45 
 46     @Override
 47     public float getMaxScale() {
 48         return mAttacher.getMaxScale();
 49     }
 50 
 51     @Override
 52     public float getScale() {
 53         return mAttacher.getScale();
 54     }
 55 
 56     @Override
 57     public ScaleType getScaleType() {
 58         return mAttacher.getScaleType();
 59     }
 60 
 61     @Override
 62     public void setAllowParentInterceptOnEdge(boolean allow) {
 63         mAttacher.setAllowParentInterceptOnEdge(allow);
 64     }
 65 
 66     @Override
 67     public void setMinScale(float minScale) {
 68         mAttacher.setMinScale(minScale);
 69     }
 70 
 71     @Override
 72     public void setMidScale(float midScale) {
 73         mAttacher.setMidScale(midScale);
 74     }
 75 
 76     @Override
 77     public void setMaxScale(float maxScale) {
 78         mAttacher.setMaxScale(maxScale);
 79     }
 80 
 81     @Override
 82     // setImageBitmap calls through to this method
 83     public void setImageDrawable(Drawable drawable) {
 84         super.setImageDrawable(drawable);
 85         if (null != mAttacher) {
 86             mAttacher.update();
 87         }
 88     }
 89 
 90     @Override
 91     public void setImageResource(int resId) {
 92         super.setImageResource(resId);
 93         if (null != mAttacher) {
 94             mAttacher.update();
 95         }
 96     }
 97 
 98     @Override
 99     public void setImageURI(Uri uri) {
100         super.setImageURI(uri);
101         if (null != mAttacher) {
102             mAttacher.update();
103         }
104     }
105 
106     @Override
107     public void setOnMatrixChangeListener(PhotoViewAttacher.OnMatrixChangedListener listener) {
108         mAttacher.setOnMatrixChangeListener(listener);
109     }
110 
111     @Override
112     public void setOnLongClickListener(OnLongClickListener l) {
113         mAttacher.setOnLongClickListener(l);
114     }
115 
116     @Override
117     public void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener) {
118         mAttacher.setOnPhotoTapListener(listener);
119     }
120 
121     @Override
122     public void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener) {
123         mAttacher.setOnViewTapListener(listener);
124     }
125 
126     @Override
127     public void setScaleType(ScaleType scaleType) {
128         if (null != mAttacher) {
129             mAttacher.setScaleType(scaleType);
130         } else {
131             mPendingScaleType = scaleType;
132         }
133     }
134 
135     @Override
136     public void setZoomable(boolean zoomable) {
137         mAttacher.setZoomable(zoomable);
138     }
139 
140     @Override
141     public void zoomTo(float scale, float focalX, float focalY) {
142         mAttacher.zoomTo(scale, focalX, focalY);
143     }
144 
145     @Override
146     protected void onDetachedFromWindow() {
147         mAttacher.cleanup();
148         super.onDetachedFromWindow();
149     }
150 
151 }
IPhotoView 
  1 public interface IPhotoView {
  2     /**
  3      * Returns true if the PhotoView is set to allow zooming of Photos.
  4      *
  5      * @return true if the PhotoView allows zooming.
  6      */
  7     boolean canZoom();
  8 
  9     /**
 10      * Gets the Display Rectangle of the currently displayed Drawable. The
 11      * Rectangle is relative to this View and includes all scaling and
 12      * translations.
 13      *
 14      * @return - RectF of Displayed Drawable
 15      */
 16     RectF getDisplayRect();
 17 
 18     /**
 19      * @return The current minimum scale level. What this value represents depends on the current {@link ImageView.ScaleType}.
 20      */
 21     float getMinScale();
 22 
 23     /**
 24      * @return The current middle scale level. What this value represents depends on the current {@link ImageView.ScaleType}.
 25      */
 26     float getMidScale();
 27 
 28     /**
 29      * @return The current maximum scale level. What this value represents depends on the current {@link ImageView.ScaleType}.
 30      */
 31     float getMaxScale();
 32 
 33     /**
 34      * Returns the current scale value
 35      *
 36      * @return float - current scale value
 37      */
 38     float getScale();
 39 
 40     /**
 41      * Return the current scale type in use by the ImageView.
 42      */
 43     ImageView.ScaleType getScaleType();
 44 
 45     /**
 46      * Whether to allow the ImageView's parent to intercept the touch event when the photo is scroll to it's horizontal edge.
 47      */
 48     void setAllowParentInterceptOnEdge(boolean allow);
 49 
 50     /**
 51      * Sets the minimum scale level. What this value represents depends on the current {@link ImageView.ScaleType}.
 52      */
 53     void setMinScale(float minScale);
 54 
 55     /**
 56      * Sets the middle scale level. What this value represents depends on the current {@link ImageView.ScaleType}.
 57      */
 58     void setMidScale(float midScale);
 59 
 60     /**
 61      * Sets the maximum scale level. What this value represents depends on the current {@link ImageView.ScaleType}.
 62      */
 63     void setMaxScale(float maxScale);
 64 
 65     /**
 66      * Register a callback to be invoked when the Photo displayed by this view is long-pressed.
 67      *
 68      * @param listener - Listener to be registered.
 69      */
 70     void setOnLongClickListener(View.OnLongClickListener listener);
 71 
 72     /**
 73      * Register a callback to be invoked when the Matrix has changed for this
 74      * View. An example would be the user panning or scaling the Photo.
 75      *
 76      * @param listener - Listener to be registered.
 77      */
 78     void setOnMatrixChangeListener(PhotoViewAttacher.OnMatrixChangedListener listener);
 79 
 80     /**
 81      * Register a callback to be invoked when the Photo displayed by this View
 82      * is tapped with a single tap.
 83      *
 84      * @param listener - Listener to be registered.
 85      */
 86     void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener);
 87 
 88     /**
 89      * Register a callback to be invoked when the View is tapped with a single
 90      * tap.
 91      *
 92      * @param listener - Listener to be registered.
 93      */
 94     void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener);
 95 
 96     /**
 97      * Controls how the image should be resized or moved to match the size of
 98      * the ImageView. Any scaling or panning will happen within the confines of
 99      * this {@link ImageView.ScaleType}.
100      *
101      * @param scaleType - The desired scaling mode.
102      */
103     void setScaleType(ImageView.ScaleType scaleType);
104 
105     /**
106      * Allows you to enable/disable the zoom functionality on the ImageView.
107      * When disable the ImageView reverts to using the FIT_CENTER matrix.
108      *
109      * @param zoomable - Whether the zoom functionality is enabled.
110      */
111     void setZoomable(boolean zoomable);
112 
113     /**
114      * Zooms to the specified scale, around the focal point given.
115      *
116      * @param scale  - Scale to zoom to
117      * @param focalX - X Focus Point
118      * @param focalY - Y Focus Point
119      */
120     void zoomTo(float scale, float focalX, float focalY);
121 }

PhotoViewAttacher 
  1 public class PhotoViewAttacher implements IPhotoView, View.OnTouchListener, VersionedGestureDetector.OnGestureListener,
  2         GestureDetector.OnDoubleTapListener, ViewTreeObserver.OnGlobalLayoutListener {
  3 
  4     static final String LOG_TAG = "PhotoViewAttacher";
  5 
  6     // let debug flag be dynamic, but still Proguard can be used to remove from release builds
  7     static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
  8 
  9     static final int EDGE_NONE = -1;
 10     static final int EDGE_LEFT = 0;
 11     static final int EDGE_RIGHT = 1;
 12     static final int EDGE_BOTH = 2;
 13 
 14     public static final float DEFAULT_MAX_SCALE = 3.0f;
 15     public static final float DEFAULT_MID_SCALE = 1.75f;
 16     public static final float DEFAULT_MIN_SCALE = 1.0f;
 17 
 18     private float mMinScale = DEFAULT_MIN_SCALE;
 19     private float mMidScale = DEFAULT_MID_SCALE;
 20     private float mMaxScale = DEFAULT_MAX_SCALE;
 21 
 22     private boolean mAllowParentInterceptOnEdge = true;
 23 
 24     private static void checkZoomLevels(float minZoom, float midZoom, float maxZoom) {
 25         if (minZoom >= midZoom) {
 26             throw new IllegalArgumentException("MinZoom should be less than MidZoom");
 27         } else if (midZoom >= maxZoom) {
 28             throw new IllegalArgumentException("MidZoom should be less than MaxZoom");
 29         }
 30     }
 31 
 32     /**
 33      * @return true if the ImageView exists, and it's Drawable existss
 34      */
 35     private static boolean hasDrawable(ImageView imageView) {
 36         return null != imageView && null != imageView.getDrawable();
 37     }
 38 
 39     /**
 40      * @return true if the ScaleType is supported.
 41      */
 42     private static boolean isSupportedScaleType(final ScaleType scaleType) {
 43         if (null == scaleType) {
 44             return false;
 45         }
 46 
 47         switch (scaleType) {
 48             case MATRIX:
 49                 throw new IllegalArgumentException(scaleType.name() + " is not supported in PhotoView");
 50 
 51             default:
 52                 return true;
 53         }
 54     }
 55 
 56     /**
 57      * Set's the ImageView's ScaleType to Matrix.
 58      */
 59     private static void setImageViewScaleTypeMatrix(ImageView imageView) {
 60         if (null != imageView) {
 61             if (imageView instanceof PhotoView) {
 62                 /**
 63                  * PhotoView sets it's own ScaleType to Matrix, then diverts all
 64                  * calls setScaleType to this.setScaleType. Basically we don't
 65                  * need to do anything here
 66                  */
 67             } else {
 68                 imageView.setScaleType(ScaleType.MATRIX);
 69             }
 70         }
 71     }
 72 
 73     private WeakReference<ImageView> mImageView;
 74     private ViewTreeObserver mViewTreeObserver;
 75 
 76     // Gesture Detectors
 77     private GestureDetector mGestureDetector;
 78     private VersionedGestureDetector mScaleDragDetector;
 79 
 80     // These are set so we don't keep allocating them on the heap
 81     private final Matrix mBaseMatrix = new Matrix();
 82     private final Matrix mDrawMatrix = new Matrix();
 83     private final Matrix mSuppMatrix = new Matrix();
 84     private final RectF mDisplayRect = new RectF();
 85     private final float[] mMatrixValues = new float[9];
 86 
 87     // Listeners
 88     private OnMatrixChangedListener mMatrixChangeListener;
 89     private OnPhotoTapListener mPhotoTapListener;
 90     private OnViewTapListener mViewTapListener;
 91     private OnLongClickListener mLongClickListener;
 92 
 93     private int mIvTop, mIvRight, mIvBottom, mIvLeft;
 94     private FlingRunnable mCurrentFlingRunnable;
 95     private int mScrollEdge = EDGE_BOTH;
 96 
 97     private boolean mZoomEnabled;
 98     private ScaleType mScaleType = ScaleType.FIT_CENTER;
 99 
100     public PhotoViewAttacher(ImageView imageView) {
101         mImageView = new WeakReference<ImageView>(imageView);
102 
103         imageView.setOnTouchListener(this);
104 
105         mViewTreeObserver = imageView.getViewTreeObserver();
106         mViewTreeObserver.addOnGlobalLayoutListener(this);
107 
108         // Make sure we using MATRIX Scale Type
109         setImageViewScaleTypeMatrix(imageView);
110 
111         if (!imageView.isInEditMode()) {
112             // Create Gesture Detectors...
113             mScaleDragDetector = VersionedGestureDetector.newInstance(imageView.getContext(), this);
114 
115             mGestureDetector = new GestureDetector(imageView.getContext(),
116                     new GestureDetector.SimpleOnGestureListener() {
117 
118                         // forward long click listener
119                         @Override
120                         public void onLongPress(MotionEvent e) {
121                             if(null != mLongClickListener) {
122                                 mLongClickListener.onLongClick(mImageView.get());
123                             }
124                         }});
125 
126             mGestureDetector.setOnDoubleTapListener(this);
127 
128             // Finally, update the UI so that we're zoomable
129             setZoomable(true);
130         }
131     }
132 
133     @Override
134     public final boolean canZoom() {
135         return mZoomEnabled;
136     }
137 
138     /**
139      * Clean-up the resources attached to this object. This needs to be called
140      * when the ImageView is no longer used. A good example is from
141      * {@link View#onDetachedFromWindow()} or from {@link android.app.Activity#onDestroy()}.
142      * This is automatically called if you are using {@link uk.co.senab.photoview.PhotoView}.
143      */
144     @SuppressWarnings("deprecation")
145     public final void cleanup() {
146 //        if (null != mImageView) {
147 //            mImageView.get().getViewTreeObserver().removeGlobalOnLayoutListener(this);
148 //        }
149 //        mViewTreeObserver = null;
150 //
151 //        // Clear listeners too
152 //        mMatrixChangeListener = null;
153 //        mPhotoTapListener = null;
154 //        mViewTapListener = null;
155 //
156 //        // Finally, clear ImageView
157 //        mImageView = null;
158             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
159                 if (null != mImageView) {
160                     mImageView.get().getViewTreeObserver()
161                             .removeOnGlobalLayoutListener(this);
162                 }
163 
164                 if (null != mViewTreeObserver && mViewTreeObserver.isAlive()) {
165                     mViewTreeObserver.removeOnGlobalLayoutListener(this);
166 
167                     mViewTreeObserver = null;
168 
169                     // Clear listeners too
170                     mMatrixChangeListener = null;
171                     mPhotoTapListener = null;
172                     mViewTapListener = null;
173                     // Finally, clear ImageView
174                     mImageView = null;
175                 }
176 
177             } else {
178                 if (null != mImageView) {
179                     mImageView.get().getViewTreeObserver()
180                             .removeGlobalOnLayoutListener(this);
181                 }
182 
183                 if (null != mViewTreeObserver && mViewTreeObserver.isAlive()) {
184                     mViewTreeObserver.removeGlobalOnLayoutListener(this);
185 
186                     mViewTreeObserver = null;
187 
188                     // Clear listeners too
189                     mMatrixChangeListener = null;
190                     mPhotoTapListener = null;
191                     mViewTapListener = null;
192                     // Finally, clear ImageView
193                     mImageView = null;
194                 }
195             }
196     }
197 
198     @Override
199     public final RectF getDisplayRect() {
200         checkMatrixBounds();
201         return getDisplayRect(getDisplayMatrix());
202     }
203 
204     public final ImageView getImageView() {
205         ImageView imageView = null;
206 
207         if (null != mImageView) {
208             imageView = mImageView.get();
209         }
210 
211         // If we don't have an ImageView, call cleanup()
212         if (null == imageView) {
213             cleanup();
214             throw new IllegalStateException(
215                     "ImageView no longer exists. You should not use this PhotoViewAttacher any more.");
216         }
217 
218         return imageView;
219     }
220 
221     @Override
222     public float getMinScale() {
223         return mMinScale;
224     }
225 
226     @Override
227     public float getMidScale() {
228         return mMidScale;
229     }
230 
231     @Override
232     public float getMaxScale() {
233         return mMaxScale;
234     }
235 
236     @Override
237     public final float getScale() {
238         return getValue(mSuppMatrix, Matrix.MSCALE_X);
239     }
240 
241     @Override
242     public final ScaleType getScaleType() {
243         return mScaleType;
244     }
245 
246     public final boolean onDoubleTap(MotionEvent ev) {
247         try {
248             float scale = getScale();
249             float x = ev.getX();
250             float y = ev.getY();
251 
252             if (scale < mMidScale) {
253                 zoomTo(mMidScale, x, y);
254             } else if (scale >= mMidScale && scale < mMaxScale) {
255                 zoomTo(mMaxScale, x, y);
256             } else {
257                 zoomTo(mMinScale, x, y);
258             }
259         } catch (ArrayIndexOutOfBoundsException e) {
260             // Can sometimes happen when getX() and getY() is called
261         }
262 
263         return true;
264     }
265 
266     public final boolean onDoubleTapEvent(MotionEvent e) {
267         // Wait for the confirmed onDoubleTap() instead
268         return false;
269     }
270 
271     public final void onDrag(float dx, float dy) {
272         if (DEBUG) {
273             Log.d(LOG_TAG, String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy));
274         }
275 
276         ImageView imageView = getImageView();
277 
278         if (null != imageView && hasDrawable(imageView)) {
279             mSuppMatrix.postTranslate(dx, dy);
280             checkAndDisplayMatrix();
281 
282             /**
283              * Here we decide whether to let the ImageView's parent to start
284              * taking over the touch event.
285              *
286              * First we check whether this function is enabled. We never want the
287              * parent to take over if we're scaling. We then check the edge we're
288              * on, and the direction of the scroll (i.e. if we're pulling against
289              * the edge, aka 'overscrolling', let the parent take over).
290              */
291             if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling()) {
292                 if (mScrollEdge == EDGE_BOTH || (mScrollEdge == EDGE_LEFT && dx >= 1f)
293                         || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
294                     imageView.getParent().requestDisallowInterceptTouchEvent(false);
295                 }
296             }
297         }
298     }
299 
300     @Override
301     public final void onFling(float startX, float startY, float velocityX, float velocityY) {
302         if (DEBUG) {
303             Log.d(LOG_TAG, "onFling. sX: " + startX + " sY: " + startY + " Vx: " + velocityX + " Vy: " + velocityY);
304         }
305 
306         ImageView imageView = getImageView();
307         if (hasDrawable(imageView)) {
308             mCurrentFlingRunnable = new FlingRunnable(imageView.getContext());
309             mCurrentFlingRunnable.fling(imageView.getWidth(), imageView.getHeight(), (int) velocityX, (int) velocityY);
310             imageView.post(mCurrentFlingRunnable);
311         }
312     }
313 
314     @Override
315     public final void onGlobalLayout() {
316         ImageView imageView = getImageView();
317 
318         if (null != imageView && mZoomEnabled) {
319             final int top = imageView.getTop();
320             final int right = imageView.getRight();
321             final int bottom = imageView.getBottom();
322             final int left = imageView.getLeft();
323 
324             /**
325              * We need to check whether the ImageView's bounds have changed.
326              * This would be easier if we targeted API 11+ as we could just use
327              * View.OnLayoutChangeListener. Instead we have to replicate the
328              * work, keeping track of the ImageView's bounds and then checking
329              * if the values change.
330              */
331             if (top != mIvTop || bottom != mIvBottom || left != mIvLeft || right != mIvRight) {
332                 // Update our base matrix, as the bounds have changed
333                 updateBaseMatrix(imageView.getDrawable());
334 
335                 // Update values as something has changed
336                 mIvTop = top;
337                 mIvRight = right;
338                 mIvBottom = bottom;
339                 mIvLeft = left;
340             }
341         }
342     }
343 
344     public final void onScale(float scaleFactor, float focusX, float focusY) {
345         if (DEBUG) {
346             Log.d(LOG_TAG, String.format("onScale: scale: %.2f. fX: %.2f. fY: %.2f", scaleFactor, focusX, focusY));
347         }
348 
349         if (hasDrawable(getImageView()) && (getScale() < mMaxScale || scaleFactor < 1f)) {
350             mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
351             checkAndDisplayMatrix();
352         }
353     }
354 
355     public final boolean onSingleTapConfirmed(MotionEvent e) {
356         ImageView imageView = getImageView();
357 
358         if (null != imageView) {
359             if (null != mPhotoTapListener) {
360                 final RectF displayRect = getDisplayRect();
361 
362                 if (null != displayRect) {
363                     final float x = e.getX(), y = e.getY();
364 
365                     // Check to see if the user tapped on the photo
366                     if (displayRect.contains(x, y)) {
367 
368                         float xResult = (x - displayRect.left) / displayRect.width();
369                         float yResult = (y - displayRect.top) / displayRect.height();
370 
371                         mPhotoTapListener.onPhotoTap(imageView, xResult, yResult);
372                         return true;
373                     }
374                 }
375             }
376             if (null != mViewTapListener) {
377                 mViewTapListener.onViewTap(imageView, e.getX(), e.getY());
378             }
379         }
380 
381         return false;
382     }
383 
384     @Override
385     public final boolean onTouch(View v, MotionEvent ev) {
386         boolean handled = false;
387 
388         if (mZoomEnabled) {
389             switch (ev.getAction()) {
390                 case MotionEvent.ACTION_DOWN:
391                     // First, disable the Parent from intercepting the touch
392                     // event
393                     v.getParent().requestDisallowInterceptTouchEvent(true);
394 
395                     // If we're flinging, and the user presses down, cancel
396                     // fling
397                     cancelFling();
398                     break;
399 
400                 case MotionEvent.ACTION_CANCEL:
401                 case MotionEvent.ACTION_UP:
402                     // If the user has zoomed less than min scale, zoom back
403                     // to min scale
404                     if (getScale() < mMinScale) {
405                         RectF rect = getDisplayRect();
406                         if (null != rect) {
407                             v.post(new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(), rect.centerY()));
408                             handled = true;
409                         }
410                     }
411                     break;
412             }
413 
414             // Check to see if the user double tapped
415             if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
416                 handled = true;
417             }
418 
419             // Finally, try the Scale/Drag detector
420             if (null != mScaleDragDetector && mScaleDragDetector.onTouchEvent(ev)) {
421                 handled = true;
422             }
423         }
424 
425         return handled;
426     }
427 
428     @Override
429     public void setAllowParentInterceptOnEdge(boolean allow) {
430         mAllowParentInterceptOnEdge = allow;
431     }
432 
433     @Override
434     public void setMinScale(float minScale) {
435         checkZoomLevels(minScale, mMidScale, mMaxScale);
436         mMinScale = minScale;
437     }
438 
439     @Override
440     public void setMidScale(float midScale) {
441         checkZoomLevels(mMinScale, midScale, mMaxScale);
442         mMidScale = midScale;
443     }
444 
445     @Override
446     public void setMaxScale(float maxScale) {
447         checkZoomLevels(mMinScale, mMidScale, maxScale);
448         mMaxScale = maxScale;
449     }
450 
451     @Override
452     public final void setOnLongClickListener(OnLongClickListener listener) {
453         mLongClickListener = listener;
454     }
455 
456     @Override
457     public final void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
458         mMatrixChangeListener = listener;
459     }
460 
461     @Override
462     public final void setOnPhotoTapListener(OnPhotoTapListener listener) {
463         mPhotoTapListener = listener;
464     }
465 
466     @Override
467     public final void setOnViewTapListener(OnViewTapListener listener) {
468         mViewTapListener = listener;
469     }
470 
471     @Override
472     public final void setScaleType(ScaleType scaleType) {
473         if (isSupportedScaleType(scaleType) && scaleType != mScaleType) {
474             mScaleType = scaleType;
475 
476             // Finally update
477             update();
478         }
479     }
480 
481     @Override
482     public final void setZoomable(boolean zoomable) {
483         mZoomEnabled = zoomable;
484         update();
485     }
486 
487     public final void update() {
488         ImageView imageView = getImageView();
489 
490         if (null != imageView) {
491             if (mZoomEnabled) {
492                 // Make sure we using MATRIX Scale Type
493                 setImageViewScaleTypeMatrix(imageView);
494 
495                 // Update the base matrix using the current drawable
496                 updateBaseMatrix(imageView.getDrawable());
497             } else {
498                 // Reset the Matrix...
499                 resetMatrix();
500             }
501         }
502     }
503 
504     @Override
505     public final void zoomTo(float scale, float focalX, float focalY) {
506         ImageView imageView = getImageView();
507 
508         if (null != imageView) {
509             imageView.post(new AnimatedZoomRunnable(getScale(), scale, focalX, focalY));
510         }
511     }
512 
513     protected Matrix getDisplayMatrix() {
514         mDrawMatrix.set(mBaseMatrix);
515         mDrawMatrix.postConcat(mSuppMatrix);
516         return mDrawMatrix;
517     }
518 
519     private void cancelFling() {
520         if (null != mCurrentFlingRunnable) {
521             mCurrentFlingRunnable.cancelFling();
522             mCurrentFlingRunnable = null;
523         }
524     }
525 
526     /**
527      * Helper method that simply checks the Matrix, and then displays the result
528      */
529     private void checkAndDisplayMatrix() {
530         checkMatrixBounds();
531         setImageViewMatrix(getDisplayMatrix());
532     }
533 
534     private void checkImageViewScaleType() {
535         ImageView imageView = getImageView();
536 
537         /**
538          * PhotoView's getScaleType() will just divert to this.getScaleType() so
539          * only call if we're not attached to a PhotoView.
540          */
541         if (null != imageView && !(imageView instanceof PhotoView)) {
542             if (imageView.getScaleType() != ScaleType.MATRIX) {
543                 throw new IllegalStateException(
544                         "The ImageView's ScaleType has been changed since attaching a PhotoViewAttacher");
545             }
546         }
547     }
548 
549     private void checkMatrixBounds() {
550         final ImageView imageView = getImageView();
551         if (null == imageView) {
552             return;
553         }
554 
555         final RectF rect = getDisplayRect(getDisplayMatrix());
556         if (null == rect) {
557             return;
558         }
559 
560         final float height = rect.height(), width = rect.width();
561         float deltaX = 0, deltaY = 0;
562 
563         final int viewHeight = imageView.getHeight();
564         if (height <= viewHeight) {
565             switch (mScaleType) {
566                 case FIT_START:
567                     deltaY = -rect.top;
568                     break;
569                 case FIT_END:
570                     deltaY = viewHeight - height - rect.top;
571                     break;
572                 default:
573                     deltaY = (viewHeight - height) / 2 - rect.top;
574                     break;
575             }
576         } else if (rect.top > 0) {
577             deltaY = -rect.top;
578         } else if (rect.bottom < viewHeight) {
579             deltaY = viewHeight - rect.bottom;
580         }
581 
582         final int viewWidth = imageView.getWidth();
583         if (width <= viewWidth) {
584             switch (mScaleType) {
585                 case FIT_START:
586                     deltaX = -rect.left;
587                     break;
588                 case FIT_END:
589                     deltaX = viewWidth - width - rect.left;
590                     break;
591                 default:
592                     deltaX = (viewWidth - width) / 2 - rect.left;
593                     break;
594             }
595             mScrollEdge = EDGE_BOTH;
596         } else if (rect.left > 0) {
597             mScrollEdge = EDGE_LEFT;
598             deltaX = -rect.left;
599         } else if (rect.right < viewWidth) {
600             deltaX = viewWidth - rect.right;
601             mScrollEdge = EDGE_RIGHT;
602         } else {
603             mScrollEdge = EDGE_NONE;
604         }
605 
606         // Finally actually translate the matrix
607         mSuppMatrix.postTranslate(deltaX, deltaY);
608     }
609 
610     /**
611      * Helper method that maps the supplied Matrix to the current Drawable
612      * 
613      * @param matrix - Matrix to map Drawable against
614      * @return RectF - Displayed Rectangle
615      */
616     private RectF getDisplayRect(Matrix matrix) {
617         ImageView imageView = getImageView();
618 
619         if (null != imageView) {
620             Drawable d = imageView.getDrawable();
621             if (null != d) {
622                 mDisplayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
623                 matrix.mapRect(mDisplayRect);
624                 return mDisplayRect;
625             }
626         }
627         return null;
628     }
629 
630     /**
631      * Helper method that 'unpacks' a Matrix and returns the required value
632      * 
633      * @param matrix - Matrix to unpack
634      * @param whichValue - Which value from Matrix.M* to return
635      * @return float - returned value
636      */
637     private float getValue(Matrix matrix, int whichValue) {
638         matrix.getValues(mMatrixValues);
639         return mMatrixValues[whichValue];
640     }
641 
642     /**
643      * Resets the Matrix back to FIT_CENTER, and then displays it.s
644      */
645     private void resetMatrix() {
646         mSuppMatrix.reset();
647         setImageViewMatrix(getDisplayMatrix());
648         checkMatrixBounds();
649     }
650 
651     private void setImageViewMatrix(Matrix matrix) {
652         ImageView imageView = getImageView();
653         if (null != imageView) {
654 
655             checkImageViewScaleType();
656             imageView.setImageMatrix(matrix);
657 
658             // Call MatrixChangedListener if needed
659             if (null != mMatrixChangeListener) {
660                 RectF displayRect = getDisplayRect(matrix);
661                 if (null != displayRect) {
662                     mMatrixChangeListener.onMatrixChanged(displayRect);
663                 }
664             }
665         }
666     }
667 
668     /**
669      * Calculate Matrix for FIT_CENTER
670      * 
671      * @param d - Drawable being displayed
672      */
673     private void updateBaseMatrix(Drawable d) {
674         ImageView imageView = getImageView();
675         if (null == imageView || null == d) {
676             return;
677         }
678 
679         final float viewWidth = imageView.getWidth();
680         final float viewHeight = imageView.getHeight();
681         final int drawableWidth = d.getIntrinsicWidth();
682         final int drawableHeight = d.getIntrinsicHeight();
683 
684         mBaseMatrix.reset();
685 
686         final float widthScale = viewWidth / drawableWidth;
687         final float heightScale = viewHeight / drawableHeight;
688 
689         if (mScaleType == ScaleType.CENTER) {
690             mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F, (viewHeight - drawableHeight) / 2F);
691 
692         } else if (mScaleType == ScaleType.CENTER_CROP) {
693             float scale = Math.max(widthScale, heightScale);
694             mBaseMatrix.postScale(scale, scale);
695             mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
696                     (viewHeight - drawableHeight * scale) / 2F);
697 
698         } else if (mScaleType == ScaleType.CENTER_INSIDE) {
699             float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
700             mBaseMatrix.postScale(scale, scale);
701             mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
702                     (viewHeight - drawableHeight * scale) / 2F);
703 
704         } else {
705             RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
706             RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
707 
708             switch (mScaleType) {
709                 case FIT_CENTER:
710                     mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
711                     break;
712 
713                 case FIT_START:
714                     mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
715                     break;
716 
717                 case FIT_END:
718                     mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
719                     break;
720 
721                 case FIT_XY:
722                     mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
723                     break;
724 
725                 default:
726                     break;
727             }
728         }
729 
730         resetMatrix();
731     }
732 
733     /**
734      * Interface definition for a callback to be invoked when the internal
735      * Matrix has changed for this View.
736      * 
737      * @author Chris Banes
738      */
739     public static interface OnMatrixChangedListener {
740         /**
741          * Callback for when the Matrix displaying the Drawable has changed.
742          * This could be because the View's bounds have changed, or the user has
743          * zoomed.
744          * 
745          * @param rect - Rectangle displaying the Drawable's new bounds.
746          */
747         void onMatrixChanged(RectF rect);
748     }
749 
750     /**
751      * Interface definition for a callback to be invoked when the Photo is
752      * tapped with a single tap.
753      * 
754      * @author Chris Banes
755      */
756     public static interface OnPhotoTapListener {
757 
758         /**
759          * A callback to receive where the user taps on a photo. You will only
760          * receive a callback if the user taps on the actual photo, tapping on
761          * 'whitespace' will be ignored.
762          * 
763          * @param view - View the user tapped.
764          * @param x - where the user tapped from the of the Drawable, as
765          *            percentage of the Drawable width.
766          * @param y - where the user tapped from the top of the Drawable, as
767          *            percentage of the Drawable height.
768          */
769         void onPhotoTap(View view, float x, float y);
770     }
771 
772     /**
773      * Interface definition for a callback to be invoked when the ImageView is
774      * tapped with a single tap.
775      * 
776      * @author Chris Banes
777      */
778     public static interface OnViewTapListener {
779 
780         /**
781          * A callback to receive where the user taps on a ImageView. You will
782          * receive a callback if the user taps anywhere on the view, tapping on
783          * 'whitespace' will not be ignored.
784          * 
785          * @param view - View the user tapped.
786          * @param x - where the user tapped from the left of the View.
787          * @param y - where the user tapped from the top of the View.
788          */
789         void onViewTap(View view, float x, float y);
790     }
791 
792     private class AnimatedZoomRunnable implements Runnable {
793 
794         // These are 'postScale' values, means they're compounded each iteration
795         static final float ANIMATION_SCALE_PER_ITERATION_IN = 1.07f;
796         static final float ANIMATION_SCALE_PER_ITERATION_OUT = 0.93f;
797 
798         private final float mFocalX, mFocalY;
799         private final float mTargetZoom;
800         private final float mDeltaScale;
801 
802         public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX,
803                 final float focalY) {
804             mTargetZoom = targetZoom;
805             mFocalX = focalX;
806             mFocalY = focalY;
807 
808             if (currentZoom < targetZoom) {
809                 mDeltaScale = ANIMATION_SCALE_PER_ITERATION_IN;
810             } else {
811                 mDeltaScale = ANIMATION_SCALE_PER_ITERATION_OUT;
812             }
813         }
814 
815         public void run() {
816             ImageView imageView = getImageView();
817 
818             if (null != imageView) {
819                 mSuppMatrix.postScale(mDeltaScale, mDeltaScale, mFocalX, mFocalY);
820                 checkAndDisplayMatrix();
821 
822                 final float currentScale = getScale();
823 
824                 if ((mDeltaScale > 1f && currentScale < mTargetZoom)
825                         || (mDeltaScale < 1f && mTargetZoom < currentScale)) {
826                     // We haven't hit our target scale yet, so post ourselves
827                     // again
828                     Compat.postOnAnimation(imageView, this);
829 
830                 } else {
831                     // We've scaled past our target zoom, so calculate the
832                     // necessary scale so we're back at target zoom
833                     final float delta = mTargetZoom / currentScale;
834                     mSuppMatrix.postScale(delta, delta, mFocalX, mFocalY);
835                     checkAndDisplayMatrix();
836                 }
837             }
838         }
839     }
840 
841     private class FlingRunnable implements Runnable {
842 
843         private final ScrollerProxy mScroller;
844         private int mCurrentX, mCurrentY;
845 
846         public FlingRunnable(Context context) {
847             mScroller = ScrollerProxy.getScroller(context);
848         }
849 
850         public void cancelFling() {
851             if (DEBUG) {
852                 Log.d(LOG_TAG, "Cancel Fling");
853             }
854             mScroller.forceFinished(true);
855         }
856 
857         public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
858             final RectF rect = getDisplayRect();
859             if (null == rect) {
860                 return;
861             }
862 
863             final int startX = Math.round(-rect.left);
864             final int minX, maxX, minY, maxY;
865 
866             if (viewWidth < rect.width()) {
867                 minX = 0;
868                 maxX = Math.round(rect.width() - viewWidth);
869             } else {
870                 minX = maxX = startX;
871             }
872 
873             final int startY = Math.round(-rect.top);
874             if (viewHeight < rect.height()) {
875                 minY = 0;
876                 maxY = Math.round(rect.height() - viewHeight);
877             } else {
878                 minY = maxY = startY;
879             }
880 
881             mCurrentX = startX;
882             mCurrentY = startY;
883 
884             if (DEBUG) {
885                 Log.d(LOG_TAG, "fling. StartX:" + startX + " StartY:" + startY + " MaxX:" + maxX + " MaxY:" + maxY);
886             }
887 
888             // If we actually can move, fling the scroller
889             if (startX != maxX || startY != maxY) {
890                 mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
891             }
892         }
893 
894         @Override
895         public void run() {
896             ImageView imageView = getImageView();
897             if (null != imageView && mScroller.computeScrollOffset()) {
898 
899                 final int newX = mScroller.getCurrX();
900                 final int newY = mScroller.getCurrY();
901 
902                 if (DEBUG) {
903                     Log.d(LOG_TAG, "fling run(). CurrentX:" + mCurrentX + " CurrentY:" + mCurrentY + " NewX:" + newX
904                             + " NewY:" + newY);
905                 }
906 
907                 mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
908                 setImageViewMatrix(getDisplayMatrix());
909 
910                 mCurrentX = newX;
911                 mCurrentY = newY;
912 
913                 // Post On animation
914                 Compat.postOnAnimation(imageView, this);
915             }
916         }
917     }
918 }
  1 public abstract class VersionedGestureDetector {
  2     static final String LOG_TAG = "VersionedGestureDetector";
  3     OnGestureListener mListener;
  4 
  5     public static VersionedGestureDetector newInstance(Context context, OnGestureListener listener) {
  6         final int sdkVersion = Build.VERSION.SDK_INT;
  7         VersionedGestureDetector detector = null;
  8 
  9         if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
 10             detector = new CupcakeDetector(context);
 11         } else if (sdkVersion < Build.VERSION_CODES.FROYO) {
 12             detector = new EclairDetector(context);
 13         } else {
 14             detector = new FroyoDetector(context);
 15         }
 16 
 17         detector.mListener = listener;
 18 
 19         return detector;
 20     }
 21 
 22     public abstract boolean onTouchEvent(MotionEvent ev);
 23 
 24     public abstract boolean isScaling();
 25 
 26     public static interface OnGestureListener {
 27         public void onDrag(float dx, float dy);
 28 
 29         public void onFling(float startX, float startY, float velocityX, float velocityY);
 30 
 31         public void onScale(float scaleFactor, float focusX, float focusY);
 32     }
 33 
 34     private static class CupcakeDetector extends VersionedGestureDetector {
 35 
 36         float mLastTouchX;
 37         float mLastTouchY;
 38         final float mTouchSlop;
 39         final float mMinimumVelocity;
 40 
 41         public CupcakeDetector(Context context) {
 42             final ViewConfiguration configuration = ViewConfiguration.get(context);
 43             mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
 44             mTouchSlop = configuration.getScaledTouchSlop();
 45         }
 46 
 47         private VelocityTracker mVelocityTracker;
 48         private boolean mIsDragging;
 49 
 50         float getActiveX(MotionEvent ev) {
 51             return ev.getX();
 52         }
 53 
 54         float getActiveY(MotionEvent ev) {
 55             return ev.getY();
 56         }
 57 
 58         public boolean isScaling() {
 59             return false;
 60         }
 61 
 62         @Override
 63         public boolean onTouchEvent(MotionEvent ev) {
 64             switch (ev.getAction()) {
 65                 case MotionEvent.ACTION_DOWN: {
 66                     mVelocityTracker = VelocityTracker.obtain();
 67                     mVelocityTracker.addMovement(ev);
 68 
 69                     mLastTouchX = getActiveX(ev);
 70                     mLastTouchY = getActiveY(ev);
 71                     mIsDragging = false;
 72                     break;
 73                 }
 74 
 75                 case MotionEvent.ACTION_MOVE: {
 76                     final float x = getActiveX(ev);
 77                     final float y = getActiveY(ev);
 78                     final float dx = x - mLastTouchX, dy = y - mLastTouchY;
 79 
 80                     if (!mIsDragging) {
 81                         // Use Pythagoras to see if drag length is larger than
 82                         // touch slop
 83                         mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
 84                     }
 85 
 86                     if (mIsDragging) {
 87                         mListener.onDrag(dx, dy);
 88                         mLastTouchX = x;
 89                         mLastTouchY = y;
 90 
 91                         if (null != mVelocityTracker) {
 92                             mVelocityTracker.addMovement(ev);
 93                         }
 94                     }
 95                     break;
 96                 }
 97 
 98                 case MotionEvent.ACTION_CANCEL: {
 99                     // Recycle Velocity Tracker
100                     if (null != mVelocityTracker) {
101                         mVelocityTracker.recycle();
102                         mVelocityTracker = null;
103                     }
104                     break;
105                 }
106 
107                 case MotionEvent.ACTION_UP: {
108                     if (mIsDragging) {
109                         if (null != mVelocityTracker) {
110                             mLastTouchX = getActiveX(ev);
111                             mLastTouchY = getActiveY(ev);
112 
113                             // Compute velocity within the last 1000ms
114                             mVelocityTracker.addMovement(ev);
115                             mVelocityTracker.computeCurrentVelocity(1000);
116 
117                             final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker.getYVelocity();
118 
119                             // If the velocity is greater than minVelocity, call
120                             // listener
121                             if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
122                                 mListener.onFling(mLastTouchX, mLastTouchY, -vX, -vY);
123                             }
124                         }
125                     }
126 
127                     // Recycle Velocity Tracker
128                     if (null != mVelocityTracker) {
129                         mVelocityTracker.recycle();
130                         mVelocityTracker = null;
131                     }
132                     break;
133                 }
134             }
135 
136             return true;
137         }
138     }
139 
140     @TargetApi(5)
141     private static class EclairDetector extends CupcakeDetector {
142         private static final int INVALID_POINTER_ID = -1;
143         private int mActivePointerId = INVALID_POINTER_ID;
144         private int mActivePointerIndex = 0;
145 
146         public EclairDetector(Context context) {
147             super(context);
148         }
149 
150         @Override
151         float getActiveX(MotionEvent ev) {
152             try {
153                 return ev.getX(mActivePointerIndex);
154             } catch (Exception e) {
155                 return ev.getX();
156             }
157         }
158 
159         @Override
160         float getActiveY(MotionEvent ev) {
161             try {
162                 return ev.getY(mActivePointerIndex);
163             } catch (Exception e) {
164                 return ev.getY();
165             }
166         }
167 
168         @Override
169         public boolean onTouchEvent(MotionEvent ev) {
170             final int action = ev.getAction();
171             switch (action & MotionEvent.ACTION_MASK) {
172                 case MotionEvent.ACTION_DOWN:
173                     mActivePointerId = ev.getPointerId(0);
174                     break;
175                 case MotionEvent.ACTION_CANCEL:
176                 case MotionEvent.ACTION_UP:
177                     mActivePointerId = INVALID_POINTER_ID;
178                     break;
179                 case MotionEvent.ACTION_POINTER_UP:
180                     final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
181                     final int pointerId = ev.getPointerId(pointerIndex);
182                     if (pointerId == mActivePointerId) {
183                         // This was our active pointer going up. Choose a new
184                         // active pointer and adjust accordingly.
185                         final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
186                         mActivePointerId = ev.getPointerId(newPointerIndex);
187                         mLastTouchX = ev.getX(newPointerIndex);
188                         mLastTouchY = ev.getY(newPointerIndex);
189                     }
190                     break;
191             }
192 
193             mActivePointerIndex = ev.findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId : 0);
194             return super.onTouchEvent(ev);
195         }
196     }
197 
198     @TargetApi(8)
199     private static class FroyoDetector extends EclairDetector {
200 
201         private final ScaleGestureDetector mDetector;
202 
203         // Needs to be an inner class so that we don't hit
204         // VerifyError's on API 4.
205         private final OnScaleGestureListener mScaleListener = new OnScaleGestureListener() {
206 
207             @Override
208             public boolean onScale(ScaleGestureDetector detector) {
209                 mListener.onScale(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY());
210                 return true;
211             }
212 
213             @Override
214             public boolean onScaleBegin(ScaleGestureDetector detector) {
215                 return true;
216             }
217 
218             @Override
219             public void onScaleEnd(ScaleGestureDetector detector) {
220                 // NO-OP
221             }
222         };
223 
224         public FroyoDetector(Context context) {
225             super(context);
226             mDetector = new ScaleGestureDetector(context, mScaleListener);
227         }
228 
229         @Override
230         public boolean isScaling() {
231             return mDetector.isInProgress();
232         }
233 
234         @Override
235         public boolean onTouchEvent(MotionEvent ev) {
236             mDetector.onTouchEvent(ev);
237             return super.onTouchEvent(ev);
238         }
239 
240     }

原文地址:https://www.cnblogs.com/wangying222/p/6479700.html