├── .classpath ├── .gitignore ├── .project ├── AndroidManifest.xml ├── License ├── README.md ├── default.properties ├── res ├── drawable │ ├── icon.png │ ├── image.png │ └── sample.jpg ├── layout │ └── main.xml └── values │ └── strings.xml └── src └── com └── matabii └── dev └── scaleimageview ├── ScaleImageView.java └── ScaleImageViewActivity.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | gen 3 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScaleImageView 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kenzo Ishii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | scale-imageview-android 2 | ======================= 3 | 4 | Add pinch-in and pinch-out function to Android ImageVIew. 5 | 6 | * Double-tap to enlarge 7 | * Can pinch to zoom in or out on the view 8 | 9 | Example Code 10 | 11 | Very similar ImageView 12 | 13 | Setting Layout XML 14 | 15 | 20 | 21 | or edit java source 22 | 23 | ScaleImageView image = (ScaleImageView) findViewById(R.id.image); 24 | Bitmap bitmap = BitmapFactory.decodeStream(is); 25 | image.setImageBitmap(bitmap); 26 | 27 | ====== 28 | Download the sample application 29 | 30 | http://code.google.com/p/scale-imageview-android/downloads/detail?name=ScaleImageView.apk 31 | -------------------------------------------------------------------------------- /default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-10 12 | -------------------------------------------------------------------------------- /res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matabii/scale-imageview-android/46278621c8e80b51c7d05a33b92207a7d01f78d5/res/drawable/icon.png -------------------------------------------------------------------------------- /res/drawable/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matabii/scale-imageview-android/46278621c8e80b51c7d05a33b92207a7d01f78d5/res/drawable/image.png -------------------------------------------------------------------------------- /res/drawable/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matabii/scale-imageview-android/46278621c8e80b51c7d05a33b92207a7d01f78d5/res/drawable/sample.jpg -------------------------------------------------------------------------------- /res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ScaleImageView 5 | 6 | -------------------------------------------------------------------------------- /src/com/matabii/dev/scaleimageview/ScaleImageView.java: -------------------------------------------------------------------------------- 1 | package com.matabii.dev.scaleimageview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Matrix; 6 | import android.graphics.drawable.Drawable; 7 | import android.util.AttributeSet; 8 | import android.util.FloatMath; 9 | import android.view.GestureDetector; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.View.OnTouchListener; 13 | import android.widget.ImageView; 14 | 15 | public class ScaleImageView extends ImageView implements OnTouchListener { 16 | private Context mContext; 17 | private float MAX_SCALE = 2f; 18 | 19 | private Matrix mMatrix; 20 | private final float[] mMatrixValues = new float[9]; 21 | 22 | // display width height. 23 | private int mWidth; 24 | private int mHeight; 25 | 26 | private int mIntrinsicWidth; 27 | private int mIntrinsicHeight; 28 | 29 | private float mScale; 30 | private float mMinScale; 31 | 32 | private float mPrevDistance; 33 | private boolean isScaling; 34 | 35 | private int mPrevMoveX; 36 | private int mPrevMoveY; 37 | private GestureDetector mDetector; 38 | 39 | String TAG = "ScaleImageView"; 40 | 41 | public ScaleImageView(Context context, AttributeSet attr) { 42 | super(context, attr); 43 | this.mContext = context; 44 | initialize(); 45 | } 46 | 47 | public ScaleImageView(Context context) { 48 | super(context); 49 | this.mContext = context; 50 | initialize(); 51 | } 52 | 53 | @Override 54 | public void setImageBitmap(Bitmap bm) { 55 | super.setImageBitmap(bm); 56 | this.initialize(); 57 | } 58 | 59 | @Override 60 | public void setImageResource(int resId) { 61 | super.setImageResource(resId); 62 | this.initialize(); 63 | } 64 | 65 | private void initialize() { 66 | this.setScaleType(ScaleType.MATRIX); 67 | this.mMatrix = new Matrix(); 68 | Drawable d = getDrawable(); 69 | if (d != null) { 70 | mIntrinsicWidth = d.getIntrinsicWidth(); 71 | mIntrinsicHeight = d.getIntrinsicHeight(); 72 | setOnTouchListener(this); 73 | } 74 | mDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() { 75 | @Override 76 | public boolean onDoubleTap(MotionEvent e) { 77 | maxZoomTo((int) e.getX(), (int) e.getY()); 78 | cutting(); 79 | return super.onDoubleTap(e); 80 | } 81 | }); 82 | 83 | } 84 | 85 | @Override 86 | protected boolean setFrame(int l, int t, int r, int b) { 87 | mWidth = r - l; 88 | mHeight = b - t; 89 | 90 | mMatrix.reset(); 91 | int r_norm = r - l; 92 | mScale = (float) r_norm / (float) mIntrinsicWidth; 93 | 94 | int paddingHeight = 0; 95 | int paddingWidth = 0; 96 | // scaling vertical 97 | if (mScale * mIntrinsicHeight > mHeight) { 98 | mScale = (float) mHeight / (float) mIntrinsicHeight; 99 | mMatrix.postScale(mScale, mScale); 100 | paddingWidth = (r - mWidth) / 2; 101 | paddingHeight = 0; 102 | // scaling horizontal 103 | } else { 104 | mMatrix.postScale(mScale, mScale); 105 | paddingHeight = (b - mHeight) / 2; 106 | paddingWidth = 0; 107 | } 108 | mMatrix.postTranslate(paddingWidth, paddingHeight); 109 | 110 | setImageMatrix(mMatrix); 111 | mMinScale = mScale; 112 | zoomTo(mScale, mWidth / 2, mHeight / 2); 113 | cutting(); 114 | return super.setFrame(l, t, r, b); 115 | } 116 | 117 | protected float getValue(Matrix matrix, int whichValue) { 118 | matrix.getValues(mMatrixValues); 119 | return mMatrixValues[whichValue]; 120 | } 121 | 122 | protected float getScale() { 123 | return getValue(mMatrix, Matrix.MSCALE_X); 124 | } 125 | 126 | public float getTranslateX() { 127 | return getValue(mMatrix, Matrix.MTRANS_X); 128 | } 129 | 130 | protected float getTranslateY() { 131 | return getValue(mMatrix, Matrix.MTRANS_Y); 132 | } 133 | 134 | protected void maxZoomTo(int x, int y) { 135 | if (mMinScale != getScale() && (getScale() - mMinScale) > 0.1f) { 136 | // threshold 0.1f 137 | float scale = mMinScale / getScale(); 138 | zoomTo(scale, x, y); 139 | } else { 140 | float scale = MAX_SCALE / getScale(); 141 | zoomTo(scale, x, y); 142 | } 143 | } 144 | 145 | public void zoomTo(float scale, int x, int y) { 146 | if (getScale() * scale < mMinScale) { 147 | return; 148 | } 149 | if (scale >= 1 && getScale() * scale > MAX_SCALE) { 150 | return; 151 | } 152 | mMatrix.postScale(scale, scale); 153 | // move to center 154 | mMatrix.postTranslate(-(mWidth * scale - mWidth) / 2, -(mHeight * scale - mHeight) / 2); 155 | 156 | // move x and y distance 157 | mMatrix.postTranslate(-(x - (mWidth / 2)) * scale, 0); 158 | mMatrix.postTranslate(0, -(y - (mHeight / 2)) * scale); 159 | setImageMatrix(mMatrix); 160 | } 161 | 162 | public void cutting() { 163 | int width = (int) (mIntrinsicWidth * getScale()); 164 | int height = (int) (mIntrinsicHeight * getScale()); 165 | if (getTranslateX() < -(width - mWidth)) { 166 | mMatrix.postTranslate(-(getTranslateX() + width - mWidth), 0); 167 | } 168 | if (getTranslateX() > 0) { 169 | mMatrix.postTranslate(-getTranslateX(), 0); 170 | } 171 | if (getTranslateY() < -(height - mHeight)) { 172 | mMatrix.postTranslate(0, -(getTranslateY() + height - mHeight)); 173 | } 174 | if (getTranslateY() > 0) { 175 | mMatrix.postTranslate(0, -getTranslateY()); 176 | } 177 | if (width < mWidth) { 178 | mMatrix.postTranslate((mWidth - width) / 2, 0); 179 | } 180 | if (height < mHeight) { 181 | mMatrix.postTranslate(0, (mHeight - height) / 2); 182 | } 183 | setImageMatrix(mMatrix); 184 | } 185 | 186 | private float distance(float x0, float x1, float y0, float y1) { 187 | float x = x0 - x1; 188 | float y = y0 - y1; 189 | return FloatMath.sqrt(x * x + y * y); 190 | } 191 | 192 | private float dispDistance() { 193 | return FloatMath.sqrt(mWidth * mWidth + mHeight * mHeight); 194 | } 195 | 196 | @Override 197 | public boolean onTouchEvent(MotionEvent event) { 198 | if (mDetector.onTouchEvent(event)) { 199 | return true; 200 | } 201 | int touchCount = event.getPointerCount(); 202 | switch (event.getAction()) { 203 | case MotionEvent.ACTION_DOWN: 204 | case MotionEvent.ACTION_POINTER_1_DOWN: 205 | case MotionEvent.ACTION_POINTER_2_DOWN: 206 | if (touchCount >= 2) { 207 | float distance = distance(event.getX(0), event.getX(1), event.getY(0), event.getY(1)); 208 | mPrevDistance = distance; 209 | isScaling = true; 210 | } else { 211 | mPrevMoveX = (int) event.getX(); 212 | mPrevMoveY = (int) event.getY(); 213 | } 214 | case MotionEvent.ACTION_MOVE: 215 | if (touchCount >= 2 && isScaling) { 216 | float dist = distance(event.getX(0), event.getX(1), event.getY(0), event.getY(1)); 217 | float scale = (dist - mPrevDistance) / dispDistance(); 218 | mPrevDistance = dist; 219 | scale += 1; 220 | scale = scale * scale; 221 | zoomTo(scale, mWidth / 2, mHeight / 2); 222 | cutting(); 223 | } else if (!isScaling) { 224 | int distanceX = mPrevMoveX - (int) event.getX(); 225 | int distanceY = mPrevMoveY - (int) event.getY(); 226 | mPrevMoveX = (int) event.getX(); 227 | mPrevMoveY = (int) event.getY(); 228 | mMatrix.postTranslate(-distanceX, -distanceY); 229 | cutting(); 230 | } 231 | break; 232 | case MotionEvent.ACTION_UP: 233 | case MotionEvent.ACTION_POINTER_UP: 234 | case MotionEvent.ACTION_POINTER_2_UP: 235 | if (event.getPointerCount() <= 1) { 236 | isScaling = false; 237 | } 238 | break; 239 | } 240 | return true; 241 | } 242 | 243 | @Override 244 | public boolean onTouch(View v, MotionEvent event) { 245 | return super.onTouchEvent(event); 246 | } 247 | 248 | } 249 | -------------------------------------------------------------------------------- /src/com/matabii/dev/scaleimageview/ScaleImageViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.matabii.dev.scaleimageview; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | public class ScaleImageViewActivity extends Activity { 7 | @Override 8 | public void onCreate(Bundle savedInstanceState) { 9 | super.onCreate(savedInstanceState); 10 | setContentView(R.layout.main); 11 | } 12 | } --------------------------------------------------------------------------------