├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── master │ │ └── demo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── master │ │ │ └── demo │ │ │ ├── Main2Activity.java │ │ │ ├── MainActivity.java │ │ │ └── TouchImageView.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_main2.xml │ │ └── second_activity.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── master │ └── demo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── master │ │ └── glideimageview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── master │ │ │ └── glideimageview │ │ │ └── GlideImageView.java │ ├── main.zip │ └── res │ │ ├── drawable-xxxhdpi │ │ └── no_image.png │ │ └── values │ │ ├── attrs.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── master │ └── glideimageview │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 35 | 36 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GlideImageView 2 | 3 | [![N|Solid](https://img.shields.io/badge/Android%20Arsenal-GlideImageView-brightgreen.svg)](https://android-arsenal.com/details/1/5719) 4 | 5 | GlideImageView used to show loader(Progress bar while loading image from url) 6 | 7 | ### Latest Version [2.1] 8 | - Added method to set application context via (setApplicationContext()), Use this method before loading url. 9 | - Added Singleton request Manager for efficient loading. 10 | 11 | 12 | ### Download 13 | Include the following dependency in your apps build.gradle file. 14 | ``` 15 | compile 'com.master.android:glideimageview:2.1' 16 | ``` 17 | 18 | ### How to use 19 | #### in xml 20 | ``` 21 | 31 | ``` 32 | 33 | You can hide/show progress while loading using 34 | ``` 35 | app:show_progress="false|true" 36 | ``` 37 | 38 | #### in java 39 | ``` 40 | glideImageView = (GlideImageView) findViewById(R.id.glide_image_view); 41 | glideImageView.loadImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Pizigani_1367_Chart_10MB.jpg/800px-Pizigani_1367_Chart_10MB.jpg"); 42 | ``` 43 | 44 | ### Libraries Used 45 | Thanks to Glide image loading library 46 | https://github.com/bumptech/glide 47 | 48 | 49 | ### License 50 | ``` 51 | Copyright 2017 Pankaj Sharma 52 | 53 | Licensed under the Apache License, Version 2.0 (the "License"); 54 | you may not use this file except in compliance with the License. 55 | You may obtain a copy of the License at 56 | 57 | http://www.apache.org/licenses/LICENSE-2.0 58 | 59 | Unless required by applicable law or agreed to in writing, software 60 | distributed under the License is distributed on an "AS IS" BASIS, 61 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 62 | See the License for the specific language governing permissions and 63 | limitations under the License. 64 | ``` 65 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "com.master.demo" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile project(path: ':library') 28 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 29 | // compile 'com.android.support:appcompat-v7:25.3.1' 30 | compile 'com.master.android:glideimageview:2.1' 31 | testCompile 'junit:junit:4.12' 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/hb/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/master/demo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.master.demo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.master.demo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/master/demo/Main2Activity.java: -------------------------------------------------------------------------------- 1 | package com.master.demo; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.widget.ImageView; 7 | 8 | import com.bumptech.glide.request.animation.GlideAnimation; 9 | import com.bumptech.glide.request.target.SimpleTarget; 10 | import com.master.glideimageview.GlideImageView; 11 | 12 | public class Main2Activity extends AppCompatActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main2); 18 | final ImageView img = (ImageView) findViewById(R.id.imageView); 19 | 20 | GlideImageView.getGlide(getApplicationContext()) 21 | .load("https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Pizigani_1367_Chart_10MB.jpg/800px-Pizigani_1367_Chart_10MB.jpg").asBitmap().into(new SimpleTarget() { 22 | @Override 23 | public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { 24 | img.setImageBitmap(resource); 25 | } 26 | }); 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/master/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.master.demo; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | import com.bumptech.glide.Glide; 9 | import com.master.glideimageview.GlideImageView; 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | 13 | GlideImageView glideImageView; 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_main); 19 | 20 | glideImageView = (GlideImageView) findViewById(R.id.glide_image_view); 21 | glideImageView.setApplicationContext(getApplication()); 22 | glideImageView.loadImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Pizigani_1367_Chart_10MB.jpg/800px-Pizigani_1367_Chart_10MB.jpg"); 23 | 24 | new Thread(new Runnable() { 25 | @Override 26 | public void run() { 27 | Glide.get(getApplicationContext()).clearDiskCache(); 28 | } 29 | }).start(); 30 | 31 | glideImageView.setOnClickListener(new View.OnClickListener() { 32 | @Override 33 | public void onClick(View v) { 34 | Intent intent = new Intent(MainActivity.this, Main2Activity.class); 35 | startActivity(intent); 36 | } 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/master/demo/TouchImageView.java: -------------------------------------------------------------------------------- 1 | package com.master.demo; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.Configuration; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Canvas; 8 | import android.graphics.Matrix; 9 | import android.graphics.PointF; 10 | import android.graphics.RectF; 11 | import android.graphics.drawable.Drawable; 12 | import android.net.Uri; 13 | import android.os.Build; 14 | import android.os.Bundle; 15 | import android.os.Parcelable; 16 | import android.util.AttributeSet; 17 | import android.util.Log; 18 | import android.view.GestureDetector; 19 | import android.view.MotionEvent; 20 | import android.view.ScaleGestureDetector; 21 | import android.view.View; 22 | import android.view.animation.AccelerateDecelerateInterpolator; 23 | import android.widget.OverScroller; 24 | import android.widget.Scroller; 25 | 26 | import com.master.glideimageview.GlideImageView; 27 | 28 | public class TouchImageView extends GlideImageView { 29 | 30 | private static final String DEBUG = "DEBUG"; 31 | 32 | // 33 | // SuperMin and SuperMax multipliers. Determine how much the image can be 34 | // zoomed below or above the zoom boundaries, before animating back to the 35 | // min/max zoom boundary. 36 | // 37 | private static final float SUPER_MIN_MULTIPLIER = .75f; 38 | private static final float SUPER_MAX_MULTIPLIER = 1.25f; 39 | 40 | // 41 | // Scale of image ranges from minScale to maxScale, where minScale == 1 42 | // when the image is stretched to fit view. 43 | // 44 | private float normalizedScale; 45 | 46 | // 47 | // Matrix applied to image. MSCALE_X and MSCALE_Y should always be equal. 48 | // MTRANS_X and MTRANS_Y are the other values used. prevMatrix is the matrix 49 | // saved prior to the screen rotating. 50 | // 51 | private Matrix matrix, prevMatrix; 52 | 53 | private static enum State {NONE, DRAG, ZOOM, FLING, ANIMATE_ZOOM} 54 | 55 | ; 56 | private State state; 57 | 58 | private float minScale; 59 | private float maxScale; 60 | private float superMinScale; 61 | private float superMaxScale; 62 | private float[] m; 63 | 64 | private Context context; 65 | private Fling fling; 66 | 67 | private ScaleType mScaleType; 68 | 69 | private boolean imageRenderedAtLeastOnce; 70 | private boolean onDrawReady; 71 | 72 | private ZoomVariables delayedZoomVariables; 73 | 74 | // 75 | // Size of view and previous view size (ie before rotation) 76 | // 77 | private int viewWidth, viewHeight, prevViewWidth, prevViewHeight; 78 | 79 | // 80 | // Size of image when it is stretched to fit view. Before and After rotation. 81 | // 82 | private float matchViewWidth, matchViewHeight, prevMatchViewWidth, prevMatchViewHeight; 83 | 84 | private ScaleGestureDetector mScaleDetector; 85 | private GestureDetector mGestureDetector; 86 | private GestureDetector.OnDoubleTapListener doubleTapListener = null; 87 | private OnTouchListener userTouchListener = null; 88 | private OnTouchImageViewListener touchImageViewListener = null; 89 | 90 | public TouchImageView(Context context) { 91 | super(context); 92 | sharedConstructing(context); 93 | } 94 | 95 | public TouchImageView(Context context, AttributeSet attrs) { 96 | super(context, attrs); 97 | sharedConstructing(context); 98 | } 99 | 100 | public TouchImageView(Context context, AttributeSet attrs, int defStyle) { 101 | super(context, attrs, defStyle); 102 | sharedConstructing(context); 103 | } 104 | 105 | private void sharedConstructing(Context context) { 106 | super.setClickable(true); 107 | this.context = context; 108 | mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); 109 | mGestureDetector = new GestureDetector(context, new GestureListener()); 110 | matrix = new Matrix(); 111 | prevMatrix = new Matrix(); 112 | m = new float[9]; 113 | normalizedScale = 1; 114 | if (mScaleType == null) { 115 | mScaleType = ScaleType.FIT_CENTER; 116 | } 117 | minScale = 1; 118 | maxScale = 3; 119 | superMinScale = SUPER_MIN_MULTIPLIER * minScale; 120 | superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; 121 | setImageMatrix(matrix); 122 | setScaleType(ScaleType.MATRIX); 123 | setState(State.NONE); 124 | onDrawReady = false; 125 | super.setOnTouchListener(new PrivateOnTouchListener()); 126 | } 127 | 128 | @Override 129 | public void setOnTouchListener(View.OnTouchListener l) { 130 | userTouchListener = l; 131 | } 132 | 133 | public void setOnTouchImageViewListener(OnTouchImageViewListener l) { 134 | touchImageViewListener = l; 135 | } 136 | 137 | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener l) { 138 | doubleTapListener = l; 139 | } 140 | 141 | @Override 142 | public void setImageResource(int resId) { 143 | super.setImageResource(resId); 144 | savePreviousImageValues(); 145 | fitImageToView(); 146 | } 147 | 148 | @Override 149 | public void setImageBitmap(Bitmap bm) { 150 | super.setImageBitmap(bm); 151 | savePreviousImageValues(); 152 | fitImageToView(); 153 | } 154 | 155 | @Override 156 | public void setImageDrawable(Drawable drawable) { 157 | super.setImageDrawable(drawable); 158 | savePreviousImageValues(); 159 | fitImageToView(); 160 | } 161 | 162 | @Override 163 | public void setImageURI(Uri uri) { 164 | super.setImageURI(uri); 165 | savePreviousImageValues(); 166 | fitImageToView(); 167 | } 168 | 169 | @Override 170 | public void setScaleType(ScaleType type) { 171 | if (type == ScaleType.FIT_START || type == ScaleType.FIT_END) { 172 | throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); 173 | } 174 | if (type == ScaleType.MATRIX) { 175 | super.setScaleType(ScaleType.MATRIX); 176 | 177 | } else { 178 | mScaleType = type; 179 | if (onDrawReady) { 180 | // 181 | // If the image is already rendered, scaleType has been called programmatically 182 | // and the TouchImageView should be updated with the new scaleType. 183 | // 184 | setZoom(this); 185 | } 186 | } 187 | } 188 | 189 | @Override 190 | public ScaleType getScaleType() { 191 | return mScaleType; 192 | } 193 | 194 | /** 195 | * Returns false if image is in initial, unzoomed state. False, otherwise. 196 | * 197 | * @return true if image is zoomed 198 | */ 199 | public boolean isZoomed() { 200 | return normalizedScale != 1; 201 | } 202 | 203 | /** 204 | * Return a Rect representing the zoomed image. 205 | * 206 | * @return rect representing zoomed image 207 | */ 208 | public RectF getZoomedRect() { 209 | if (mScaleType == ScaleType.FIT_XY) { 210 | throw new UnsupportedOperationException("getZoomedRect() not supported with FIT_XY"); 211 | } 212 | PointF topLeft = transformCoordTouchToBitmap(0, 0, true); 213 | PointF bottomRight = transformCoordTouchToBitmap(viewWidth, viewHeight, true); 214 | 215 | float w = getDrawable().getIntrinsicWidth(); 216 | float h = getDrawable().getIntrinsicHeight(); 217 | return new RectF(topLeft.x / w, topLeft.y / h, bottomRight.x / w, bottomRight.y / h); 218 | } 219 | 220 | /** 221 | * Save the current matrix and view dimensions 222 | * in the prevMatrix and prevView variables. 223 | */ 224 | private void savePreviousImageValues() { 225 | if (matrix != null && viewHeight != 0 && viewWidth != 0) { 226 | matrix.getValues(m); 227 | prevMatrix.setValues(m); 228 | prevMatchViewHeight = matchViewHeight; 229 | prevMatchViewWidth = matchViewWidth; 230 | prevViewHeight = viewHeight; 231 | prevViewWidth = viewWidth; 232 | } 233 | } 234 | 235 | @Override 236 | public Parcelable onSaveInstanceState() { 237 | Bundle bundle = new Bundle(); 238 | bundle.putParcelable("instanceState", super.onSaveInstanceState()); 239 | bundle.putFloat("saveScale", normalizedScale); 240 | bundle.putFloat("matchViewHeight", matchViewHeight); 241 | bundle.putFloat("matchViewWidth", matchViewWidth); 242 | bundle.putInt("viewWidth", viewWidth); 243 | bundle.putInt("viewHeight", viewHeight); 244 | matrix.getValues(m); 245 | bundle.putFloatArray("matrix", m); 246 | bundle.putBoolean("imageRendered", imageRenderedAtLeastOnce); 247 | return bundle; 248 | } 249 | 250 | @Override 251 | public void onRestoreInstanceState(Parcelable state) { 252 | if (state instanceof Bundle) { 253 | Bundle bundle = (Bundle) state; 254 | normalizedScale = bundle.getFloat("saveScale"); 255 | m = bundle.getFloatArray("matrix"); 256 | prevMatrix.setValues(m); 257 | prevMatchViewHeight = bundle.getFloat("matchViewHeight"); 258 | prevMatchViewWidth = bundle.getFloat("matchViewWidth"); 259 | prevViewHeight = bundle.getInt("viewHeight"); 260 | prevViewWidth = bundle.getInt("viewWidth"); 261 | imageRenderedAtLeastOnce = bundle.getBoolean("imageRendered"); 262 | super.onRestoreInstanceState(bundle.getParcelable("instanceState")); 263 | return; 264 | } 265 | 266 | super.onRestoreInstanceState(state); 267 | } 268 | 269 | @Override 270 | protected void onDraw(Canvas canvas) { 271 | onDrawReady = true; 272 | imageRenderedAtLeastOnce = true; 273 | if (delayedZoomVariables != null) { 274 | setZoom(delayedZoomVariables.scale, delayedZoomVariables.focusX, delayedZoomVariables.focusY, delayedZoomVariables.scaleType); 275 | delayedZoomVariables = null; 276 | } 277 | super.onDraw(canvas); 278 | } 279 | 280 | @Override 281 | public void onConfigurationChanged(Configuration newConfig) { 282 | super.onConfigurationChanged(newConfig); 283 | savePreviousImageValues(); 284 | } 285 | 286 | /** 287 | * Get the max zoom multiplier. 288 | * 289 | * @return max zoom multiplier. 290 | */ 291 | public float getMaxZoom() { 292 | return maxScale; 293 | } 294 | 295 | /** 296 | * Set the max zoom multiplier. Default value: 3. 297 | * 298 | * @param max max zoom multiplier. 299 | */ 300 | public void setMaxZoom(float max) { 301 | maxScale = max; 302 | superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; 303 | } 304 | 305 | /** 306 | * Get the min zoom multiplier. 307 | * 308 | * @return min zoom multiplier. 309 | */ 310 | public float getMinZoom() { 311 | return minScale; 312 | } 313 | 314 | /** 315 | * Get the current zoom. This is the zoom relative to the initial 316 | * scale, not the original resource. 317 | * 318 | * @return current zoom multiplier. 319 | */ 320 | public float getCurrentZoom() { 321 | return normalizedScale; 322 | } 323 | 324 | /** 325 | * Set the min zoom multiplier. Default value: 1. 326 | * 327 | * @param min min zoom multiplier. 328 | */ 329 | public void setMinZoom(float min) { 330 | minScale = min; 331 | superMinScale = SUPER_MIN_MULTIPLIER * minScale; 332 | } 333 | 334 | /** 335 | * Reset zoom and translation to initial state. 336 | */ 337 | public void resetZoom() { 338 | normalizedScale = 1; 339 | fitImageToView(); 340 | } 341 | 342 | /** 343 | * Set zoom to the specified scale. Image will be centered by default. 344 | * 345 | * @param scale 346 | */ 347 | public void setZoom(float scale) { 348 | setZoom(scale, 0.5f, 0.5f); 349 | } 350 | 351 | /** 352 | * Set zoom to the specified scale. Image will be centered around the point 353 | * (focusX, focusY). These floats range from 0 to 1 and denote the focus point 354 | * as a fraction from the left and top of the view. For example, the top left 355 | * corner of the image would be (0, 0). And the bottom right corner would be (1, 1). 356 | * 357 | * @param scale 358 | * @param focusX 359 | * @param focusY 360 | */ 361 | public void setZoom(float scale, float focusX, float focusY) { 362 | setZoom(scale, focusX, focusY, mScaleType); 363 | } 364 | 365 | /** 366 | * Set zoom to the specified scale. Image will be centered around the point 367 | * (focusX, focusY). These floats range from 0 to 1 and denote the focus point 368 | * as a fraction from the left and top of the view. For example, the top left 369 | * corner of the image would be (0, 0). And the bottom right corner would be (1, 1). 370 | * 371 | * @param scale 372 | * @param focusX 373 | * @param focusY 374 | * @param scaleType 375 | */ 376 | public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) { 377 | // 378 | // setZoom can be called before the image is on the screen, but at this point, 379 | // image and view sizes have not yet been calculated in onMeasure. Thus, we should 380 | // delay calling setZoom until the view has been measured. 381 | // 382 | if (!onDrawReady) { 383 | delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType); 384 | return; 385 | } 386 | 387 | if (scaleType != mScaleType) { 388 | setScaleType(scaleType); 389 | } 390 | resetZoom(); 391 | scaleImage(scale, viewWidth / 2, viewHeight / 2, true); 392 | matrix.getValues(m); 393 | m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f)); 394 | m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f)); 395 | matrix.setValues(m); 396 | fixTrans(); 397 | setImageMatrix(matrix); 398 | } 399 | 400 | /** 401 | * Set zoom parameters equal to another TouchImageView. Including scale, position, 402 | * and ScaleType. 403 | * 404 | * @param TouchImageView 405 | */ 406 | public void setZoom(TouchImageView img) { 407 | PointF center = img.getScrollPosition(); 408 | setZoom(img.getCurrentZoom(), center.x, center.y, img.getScaleType()); 409 | } 410 | 411 | /** 412 | * Return the point at the center of the zoomed image. The PointF coordinates range 413 | * in value between 0 and 1 and the focus point is denoted as a fraction from the left 414 | * and top of the view. For example, the top left corner of the image would be (0, 0). 415 | * And the bottom right corner would be (1, 1). 416 | * 417 | * @return PointF representing the scroll position of the zoomed image. 418 | */ 419 | public PointF getScrollPosition() { 420 | Drawable drawable = getDrawable(); 421 | if (drawable == null) { 422 | return null; 423 | } 424 | int drawableWidth = drawable.getIntrinsicWidth(); 425 | int drawableHeight = drawable.getIntrinsicHeight(); 426 | 427 | PointF point = transformCoordTouchToBitmap(viewWidth / 2, viewHeight / 2, true); 428 | point.x /= drawableWidth; 429 | point.y /= drawableHeight; 430 | return point; 431 | } 432 | 433 | /** 434 | * Set the focus point of the zoomed image. The focus points are denoted as a fraction from the 435 | * left and top of the view. The focus points can range in value between 0 and 1. 436 | * 437 | * @param focusX 438 | * @param focusY 439 | */ 440 | public void setScrollPosition(float focusX, float focusY) { 441 | setZoom(normalizedScale, focusX, focusY); 442 | } 443 | 444 | /** 445 | * Performs boundary checking and fixes the image matrix if it 446 | * is out of bounds. 447 | */ 448 | private void fixTrans() { 449 | matrix.getValues(m); 450 | float transX = m[Matrix.MTRANS_X]; 451 | float transY = m[Matrix.MTRANS_Y]; 452 | 453 | float fixTransX = getFixTrans(transX, viewWidth, getImageWidth()); 454 | float fixTransY = getFixTrans(transY, viewHeight, getImageHeight()); 455 | 456 | if (fixTransX != 0 || fixTransY != 0) { 457 | matrix.postTranslate(fixTransX, fixTransY); 458 | } 459 | } 460 | 461 | /** 462 | * When transitioning from zooming from focus to zoom from center (or vice versa) 463 | * the image can become unaligned within the view. This is apparent when zooming 464 | * quickly. When the content size is less than the view size, the content will often 465 | * be centered incorrectly within the view. fixScaleTrans first calls fixTrans() and 466 | * then makes sure the image is centered correctly within the view. 467 | */ 468 | private void fixScaleTrans() { 469 | fixTrans(); 470 | matrix.getValues(m); 471 | if (getImageWidth() < viewWidth) { 472 | m[Matrix.MTRANS_X] = (viewWidth - getImageWidth()) / 2; 473 | } 474 | 475 | if (getImageHeight() < viewHeight) { 476 | m[Matrix.MTRANS_Y] = (viewHeight - getImageHeight()) / 2; 477 | } 478 | matrix.setValues(m); 479 | } 480 | 481 | private float getFixTrans(float trans, float viewSize, float contentSize) { 482 | float minTrans, maxTrans; 483 | 484 | if (contentSize <= viewSize) { 485 | minTrans = 0; 486 | maxTrans = viewSize - contentSize; 487 | 488 | } else { 489 | minTrans = viewSize - contentSize; 490 | maxTrans = 0; 491 | } 492 | 493 | if (trans < minTrans) 494 | return -trans + minTrans; 495 | if (trans > maxTrans) 496 | return -trans + maxTrans; 497 | return 0; 498 | } 499 | 500 | private float getFixDragTrans(float delta, float viewSize, float contentSize) { 501 | if (contentSize <= viewSize) { 502 | return 0; 503 | } 504 | return delta; 505 | } 506 | 507 | private float getImageWidth() { 508 | return matchViewWidth * normalizedScale; 509 | } 510 | 511 | private float getImageHeight() { 512 | return matchViewHeight * normalizedScale; 513 | } 514 | 515 | @Override 516 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 517 | Drawable drawable = getDrawable(); 518 | if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { 519 | setMeasuredDimension(0, 0); 520 | return; 521 | } 522 | 523 | int drawableWidth = drawable.getIntrinsicWidth(); 524 | int drawableHeight = drawable.getIntrinsicHeight(); 525 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 526 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 527 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 528 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 529 | viewWidth = setViewSize(widthMode, widthSize, drawableWidth); 530 | viewHeight = setViewSize(heightMode, heightSize, drawableHeight); 531 | 532 | // 533 | // Set view dimensions 534 | // 535 | setMeasuredDimension(viewWidth, viewHeight); 536 | 537 | // 538 | // Fit content within view 539 | // 540 | fitImageToView(); 541 | } 542 | 543 | /** 544 | * If the normalizedScale is equal to 1, then the image is made to fit the screen. Otherwise, 545 | * it is made to fit the screen according to the dimensions of the previous image matrix. This 546 | * allows the image to maintain its zoom after rotation. 547 | */ 548 | private void fitImageToView() { 549 | Drawable drawable = getDrawable(); 550 | if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { 551 | return; 552 | } 553 | if (matrix == null || prevMatrix == null) { 554 | return; 555 | } 556 | 557 | int drawableWidth = drawable.getIntrinsicWidth(); 558 | int drawableHeight = drawable.getIntrinsicHeight(); 559 | 560 | // 561 | // Scale image for view 562 | // 563 | float scaleX = (float) viewWidth / drawableWidth; 564 | float scaleY = (float) viewHeight / drawableHeight; 565 | 566 | switch (mScaleType) { 567 | case CENTER: 568 | scaleX = scaleY = 1; 569 | break; 570 | 571 | case CENTER_CROP: 572 | scaleX = scaleY = Math.max(scaleX, scaleY); 573 | break; 574 | 575 | case CENTER_INSIDE: 576 | scaleX = scaleY = Math.min(1, Math.min(scaleX, scaleY)); 577 | 578 | case FIT_CENTER: 579 | scaleX = scaleY = Math.min(scaleX, scaleY); 580 | break; 581 | 582 | case FIT_XY: 583 | break; 584 | 585 | default: 586 | // 587 | // FIT_START and FIT_END not supported 588 | // 589 | throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); 590 | 591 | } 592 | 593 | // 594 | // Center the image 595 | // 596 | float redundantXSpace = viewWidth - (scaleX * drawableWidth); 597 | float redundantYSpace = viewHeight - (scaleY * drawableHeight); 598 | matchViewWidth = viewWidth - redundantXSpace; 599 | matchViewHeight = viewHeight - redundantYSpace; 600 | if (!isZoomed() && !imageRenderedAtLeastOnce) { 601 | // 602 | // Stretch and center image to fit view 603 | // 604 | matrix.setScale(scaleX, scaleY); 605 | matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2); 606 | normalizedScale = 1; 607 | 608 | } else { 609 | // 610 | // These values should never be 0 or we will set viewWidth and viewHeight 611 | // to NaN in translateMatrixAfterRotate. To avoid this, call savePreviousImageValues 612 | // to set them equal to the current values. 613 | // 614 | if (prevMatchViewWidth == 0 || prevMatchViewHeight == 0) { 615 | savePreviousImageValues(); 616 | } 617 | 618 | prevMatrix.getValues(m); 619 | 620 | // 621 | // Rescale Matrix after rotation 622 | // 623 | m[Matrix.MSCALE_X] = matchViewWidth / drawableWidth * normalizedScale; 624 | m[Matrix.MSCALE_Y] = matchViewHeight / drawableHeight * normalizedScale; 625 | 626 | // 627 | // TransX and TransY from previous matrix 628 | // 629 | float transX = m[Matrix.MTRANS_X]; 630 | float transY = m[Matrix.MTRANS_Y]; 631 | 632 | // 633 | // Width 634 | // 635 | float prevActualWidth = prevMatchViewWidth * normalizedScale; 636 | float actualWidth = getImageWidth(); 637 | translateMatrixAfterRotate(Matrix.MTRANS_X, transX, prevActualWidth, actualWidth, prevViewWidth, viewWidth, drawableWidth); 638 | 639 | // 640 | // Height 641 | // 642 | float prevActualHeight = prevMatchViewHeight * normalizedScale; 643 | float actualHeight = getImageHeight(); 644 | translateMatrixAfterRotate(Matrix.MTRANS_Y, transY, prevActualHeight, actualHeight, prevViewHeight, viewHeight, drawableHeight); 645 | 646 | // 647 | // Set the matrix to the adjusted scale and translate values. 648 | // 649 | matrix.setValues(m); 650 | } 651 | fixTrans(); 652 | setImageMatrix(matrix); 653 | } 654 | 655 | /** 656 | * Set view dimensions based on layout params 657 | * 658 | * @param mode 659 | * @param size 660 | * @param drawableWidth 661 | * @return 662 | */ 663 | private int setViewSize(int mode, int size, int drawableWidth) { 664 | int viewSize; 665 | switch (mode) { 666 | case MeasureSpec.EXACTLY: 667 | viewSize = size; 668 | break; 669 | 670 | case MeasureSpec.AT_MOST: 671 | viewSize = Math.min(drawableWidth, size); 672 | break; 673 | 674 | case MeasureSpec.UNSPECIFIED: 675 | viewSize = drawableWidth; 676 | break; 677 | 678 | default: 679 | viewSize = size; 680 | break; 681 | } 682 | return viewSize; 683 | } 684 | 685 | /** 686 | * After rotating, the matrix needs to be translated. This function finds the area of image 687 | * which was previously centered and adjusts translations so that is again the center, post-rotation. 688 | * 689 | * @param axis Matrix.MTRANS_X or Matrix.MTRANS_Y 690 | * @param trans the value of trans in that axis before the rotation 691 | * @param prevImageSize the width/height of the image before the rotation 692 | * @param imageSize width/height of the image after rotation 693 | * @param prevViewSize width/height of view before rotation 694 | * @param viewSize width/height of view after rotation 695 | * @param drawableSize width/height of drawable 696 | */ 697 | private void translateMatrixAfterRotate(int axis, float trans, float prevImageSize, float imageSize, int prevViewSize, int viewSize, int drawableSize) { 698 | if (imageSize < viewSize) { 699 | // 700 | // The width/height of image is less than the view's width/height. Center it. 701 | // 702 | m[axis] = (viewSize - (drawableSize * m[Matrix.MSCALE_X])) * 0.5f; 703 | 704 | } else if (trans > 0) { 705 | // 706 | // The image is larger than the view, but was not before rotation. Center it. 707 | // 708 | m[axis] = -((imageSize - viewSize) * 0.5f); 709 | 710 | } else { 711 | // 712 | // Find the area of the image which was previously centered in the view. Determine its distance 713 | // from the left/top side of the view as a fraction of the entire image's width/height. Use that percentage 714 | // to calculate the trans in the new view width/height. 715 | // 716 | float percentage = (Math.abs(trans) + (0.5f * prevViewSize)) / prevImageSize; 717 | m[axis] = -((percentage * imageSize) - (viewSize * 0.5f)); 718 | } 719 | } 720 | 721 | private void setState(State state) { 722 | this.state = state; 723 | } 724 | 725 | public boolean canScrollHorizontallyFroyo(int direction) { 726 | return canScrollHorizontally(direction); 727 | } 728 | 729 | @Override 730 | public boolean canScrollHorizontally(int direction) { 731 | matrix.getValues(m); 732 | float x = m[Matrix.MTRANS_X]; 733 | 734 | if (getImageWidth() < viewWidth) { 735 | return false; 736 | 737 | } else if (x >= -1 && direction < 0) { 738 | return false; 739 | 740 | } else if (Math.abs(x) + viewWidth + 1 >= getImageWidth() && direction > 0) { 741 | return false; 742 | } 743 | 744 | return true; 745 | } 746 | 747 | /** 748 | * Gesture Listener detects a single click or long click and passes that on 749 | * to the view's listener. 750 | * 751 | * @author Ortiz 752 | */ 753 | private class GestureListener extends GestureDetector.SimpleOnGestureListener { 754 | 755 | @Override 756 | public boolean onSingleTapConfirmed(MotionEvent e) { 757 | if (doubleTapListener != null) { 758 | return doubleTapListener.onSingleTapConfirmed(e); 759 | } 760 | return performClick(); 761 | } 762 | 763 | @Override 764 | public void onLongPress(MotionEvent e) { 765 | performLongClick(); 766 | } 767 | 768 | @Override 769 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 770 | if (fling != null) { 771 | // 772 | // If a previous fling is still active, it should be cancelled so that two flings 773 | // are not run simultaenously. 774 | // 775 | fling.cancelFling(); 776 | } 777 | fling = new Fling((int) velocityX, (int) velocityY); 778 | compatPostOnAnimation(fling); 779 | return super.onFling(e1, e2, velocityX, velocityY); 780 | } 781 | 782 | @Override 783 | public boolean onDoubleTap(MotionEvent e) { 784 | boolean consumed = false; 785 | if (doubleTapListener != null) { 786 | consumed = doubleTapListener.onDoubleTap(e); 787 | } 788 | if (state == State.NONE) { 789 | float targetZoom = (normalizedScale == minScale) ? maxScale : minScale; 790 | DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false); 791 | compatPostOnAnimation(doubleTap); 792 | consumed = true; 793 | } 794 | return consumed; 795 | } 796 | 797 | @Override 798 | public boolean onDoubleTapEvent(MotionEvent e) { 799 | if (doubleTapListener != null) { 800 | return doubleTapListener.onDoubleTapEvent(e); 801 | } 802 | return false; 803 | } 804 | } 805 | 806 | public interface OnTouchImageViewListener { 807 | public void onMove(); 808 | } 809 | 810 | /** 811 | * Responsible for all touch events. Handles the heavy lifting of drag and also sends 812 | * touch events to Scale Detector and Gesture Detector. 813 | * 814 | * @author Ortiz 815 | */ 816 | private class PrivateOnTouchListener implements OnTouchListener { 817 | 818 | // 819 | // Remember last point position for dragging 820 | // 821 | private PointF last = new PointF(); 822 | 823 | @Override 824 | public boolean onTouch(View v, MotionEvent event) { 825 | mScaleDetector.onTouchEvent(event); 826 | mGestureDetector.onTouchEvent(event); 827 | PointF curr = new PointF(event.getX(), event.getY()); 828 | 829 | if (state == State.NONE || state == State.DRAG || state == State.FLING) { 830 | switch (event.getAction()) { 831 | case MotionEvent.ACTION_DOWN: 832 | last.set(curr); 833 | if (fling != null) 834 | fling.cancelFling(); 835 | setState(State.DRAG); 836 | break; 837 | 838 | case MotionEvent.ACTION_MOVE: 839 | if (state == State.DRAG) { 840 | float deltaX = curr.x - last.x; 841 | float deltaY = curr.y - last.y; 842 | float fixTransX = getFixDragTrans(deltaX, viewWidth, getImageWidth()); 843 | float fixTransY = getFixDragTrans(deltaY, viewHeight, getImageHeight()); 844 | matrix.postTranslate(fixTransX, fixTransY); 845 | fixTrans(); 846 | last.set(curr.x, curr.y); 847 | } 848 | break; 849 | 850 | case MotionEvent.ACTION_UP: 851 | case MotionEvent.ACTION_POINTER_UP: 852 | setState(State.NONE); 853 | break; 854 | } 855 | } 856 | 857 | setImageMatrix(matrix); 858 | 859 | // 860 | // User-defined OnTouchListener 861 | // 862 | if (userTouchListener != null) { 863 | userTouchListener.onTouch(v, event); 864 | } 865 | 866 | // 867 | // OnTouchImageViewListener is set: TouchImageView dragged by user. 868 | // 869 | if (touchImageViewListener != null) { 870 | touchImageViewListener.onMove(); 871 | } 872 | 873 | // 874 | // indicate event was handled 875 | // 876 | return true; 877 | } 878 | } 879 | 880 | /** 881 | * ScaleListener detects user two finger scaling and scales image. 882 | * 883 | * @author Ortiz 884 | */ 885 | private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { 886 | @Override 887 | public boolean onScaleBegin(ScaleGestureDetector detector) { 888 | setState(State.ZOOM); 889 | return true; 890 | } 891 | 892 | @Override 893 | public boolean onScale(ScaleGestureDetector detector) { 894 | scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true); 895 | 896 | // 897 | // OnTouchImageViewListener is set: TouchImageView pinch zoomed by user. 898 | // 899 | if (touchImageViewListener != null) { 900 | touchImageViewListener.onMove(); 901 | } 902 | return true; 903 | } 904 | 905 | @Override 906 | public void onScaleEnd(ScaleGestureDetector detector) { 907 | super.onScaleEnd(detector); 908 | setState(State.NONE); 909 | boolean animateToZoomBoundary = false; 910 | float targetZoom = normalizedScale; 911 | if (normalizedScale > maxScale) { 912 | targetZoom = maxScale; 913 | animateToZoomBoundary = true; 914 | 915 | } else if (normalizedScale < minScale) { 916 | targetZoom = minScale; 917 | animateToZoomBoundary = true; 918 | } 919 | 920 | if (animateToZoomBoundary) { 921 | DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true); 922 | compatPostOnAnimation(doubleTap); 923 | } 924 | } 925 | } 926 | 927 | private void scaleImage(double deltaScale, float focusX, float focusY, boolean stretchImageToSuper) { 928 | 929 | float lowerScale, upperScale; 930 | if (stretchImageToSuper) { 931 | lowerScale = superMinScale; 932 | upperScale = superMaxScale; 933 | 934 | } else { 935 | lowerScale = minScale; 936 | upperScale = maxScale; 937 | } 938 | 939 | float origScale = normalizedScale; 940 | normalizedScale *= deltaScale; 941 | if (normalizedScale > upperScale) { 942 | normalizedScale = upperScale; 943 | deltaScale = upperScale / origScale; 944 | } else if (normalizedScale < lowerScale) { 945 | normalizedScale = lowerScale; 946 | deltaScale = lowerScale / origScale; 947 | } 948 | 949 | matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY); 950 | fixScaleTrans(); 951 | } 952 | 953 | /** 954 | * DoubleTapZoom calls a series of runnables which apply 955 | * an animated zoom in/out graphic to the image. 956 | * 957 | * @author Ortiz 958 | */ 959 | private class DoubleTapZoom implements Runnable { 960 | 961 | private long startTime; 962 | private static final float ZOOM_TIME = 500; 963 | private float startZoom, targetZoom; 964 | private float bitmapX, bitmapY; 965 | private boolean stretchImageToSuper; 966 | private AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator(); 967 | private PointF startTouch; 968 | private PointF endTouch; 969 | 970 | DoubleTapZoom(float targetZoom, float focusX, float focusY, boolean stretchImageToSuper) { 971 | setState(State.ANIMATE_ZOOM); 972 | startTime = System.currentTimeMillis(); 973 | this.startZoom = normalizedScale; 974 | this.targetZoom = targetZoom; 975 | this.stretchImageToSuper = stretchImageToSuper; 976 | PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY, false); 977 | this.bitmapX = bitmapPoint.x; 978 | this.bitmapY = bitmapPoint.y; 979 | 980 | // 981 | // Used for translating image during scaling 982 | // 983 | startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY); 984 | endTouch = new PointF(viewWidth / 2, viewHeight / 2); 985 | } 986 | 987 | @Override 988 | public void run() { 989 | float t = interpolate(); 990 | double deltaScale = calculateDeltaScale(t); 991 | scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper); 992 | translateImageToCenterTouchPosition(t); 993 | fixScaleTrans(); 994 | setImageMatrix(matrix); 995 | 996 | // 997 | // OnTouchImageViewListener is set: double tap runnable updates listener 998 | // with every frame. 999 | // 1000 | if (touchImageViewListener != null) { 1001 | touchImageViewListener.onMove(); 1002 | } 1003 | 1004 | if (t < 1f) { 1005 | // 1006 | // We haven't finished zooming 1007 | // 1008 | compatPostOnAnimation(this); 1009 | 1010 | } else { 1011 | // 1012 | // Finished zooming 1013 | // 1014 | setState(State.NONE); 1015 | } 1016 | } 1017 | 1018 | /** 1019 | * Interpolate between where the image should start and end in order to translate 1020 | * the image so that the point that is touched is what ends up centered at the end 1021 | * of the zoom. 1022 | * 1023 | * @param t 1024 | */ 1025 | private void translateImageToCenterTouchPosition(float t) { 1026 | float targetX = startTouch.x + t * (endTouch.x - startTouch.x); 1027 | float targetY = startTouch.y + t * (endTouch.y - startTouch.y); 1028 | PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY); 1029 | matrix.postTranslate(targetX - curr.x, targetY - curr.y); 1030 | } 1031 | 1032 | /** 1033 | * Use interpolator to get t 1034 | * 1035 | * @return 1036 | */ 1037 | private float interpolate() { 1038 | long currTime = System.currentTimeMillis(); 1039 | float elapsed = (currTime - startTime) / ZOOM_TIME; 1040 | elapsed = Math.min(1f, elapsed); 1041 | return interpolator.getInterpolation(elapsed); 1042 | } 1043 | 1044 | /** 1045 | * Interpolate the current targeted zoom and get the delta 1046 | * from the current zoom. 1047 | * 1048 | * @param t 1049 | * @return 1050 | */ 1051 | private double calculateDeltaScale(float t) { 1052 | double zoom = startZoom + t * (targetZoom - startZoom); 1053 | return zoom / normalizedScale; 1054 | } 1055 | } 1056 | 1057 | /** 1058 | * This function will transform the coordinates in the touch event to the coordinate 1059 | * system of the drawable that the imageview contain 1060 | * 1061 | * @param x x-coordinate of touch event 1062 | * @param y y-coordinate of touch event 1063 | * @param clipToBitmap Touch event may occur within view, but outside image content. True, to clip return value 1064 | * to the bounds of the bitmap size. 1065 | * @return Coordinates of the point touched, in the coordinate system of the original drawable. 1066 | */ 1067 | private PointF transformCoordTouchToBitmap(float x, float y, boolean clipToBitmap) { 1068 | matrix.getValues(m); 1069 | float origW = getDrawable().getIntrinsicWidth(); 1070 | float origH = getDrawable().getIntrinsicHeight(); 1071 | float transX = m[Matrix.MTRANS_X]; 1072 | float transY = m[Matrix.MTRANS_Y]; 1073 | float finalX = ((x - transX) * origW) / getImageWidth(); 1074 | float finalY = ((y - transY) * origH) / getImageHeight(); 1075 | 1076 | if (clipToBitmap) { 1077 | finalX = Math.min(Math.max(finalX, 0), origW); 1078 | finalY = Math.min(Math.max(finalY, 0), origH); 1079 | } 1080 | 1081 | return new PointF(finalX, finalY); 1082 | } 1083 | 1084 | /** 1085 | * Inverse of transformCoordTouchToBitmap. This function will transform the coordinates in the 1086 | * drawable's coordinate system to the view's coordinate system. 1087 | * 1088 | * @param bx x-coordinate in original bitmap coordinate system 1089 | * @param by y-coordinate in original bitmap coordinate system 1090 | * @return Coordinates of the point in the view's coordinate system. 1091 | */ 1092 | private PointF transformCoordBitmapToTouch(float bx, float by) { 1093 | matrix.getValues(m); 1094 | float origW = getDrawable().getIntrinsicWidth(); 1095 | float origH = getDrawable().getIntrinsicHeight(); 1096 | float px = bx / origW; 1097 | float py = by / origH; 1098 | float finalX = m[Matrix.MTRANS_X] + getImageWidth() * px; 1099 | float finalY = m[Matrix.MTRANS_Y] + getImageHeight() * py; 1100 | return new PointF(finalX, finalY); 1101 | } 1102 | 1103 | /** 1104 | * Fling launches sequential runnables which apply 1105 | * the fling graphic to the image. The values for the translation 1106 | * are interpolated by the Scroller. 1107 | * 1108 | * @author Ortiz 1109 | */ 1110 | private class Fling implements Runnable { 1111 | 1112 | CompatScroller scroller; 1113 | int currX, currY; 1114 | 1115 | Fling(int velocityX, int velocityY) { 1116 | setState(State.FLING); 1117 | scroller = new CompatScroller(context); 1118 | matrix.getValues(m); 1119 | 1120 | int startX = (int) m[Matrix.MTRANS_X]; 1121 | int startY = (int) m[Matrix.MTRANS_Y]; 1122 | int minX, maxX, minY, maxY; 1123 | 1124 | if (getImageWidth() > viewWidth) { 1125 | minX = viewWidth - (int) getImageWidth(); 1126 | maxX = 0; 1127 | 1128 | } else { 1129 | minX = maxX = startX; 1130 | } 1131 | 1132 | if (getImageHeight() > viewHeight) { 1133 | minY = viewHeight - (int) getImageHeight(); 1134 | maxY = 0; 1135 | 1136 | } else { 1137 | minY = maxY = startY; 1138 | } 1139 | 1140 | scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX, 1141 | maxX, minY, maxY); 1142 | currX = startX; 1143 | currY = startY; 1144 | } 1145 | 1146 | public void cancelFling() { 1147 | if (scroller != null) { 1148 | setState(State.NONE); 1149 | scroller.forceFinished(true); 1150 | } 1151 | } 1152 | 1153 | @Override 1154 | public void run() { 1155 | 1156 | // 1157 | // OnTouchImageViewListener is set: TouchImageView listener has been flung by user. 1158 | // Listener runnable updated with each frame of fling animation. 1159 | // 1160 | if (touchImageViewListener != null) { 1161 | touchImageViewListener.onMove(); 1162 | } 1163 | 1164 | if (scroller.isFinished()) { 1165 | scroller = null; 1166 | return; 1167 | } 1168 | 1169 | if (scroller.computeScrollOffset()) { 1170 | int newX = scroller.getCurrX(); 1171 | int newY = scroller.getCurrY(); 1172 | int transX = newX - currX; 1173 | int transY = newY - currY; 1174 | currX = newX; 1175 | currY = newY; 1176 | matrix.postTranslate(transX, transY); 1177 | fixTrans(); 1178 | setImageMatrix(matrix); 1179 | compatPostOnAnimation(this); 1180 | } 1181 | } 1182 | } 1183 | 1184 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 1185 | private class CompatScroller { 1186 | Scroller scroller; 1187 | OverScroller overScroller; 1188 | boolean isPreGingerbread; 1189 | 1190 | public CompatScroller(Context context) { 1191 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { 1192 | isPreGingerbread = true; 1193 | scroller = new Scroller(context); 1194 | 1195 | } else { 1196 | isPreGingerbread = false; 1197 | overScroller = new OverScroller(context); 1198 | } 1199 | } 1200 | 1201 | public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { 1202 | if (isPreGingerbread) { 1203 | scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); 1204 | } else { 1205 | overScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); 1206 | } 1207 | } 1208 | 1209 | public void forceFinished(boolean finished) { 1210 | if (isPreGingerbread) { 1211 | scroller.forceFinished(finished); 1212 | } else { 1213 | overScroller.forceFinished(finished); 1214 | } 1215 | } 1216 | 1217 | public boolean isFinished() { 1218 | if (isPreGingerbread) { 1219 | return scroller.isFinished(); 1220 | } else { 1221 | return overScroller.isFinished(); 1222 | } 1223 | } 1224 | 1225 | public boolean computeScrollOffset() { 1226 | if (isPreGingerbread) { 1227 | return scroller.computeScrollOffset(); 1228 | } else { 1229 | overScroller.computeScrollOffset(); 1230 | return overScroller.computeScrollOffset(); 1231 | } 1232 | } 1233 | 1234 | public int getCurrX() { 1235 | if (isPreGingerbread) { 1236 | return scroller.getCurrX(); 1237 | } else { 1238 | return overScroller.getCurrX(); 1239 | } 1240 | } 1241 | 1242 | public int getCurrY() { 1243 | if (isPreGingerbread) { 1244 | return scroller.getCurrY(); 1245 | } else { 1246 | return overScroller.getCurrY(); 1247 | } 1248 | } 1249 | } 1250 | 1251 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 1252 | private void compatPostOnAnimation(Runnable runnable) { 1253 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 1254 | postOnAnimation(runnable); 1255 | 1256 | } else { 1257 | postDelayed(runnable, 1000 / 60); 1258 | } 1259 | } 1260 | 1261 | private class ZoomVariables { 1262 | public float scale; 1263 | public float focusX; 1264 | public float focusY; 1265 | public ScaleType scaleType; 1266 | 1267 | public ZoomVariables(float scale, float focusX, float focusY, ScaleType scaleType) { 1268 | this.scale = scale; 1269 | this.focusX = focusX; 1270 | this.focusY = focusY; 1271 | this.scaleType = scaleType; 1272 | } 1273 | } 1274 | 1275 | private void printMatrixInfo() { 1276 | float[] n = new float[9]; 1277 | matrix.getValues(n); 1278 | Log.d(DEBUG, "Scale: " + n[Matrix.MSCALE_X] + " TransX: " + n[Matrix.MTRANS_X] + " TransY: " + n[Matrix.MTRANS_Y]); 1279 | } 1280 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 23 | 24 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main2.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/second_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | GlideImageview 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/master/demo/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.master.demo; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Apr 29 14:22:45 IST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | # <> 65 | JAVA_HOME="/home/hb/android-studio/jre" 66 | # Determine the Java command to use to start the JVM. 67 | if [ -n "$JAVA_HOME" ] ; then 68 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 69 | # IBM's JDK on AIX uses strange locations for the executables 70 | JAVACMD="$JAVA_HOME/jre/sh/java" 71 | else 72 | JAVACMD="$JAVA_HOME/bin/java" 73 | fi 74 | if [ ! -x "$JAVACMD" ] ; then 75 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 76 | 77 | Please set the JAVA_HOME variable in your environment to match the 78 | location of your Java installation." 79 | fi 80 | else 81 | JAVACMD="java" 82 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 83 | 84 | Please set the JAVA_HOME variable in your environment to match the 85 | location of your Java installation." 86 | fi 87 | 88 | # Increase the maximum file descriptors if we can. 89 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 90 | MAX_FD_LIMIT=`ulimit -H -n` 91 | if [ $? -eq 0 ] ; then 92 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 93 | MAX_FD="$MAX_FD_LIMIT" 94 | fi 95 | ulimit -n $MAX_FD 96 | if [ $? -ne 0 ] ; then 97 | warn "Could not set maximum file descriptor limit: $MAX_FD" 98 | fi 99 | else 100 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 101 | fi 102 | fi 103 | 104 | # For Darwin, add options to specify how the application appears in the dock 105 | if $darwin; then 106 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 107 | fi 108 | 109 | # For Cygwin, switch paths to Windows format before running java 110 | if $cygwin ; then 111 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 112 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 113 | JAVACMD=`cygpath --unix "$JAVACMD"` 114 | 115 | # We build the pattern for arguments to be converted via cygpath 116 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 117 | SEP="" 118 | for dir in $ROOTDIRSRAW ; do 119 | ROOTDIRS="$ROOTDIRS$SEP$dir" 120 | SEP="|" 121 | done 122 | OURCYGPATTERN="(^($ROOTDIRS))" 123 | # Add a user-defined pattern to the cygpath arguments 124 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 125 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 126 | fi 127 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 128 | i=0 129 | for arg in "$@" ; do 130 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 131 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 132 | 133 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 134 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 135 | else 136 | eval `echo args$i`="\"$arg\"" 137 | fi 138 | i=$((i+1)) 139 | done 140 | case $i in 141 | (0) set -- ;; 142 | (1) set -- "$args0" ;; 143 | (2) set -- "$args0" "$args1" ;; 144 | (3) set -- "$args0" "$args1" "$args2" ;; 145 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 146 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 147 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 148 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 149 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 150 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 151 | esac 152 | fi 153 | 154 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 155 | function splitJvmOpts() { 156 | JVM_OPTS=("$@") 157 | } 158 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 159 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 160 | 161 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 162 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | ext { 3 | PUBLISH_GROUP_ID = 'com.master.android' 4 | PUBLISH_ARTIFACT_ID = 'glideimageview' 5 | PUBLISH_VERSION = '2.1' 6 | } 7 | android { 8 | compileSdkVersion 25 9 | buildToolsVersion "25.0.2" 10 | 11 | defaultConfig { 12 | minSdkVersion 15 13 | targetSdkVersion 25 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 18 | 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | compile fileTree(dir: 'libs', include: ['*.jar']) 30 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 31 | exclude group: 'com.android.support', module: 'support-annotations' 32 | }) 33 | compile 'com.android.support:appcompat-v7:25.3.1' 34 | compile 'com.github.bumptech.glide:glide:3.8.0' 35 | testCompile 'junit:junit:4.12' 36 | } 37 | 38 | //./gradlew clean build generateRelease 39 | apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle' -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/hb/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/master/glideimageview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.master.glideimageview; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.master.glideimageview.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /library/src/main/java/com/master/glideimageview/GlideImageView.java: -------------------------------------------------------------------------------- 1 | package com.master.glideimageview; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.net.Uri; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.widget.AppCompatImageView; 9 | import android.util.AttributeSet; 10 | import android.view.Gravity; 11 | import android.view.ViewGroup; 12 | import android.view.ViewParent; 13 | import android.widget.FrameLayout; 14 | import android.widget.ProgressBar; 15 | 16 | import com.bumptech.glide.Glide; 17 | import com.bumptech.glide.RequestManager; 18 | import com.bumptech.glide.load.resource.drawable.GlideDrawable; 19 | import com.bumptech.glide.request.RequestListener; 20 | import com.bumptech.glide.request.target.Target; 21 | 22 | import java.io.File; 23 | import java.net.URL; 24 | 25 | /** 26 | * Developed By Pankaj Sharma 27 | * https://github.com/pankaj89/ 28 | *

29 | * GlideImageView used to show progress bar while loading image from server. 30 | */ 31 | public class GlideImageView extends AppCompatImageView implements RequestListener { 32 | 33 | ProgressBar progressBar; 34 | private boolean showProgressBar; 35 | 36 | private int errorRes; 37 | private Application applicationContext; 38 | 39 | public void setApplicationContext(Application applicationContext) { 40 | this.applicationContext = applicationContext; 41 | } 42 | 43 | private int placeHolderRes; 44 | 45 | public GlideImageView(Context context) { 46 | super(context); 47 | } 48 | 49 | public GlideImageView(Context context, @Nullable AttributeSet attrs) { 50 | super(context, attrs); 51 | init(attrs); 52 | } 53 | 54 | public GlideImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 55 | super(context, attrs, defStyleAttr); 56 | init(attrs); 57 | } 58 | 59 | private void init(AttributeSet attrs) { 60 | 61 | final TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.GlideImageView); 62 | if (typedArray.hasValue(R.styleable.GlideImageView_show_progress)) { 63 | showProgressBar = typedArray.getBoolean(R.styleable.GlideImageView_show_progress, true); 64 | // int style = typedArray.getResourceId(R.styleable.GlideImageView_progress_bar_style, android.R.attr.progressBarStyleSmall); 65 | progressBar = new ProgressBar(getContext(), attrs, android.R.attr.progressBarStyleSmall); 66 | // progressBar.setVisibility(GONE); 67 | } 68 | 69 | if (typedArray.hasValue(R.styleable.GlideImageView_error_res)) { 70 | errorRes = typedArray.getResourceId(R.styleable.GlideImageView_error_res, 0); 71 | } 72 | if (typedArray.hasValue(R.styleable.GlideImageView_placeholder_res)) { 73 | placeHolderRes = typedArray.getResourceId(R.styleable.GlideImageView_placeholder_res, 0); 74 | } 75 | } 76 | 77 | public void setErrorRes(int errorRes) { 78 | this.errorRes = errorRes; 79 | } 80 | 81 | public void setPlaceHolderRes(int placeHolderRes) { 82 | this.placeHolderRes = placeHolderRes; 83 | } 84 | 85 | @Override 86 | protected void onAttachedToWindow() { 87 | super.onAttachedToWindow(); 88 | if (showProgressBar && progressBar.getParent() == null) { 89 | ViewParent viewGroupParent = getParent(); 90 | if (viewGroupParent != null && viewGroupParent instanceof ViewGroup) { 91 | 92 | ViewGroup parent = (ViewGroup) viewGroupParent; 93 | 94 | if (!(parent instanceof FrameLayout) || (getLayoutParams().width != FrameLayout.LayoutParams.MATCH_PARENT) 95 | || 96 | (getLayoutParams().height != FrameLayout.LayoutParams.MATCH_PARENT)) { 97 | 98 | FrameLayout frameLayout = new FrameLayout(getContext()); 99 | int position = parent.indexOfChild(this); 100 | 101 | parent.removeView(this); 102 | frameLayout.addView(this); 103 | 104 | progressBar.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); 105 | frameLayout.addView(progressBar); 106 | frameLayout.setLayoutParams(getLayoutParams()); 107 | parent.addView(frameLayout, position); 108 | 109 | } else { 110 | int position = parent.indexOfChild(this); 111 | parent.addView(progressBar, position + 1); 112 | ViewGroup.LayoutParams layoutParams = progressBar.getLayoutParams(); 113 | layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; 114 | layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT; 115 | if (layoutParams instanceof FrameLayout.LayoutParams) 116 | ((FrameLayout.LayoutParams) layoutParams).gravity = Gravity.CENTER; 117 | } 118 | // } 119 | } 120 | } 121 | } 122 | 123 | static RequestManager glide; 124 | 125 | public static RequestManager getGlide(Context context) { 126 | if (glide == null) { 127 | glide = Glide.with(context); 128 | } 129 | return glide; 130 | } 131 | 132 | public void loadImageUrl(String stringUrl) { 133 | if (progressBar != null) 134 | progressBar.setVisibility(VISIBLE); 135 | getGlide(getMyContext()).load(stringUrl).placeholder(placeHolderRes).error(errorRes).listener(this).dontAnimate().into(this); 136 | } 137 | 138 | public void load(String string) { 139 | if (progressBar != null) 140 | progressBar.setVisibility(VISIBLE); 141 | getGlide(getMyContext()).load(string).placeholder(placeHolderRes).error(errorRes).listener(this).dontAnimate().into(this); 142 | } 143 | 144 | private Context getMyContext() { 145 | if (applicationContext != null) return applicationContext; 146 | return getContext(); 147 | } 148 | 149 | public void load(Uri uri) { 150 | if (progressBar != null) 151 | progressBar.setVisibility(VISIBLE); 152 | getGlide(getMyContext()).load(uri).placeholder(placeHolderRes).error(errorRes).listener(new RequestListener() { 153 | @Override 154 | public boolean onException(Exception e, Uri model, Target target, boolean isFirstResource) { 155 | hideProgress(); 156 | return false; 157 | } 158 | 159 | @Override 160 | public boolean onResourceReady(GlideDrawable resource, Uri model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { 161 | hideProgress(); 162 | return false; 163 | } 164 | }).dontAnimate().into(this); 165 | } 166 | 167 | public void load(File file) { 168 | if (progressBar != null) 169 | progressBar.setVisibility(VISIBLE); 170 | getGlide(getMyContext()).load(file).placeholder(placeHolderRes).error(errorRes).listener(new RequestListener() { 171 | @Override 172 | public boolean onException(Exception e, File model, Target target, boolean isFirstResource) { 173 | hideProgress(); 174 | return false; 175 | } 176 | 177 | @Override 178 | public boolean onResourceReady(GlideDrawable resource, File model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { 179 | hideProgress(); 180 | return false; 181 | } 182 | }).dontAnimate().into(this); 183 | } 184 | 185 | public void load(Integer resourceId) { 186 | if (progressBar != null) 187 | progressBar.setVisibility(VISIBLE); 188 | getGlide(getMyContext()).load(resourceId).placeholder(placeHolderRes).error(errorRes).listener(new RequestListener() { 189 | @Override 190 | public boolean onException(Exception e, Integer model, Target target, boolean isFirstResource) { 191 | hideProgress(); 192 | return false; 193 | } 194 | 195 | @Override 196 | public boolean onResourceReady(GlideDrawable resource, Integer model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { 197 | hideProgress(); 198 | return false; 199 | } 200 | }).dontAnimate().into(this); 201 | } 202 | 203 | public void load(URL url) { 204 | if (progressBar != null) 205 | progressBar.setVisibility(VISIBLE); 206 | getGlide(getMyContext()).load(url).placeholder(placeHolderRes).error(errorRes).listener(new RequestListener() { 207 | @Override 208 | public boolean onException(Exception e, URL model, Target target, boolean isFirstResource) { 209 | hideProgress(); 210 | return false; 211 | } 212 | 213 | @Override 214 | public boolean onResourceReady(GlideDrawable resource, URL model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { 215 | hideProgress(); 216 | return false; 217 | } 218 | }).dontAnimate().into(this); 219 | } 220 | 221 | public void load(byte[] model, final String id) { 222 | if (progressBar != null) 223 | progressBar.setVisibility(VISIBLE); 224 | getGlide(getMyContext()).load(model, id).placeholder(placeHolderRes).error(errorRes).listener(new RequestListener() { 225 | @Override 226 | public boolean onException(Exception e, byte[] model, Target target, boolean isFirstResource) { 227 | return false; 228 | } 229 | 230 | @Override 231 | public boolean onResourceReady(GlideDrawable resource, byte[] model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { 232 | return false; 233 | } 234 | }).dontAnimate().into(this); 235 | } 236 | 237 | public void load(byte[] model) { 238 | if (progressBar != null) 239 | progressBar.setVisibility(VISIBLE); 240 | getGlide(getMyContext()).load(model).placeholder(placeHolderRes).error(errorRes).listener(new RequestListener() { 241 | @Override 242 | public boolean onException(Exception e, byte[] model, Target target, boolean isFirstResource) { 243 | hideProgress(); 244 | return false; 245 | } 246 | 247 | @Override 248 | public boolean onResourceReady(GlideDrawable resource, byte[] model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { 249 | hideProgress(); 250 | return false; 251 | } 252 | }).dontAnimate().into(this); 253 | } 254 | 255 | 256 | @Override 257 | public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { 258 | if (progressBar != null) 259 | progressBar.setVisibility(GONE); 260 | return false; 261 | } 262 | 263 | @Override 264 | public boolean onResourceReady(GlideDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { 265 | if (progressBar != null) 266 | progressBar.setVisibility(GONE); 267 | return false; 268 | } 269 | 270 | public void hideProgress() { 271 | if (progressBar != null) 272 | progressBar.setVisibility(GONE); 273 | } 274 | 275 | public void showProgress() { 276 | if (progressBar != null) 277 | progressBar.setVisibility(VISIBLE); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /library/src/main/main.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/library/src/main/main.zip -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxxhdpi/no_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankaj89/GlideImageView/5fccdc9379bae17d1300eb44c545d9d37f728372/library/src/main/res/drawable-xxxhdpi/no_image.png -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | library 3 | 4 | -------------------------------------------------------------------------------- /library/src/test/java/com/master/glideimageview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.master.glideimageview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------