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


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

    从主布局开始了

    <?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     }
    
    
  • 相关阅读:
    php 实现四种排序两种查找
    GIT常用命令
    2016-the brave never die
    Apache 的ab压力测试工具
    SQL语句优化实践
    C#设计模式:访问者模式(Vistor Pattern)
    泛型反射性能优化
    C#GC垃圾回收和析构函数和IDisposable的使用
    C#配合大数据开发,nest.dll的使用
    C#导出大量数据到excel,怎么提升性能
  • 原文地址:https://www.cnblogs.com/wangying222/p/6479700.html
Copyright © 2020-2023  润新知