├── .classpath ├── .gitignore ├── .gitmodules ├── .idea ├── .name ├── ant.xml ├── artifacts │ └── imageviewex.xml ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml ├── uiDesigner.xml └── vcs.xml ├── .project ├── .settings ├── org.eclipse.jdt.core.prefs └── org.hibernate.eclipse.console.prefs ├── AndroidManifest.xml ├── ImageViewEx-module.iml ├── ImageViewEx.iml ├── README.md ├── libs └── android-support-v4.jar ├── lint.xml ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png └── values │ ├── attribs.xml │ └── strings.xml ├── src └── net │ └── frakbot │ ├── cache │ └── CacheHelper.java │ ├── imageviewex │ ├── Converters.java │ ├── ImageAlign.java │ ├── ImageViewEx.java │ ├── ImageViewNext.java │ ├── broadcastreceiver │ │ └── ConnectivityChangeBroadcastReceiver.java │ ├── listener │ │ └── ImageViewExRequestListener.java │ ├── operation │ │ ├── ImageDiskCacheOperation.java │ │ ├── ImageDownloadOperation.java │ │ └── ImageMemCacheOperation.java │ ├── requestmanager │ │ ├── ImageViewExRequestFactory.java │ │ └── ImageViewExRequestManager.java │ └── service │ │ └── ImageViewExService.java │ └── remote │ └── RemoteHelper.java └── test ├── .classpath ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── AndroidManifest.xml ├── ImageViewEx-Test.iml ├── assets ├── Awake.Finale.png ├── Episodes_thumb.png ├── Lost_anim.gif ├── Lost_thumb.png ├── Simpsons_anim.gif ├── himym-banner.jpg └── suicidiosenzafronzoli.gif ├── lint.xml ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ ├── empty_newsthumb.png │ ├── ic_launcher.png │ ├── loader_0.png │ ├── loader_1.png │ ├── loader_2.png │ ├── loader_3.png │ ├── loader_4.png │ ├── loader_5.png │ ├── loader_6.png │ └── loader_7.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable │ └── loader.xml ├── layout │ ├── main.xml │ └── next.xml └── values │ └── strings.xml └── src └── net └── frakbot └── imageviewex └── test ├── ImageViewExActivity.java └── ImageViewNextActivity.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /gen 3 | /out 4 | /src/com/github/ignition 5 | /test/bin 6 | /test/gen 7 | /.idea 8 | /local.properties 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submods/DataDroid"] 2 | path = submods/DataDroid 3 | url = git://github.com/foxykeep/DataDroid.git 4 | [submodule "submods/DiskLruCache"] 5 | path = submods/DiskLruCache 6 | url = git://github.com/JakeWharton/DiskLruCache.git 7 | [submodule "submods/okhttp"] 8 | path = submods/okhttp 9 | url = git://github.com/square/okhttp.git 10 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ImageViewEx -------------------------------------------------------------------------------- /.idea/ant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/artifacts/imageviewex.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/production/ 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 42 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 40 | 41 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ImageViewEx 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 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /.settings/org.hibernate.eclipse.console.prefs: -------------------------------------------------------------------------------- 1 | default.configuration= 2 | eclipse.preferences.version=1 3 | hibernate3.enabled=false 4 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ImageViewEx-module.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ImageViewEx.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageViewEx [DEPRECATED] 2 | 3 | **This library has been deprecated. Please consider using [square/picasso](https://github.com/square/picasso) instead.** 4 | 5 | --- 6 | 7 | Extension of Android's ImageView that supports animated GIFs and includes a better density management. 8 | 9 | ## Author 10 | 11 | **Francesco Pontillo** and **Sebastiano Poggi** 12 | 13 | ## Description 14 | 15 | The **ImageViewEx** is an extension of the standard Android `ImageView` that fills in one of the biggest gaps in the standard `ImageView`: displaying **animated GIFs**. 16 | 17 | The Android Framework `Drawable` class, in fact, only supports static GIFs. This View is able to receive a raw image in `byte[]` form, bypassing the standard `Drawable`s handling and thus providing broader support. 18 | 19 | The `ImageViewEx` also allows you to specify what has to be considered the default image density when loading images from raw data. Android defaults to considering `mdpi` as the baseline, but using `setInDensity` you can choose to change it to whatever value you like (we suggest to stick to standard abstracted density buckets like `hdpi` thou). 20 | 21 | The following is a brief documentation of the classes, methods and views included in this library. 22 | 23 | ## Index 24 | 25 | 1. [Import and usage](#import) 26 | 2. [ImageViewEx](#imageviewex) 27 | * [Animated GIF](#animated-gifs) 28 | * [Conditional animation](#conditional-animation) 29 | * [Density level](#density-level) 30 | * [Fill direction](#fillDirection) 31 | * [Regular behavior](#regular-behavior) 32 | * [Example of use](#imageviewex-example) 33 | 3. [ImageViewNext](#imageviewnext) 34 | * [Remote loading and caching of images](#remote-loading) 35 | * [Loading and Error Drawables](#loading-error-drawables) 36 | * [Getting images from the Internet](#getting-from-internet) 37 | * [Handling network failures](#network-failures) 38 | * [Maximum number of threads](#thread-number) 39 | * [Example of use](#imageviewnext-example) 40 | 4. [Known issues and workarounds](#issues-workarounds) 41 | 5. [Some boring stuff](#boring-stuff) 42 | 6. [Version history](#history) 43 | 7. [License](#license) 44 | 45 | 46 | ## Import and usage 47 | 48 | This library requires Android **API level 8** (Android 2.2) as minimum, and targets the Android **API level 17**. 49 | 50 | You need to include in your destination project: 51 | 52 | * [JakeWharton/DiskLruCache](https://github.com/JakeWharton/DiskLruCache) library, used for caching on disk. 53 | * [foxykeep/DataDroid](https://github.com/foxykeep/DataDroid) library, used for handling async operations. 54 | * [square/okhttp](https://github.com/square/okhttp) library, for a better connection management. 55 | 56 | The Eclipse project included specifies this is a library project, although it provides two basic Activities for testing the extended `ImageView`s provided. 57 | 58 | For your application, you need to include the permissions specified in the AndroidManifest of the library, which are: 59 | 60 | * `android.permission.INTERNET` for getting images on the internet 61 | * `android.permission.ACCESS_NETWORK_STATE` to monitor the network state 62 | * `android.permission.WRITE_EXTERNAL_STORAGE` for making the cache access and write the SD card 63 | 64 | The `ImageViewExService` service is also internally used by `ImageViewNext` for handling asynchronous operation. You need to declare this service in your `AndroidManifest.xml`: 65 | 66 | ```xml 67 | 68 | ``` 69 | 70 | 71 | ## ImageViewEx 72 | 73 | `ImageViewEx` is an extended `ImageView` that supports some additional methods for your every-day life. 74 | 75 | 76 | ### Animated GIF 77 | 78 | The best thing about `ImageViewEx` is its **automatic handling of animated GIF images** starting from a simple `byte[]`. 79 | 80 | Simply call `img.setSource(mGIF)` and see your GIF animating. Note that there may be some issues under some conditions (see [Known issues and workarounds](#known-issues-and-workarounds)). 81 | 82 | What if you don't know if an image is a GIF or a regular one? No problem, simply call `setSource` and `ImageViewEx` will do the rest, displaying your image as a regular one or an animated GIF when necessary. 83 | 84 | Accessory methods are: 85 | 86 | * `void setFramesDuration(int duration)` to set the duration, in milliseconds, of each frame during the GIF animation (it is the refresh period) 87 | * `void setFPS(float fps)` to set the number of frames per second during the GIF animation 88 | * `boolean isPlaying()` to know if your GIF is playing 89 | * `boolean canPlay()` to know if your source set by `setSource` was an animated GIF after all 90 | * `int getFramesDuration()` to get the frame duration, in milliseconds 91 | * `float getFPS()` to get the number of frames per second during the GIF animation 92 | * `void play()` to start the GIF, if it hasn't started yet. 93 | * `void pause()` to pause the GIF, if it has started 94 | * `void stop()` to stop playing the GIF, if it has started 95 | 96 | 97 | ### Conditional animation 98 | 99 | As mentioned earlier, you may not want to animate some GIF under some conditions. 100 | 101 | So we've provided you with a **conditional method** that gets triggered just before each animation begins, `boolean canAnimate()`. This method should be overridden by your custom implementation. By default, it always returns `true`. This method decides whether animations can be started for this instance of `ImageViewEx`. 102 | 103 | If you don't want to have another class extending `ImageViewEx` and your `canAnimate()` returns the same value throughout your application, you can use the following 104 | 105 | ```java 106 | ImageViewNext.setCanAlwaysAnimate(false); 107 | ``` 108 | 109 | to specify you never want to animate GIFs. If you don't set any value to `setCanAlwaysAnimate`, it defaults to `true`. The result you get by setting the value to `false` is that it will stop all animations, no matter what `canAnimate()` returns. 110 | 111 | You can check the current behavior by calling the `static boolean getCanAlwaysAnimate()` method. 112 | 113 | 114 | ### Density Level 115 | 116 | You can set a **specific density to simulate** for every instance of `ImageViewEx` by using the following methods: 117 | 118 | * `static void setClassLevelDensity(int classLevelDensity)` to set a specific density for every image 119 | * `static void removeClassLevelDensity()` to remove the class-level customization 120 | * `static boolean isClassLevelDensitySet()`, checks if a class-level density has been set 121 | * `static int getClassLevelDensity()`, gets the set class-level density, or null if none has been set 122 | 123 | You can even set a density for just one of your `ImageViewEx`s: 124 | 125 | * `void setDensity(int fixedDensity)`, to set the density for a particular instance of `ImageViewEx` 126 | * `int getDensity()`, gets the set density for a particular instance of `ImageViewEx` (an instance-level density has higher priority over a class-level density) 127 | * `void dontOverrideDensity()`, restores the regular density of the `ImageViewEx` 128 | 129 | 130 | ### Fill direction 131 | The `ImageViewEx` has one unique feature: it allows you to decide which direction to fit the image on, and then resize 132 | the other dimension (this function implies `adjustViewBounds`) to show the scaled image. For example, you can use this 133 | to show a banner which fills the whole horizontal available space (the `ImageViewEx` has 134 | `android:layout_width="match_parent"` and `android:layout_height="wrap_content"`, plus 135 | `android:adjustViewBounds="true"`). 136 | Setting `fillDirection="horizontal"` will prioritize filling the available horizontal space while keeping the image 137 | aspect ratio and expanding the `ImageViewEx` to fit the height. 138 | 139 | 140 | ### Regular behavior 141 | 142 | `ImageViewEx` is, after all, a regular `ImageView`, so you can go ahead and use its regular methods: 143 | 144 | * `void setImageResource(int resId)` 145 | * `void setImageDrawable(Drawable drawable)` 146 | * `void setImageBitmap(Bitmap bm)` 147 | * and so on. 148 | 149 | 150 | ### Example of use 151 | 152 | ```java 153 | // Disables animation, behaving like a regular ImageView, 154 | // except you can still set byte[] as the source 155 | ImageViewEx.setCanAlwaysAnimate(false); 156 | 157 | // Sets a default density for all of the images in each ImageViewEx. 158 | ImageViewEx.setClassLevelDensity(DisplayMetrics.DENSITY_MEDIUM); 159 | 160 | // Sets a density for the img1 only. 161 | // Changing the density after an object has been set will 162 | // do nothing, you will have to re-set the object. 163 | img1.setInDensity(DisplayMetrics.DENSITY_LOW); 164 | 165 | img1.setSource(Converters.assetToByteArray(getAssets(), "image.png")); 166 | img2.setSource(Converters.assetToByteArray(getAssets(), "animated_image.gif")); 167 | ``` 168 | 169 | 170 | ## ImageViewNext 171 | 172 | `ImageViewExService` is used by `ImageViewNext`, an extension of `ImageViewEx` that handles **downloading, displaying and caching of images (and animated GIFs, of course)**. 173 | 174 | `ImageViewNext` extends `ImageViewEx`, thus supporting all of its methods, plus some more. 175 | 176 | 177 | ### Remote loading and caching of images 178 | 179 | `ImageViewNext` uses `ImageViewExService` and some DataDroid `Operation`s to retrieve images from a two-level cache and the internet and set them into your `ImageViewNext`. 180 | 181 | `ImageViewNext` takes care of instantiating the cache to some default values, which can be overridden/read by using the following `static` methods (pretty self-explanatory, read the JavaDoc for more information about them): 182 | 183 | * `getMemCache()` 184 | * `getDiskCache()` 185 | * `getMemCacheSize()` 186 | * `setMemCacheSize(int memCacheSize)` 187 | * `getAppVersion()` 188 | * `setAppVersion(int appVersion)` 189 | * `getDiskCacheSize()` 190 | * `setDiskCacheSize(int diskCacheSize)` 191 | 192 | 193 | ### Loading and Error Drawables 194 | 195 | `ImageViewNext` supports loading and error `Drawable`s: 196 | 197 | * `static void setClassLoadingDrawable(int classLoadingDrawableResId)` sets a `Drawable` for every instance of `ImageViewNext` from the resources to be displayed (and animated, if it's an `AnimatedDrawable`) as soon as the caching tells us there's no in-memory reference for the asked resource. If you have enabled a disk cache, this `Drawable` will be set before fetching the disk memory. 198 | * `void setLoadingDrawable(Drawable loadingDrawable)` sets a `Drawable` for the current instance of `ImageViewNext` from the resources to be displayed (and animated, if it's an `AnimatedDrawable`) as soon as the caching tells us there's no in-memory reference for the asked resource. If you have enabled a disk cache, this `Drawable` will be set before fetching the disk memory. 199 | * `static void setClassErrorDrawable(int classErrorDrawableResId)` sets a `Drawable` for every instance of `ImageViewNext` from the resources to be displayed (and animated, if it's an `AnimatedDrawable`) as soon as the RemoteLoader returns an error, not being able to retrieve the image. 200 | * `void setErrorDrawable(Drawable errorDrawable)` sets a `Drawable` for the current instance of of `ImageViewNext` from the resources to be displayed (and animated, if it's an `AnimatedDrawable`) as soon as the RemoteLoader returns an error, not being able to retrieve the image. 201 | * `Drawable getLoadingDrawable()` returns the `Drawable` to be displayed while waiting for long-running operations. 202 | * `Drawable getErrorDrawable()` returns the `Drawable` to be displayed in case of an error. 203 | 204 | 205 | ### Getting images from the Internet 206 | 207 | In order to get images from the Internet, simply call `setUrl(String url)` to start retrieving an image from the internet or the caches. 208 | 209 | `ImageViewNext` can be overridden in order to do some custom operations in the following methods: 210 | 211 | * `void onMemCacheHit(byte[] image)` is called as soon as there's a memory cache hit for the requested URL 212 | * `void onMemCacheMiss()` is called as soon as there's a memory cache miss for the requested URL 213 | * `void onDiskCacheHit(byte[] image)` is called as soon as there's a disk cache hit for the requested URL 214 | * `void onDiskCacheMiss()` is called as soon as there's a disk cache miss for the requested URL 215 | * `void onNetworkHit(byte[] image)` is called as soon as there's a network hit for the requested URL 216 | * `void onNetworkMiss()` is called as soon as there's a network miss for the requested URL 217 | * `void onMiss()` is called when an error occurs or the resource can't be found anywhere 218 | * `void onSuccess(byte[] image)` is automatically called after the image has been retrieved 219 | 220 | You should not worry about setting images, as this is handled by `ImageViewNext` itself , which by defaults sets the loading image when there's a memory miss (on `onMemCacheMiss()`), an error one in case of error (`onMiss()`) and the retrieved image in case of success (`onSuccess(byte[] image)`). 221 | 222 | If you override `ImageViewNext`, always call the default implementation of these methods. 223 | 224 | 225 | ### Handling network failures 226 | 227 | By default, starting from version 2.2.0, each `ImageViewNext` will listen to network availability changes and automatically retry and get the image from the Internet, if and only if the same instance failed to do so in the previous attempt. 228 | 229 | If you want to override the default behavior you can use: 230 | 231 | * `setClassAutoRetryFromNetwork(boolean classAutoRetryFromNetwork)` to set a class-level behavior 232 | * `setAutoRetryFromNetwork(boolean autoRetryFromNetwork)` to set an instance-specific behavior 233 | 234 | To know what the current settings are in regards to auto retry, use: 235 | 236 | * `isClassAutoRetryFromNetwork()` to get the class-level setting 237 | * `isAutoRetryFromNetwork()` to retrieve the instance-specific setting 238 | 239 | **Remember**: the instance-specific setting has an higher priority than the class-level setting. 240 | 241 | 242 | ### Maximum number of threads 243 | 244 | You can set the maximum number of concurrent threads; threads are used to retrieve an image, given its URL, from the memory cache, the disk cache or the network. 245 | 246 | Use `ImageViewNext.setMaximumNumberOfThreads(THREAD_NUMBER)` BEFORE any `ImageViewNext` object is instantiated (ideally, in your `Application` class), as calling this function again after an `ImageViewNext` has been instantiated will have no effect. 247 | 248 | You can retrieve the maximum number of concurrent threads with `ImageViewNext.getMaximumNumberOfThreads()`. 249 | 250 | 251 | ### Example of use 252 | 253 | ```java 254 | // Sets class-level loading/error Drawables 255 | ImageViewNext.setClassErrorDrawable(R.drawable.error_thumb); 256 | ImageViewNext.setClassLoadingDrawable(R.drawable.loading_spinner); 257 | 258 | img1.setUrl("http://upload.wikimedia.org/wikipedia/commons/9/91/Cittadimatera1.jpg"); 259 | img2.setUrl("http://upload.wikimedia.org/wikipedia/commons/4/49/Basilicata_Matera1_tango7174.jpg"); 260 | ``` 261 | 262 | 263 | ## Known issues and workarounds 264 | 265 | `ImageViewEx`internally uses an old Android Framework class, `Movie`, to parse animated GIFs. This ensures fast execution, since the `Movie` class internally relies on native code. Due to `Movie` being a legacy class, though, there are a few quirks. 266 | 267 | Firstly, you can't have `Movie` working on an hardware-accelerated canvas in Honeycomb and newer versions of Android. The `ImageViewEx` thus automatically disables hardware acceleration by itself when it has to display a GIF image. One side effect is that hardware acceleration is "lost" forever on the View once turned off, so if you reuse the `ImageViewEx` and at some point you assign a GIF image to it, from that point onwards it won't be hardware accelerated anymore. That's a limitation Android itself imposes, so there's not much we can do about that. On the bright side, this only affects cases where hardware acceleration is available; even when software rendering is active, there's not a big performance hit thou. 268 | 269 | The second issue is that `Movie` has serious issues on some emulator instances and some retail devices. This is most likely due to some broken code down at native (maybe in Skia) or video driver level. So not much we can do on this one either. On the bright side, we've provided a workaround, that is setting `setCanAlwaysAnimate(false)` on phones known to cause issues. You will lose animation support, but you don't need to get crazy trying to handle several layouts, some using `ImageView`s and some using `ImageViewEx`es. 270 | 271 | 272 | ## Some boring stuff 273 | If you like this project and want to make a contribution, feel free to make a pull request, submit a bug report or ask for anything. Any contribution is appreciated! 274 | 275 | If you use this library, letting us know would make us proud. We do not ask for anything else. 276 | 277 | 278 | ## Version history 279 | 280 | ### 2.1.0-alpha1 281 | * Updated `DiskLruCache` to 2.0.0. 282 | 283 | ### 2.0.0-alpha3 284 | * Enabled setting of maximum number of concurrent threads. 285 | * Minor fixes and improvements. 286 | 287 | ### 2.0.0-alpha2 288 | * Speed improvements. 289 | * Moved assets to the test project. 290 | 291 | ### 2.0.0-alpha 292 | * Caching/async system completely rewritten. 293 | * Several performance optimization. 294 | * Few bugs fixed. 295 | 296 | ### 1.1.0 297 | * Few bugs fixed. 298 | 299 | ### 1.0.0 300 | * First release. 301 | 302 | 303 | ## License 304 | 305 | ``` 306 | Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php). 307 | 308 | Copyright (c) 2011-2013 Francesco Pontillo and Sebastiano Poggi 309 | 310 | Permission is hereby granted, free of charge, to any person obtaining a copy 311 | of this software and associated documentation files (the "Software"), to deal 312 | in the Software without restriction, including without limitation the rights 313 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 314 | copies of the Software, and to permit persons to whom the Software is 315 | furnished to do so, subject to the following conditions: 316 | 317 | The above copyright notice and this permission notice shall be included in 318 | all copies or substantial portions of the Software. 319 | 320 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 321 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 322 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 323 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 324 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 325 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 326 | THE SOFTWARE. 327 | ``` 328 | -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/libs/android-support-v4.jar -------------------------------------------------------------------------------- /lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.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 edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | android.library=true 16 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/values/attribs.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ImageViewEx 5 | 6 | -------------------------------------------------------------------------------- /src/net/frakbot/cache/CacheHelper.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.cache; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.os.Environment; 7 | import com.jakewharton.disklrucache.DiskLruCache.Editor; 8 | 9 | import java.io.*; 10 | import java.security.MessageDigest; 11 | import java.security.NoSuchAlgorithmException; 12 | import java.util.Locale; 13 | 14 | public class CacheHelper { 15 | 16 | public static File getDiskCacheDir(Context context, String uniqueName) { 17 | // Check if media is mounted or storage is built-in, if so, 18 | // try and use external cache dir, otherwise use internal cache dir. 19 | final String cachePath = 20 | Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || 21 | !isExternalStorageRemovable() ? 22 | getExternalCacheDir(context).getPath() : 23 | context.getCacheDir().getPath(); 24 | 25 | return new File(cachePath + File.separator + uniqueName); 26 | } 27 | 28 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 29 | public static boolean isExternalStorageRemovable() { 30 | return Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD || Environment.isExternalStorageRemovable(); 31 | } 32 | 33 | public static File getExternalCacheDir(Context context) { 34 | if (hasExternalCacheDir()) { 35 | return context.getExternalCacheDir(); 36 | } 37 | 38 | // Before Froyo we need to construct the external cache dir ourselves 39 | final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/"; 40 | return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir); 41 | } 42 | 43 | public static boolean hasExternalCacheDir() { 44 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; 45 | } 46 | 47 | /** 48 | * Writes a byte array into a DiskLruCache {@link Editor}. 49 | * 50 | * @param source The input byte array. 51 | * @param editor The {@link Editor} to write the byte array into. 52 | * 53 | * @return true if there were no errors, false otherwise. 54 | * @throws IOException If there was an error while writing the file. 55 | */ 56 | public static boolean writeByteArrayToEditor(byte[] source, Editor editor) throws IOException { 57 | OutputStream out = null; 58 | try { 59 | out = new BufferedOutputStream(editor.newOutputStream(0), source.length); 60 | editor.newOutputStream(0).write(source); 61 | return true; 62 | } finally { 63 | if (out != null) { 64 | out.close(); 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Encodes URLs with the SHA-256 algorithm. 71 | * @param uri The URL to encode. 72 | * 73 | * @return The encoded URL. 74 | * 75 | * @throws NoSuchAlgorithmException If the SHA-256 algorithm is not found. 76 | * @throws UnsupportedEncodingException If the UTF-8 encoding is not supported. 77 | */ 78 | public static String UriToDiskLruCacheString(String uri) throws 79 | NoSuchAlgorithmException, 80 | UnsupportedEncodingException { 81 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 82 | byte[] convBytes = digest.digest(uri.getBytes("UTF-8")); 83 | String result; 84 | StringBuilder sb = new StringBuilder(); 85 | for (byte b : convBytes) { 86 | sb.append(String.format("%02X", b)); 87 | } 88 | result = sb.toString(); 89 | result = result.toLowerCase(Locale.US); 90 | return result; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/Converters.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.BitmapFactory.Options; 8 | import android.graphics.drawable.BitmapDrawable; 9 | import android.graphics.drawable.Drawable; 10 | import android.util.Log; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | 16 | /** 17 | * Converters class, it's abstract and all of its methods are static. 18 | * 19 | * @author Francesco Pontillo, Sebastiano Poggi 20 | */ 21 | public abstract class Converters { 22 | private static final String TAG = Converters.class.getSimpleName(); 23 | 24 | /** 25 | * Converts a byte array into a BitmapDrawable, using the provided options. 26 | * 27 | * @param image The byte array representing the image. 28 | * @param opts The decoding options to use, or null if you'd like to use predefined 29 | * options (scaling will be not active). 30 | * @param context The Context for getting the Resources. 31 | * 32 | * @return The initialized BitmapDrawable. 33 | */ 34 | public static BitmapDrawable byteArrayToDrawable(byte[] image, Options opts, Context context) { 35 | if (opts == null) { 36 | Log.v(TAG, "opts is null, initializing without scaling"); 37 | opts = new Options(); 38 | opts.inScaled = false; 39 | } 40 | Bitmap bmp = BitmapFactory.decodeByteArray(image, 0, image.length, opts); 41 | // bmp.setDensity(DisplayMetrics.DENSITY_HIGH); 42 | return new BitmapDrawable(context.getResources(), bmp); 43 | } 44 | 45 | /** 46 | * Converts a byte array into a Bitmap, using the provided options. 47 | * 48 | * @param image The byte array representing the image. 49 | * @param opts The decoding options to use, or null if you'd like to use predefined 50 | * options (scaling will be not active). 51 | * 52 | * @return The initialized BitmapDrawable. 53 | */ 54 | public static Bitmap byteArrayToBitmap(byte[] image, Options opts) { 55 | if (opts == null) { 56 | Log.v(TAG, "opts is null, initializing without scaling"); 57 | opts = new Options(); 58 | opts.inScaled = false; 59 | } 60 | return BitmapFactory.decodeByteArray(image, 0, image.length, opts); 61 | } 62 | 63 | /** 64 | * Covnerts a Bitmap into a byte array. 65 | * 66 | * @param image The Bitmap to convert. 67 | * 68 | * @return The byte array representing the Bitmap (compressed in PNG). 69 | */ 70 | public static byte[] bitmapToByteArray(Bitmap image) { 71 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 72 | image.compress(Bitmap.CompressFormat.PNG, 100, stream); 73 | return stream.toByteArray(); 74 | } 75 | 76 | /** 77 | * Converts a Drawable into a byte array. 78 | * 79 | * @param image The Drawable to convertLa Drawable da convertire. 80 | * 81 | * @return The byte array representing the Drawable (compressed in PNG). 82 | */ 83 | public static byte[] drawableToByteArray(Drawable image) { 84 | Bitmap bitmap = ((BitmapDrawable) image).getBitmap(); 85 | return bitmapToByteArray(bitmap); 86 | } 87 | 88 | /** 89 | * Gets an asset from a provided AssetManager and its name in the directory and returns a 90 | * byte array representing the object content. 91 | * 92 | * @param assetManager An {@link AssetManager}. 93 | * @param asset String of the file name. 94 | * 95 | * @return byte[] representing the object content. 96 | */ 97 | public static byte[] assetToByteArray(AssetManager assetManager, String asset) { 98 | byte[] image = null; 99 | int b; 100 | InputStream is = null; 101 | ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 102 | 103 | try { 104 | is = assetManager.open(asset); 105 | while ((b = is.read()) != -1) { 106 | outStream.write(b); 107 | } 108 | image = outStream.toByteArray(); 109 | } 110 | catch (IOException e) { 111 | Log.v(TAG, "Error while reading asset to byte array: " + asset, e); 112 | image = null; 113 | } 114 | finally { 115 | if (is != null) { 116 | try { 117 | is.close(); 118 | } 119 | catch (IOException ignored) { } 120 | } 121 | 122 | try { 123 | outStream.close(); 124 | } 125 | catch (IOException ignored) { } 126 | } 127 | 128 | return image; 129 | } 130 | 131 | /** 132 | * Converts an {@link InputStream} into a byte array. 133 | * 134 | * @param is The {@link InputStream} to convert. 135 | * @param size The size of the {@link InputStream}. 136 | * 137 | * @return The converted byte array. 138 | */ 139 | public static byte[] inputStreamToByteArray(InputStream is, int size) { 140 | ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); 141 | byte[] buffer = new byte[size]; 142 | 143 | int len = 0; 144 | try { 145 | while ((len = is.read(buffer)) != -1) { 146 | byteBuffer.write(buffer, 0, len); 147 | } 148 | } catch (IOException e) { 149 | e.printStackTrace(); 150 | } 151 | 152 | buffer = byteBuffer.toByteArray(); 153 | return buffer; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/ImageAlign.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex; 2 | 3 | /** 4 | * Enum that contains image alignments for ImageViewEx. 5 | * 6 | * @author Sebastiano Poggi, Francesco Pontillo 7 | * 8 | * @deprecated Use ScaleType.FIT_START and ScaleType.FIT_END instead. 9 | */ 10 | public enum ImageAlign { 11 | /** 12 | * No forced alignment. Image will be placed where the 13 | * scaleType dictates it to. 14 | */ 15 | NONE, 16 | 17 | /** 18 | * Force top alignment: the top edge is aligned with 19 | * the View top. 20 | */ 21 | TOP 22 | } 23 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/ImageViewEx.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.Canvas; 10 | import android.graphics.Movie; 11 | import android.graphics.drawable.AnimationDrawable; 12 | import android.graphics.drawable.BitmapDrawable; 13 | import android.graphics.drawable.ColorDrawable; 14 | import android.graphics.drawable.Drawable; 15 | import android.os.Build; 16 | import android.os.Handler; 17 | import android.os.Parcel; 18 | import android.os.Parcelable; 19 | import android.util.AttributeSet; 20 | import android.util.DisplayMetrics; 21 | import android.util.Log; 22 | import android.view.View; 23 | import android.widget.ImageView; 24 | 25 | import java.io.InputStream; 26 | import java.lang.reflect.Method; 27 | 28 | /** 29 | * Extension of the ImageView that handles any kind of image already supported 30 | * by ImageView, plus animated GIF images. 31 | *

32 | * WARNING: due to Android limitations, the android:adjustViewBounds 33 | * attribute is ignored on API levels < 16 (Jelly Bean 4.1). Use our own 34 | * adjustViewBounds attribute to obtain the same behaviour! 35 | * 36 | * @author Sebastiano Poggi, Francesco Pontillo 37 | */ 38 | @SuppressWarnings({"deprecation"}) 39 | public class ImageViewEx extends ImageView { 40 | 41 | private static final String TAG = ImageViewEx.class.getSimpleName(); 42 | 43 | private static boolean mCanAlwaysAnimate = true; 44 | private float mScale = -1; 45 | private boolean mAdjustViewBounds = false; 46 | 47 | private static final int IMAGE_SOURCE_UNKNOWN = -1; 48 | private static final int IMAGE_SOURCE_RESOURCE = 0; 49 | private static final int IMAGE_SOURCE_DRAWABLE = 1; 50 | private static final int IMAGE_SOURCE_BITMAP = 2; 51 | private static final int IMAGE_SOURCE_GIF = 2; 52 | 53 | @SuppressWarnings("unused") 54 | private int mImageSource; 55 | 56 | // Used by the fixed size optimizations 57 | private boolean mIsFixedSize = false; 58 | private boolean mBlockLayout = false; 59 | 60 | private BitmapFactory.Options mOptions; 61 | private int mOverriddenDensity = -1; 62 | private static int mOverriddenClassDensity = -1; 63 | 64 | private int mMaxHeight, mMaxWidth; 65 | 66 | private Movie mGif; 67 | private double mGifStartTime; 68 | private int mFrameDuration = 67; 69 | private final Handler mHandler = new Handler(); 70 | private Thread mUpdater; 71 | 72 | private ImageAlign mImageAlign = ImageAlign.NONE; 73 | 74 | private final DisplayMetrics mDm; 75 | private final SetDrawableRunnable mSetDrawableRunnable = new SetDrawableRunnable(); 76 | private final SetGifRunnable mSetGifRunnable = new SetGifRunnable(); 77 | private ScaleType mScaleType; 78 | 79 | protected Drawable mEmptyDrawable = new ColorDrawable(0x00000000); 80 | protected FillDirection mFillDirection = FillDirection.NONE; 81 | 82 | /////////////////////////////////////////////////////////// 83 | /// CONSTRUCTORS /// 84 | /////////////////////////////////////////////////////////// 85 | 86 | /** 87 | * Creates an instance for the class. 88 | * 89 | * @param context The context to instantiate the object for. 90 | */ 91 | public ImageViewEx(Context context) { 92 | super(context); 93 | mDm = context.getResources().getDisplayMetrics(); 94 | } 95 | 96 | /** 97 | * Creates an instance for the class and initializes it with a given image. 98 | * 99 | * @param context The context to initialize the instance into. 100 | * @param src InputStream containing the GIF to view. 101 | */ 102 | public ImageViewEx(Context context, InputStream src) { 103 | super(context); 104 | mGif = Movie.decodeStream(src); 105 | mDm = context.getResources().getDisplayMetrics(); 106 | } 107 | 108 | /** 109 | * Creates an instance for the class. 110 | * 111 | * @param context The context to initialize the instance into. 112 | * @param attrs The parameters to initialize the instance with. 113 | */ 114 | public ImageViewEx(Context context, AttributeSet attrs) { 115 | super(context, attrs); 116 | mDm = context.getResources().getDisplayMetrics(); 117 | 118 | TypedArray a = context.obtainStyledAttributes(attrs, 119 | R.styleable.ImageViewEx, 0, 0); 120 | 121 | if (a.hasValue(R.styleable.ImageViewEx_adjustViewBounds)) { 122 | // Prioritize our own adjustViewBounds 123 | setAdjustViewBounds(a.getBoolean(R.styleable.ImageViewEx_adjustViewBounds, false)); 124 | } 125 | else { 126 | // Fallback strategy: try to use ImageView's own adjustViewBounds 127 | // attribute value 128 | if (Build.VERSION.SDK_INT >= 16) { 129 | // The ImageView#getAdjustViewBounds() method only exists from 130 | // API Level 16+, for some reason. 131 | try { 132 | Method m = super.getClass().getMethod("getAdjustViewBounds"); 133 | mAdjustViewBounds = (Boolean) m.invoke(this); 134 | } 135 | catch (Exception ignored) { 136 | } 137 | } 138 | } 139 | 140 | if (a.hasValue(R.styleable.ImageViewEx_fillDirection)) { 141 | setFillDirection(a.getInt(R.styleable.ImageViewEx_fillDirection, 0)); 142 | } 143 | 144 | if (a.hasValue(R.styleable.ImageViewEx_emptyDrawable)) { 145 | setEmptyDrawable(a.getDrawable(R.styleable.ImageViewEx_emptyDrawable)); 146 | } 147 | 148 | a.recycle(); 149 | } 150 | 151 | /** 152 | * Creates an instance for the class and initializes it with a provided GIF. 153 | * 154 | * @param context The context to initialize the instance into. 155 | * @param src The byte array containing the GIF to view. 156 | */ 157 | public ImageViewEx(Context context, byte[] src) { 158 | super(context); 159 | mGif = Movie.decodeByteArray(src, 0, src.length); 160 | mDm = context.getResources().getDisplayMetrics(); 161 | } 162 | 163 | /** 164 | * Creates an instance for the class and initializes it with a provided GIF. 165 | * 166 | * @param context Il contesto in cui viene inizializzata l'istanza. 167 | * @param src The path of the GIF file to view. 168 | */ 169 | public ImageViewEx(Context context, String src) { 170 | super(context); 171 | mGif = Movie.decodeFile(src); 172 | mDm = context.getResources().getDisplayMetrics(); 173 | } 174 | 175 | /////////////////////////////////////////////////////////// 176 | /// PUBLIC SETTERS /// 177 | /////////////////////////////////////////////////////////// 178 | 179 | /** Initalizes the inner variable describing the kind of resource attached to the ImageViewEx. */ 180 | public void initializeDefaultValues() { 181 | if (isPlaying()) stop(); 182 | mGif = null; 183 | setTag(null); 184 | mImageSource = IMAGE_SOURCE_UNKNOWN; 185 | } 186 | 187 | 188 | /** 189 | * Sets the image from a byte array. The actual image-setting is 190 | * called on a worker thread because it can be pretty CPU-consuming. 191 | * 192 | * @param src The byte array containing the image to set into the ImageViewEx. 193 | */ 194 | public void setSource(final byte[] src) { 195 | if (src != null) { 196 | final ImageViewEx thisImageView = this; 197 | setImageDrawable(mEmptyDrawable); 198 | Thread t = new Thread(new Runnable() { 199 | @Override 200 | public void run() { 201 | thisImageView.setSourceBlocking(src); 202 | } 203 | }); 204 | t.setPriority(Thread.MIN_PRIORITY); 205 | t.setName("ImageSetter@" + hashCode()); 206 | t.run(); 207 | } 208 | } 209 | 210 | /** 211 | * Sets the image from a byte array in a blocking, CPU-consuming way. 212 | * Will handle itself referring back to the UI thread when needed. 213 | * 214 | * @param src The byte array containing the image to set into the ImageViewEx. 215 | */ 216 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 217 | public void setSourceBlocking(final byte[] src) { 218 | if (src == null) { 219 | try { 220 | stop(); 221 | mGif = null; 222 | setTag(null); 223 | } 224 | catch (Throwable ignored) { 225 | } 226 | return; 227 | } 228 | 229 | Movie gif = null; 230 | 231 | // If the animation is not requested 232 | // decoding into a Movie is pointless (read: expensive) 233 | if (internalCanAnimate()) { 234 | gif = Movie.decodeByteArray(src, 0, src.length); 235 | } 236 | 237 | // If gif is null, it's probably not a gif 238 | if (gif == null || !internalCanAnimate()) { 239 | 240 | // If not a gif and if on Android 3+, enable HW acceleration 241 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 242 | setLayerType(View.LAYER_TYPE_HARDWARE, null); 243 | } 244 | 245 | // Sets the image as a regular Drawable 246 | setTag(null); 247 | 248 | final Drawable d = Converters.byteArrayToDrawable(src, mOptions, getContext()); 249 | 250 | // We need to run this on the UI thread 251 | stopLoading(); 252 | mSetDrawableRunnable.setDrawable(d); 253 | mHandler.post(mSetDrawableRunnable); 254 | } 255 | else { 256 | // Disables the HW acceleration when viewing a GIF on Android 3+ 257 | if (Build.VERSION.SDK_INT >= 11) { 258 | setLayerType(View.LAYER_TYPE_SOFTWARE, null); 259 | } 260 | 261 | // We need to run this on the UI thread 262 | stopLoading(); 263 | mSetGifRunnable.setGif(gif); 264 | mHandler.post(mSetGifRunnable); 265 | } 266 | } 267 | 268 | /** {@inheritDoc} */ 269 | public void setImageResource(int resId) { 270 | initializeDefaultValues(); 271 | stopLoading(); 272 | stop(); 273 | super.setImageResource(resId); 274 | mImageSource = IMAGE_SOURCE_RESOURCE; 275 | mGif = null; 276 | } 277 | 278 | /** {@inheritDoc} */ 279 | public void setImageDrawable(Drawable drawable) { 280 | blockLayoutIfPossible(); 281 | initializeDefaultValues(); 282 | stopLoading(); 283 | stop(); 284 | super.setImageDrawable(drawable); 285 | mBlockLayout = false; 286 | mGif = null; 287 | mImageSource = IMAGE_SOURCE_DRAWABLE; 288 | } 289 | 290 | /** {@inheritDoc} */ 291 | public void setImageBitmap(Bitmap bm) { 292 | initializeDefaultValues(); 293 | stopLoading(); 294 | stop(); 295 | super.setImageBitmap(bm); 296 | mImageSource = IMAGE_SOURCE_BITMAP; 297 | mGif = null; 298 | } 299 | 300 | /** {@inheritDoc} */ 301 | @Override 302 | public void setScaleType(ScaleType scaleType) { 303 | super.setScaleType(scaleType); 304 | } 305 | 306 | /** 307 | * Sets the fill direction for the image. This is used 308 | * in conjunction with {@link #setAdjustViewBounds(boolean)}. 309 | * If adjustViewBounds is not already enabled, 310 | * it will be automatically enabled by setting the direction 311 | * to anything other than {@link FillDirection#NONE}. 312 | * 313 | * @param direction The fill direction. 314 | */ 315 | public void setFillDirection(FillDirection direction) { 316 | if (direction != mFillDirection) { 317 | mFillDirection = direction; 318 | 319 | if (mFillDirection != FillDirection.NONE && !mAdjustViewBounds) { 320 | setAdjustViewBounds(true); 321 | } 322 | 323 | requestLayout(); 324 | } 325 | } 326 | 327 | /** 328 | * Private helper for 329 | * {@link #setFillDirection(net.frakbot.imageviewex.ImageViewEx.FillDirection)}. 330 | * 331 | * @param direction The direction integer. 0 = NONE, 1 = HORIZONTAL, 332 | * 2 = VERTICAL. 333 | */ 334 | private void setFillDirection(int direction) { 335 | FillDirection fd; 336 | 337 | switch (direction) { 338 | case 1: 339 | fd = FillDirection.HORIZONTAL; 340 | break; 341 | case 2: 342 | fd = FillDirection.VERTICAL; 343 | break; 344 | default: 345 | fd = FillDirection.NONE; 346 | } 347 | 348 | setFillDirection(fd); 349 | } 350 | 351 | /** 352 | * Sets the duration, in milliseconds, of each frame during the GIF animation. 353 | * It is the refresh period. 354 | * 355 | * @param duration The duration, in milliseconds, of each frame. 356 | */ 357 | public void setFramesDuration(int duration) { 358 | if (duration < 1) { 359 | throw new IllegalArgumentException 360 | ("Frame duration can't be less or equal than zero."); 361 | } 362 | 363 | mFrameDuration = duration; 364 | } 365 | 366 | /** 367 | * Sets the number of frames per second during the GIF animation. 368 | * 369 | * @param fps The fps amount. 370 | */ 371 | public void setFPS(float fps) { 372 | if (fps <= 0.0) { 373 | throw new IllegalArgumentException 374 | ("FPS can't be less or equal than zero."); 375 | } 376 | 377 | mFrameDuration = Math.round(1000f / fps); 378 | } 379 | 380 | /** 381 | * Sets a density for every image set to any {@link ImageViewEx}. 382 | * If a custom density level is set for a particular instance of {@link ImageViewEx}, 383 | * this will be ignored. 384 | * 385 | * @param classLevelDensity the density to apply to every instance of {@link ImageViewEx}. 386 | */ 387 | public static void setClassLevelDensity(int classLevelDensity) { 388 | mOverriddenClassDensity = classLevelDensity; 389 | } 390 | 391 | /** 392 | * Assign an Options object to this {@link ImageViewEx}. Those options 393 | * are used internally by the {@link ImageViewEx} when decoding the 394 | * image. This may be used to prevent the default behavior that loads all 395 | * images as mdpi density. 396 | * 397 | * @param options The BitmapFactory.Options used to decode the images. 398 | */ 399 | public void setOptions(BitmapFactory.Options options) { 400 | mOptions = options; 401 | } 402 | 403 | /** 404 | * Programmatically overrides this view's density. 405 | * The new density will be set on the next {@link #onMeasure(int, int)}. 406 | * 407 | * @param fixedDensity the new density the view has to use. 408 | */ 409 | public void setDensity(int fixedDensity) { 410 | mOverriddenDensity = fixedDensity; 411 | } 412 | 413 | /** 414 | * Removes the class level density for {@link ImageViewEx}. 415 | * 416 | * @see ImageViewEx#setClassLevelDensity(int) 417 | */ 418 | public static void removeClassLevelDensity() { 419 | setClassLevelDensity(-1); 420 | } 421 | 422 | /** 423 | * Class method. 424 | * Sets the mCanAlwaysAnimate value. If it is true, {@link #canAnimate()} will be 425 | * triggered, determining if the animation can be played in that particular instance of 426 | * {@link ImageViewEx}. If it is false, {@link #canAnimate()} will never be triggered 427 | * and GIF animations will never start. 428 | * {@link #mCanAlwaysAnimate} defaults to true. 429 | * 430 | * @param mCanAlwaysAnimate boolean, true to always animate for every instance of 431 | * {@link ImageViewEx}, false if you want to perform the 432 | * decision method {@link #canAnimate()} on every 433 | * {@link #setSource(byte[])} call. 434 | */ 435 | public static void setCanAlwaysAnimate(boolean mCanAlwaysAnimate) { 436 | ImageViewEx.mCanAlwaysAnimate = mCanAlwaysAnimate; 437 | } 438 | 439 | /** 440 | * Sets a value indicating wether the image is considered as having a fixed size. 441 | * This will enable an optimization when assigning images to the ImageViewEx, but 442 | * has to be used sparingly or it may cause artifacts if the image isn't really 443 | * fixed in size. 444 | *

445 | * An example of usage for this optimization is in ListViews, where items images 446 | * are supposed to be fixed size, and this enables buttery smoothness. 447 | *

448 | * See: https://plus.google.com/u/0/113058165720861374515/posts/iTk4PjgeAWX 449 | */ 450 | public void setIsFixedSize(boolean fixedSize) { 451 | mIsFixedSize = fixedSize; 452 | } 453 | 454 | /** 455 | * Sets a new ImageAlign value and redraws the View. 456 | * If the ImageViewEx has a ScaleType set too, this 457 | * will override it! 458 | * 459 | * @param align The new ImageAlign value. 460 | * 461 | * @deprecated Use setScaleType(ScaleType.FIT_START) 462 | * and setScaleType(ScaleType.FIT_END) instead. 463 | */ 464 | public void setImageAlign(ImageAlign align) { 465 | if (align != mImageAlign) { 466 | mImageAlign = align; 467 | invalidate(); 468 | } 469 | } 470 | 471 | /** 472 | * Sets the drawable used as "empty". Note that this 473 | * is not automatically assigned by {@link ImageViewEx} 474 | * but is used by descendants such as {@link ImageViewNext}. 475 | * 476 | * @param d The "empty" drawable 477 | */ 478 | public void setEmptyDrawable(Drawable d) { 479 | mEmptyDrawable = d; 480 | } 481 | 482 | @Override 483 | public void setAdjustViewBounds(boolean adjustViewBounds) { 484 | if (mFillDirection != FillDirection.NONE) { 485 | // Just in case, shouldn't be ever necessary 486 | if (!mAdjustViewBounds) { 487 | mAdjustViewBounds = true; 488 | super.setAdjustViewBounds(true); 489 | } 490 | 491 | return; 492 | } 493 | 494 | mAdjustViewBounds = adjustViewBounds; 495 | super.setAdjustViewBounds(adjustViewBounds); 496 | } 497 | 498 | /////////////////////////////////////////////////////////// 499 | /// PUBLIC GETTERS /// 500 | /////////////////////////////////////////////////////////// 501 | 502 | /** Disables density ovverriding. */ 503 | public void dontOverrideDensity() { 504 | mOverriddenDensity = -1; 505 | } 506 | 507 | /** 508 | * Returns a boolean indicating if an animation is currently playing. 509 | * 510 | * @return true if animating, false otherwise. 511 | */ 512 | public boolean isPlaying() { 513 | return mUpdater != null && mUpdater.isAlive(); 514 | } 515 | 516 | /** 517 | * Returns a boolean indicating if the instance was initialized and if 518 | * it is ready for playing the animation. 519 | * 520 | * @return true if the instance is ready for playing, false otherwise. 521 | */ 522 | public boolean canPlay() { 523 | return mGif != null; 524 | } 525 | 526 | /** 527 | * Class method. 528 | * Returns the mCanAlwaysAnimate value. If it is true, {@link #canAnimate()} will be 529 | * triggered, determining if the animation can be played in that particular instance of 530 | * {@link ImageViewEx}. If it is false, {@link #canAnimate()} will never be triggered 531 | * and GIF animations will never start. 532 | * {@link #mCanAlwaysAnimate} defaults to true. 533 | * 534 | * @return boolean, true to see if this instance can be animated by calling 535 | * {@link #canAnimate()}, if false, animations will never be triggered and 536 | * {@link #canAnimate()} will never be evaluated for this instance. 537 | */ 538 | public static boolean canAlwaysAnimate() { 539 | return mCanAlwaysAnimate; 540 | } 541 | 542 | /** 543 | * This method should be overridden with your custom implementation. By default, 544 | * it always returns {@code true}. 545 | *

546 | *

This method decides whether animations can be started for this instance of 547 | * {@link ImageViewEx}. Still, if {@link #canAlwaysAnimate()} equals 548 | * {@code false} this method will never be called for all of the 549 | * instances of {@link ImageViewEx}. 550 | * 551 | * @return {@code true} if it can animate the current instance of 552 | * {@link ImageViewEx}, false otherwise. 553 | * @see {@link #setCanAlwaysAnimate(boolean)} to set the predefined class behavior 554 | * in regards to animations. 555 | */ 556 | public boolean canAnimate() { 557 | return true; 558 | } 559 | 560 | /** 561 | * Gets the frame duration, in milliseconds, of each frame during the GIF animation. 562 | * It is the refresh period. 563 | * 564 | * @return The duration, in milliseconds, of each frame. 565 | */ 566 | public int getFramesDuration() { 567 | return mFrameDuration; 568 | } 569 | 570 | /** 571 | * Gets the number of frames per second during the GIF animation. 572 | * 573 | * @return The fps amount. 574 | */ 575 | public float getFPS() { 576 | return 1000.0f / mFrameDuration; 577 | } 578 | 579 | /** 580 | * Gets the current scale value. 581 | * 582 | * @return Returns the scale value for this ImageViewEx. 583 | */ 584 | public float getScale() { 585 | float targetDensity = getContext().getResources().getDisplayMetrics().densityDpi; 586 | float displayThisDensity = getDensity(); 587 | mScale = targetDensity / displayThisDensity; 588 | if (mScale < 0.1f) mScale = 0.1f; 589 | if (mScale > 5.0f) mScale = 5.0f; 590 | return mScale; 591 | } 592 | 593 | /** 594 | * Gets the fill direction for this ImageViewEx. 595 | * 596 | * @return Returns the fill direction. 597 | */ 598 | public FillDirection getFillDirection() { 599 | return mFillDirection; 600 | } 601 | 602 | /** 603 | * Gets the drawable used as "empty" state. 604 | * 605 | * @return Returns the drawable used ad "empty". 606 | */ 607 | public Drawable getEmptyDrawable() { 608 | return mEmptyDrawable; 609 | } 610 | 611 | /** 612 | * Checks whether the class level density has been set. 613 | * 614 | * @return true if it has been set, false otherwise. 615 | * @see ImageViewEx#setClassLevelDensity(int) 616 | */ 617 | public static boolean isClassLevelDensitySet() { 618 | return mOverriddenClassDensity != -1; 619 | } 620 | 621 | /** 622 | * Gets the class level density has been set. 623 | * 624 | * @return int, the class level density 625 | * @see ImageViewEx#setClassLevelDensity(int) 626 | */ 627 | public static int getClassLevelDensity() { 628 | return mOverriddenClassDensity; 629 | } 630 | 631 | /** 632 | * Gets the set density of the view, given by the screen density or by value 633 | * overridden with {@link #setDensity(int)}. 634 | * If the density was not overridden and it can't be retrieved by the context, 635 | * it simply returns the DENSITY_HIGH constant. 636 | * 637 | * @return int representing the current set density of the view. 638 | */ 639 | public int getDensity() { 640 | int density; 641 | 642 | // If a custom instance density was set, set the image to this density 643 | if (mOverriddenDensity > 0) { 644 | density = mOverriddenDensity; 645 | } 646 | else if (isClassLevelDensitySet()) { 647 | // If a class level density has been set, set every image to that density 648 | density = getClassLevelDensity(); 649 | } 650 | else { 651 | // If the instance density was not overridden, get the one from the display 652 | DisplayMetrics metrics = new DisplayMetrics(); 653 | 654 | if (!(getContext() instanceof Activity)) { 655 | density = DisplayMetrics.DENSITY_HIGH; 656 | } 657 | else { 658 | Activity activity = (Activity) getContext(); 659 | activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); 660 | density = metrics.densityDpi; 661 | } 662 | } 663 | 664 | return density; 665 | } 666 | 667 | /** 668 | * Sets a value indicating wether the image is considered as having a fixed size. 669 | * See {@link #setIsFixedSize(boolean)} for further details. 670 | */ 671 | public boolean getIsFixedSize() { 672 | return mIsFixedSize; 673 | } 674 | 675 | /** 676 | * Returns the current ImageAlign setting. 677 | * 678 | * @return Returns the current ImageAlign setting. 679 | * @deprecated Use setScaleType(ScaleType.FIT_START) 680 | * and setScaleType(ScaleType.FIT_END) instead. 681 | */ 682 | public ImageAlign getImageAlign() { 683 | return mImageAlign; 684 | } 685 | 686 | /////////////////////////////////////////////////////////// 687 | /// PUBLIC METHODS /// 688 | /////////////////////////////////////////////////////////// 689 | 690 | /** 691 | * Starts playing the GIF, if it hasn't started yet. 692 | * FPS defaults to 15.. 693 | */ 694 | public void play() { 695 | // Do something if the animation hasn't started yet 696 | if (mUpdater == null || !mUpdater.isAlive()) { 697 | // Check id the animation is ready 698 | if (!canPlay()) { 699 | throw new IllegalStateException 700 | ("Animation can't start before a GIF is loaded."); 701 | } 702 | 703 | // Initialize the thread and start it 704 | mUpdater = new Thread() { 705 | 706 | @Override 707 | public void run() { 708 | 709 | // Infinite loop: invalidates the View. 710 | // Stopped when the thread is stopped or interrupted. 711 | while (mUpdater != null && !mUpdater.isInterrupted()) { 712 | 713 | mHandler.post(new Runnable() { 714 | public void run() { 715 | invalidate(); 716 | } 717 | }); 718 | 719 | // The thread sleeps until the next frame 720 | try { 721 | Thread.sleep(mFrameDuration); 722 | } 723 | catch (InterruptedException e) { 724 | Thread.currentThread().interrupt(); 725 | } 726 | } 727 | } 728 | }; 729 | 730 | mUpdater.start(); 731 | } 732 | } 733 | 734 | /** Pause playing the GIF, if it has started. */ 735 | public void pause() { 736 | // If the animation has started 737 | if (mUpdater != null && mUpdater.isAlive()) { 738 | mUpdater.suspend(); 739 | } 740 | } 741 | 742 | /** Stops playing the GIF, if it has started. */ 743 | public void stop() { 744 | // If the animation has started 745 | if (mUpdater != null && mUpdater.isAlive() && canPlay()) { 746 | mUpdater.interrupt(); 747 | mGifStartTime = 0; 748 | } 749 | } 750 | 751 | /** {@inheritDoc} */ 752 | @Override 753 | public void requestLayout() { 754 | if (!mBlockLayout) { 755 | super.requestLayout(); 756 | } 757 | } 758 | 759 | @Override 760 | public void setMaxHeight(int maxHeight) { 761 | super.setMaxHeight(maxHeight); 762 | mMaxHeight = maxHeight; 763 | } 764 | 765 | @Override 766 | public void setMaxWidth(int maxWidth) { 767 | super.setMaxWidth(maxWidth); 768 | mMaxWidth = maxWidth; 769 | } 770 | 771 | /////////////////////////////////////////////////////////// 772 | /// EVENT HANDLERS /// 773 | /////////////////////////////////////////////////////////// 774 | 775 | /** 776 | * Draws the control 777 | * 778 | * @param canvas The canvas to drow onto. 779 | */ 780 | @Override 781 | protected void onDraw(Canvas canvas) { 782 | if (mGif != null) { 783 | long now = android.os.SystemClock.uptimeMillis(); 784 | 785 | // first time 786 | if (mGifStartTime == 0) { 787 | mGifStartTime = now; 788 | } 789 | 790 | int dur = mGif.duration(); 791 | if (dur == 0) { 792 | dur = 1000; 793 | } 794 | int relTime = (int) ((now - mGifStartTime) % dur); 795 | mGif.setTime(relTime); 796 | int saveCnt = canvas.save(Canvas.MATRIX_SAVE_FLAG); 797 | 798 | canvas.scale(mScale, mScale); 799 | 800 | float[] gifDrawParams = applyScaleType(canvas); 801 | 802 | mGif.draw(canvas, gifDrawParams[0], gifDrawParams[1]); 803 | 804 | if (mImageAlign != ImageAlign.NONE) { 805 | // We have an alignment override. 806 | // Note: at the moment we only have TOP as custom alignment, 807 | // so the code here is simplified. Will need refactoring 808 | // if other custom alignments are implemented further on. 809 | 810 | // ImageAlign.TOP: align top edge with the View 811 | 812 | canvas.translate(0.0f, calcTopAlignYDisplacement()); 813 | } 814 | 815 | canvas.restoreToCount(saveCnt); 816 | } 817 | else { 818 | // Reset the original scale type 819 | super.setScaleType(getScaleType()); 820 | 821 | if (mImageAlign == ImageAlign.NONE) { 822 | // Everything is normal when there is no alignment override 823 | super.onDraw(canvas); 824 | } 825 | else { 826 | // We have an alignment override. 827 | // Note: at the moment we only have TOP as custom alignment, 828 | // so the code here is simplified. Will need refactoring 829 | // if other custom alignments are implemented further on. 830 | 831 | // ImageAlign.TOP: scaling forced to CENTER_CROP, align top edge with the View 832 | setScaleType(ScaleType.CENTER_CROP); 833 | 834 | int saveCnt = canvas.save(Canvas.MATRIX_SAVE_FLAG); 835 | canvas.translate(0.0f, calcTopAlignYDisplacement()); 836 | 837 | super.onDraw(canvas); 838 | 839 | canvas.restoreToCount(saveCnt); 840 | } 841 | } 842 | } 843 | 844 | /** 845 | * Applies the scale type of the ImageViewEx to the GIF. 846 | * Use the returned value to draw the GIF and calculate 847 | * the right y-offset, if any has to be set. 848 | * 849 | * @param canvas The {@link Canvas} to apply the {@link ScaleType} to. 850 | * 851 | * @return A float array containing, for each position: 852 | * - 0 The x position of the gif 853 | * - 1 The y position of the gif 854 | * - 2 The scaling applied to the y-axis 855 | */ 856 | private float[] applyScaleType(Canvas canvas) { 857 | // Get the current dimensions of the view and the gif 858 | float vWidth = getWidth(); 859 | float vHeight = getHeight(); 860 | float gWidth = mGif.width() * mScale; 861 | float gHeight = mGif.height() * mScale; 862 | 863 | // Disable the default scaling, it can mess things up 864 | if (mScaleType == null) { 865 | mScaleType = getScaleType(); 866 | setScaleType(ScaleType.MATRIX); 867 | } 868 | 869 | float x = 0; 870 | float y = 0; 871 | float s = 1; 872 | 873 | switch (mScaleType) { 874 | case CENTER: 875 | /* Center the image in the view, but perform no scaling. */ 876 | x = (vWidth - gWidth) / 2 / mScale; 877 | y = (vHeight - gHeight) / 2 / mScale; 878 | break; 879 | 880 | case CENTER_CROP: 881 | /* 882 | * Scale the image uniformly (maintain the image's aspect ratio) 883 | * so that both dimensions (width and height) of the image will 884 | * be equal to or larger than the corresponding dimension of the 885 | * view (minus padding). The image is then centered in the view. 886 | */ 887 | float minDimensionCenterCrop = Math.min(gWidth, gHeight); 888 | if (minDimensionCenterCrop == gWidth) { 889 | s = vWidth / gWidth; 890 | } 891 | else { 892 | s = vHeight / gHeight; 893 | } 894 | x = (vWidth - gWidth * s) / 2 / (s * mScale); 895 | y = (vHeight - gHeight * s) / 2 / (s * mScale); 896 | canvas.scale(s, s); 897 | break; 898 | 899 | case CENTER_INSIDE: 900 | /* 901 | * Scale the image uniformly (maintain the image's aspect ratio) 902 | * so that both dimensions (width and height) of the image will 903 | * be equal to or less than the corresponding dimension of the 904 | * view (minus padding). The image is then centered in the view. 905 | */ 906 | // Scaling only applies if the gif is larger than the container! 907 | if (gWidth > vWidth || gHeight > vHeight) { 908 | float maxDimensionCenterInside = Math.max(gWidth, gHeight); 909 | if (maxDimensionCenterInside == gWidth) { 910 | s = vWidth / gWidth; 911 | } 912 | else { 913 | s = vHeight / gHeight; 914 | } 915 | } 916 | x = (vWidth - gWidth * s) / 2 / (s * mScale); 917 | y = (vHeight - gHeight * s) / 2 / (s * mScale); 918 | canvas.scale(s, s); 919 | break; 920 | 921 | case FIT_CENTER: 922 | /* 923 | * Compute a scale that will maintain the original src aspect ratio, 924 | * but will also ensure that src fits entirely inside dst. 925 | * At least one axis (X or Y) will fit exactly. 926 | * The result is centered inside dst. 927 | */ 928 | // This scale type always scales the gif to the exact dimension of the View 929 | float maxDimensionFitCenter = Math.max(gWidth, gHeight); 930 | if (maxDimensionFitCenter == gWidth) { 931 | s = vWidth / gWidth; 932 | } 933 | else { 934 | s = vHeight / gHeight; 935 | } 936 | x = (vWidth - gWidth * s) / 2 / (s * mScale); 937 | y = (vHeight - gHeight * s) / 2 / (s * mScale); 938 | canvas.scale(s, s); 939 | break; 940 | 941 | case FIT_START: 942 | /* 943 | * Compute a scale that will maintain the original src aspect ratio, 944 | * but will also ensure that src fits entirely inside dst. 945 | * At least one axis (X or Y) will fit exactly. 946 | * The result is centered inside dst. 947 | */ 948 | // This scale type always scales the gif to the exact dimension of the View 949 | float maxDimensionFitStart = Math.max(gWidth, gHeight); 950 | if (maxDimensionFitStart == gWidth) { 951 | s = vWidth / gWidth; 952 | } 953 | else { 954 | s = vHeight / gHeight; 955 | } 956 | x = 0; 957 | y = 0; 958 | canvas.scale(s, s); 959 | break; 960 | 961 | case FIT_END: 962 | /* 963 | * Compute a scale that will maintain the original src aspect ratio, 964 | * but will also ensure that src fits entirely inside dst. 965 | * At least one axis (X or Y) will fit exactly. 966 | * END aligns the result to the right and bottom edges of dst. 967 | */ 968 | // This scale type always scales the gif to the exact dimension of the View 969 | float maxDimensionFitEnd = Math.max(gWidth, gHeight); 970 | if (maxDimensionFitEnd == gWidth) { 971 | s = vWidth / gWidth; 972 | } 973 | else { 974 | s = vHeight / gHeight; 975 | } 976 | x = (vWidth - gWidth * s) / mScale / s; 977 | y = (vHeight - gHeight * s) / mScale / s; 978 | canvas.scale(s, s); 979 | break; 980 | 981 | case FIT_XY: 982 | /* 983 | * Scale in X and Y independently, so that src matches dst exactly. 984 | * This may change the aspect ratio of the src. 985 | */ 986 | float sFitX = vWidth / gWidth; 987 | s = vHeight / gHeight; 988 | x = 0; 989 | y = 0; 990 | canvas.scale(sFitX, s); 991 | break; 992 | default: 993 | break; 994 | } 995 | 996 | return new float[] {x, y, s}; 997 | } 998 | 999 | /** @see android.view.View#measure(int, int) */ 1000 | @Override 1001 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1002 | mScale = getScale(); 1003 | 1004 | int w; 1005 | int h; 1006 | 1007 | // Desired aspect ratio of the view's contents (not including padding) 1008 | float desiredAspect = 0.0f; 1009 | 1010 | // We are allowed to change the view's width 1011 | boolean resizeWidth = false; 1012 | 1013 | // We are allowed to change the view's height 1014 | boolean resizeHeight = false; 1015 | 1016 | final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 1017 | final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 1018 | 1019 | final Drawable drawable = getDrawable(); 1020 | 1021 | if (drawable != null) { 1022 | w = drawable.getIntrinsicWidth(); 1023 | h = drawable.getIntrinsicHeight(); 1024 | if (w <= 0) w = 1; 1025 | if (h <= 0) h = 1; 1026 | } 1027 | else if (mGif != null) { 1028 | w = mGif.width(); 1029 | h = mGif.height(); 1030 | if (w <= 0) w = 1; 1031 | if (h <= 0) h = 1; 1032 | } 1033 | else { 1034 | // If no drawable, its intrinsic size is 0. 1035 | w = 0; 1036 | h = 0; 1037 | } 1038 | 1039 | // We are supposed to adjust view bounds to match the aspect 1040 | // ratio of our drawable. See if that is possible. 1041 | if (w > 0 && h > 0) { 1042 | if (mAdjustViewBounds) { 1043 | resizeWidth = widthSpecMode != MeasureSpec.EXACTLY && mFillDirection != FillDirection.HORIZONTAL; 1044 | resizeHeight = heightSpecMode != MeasureSpec.EXACTLY && mFillDirection != FillDirection.VERTICAL; 1045 | 1046 | desiredAspect = (float) w / (float) h; 1047 | } 1048 | } 1049 | 1050 | int pleft = getPaddingLeft(); 1051 | int pright = getPaddingRight(); 1052 | int ptop = getPaddingTop(); 1053 | int pbottom = getPaddingBottom(); 1054 | 1055 | int widthSize; 1056 | int heightSize; 1057 | 1058 | if (resizeWidth || resizeHeight) { 1059 | // If we get here, it means we want to resize to match the 1060 | // drawables aspect ratio, and we have the freedom to change at 1061 | // least one dimension. 1062 | 1063 | // Get the max possible width given our constraints 1064 | widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec); 1065 | 1066 | // Get the max possible height given our constraints 1067 | heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec); 1068 | 1069 | if (desiredAspect != 0.0f) { 1070 | // See what our actual aspect ratio is 1071 | float actualAspect = (float) (widthSize - pleft - pright) / 1072 | (heightSize - ptop - pbottom); 1073 | 1074 | if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { 1075 | 1076 | boolean done = false; 1077 | 1078 | // Try adjusting width to be proportional to height 1079 | if (resizeWidth) { 1080 | int newWidth = (int) (desiredAspect * (heightSize - ptop - pbottom)) + 1081 | pleft + pright; 1082 | if (newWidth <= widthSize || mFillDirection == FillDirection.VERTICAL) { 1083 | widthSize = newWidth; 1084 | done = true; 1085 | } 1086 | } 1087 | 1088 | // Try adjusting height to be proportional to width 1089 | if (!done && resizeHeight) { 1090 | int newHeight = (int) ((widthSize - pleft - pright) / desiredAspect) + 1091 | ptop + pbottom; 1092 | if (newHeight <= heightSize || mFillDirection == FillDirection.HORIZONTAL) { 1093 | heightSize = newHeight; 1094 | } 1095 | } 1096 | } 1097 | } 1098 | } 1099 | else { 1100 | /* We either don't want to preserve the drawables aspect ratio, 1101 | or we are not allowed to change view dimensions. Just measure in 1102 | the normal way. 1103 | */ 1104 | w += pleft + pright; 1105 | h += ptop + pbottom; 1106 | 1107 | w = Math.max(w, getSuggestedMinimumWidth()); 1108 | h = Math.max(h, getSuggestedMinimumHeight()); 1109 | 1110 | widthSize = resolveSize(w, widthMeasureSpec); 1111 | heightSize = resolveSize(h, heightMeasureSpec); 1112 | } 1113 | 1114 | setMeasuredDimension(widthSize, heightSize); 1115 | } 1116 | 1117 | @Override 1118 | public Parcelable onSaveInstanceState() { 1119 | Parcelable superState = super.onSaveInstanceState(); 1120 | return new SavedState(superState); 1121 | } 1122 | 1123 | @Override 1124 | public void onRestoreInstanceState(Parcelable state) { 1125 | SavedState ss = (SavedState) state; 1126 | super.onRestoreInstanceState(ss.getSuperState()); 1127 | } 1128 | 1129 | /////////////////////////////////////////////////////////// 1130 | /// PRIVATE HELPERS /// 1131 | /////////////////////////////////////////////////////////// 1132 | 1133 | /** Copied from {@link ImageView}'s implementation. */ 1134 | private int resolveAdjustedSize(int desiredSize, int maxSize, 1135 | int measureSpec) { 1136 | int result = desiredSize; 1137 | int specMode = MeasureSpec.getMode(measureSpec); 1138 | int specSize = MeasureSpec.getSize(measureSpec); 1139 | 1140 | switch (specMode) { 1141 | case MeasureSpec.UNSPECIFIED: 1142 | // Parent says we can be as big as we want. Just don't be larger 1143 | // than max size imposed on ourselves. 1144 | 1145 | result = Math.min(desiredSize, maxSize); 1146 | break; 1147 | 1148 | case MeasureSpec.AT_MOST: 1149 | // Parent says we can be as big as we want, up to specSize. 1150 | // Don't be larger than specSize, and don't be larger than 1151 | // the max size imposed on ourselves. 1152 | result = Math.min(Math.min(desiredSize, specSize), maxSize); 1153 | break; 1154 | 1155 | case MeasureSpec.EXACTLY: 1156 | // No choice. Do what we are told. 1157 | result = specSize; 1158 | break; 1159 | } 1160 | return result; 1161 | } 1162 | 1163 | /** 1164 | * Calculates the top displacement for the image to make sure it 1165 | * is aligned at the top of the ImageViewEx. 1166 | */ 1167 | private float calcTopAlignYDisplacement() { 1168 | int viewHeight = getHeight(); 1169 | int imgHeight; 1170 | float displacement = 0f; 1171 | 1172 | if (viewHeight <= 0) { 1173 | Log.v(TAG, "The ImageViewEx is still initializing..."); 1174 | return displacement; 1175 | } 1176 | 1177 | if (mGif == null) { 1178 | final Drawable tmpDrawable = getDrawable(); 1179 | if (!(tmpDrawable instanceof BitmapDrawable) || mGif == null) { 1180 | return 0f; // Nothing to do here 1181 | } 1182 | 1183 | // Retrieve the bitmap, its height and the ImageView height 1184 | Bitmap bmp = ((BitmapDrawable) tmpDrawable).getBitmap(); 1185 | imgHeight = bmp.getScaledHeight(mDm); 1186 | } 1187 | else { 1188 | // This is a GIF... 1189 | imgHeight = mGif.height(); 1190 | } 1191 | 1192 | //noinspection IfMayBeConditional 1193 | if (viewHeight > imgHeight) { 1194 | displacement = -1 * (viewHeight - imgHeight); // Just align to top edge 1195 | } 1196 | else { 1197 | // Top displacement [px] = (image height / 2) - (view height / 2) 1198 | displacement = -1 * ((imgHeight - viewHeight) / 2); // This is in pixels... 1199 | } 1200 | return displacement; 1201 | } 1202 | 1203 | /** 1204 | * Blocks layout recalculation if the image is set as fixed size 1205 | * to prevent unnecessary calculations and provide butteriness. 1206 | */ 1207 | private void blockLayoutIfPossible() { 1208 | if (mIsFixedSize) { 1209 | mBlockLayout = true; 1210 | } 1211 | } 1212 | 1213 | /** 1214 | * Internal method, deciding whether to trigger the custom decision method {@link #canAnimate()} 1215 | * or to use the static class value of mCanAlwaysAnimate. 1216 | * 1217 | * @return true if the animation can be started, false otherwise. 1218 | */ 1219 | private boolean internalCanAnimate() { 1220 | return canAlwaysAnimate() ? canAnimate() : canAlwaysAnimate(); 1221 | } 1222 | 1223 | /** 1224 | * Stops any currently running async loading (deserialization and 1225 | * parsing of the image). 1226 | */ 1227 | public void stopLoading() { 1228 | //noinspection ConstantConditions 1229 | if (mHandler != null) { 1230 | mHandler.removeCallbacks(mSetDrawableRunnable); 1231 | mHandler.removeCallbacks(mSetGifRunnable); 1232 | } 1233 | } 1234 | 1235 | /** 1236 | * Temporarily shows the empty drawable (or empties 1237 | * the view if none is defined). Note that this does not 1238 | * follow all procedures {@link #setImageDrawable(android.graphics.drawable.Drawable)} 1239 | * follows and is only intended for temporary assignments such as in 1240 | * {@link ImageViewNext.ImageLoadCompletionListener#onLoadStarted(ImageViewNext, ImageViewNext.CacheLevel)}. 1241 | */ 1242 | public void showEmptyDrawable() { 1243 | setScaleType(ScaleType.CENTER_CROP); 1244 | super.setImageDrawable(mEmptyDrawable); 1245 | } 1246 | 1247 | 1248 | /////////////////////////////////////////////////////////// 1249 | /// PRIVATE CLASSES /// 1250 | /////////////////////////////////////////////////////////// 1251 | 1252 | /** Class that represents a saved state for the ImageViewEx. */ 1253 | private static class SavedState extends BaseSavedState { 1254 | SavedState(Parcelable superState) { 1255 | super(superState); 1256 | } 1257 | 1258 | private SavedState(Parcel in) { 1259 | super(in); 1260 | } 1261 | 1262 | @Override 1263 | public void writeToParcel(Parcel out, int flags) { 1264 | super.writeToParcel(out, flags); 1265 | } 1266 | 1267 | @SuppressWarnings("unused") 1268 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 1269 | public SavedState createFromParcel(Parcel in) { 1270 | return new SavedState(in); 1271 | } 1272 | 1273 | public SavedState[] newArray(int size) { 1274 | return new SavedState[size]; 1275 | } 1276 | }; 1277 | } 1278 | 1279 | /** A Runnable that sets a specified Drawable on the ImageView. */ 1280 | private class SetDrawableRunnable implements Runnable { 1281 | 1282 | private Drawable mDrawable; 1283 | private final Object mDrawableLock = new Object(); 1284 | 1285 | private void setDrawable(Drawable drawable) { 1286 | synchronized (mDrawableLock) { 1287 | mDrawable = drawable; 1288 | } 1289 | } 1290 | 1291 | @Override 1292 | public void run() { 1293 | synchronized (mDrawableLock) { 1294 | if (mDrawable == null) { 1295 | Log.v(TAG, "Loading the Drawable has been aborted"); 1296 | return; 1297 | } 1298 | 1299 | setImageDrawable(mDrawable); 1300 | measure(0, 0); 1301 | requestLayout(); 1302 | 1303 | try { 1304 | AnimationDrawable animationDrawable = (AnimationDrawable) getDrawable(); 1305 | animationDrawable.start(); 1306 | } 1307 | catch (Exception ignored) { 1308 | } 1309 | } 1310 | } 1311 | } 1312 | 1313 | /** A Runnable that sets a specified Movie on the ImageView. */ 1314 | private class SetGifRunnable implements Runnable { 1315 | 1316 | private Movie mGifMovie; 1317 | private final Object mGifMovieLock = new Object(); 1318 | 1319 | private void setGif(Movie drawable) { 1320 | synchronized (mGifMovieLock) { 1321 | mGifMovie = drawable; 1322 | } 1323 | } 1324 | 1325 | @Override 1326 | public void run() { 1327 | synchronized (mGifMovieLock) { 1328 | if (mGifMovie == null) { 1329 | Log.v(TAG, "Loading the GIF has been aborted"); 1330 | return; 1331 | } 1332 | 1333 | initializeDefaultValues(); 1334 | mImageSource = IMAGE_SOURCE_GIF; 1335 | setImageDrawable(null); 1336 | mGif = mGifMovie; 1337 | 1338 | measure(0, 0); 1339 | requestLayout(); 1340 | 1341 | play(); 1342 | } 1343 | } 1344 | } 1345 | 1346 | /** 1347 | * The fill direction for the image. All values other than 1348 | * {@link FillDirection#NONE} imply having the 1349 | * adjustViewBounds function active on the 1350 | * {@link ImageViewEx}. 1351 | */ 1352 | public enum FillDirection { 1353 | /** 1354 | * No fill direction. Acts just like a common 1355 | * {@link ImageView} does. 1356 | */ 1357 | NONE, 1358 | 1359 | /** 1360 | * If the width of the {@link ImageViewEx} is longer 1361 | * than the width of the image it contains, the image 1362 | * is scaled to fit the width of the view. The height 1363 | * of the view is then adjusted to fit the height of 1364 | * the scaled image. 1365 | */ 1366 | HORIZONTAL, 1367 | 1368 | /** 1369 | * If the height of the {@link ImageViewEx} is longer 1370 | * than the height of the image it contains, the image 1371 | * is scaled to fit the height of the view. The width 1372 | * of the view is then adjusted to fit the width of 1373 | * the scaled image. 1374 | */ 1375 | VERTICAL 1376 | } 1377 | } 1378 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/ImageViewNext.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex; 2 | 3 | import android.content.Context; 4 | import android.content.IntentFilter; 5 | import android.graphics.drawable.AnimationDrawable; 6 | import android.graphics.drawable.Drawable; 7 | import android.os.Bundle; 8 | import android.support.v4.util.LruCache; 9 | import android.util.AttributeSet; 10 | import android.util.Log; 11 | import com.foxykeep.datadroid.requestmanager.Request; 12 | import com.foxykeep.datadroid.requestmanager.RequestManager.RequestListener; 13 | import com.jakewharton.disklrucache.DiskLruCache; 14 | import net.frakbot.cache.CacheHelper; 15 | import net.frakbot.imageviewex.broadcastreceiver.ConnectivityChangeBroadcastReceiver; 16 | import net.frakbot.imageviewex.listener.ImageViewExRequestListener; 17 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 18 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestManager; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | 23 | /** 24 | * Extension of the ImageViewEx that handles the download and caching of 25 | * images and animated GIFs. 26 | * 27 | * @author Francesco Pontillo, Sebastiano Poggi 28 | */ 29 | public class ImageViewNext extends ImageViewEx { 30 | 31 | private static final String TAG = ImageViewNext.class.getSimpleName(); 32 | private static final int DISK_CACHE_VALUE_COUNT = 1; 33 | 34 | private Drawable mLoadingD; 35 | private static int mClassLoadingResId; 36 | private Drawable mErrorD; 37 | private static int mClassErrorResId; 38 | 39 | private boolean mAutoRetryFromNetwork; 40 | private static boolean mClassAutoRetryFromNetwork; 41 | private boolean hasFailedDownload; 42 | 43 | private String mUrl; 44 | private ImageLoadCompletionListener mLoadCallbacks; 45 | 46 | protected ImageViewExRequestManager mRequestManager; 47 | protected Request mCurrentRequest; 48 | protected RequestListener mCurrentRequestListener; 49 | 50 | private Context mContext; 51 | 52 | private static int mMemCacheSize = 10 * 1024 * 1024; // 10MiB 53 | private static LruCache mMemCache; 54 | private static int mAppVersion = 1; 55 | private static int mDiskCacheSize = 50 * 1024 * 1024; // 50MiB 56 | private static DiskLruCache mDiskCache; 57 | private static boolean mCacheInit = false; 58 | private static int mConcurrentThreads = 10; 59 | 60 | private ConnectivityChangeBroadcastReceiver mReceiver; 61 | private static final String RECEIVER_ACTION = android.net.ConnectivityManager.CONNECTIVITY_ACTION; 62 | 63 | /** Represents a cache level. */ 64 | public enum CacheLevel { 65 | /** The first level of cache: the memory cache */ 66 | MEMORY, 67 | /** The second level of cache: the disk cache */ 68 | DISK, 69 | /** No caching, direct fetching from the network */ 70 | NETWORK 71 | } 72 | 73 | /** {@inheritDoc} */ 74 | public ImageViewNext(Context context) { 75 | super(context); 76 | init(context); 77 | } 78 | 79 | /** 80 | * Creates an instance for the class. 81 | * Initializes the auto retry from network to true. 82 | * 83 | * @param context The context to initialize the instance into. 84 | * @param attrs The parameters to initialize the instance with. 85 | */ 86 | public ImageViewNext(Context context, AttributeSet attrs) { 87 | super(context, attrs); 88 | init(context); 89 | } 90 | 91 | /** 92 | * Initializes a few instance level variables. 93 | * 94 | * @param context The Context used for initialization. 95 | */ 96 | private void init(Context context) { 97 | mContext = context; 98 | mRequestManager = ImageViewExRequestManager.from(context); 99 | mClassAutoRetryFromNetwork = true; 100 | mAutoRetryFromNetwork = true; 101 | hasFailedDownload = false; 102 | } 103 | 104 | /** {@inheritDoc} */ 105 | @Override 106 | protected void onAttachedToWindow() { 107 | super.onAttachedToWindow(); 108 | registerReceiver(); 109 | } 110 | 111 | /** {@inheritDoc} */ 112 | @Override 113 | protected void onDetachedFromWindow() { 114 | super.onDetachedFromWindow(); 115 | unregisterReceiver(); 116 | } 117 | 118 | /** Register the {@link ConnectivityChangeBroadcastReceiver} for this instance. */ 119 | private void registerReceiver() { 120 | // If the receiver does not exist 121 | if (mReceiver == null) { 122 | mReceiver = new ConnectivityChangeBroadcastReceiver(this); 123 | final IntentFilter intentFilter = new IntentFilter(); 124 | intentFilter.addAction(RECEIVER_ACTION); 125 | mContext.registerReceiver(mReceiver, intentFilter); 126 | } 127 | } 128 | 129 | /** Unregister the {@link ConnectivityChangeBroadcastReceiver} for this instance. */ 130 | private void unregisterReceiver() { 131 | // If the receiver does exists 132 | if (mReceiver != null) { 133 | mContext.unregisterReceiver(mReceiver); 134 | mReceiver = null; 135 | } 136 | } 137 | 138 | /** Gets the current image loading callback, if any */ 139 | public ImageLoadCompletionListener getLoadCallbacks() { 140 | return mLoadCallbacks; 141 | } 142 | 143 | /** 144 | * Sets the image loading callback. 145 | * 146 | * @param loadCallbacks The listener instance, or null to clear it. 147 | */ 148 | public void setLoadCallbacks(ImageLoadCompletionListener loadCallbacks) { 149 | mLoadCallbacks = loadCallbacks; 150 | } 151 | 152 | /** @return The in-memory cache. */ 153 | public static LruCache getMemCache() { 154 | return mMemCache; 155 | } 156 | 157 | /** @return The disk cache. */ 158 | public static DiskLruCache getDiskCache() { 159 | return mDiskCache; 160 | } 161 | 162 | /** @return The in-memory cache size, in bits. */ 163 | public static int getMemCacheSize() { 164 | return mMemCacheSize; 165 | } 166 | 167 | /** @param memCacheSize The in-memory cache size to set, in bits. */ 168 | public static void setMemCacheSize(int memCacheSize) { 169 | mMemCacheSize = memCacheSize; 170 | } 171 | 172 | /** @return The version of the app. */ 173 | public static int getAppVersion() { 174 | return mAppVersion; 175 | } 176 | 177 | /** @param appVersion The app version to set. */ 178 | public static void setAppVersion(int appVersion) { 179 | ImageViewNext.mAppVersion = appVersion; 180 | } 181 | 182 | /** 183 | * Sets the image loading callbacks listener. 184 | * 185 | * @param l The listener, or null to clear it. 186 | */ 187 | public void setImageLoadCallbacks(ImageLoadCompletionListener l) { 188 | mLoadCallbacks = l; 189 | } 190 | 191 | /** 192 | * Gets the current image loading callbacks listener, if any. 193 | * 194 | * @return Returns the callbacks listener. 195 | */ 196 | public ImageLoadCompletionListener getImageLoadCallbacks() { 197 | return mLoadCallbacks; 198 | } 199 | 200 | /** @return The disk cache max size, in bits. */ 201 | public static int getDiskCacheSize() { 202 | return mDiskCacheSize; 203 | } 204 | 205 | /** @param diskCacheSize The disk cache max size to set, in bits. */ 206 | public static void setDiskCacheSize(int diskCacheSize) { 207 | ImageViewNext.mDiskCacheSize = diskCacheSize; 208 | } 209 | 210 | /** 211 | * Initializes both the in-memory and the disk-cache 212 | * at class-level, if it hasn't been done already. 213 | * This method is idempotent. 214 | */ 215 | public static void initCaches(Context context) { 216 | if (!mCacheInit) { 217 | mMemCache = new LruCache(mMemCacheSize) { 218 | protected int sizeOf(String key, byte[] value) { 219 | return value.length; 220 | } 221 | }; 222 | File diskCacheDir = 223 | CacheHelper.getDiskCacheDir(context, "imagecache"); 224 | try { 225 | mDiskCache = DiskLruCache.open( 226 | diskCacheDir, mAppVersion, DISK_CACHE_VALUE_COUNT, mDiskCacheSize); 227 | } 228 | catch (IOException ignored) { 229 | } 230 | mCacheInit = true; 231 | } 232 | } 233 | 234 | /** 235 | * Sets the loading {@link Drawable} to be used for every {@link ImageViewNext}. 236 | * 237 | * @param classLoadingDrawableResId the {@link int} resource ID of the Drawable 238 | * while loading an image. 239 | */ 240 | public static void setClassLoadingDrawable(int classLoadingDrawableResId) { 241 | mClassLoadingResId = classLoadingDrawableResId; 242 | } 243 | 244 | /** 245 | * Sets the loading {@link Drawable} to be used for this {@link ImageViewNext}. 246 | * 247 | * @param loadingDrawable the {@link Drawable} to display while loading an image. 248 | */ 249 | public void setLoadingDrawable(Drawable loadingDrawable) { 250 | mLoadingD = loadingDrawable; 251 | } 252 | 253 | /** 254 | * Gets the {@link Drawable} to display while loading an image. 255 | * 256 | * @return {@link Drawable} to display while loading. 257 | */ 258 | public Drawable getLoadingDrawable() { 259 | if (mLoadingD != null) { 260 | return mLoadingD; 261 | } 262 | else { 263 | return mClassLoadingResId > 0 ? getResources().getDrawable(mClassLoadingResId) : null; 264 | } 265 | } 266 | 267 | /** 268 | * Sets the error {@link Drawable} to be used for every {@link ImageViewNext}. 269 | * 270 | * @param classErrorDrawableResId the {@link int} resource ID of the Drawable 271 | * to display after an error getting an image. 272 | */ 273 | public static void setClassErrorDrawable(int classErrorDrawableResId) { 274 | mClassErrorResId = classErrorDrawableResId; 275 | } 276 | 277 | /** 278 | * Sets the error {@link Drawable} to be used for this {@link ImageViewNext}. 279 | * 280 | * @param errorDrawable the {@link Drawable} to display after an error getting an image. 281 | */ 282 | public void setErrorDrawable(Drawable errorDrawable) { 283 | mErrorD = errorDrawable; 284 | } 285 | 286 | /** 287 | * Gets the {@link Drawable} to display after an error loading an image. 288 | * 289 | * @return {@link Drawable} to display after an error loading an image. 290 | */ 291 | public Drawable getErrorDrawable() { 292 | return mErrorD != null ? mErrorD : getResources().getDrawable(mClassErrorResId); 293 | } 294 | 295 | /** 296 | * Checks if a request is already in progress. 297 | * 298 | * @return true if there is a pending request, false otherwise. 299 | */ 300 | private boolean isRequestInProgress() { 301 | return mCurrentRequest != null 302 | && mRequestManager.isRequestInProgress(mCurrentRequest); 303 | } 304 | 305 | /** Aborts the current request, if any, and stops everything else. */ 306 | private void abortEverything() { 307 | // Abort the current request before starting another one 308 | if (isRequestInProgress()) { 309 | mRequestManager.removeRequestListener(mCurrentRequestListener); 310 | } 311 | 312 | stop(); 313 | stopLoading(); 314 | } 315 | 316 | /** 317 | * Sets the content of the {@link ImageViewNext} with the data to be downloaded 318 | * from the provided URL. 319 | * 320 | * @param url The URL to download the image from. It can be an animated GIF. 321 | */ 322 | public void setUrl(String url) { 323 | mUrl = url; 324 | 325 | // Abort the pending request (if any) and stop animating/loading 326 | abortEverything(); 327 | 328 | // Start the whole retrieval chain 329 | getFromMemCache(url); 330 | } 331 | 332 | /** 333 | * Returns the current URL set to the {@link ImageViewNext}. 334 | * The URL will be returned regardless of the existence of 335 | * the image or of the caching/downloading progress. 336 | * 337 | * @return The URL set for this {@link ImageViewNext}. 338 | */ 339 | public String getUrl() { 340 | return mUrl; 341 | } 342 | 343 | /** 344 | * Returns true if this instance will automatically retry the download from 345 | * the network when it becomes available once again. 346 | * The instance level settings has priority over the class level's. 347 | * 348 | * @return true if the instance retries to download the image when the 349 | * network is once again available, false otherwise. 350 | */ 351 | public boolean isAutoRetryFromNetwork() { 352 | return mAutoRetryFromNetwork; 353 | } 354 | 355 | /** 356 | * Sets the value of auto retry from network for this instance, set it to 357 | * true if this instance has to automatically retry the download from the 358 | * network when it becomes available once again, false otherwise. The 359 | * instance level settings has priority over the class level's. 360 | *

361 | * If the instance was previously forbidden to auto-retry, it will be 362 | * allowed as soon as this method is called with a true argument. 363 | *

364 | * If the instance was previously allowed to auto-retry, it will be 365 | * forbidden as soon as this method is called with a false argument. 366 | * 367 | * @param autoRetryFromNetwork The instance value for the auto retry. 368 | */ 369 | 370 | public void setAutoRetryFromNetwork(boolean autoRetryFromNetwork) { 371 | boolean registerAfter; 372 | boolean unregisterAfter; 373 | 374 | // If nothing changes, do nothing 375 | if (mAutoRetryFromNetwork == autoRetryFromNetwork) return; 376 | 377 | // Set the "after" booleans 378 | registerAfter = !mAutoRetryFromNetwork; 379 | unregisterAfter = !autoRetryFromNetwork; 380 | 381 | // Set the state value 382 | mAutoRetryFromNetwork = autoRetryFromNetwork; 383 | 384 | // Register or unregister the receiver according to the new value 385 | if (registerAfter) { 386 | registerReceiver(); 387 | } 388 | else if (unregisterAfter) { 389 | unregisterReceiver(); 390 | } 391 | } 392 | 393 | /** 394 | * Returns true if every ImageViewNext will automatically retry the download from 395 | * the network when it becomes available once again. 396 | * 397 | * @return true if ImageViewNext retries to download the image when the 398 | * network is once again available, false otherwise. 399 | */ 400 | 401 | public static boolean isClassAutoRetryFromNetwork() { 402 | return ImageViewNext.mClassAutoRetryFromNetwork; 403 | } 404 | 405 | /** 406 | * Sets the value of auto retry from network for ImageViewNext, set it to true 407 | * if ImageViewNext has to automatically retry the download from 408 | * the network when it becomes available once again, false otherwise. 409 | *

410 | * All of the existing constructed instances won't be affected by this. 411 | * 412 | * @param classAutoRetryFromNetwork The instance value for the auto retry. 413 | */ 414 | 415 | public static void setClassAutoRetryFromNetwork( 416 | boolean classAutoRetryFromNetwork) { 417 | ImageViewNext.mClassAutoRetryFromNetwork = classAutoRetryFromNetwork; 418 | } 419 | 420 | /** 421 | * Checks if the auto retry can be applied for the current instance. 422 | * 423 | * @return true if this instance is allowed to auto retry network ops, false 424 | * otherwise. 425 | */ 426 | private boolean isAutoRetryTrueSomewhere() { 427 | return isAutoRetryFromNetwork() || isClassAutoRetryFromNetwork(); 428 | } 429 | 430 | /** 431 | * Tries to retrieve the image from network, if and only if: 432 | *

438 | */ 439 | public void retryFromNetworkIfPossible() { 440 | // Only retry to get the image from the network: 441 | // - if no requests are in progress 442 | // - if the download previously failed 443 | // - auto retry is set to true for the instance or the class (in order) 444 | if (!isRequestInProgress() && hasFailedDownload && isAutoRetryTrueSomewhere()) { 445 | if (BuildConfig.DEBUG) Log.i(TAG, "Autoretry: true somewhere, retrying..."); 446 | // Abort the pending request (if any) and stop animating/loading 447 | abortEverything(); 448 | // Initalize caches 449 | ImageViewNext.initCaches(mContext); 450 | // Starts the retrieval from the network once again 451 | getFromNetwork(getUrl()); 452 | // Cross ye fingers 453 | } 454 | else { 455 | if (BuildConfig.DEBUG) Log.i(TAG, "Autoretry: false, sorry."); 456 | } 457 | } 458 | 459 | /** 460 | * Tries to get the image from the memory cache. 461 | * 462 | * @param url The URL to download the image from. It can be an animated GIF. 463 | */ 464 | private void getFromMemCache(String url) { 465 | if (BuildConfig.DEBUG) Log.i(TAG, "Memcache: getting for URL " + url + " @" + hashCode()); 466 | 467 | if (mLoadCallbacks != null) { 468 | mLoadCallbacks.onLoadStarted(this, CacheLevel.MEMORY); 469 | } 470 | 471 | // Get the URL from the input Bundle 472 | if (url == null || "".equals(url)) return; 473 | 474 | // Initializes the caches, if they're not initialized already 475 | ImageViewNext.initCaches(mContext); 476 | 477 | LruCache cache = ImageViewNext.getMemCache(); 478 | byte[] image = cache.get(url); 479 | 480 | if (image == null) { 481 | handleMemCacheMiss(); 482 | } 483 | else { 484 | onMemCacheHit(image, url); 485 | } 486 | } 487 | 488 | /** Generic function to handle the mem cache miss. */ 489 | private void handleMemCacheMiss() { 490 | // Calls the class callback 491 | onMemCacheMiss(); 492 | // Starts searching in the disk cache 493 | getFromDiskCache(getUrl()); 494 | } 495 | 496 | /** 497 | * Tries to get the image from the disk cache. 498 | * 499 | * @param url The URL to download the image from. It can be an animated GIF. 500 | */ 501 | private void getFromDiskCache(String url) { 502 | if (BuildConfig.DEBUG) Log.i(TAG, "Diskcache: getting for URL " + url + " @" + hashCode()); 503 | Request mRequest = 504 | ImageViewExRequestFactory.getImageDiskCacheRequest(url); 505 | mCurrentRequestListener = new ImageDiskCacheListener(this); 506 | mRequestManager.execute(mRequest, mCurrentRequestListener); 507 | 508 | if (mLoadCallbacks != null) { 509 | mLoadCallbacks.onLoadStarted(this, CacheLevel.DISK); 510 | } 511 | } 512 | 513 | /** 514 | * Tries to get the image from the network. 515 | * 516 | * @param url The URL to download the image from. It can be an animated GIF. 517 | */ 518 | private void getFromNetwork(String url) { 519 | if (BuildConfig.DEBUG) Log.i(TAG, "Network: getting for URL " + url + " @" + hashCode()); 520 | Request mRequest = 521 | ImageViewExRequestFactory.getImageDownloaderRequest(url); 522 | mCurrentRequestListener = new ImageDownloadListener(this); 523 | mRequestManager.execute(mRequest, mCurrentRequestListener); 524 | 525 | if (mLoadCallbacks != null) { 526 | mLoadCallbacks.onLoadStarted(this, CacheLevel.NETWORK); 527 | } 528 | } 529 | 530 | /** 531 | * Called when the image is got from whatever the source. 532 | * Override this to get the appropriate callback. 533 | * 534 | * @param image The image as a byte array. 535 | */ 536 | protected void onSuccess(byte[] image) { 537 | setByteArray(image); 538 | } 539 | 540 | /** 541 | * Called when the image is got from whatever the source. 542 | * Checks if the original URL matches the current one set 543 | * in the instance of ImageViewNext. 544 | * 545 | * @param image The image as a byte array. 546 | * @param url The URL of the retrieved image. 547 | */ 548 | private void onPreSuccess(byte[] image, String url) { 549 | // Only set the image if the current url equals to the retrieved image's url 550 | if (url != null && url.equals(getUrl())) { 551 | onSuccess(image); 552 | } 553 | } 554 | 555 | /** 556 | * Called when the image is got from the memory cache. 557 | * Override this to get the appropriate callback. 558 | * 559 | * @param image The image as a byte array. 560 | * @param url The URL of the retrieved image. 561 | */ 562 | protected void onMemCacheHit(byte[] image, String url) { 563 | if (BuildConfig.DEBUG) Log.i(TAG, "Memory cache HIT @" + hashCode()); 564 | onPreSuccess(image, url); 565 | 566 | if (mLoadCallbacks != null) { 567 | mLoadCallbacks.onLoadCompleted(this, CacheLevel.MEMORY); 568 | } 569 | } 570 | 571 | /** 572 | * Called when there is a memory cache miss for the image. 573 | * Override this to get the appropriate callback. 574 | */ 575 | protected void onMemCacheMiss() { 576 | Drawable loadingDrawable = getLoadingDrawable(); 577 | if (loadingDrawable != null) { 578 | ScaleType scaleType = getScaleType(); 579 | if (scaleType != null) { 580 | setScaleType(scaleType); 581 | } 582 | else { 583 | setScaleType(ScaleType.CENTER_INSIDE); 584 | } 585 | setImageDrawable(loadingDrawable); 586 | if (loadingDrawable instanceof AnimationDrawable) { 587 | ((AnimationDrawable) loadingDrawable).start(); 588 | } 589 | } 590 | else { 591 | setImageDrawable(mEmptyDrawable); // This also stops any ongoing loading process 592 | } 593 | 594 | if (mLoadCallbacks != null) { 595 | mLoadCallbacks.onLoadError(this, CacheLevel.MEMORY); 596 | } 597 | } 598 | 599 | /** 600 | * Called when the image is got from the disk cache. 601 | * Override this to get the appropriate callback. 602 | * 603 | * @param image The image as a byte array. 604 | * @param url The URL of the retrieved image. 605 | */ 606 | protected void onDiskCacheHit(byte[] image, String url) { 607 | if (BuildConfig.DEBUG) Log.i(TAG, "Disk cache HIT @" + hashCode()); 608 | onPreSuccess(image, url); 609 | 610 | if (mLoadCallbacks != null) { 611 | mLoadCallbacks.onLoadCompleted(this, CacheLevel.DISK); 612 | } 613 | } 614 | 615 | /** 616 | * Called when there is a disk cache miss for the image. 617 | * Override this to get the appropriate callback. 618 | */ 619 | protected void onDiskCacheMiss() { 620 | if (mLoadCallbacks != null) { 621 | mLoadCallbacks.onLoadError(this, CacheLevel.DISK); 622 | } 623 | } 624 | 625 | /** 626 | * Called when the image is got from the network. 627 | * Override this to get the appropriate callback. 628 | * 629 | * @param image The image as a byte array. 630 | * @param url The URL of the retrieved image. 631 | */ 632 | protected void onNetworkHit(byte[] image, String url) { 633 | if (BuildConfig.DEBUG) Log.i(TAG, "Network HIT @" + hashCode()); 634 | onPreSuccess(image, url); 635 | hasFailedDownload = false; 636 | 637 | if (mLoadCallbacks != null) { 638 | mLoadCallbacks.onLoadCompleted(this, CacheLevel.NETWORK); 639 | } 640 | } 641 | 642 | /** 643 | * Called when there is a network miss for the image, 644 | * usually a 404. 645 | * Override this to get the appropriate callback. 646 | */ 647 | protected void onNetworkMiss() { 648 | if (mLoadCallbacks != null) { 649 | mLoadCallbacks.onLoadError(this, CacheLevel.NETWORK); 650 | } 651 | hasFailedDownload = true; 652 | } 653 | 654 | /** 655 | * Called when the image could not be found anywhere. 656 | * Override this to get the appropriate callback. 657 | */ 658 | protected void onMiss() { 659 | Drawable errorDrawable = getErrorDrawable(); 660 | if (getErrorDrawable() != null) { 661 | ScaleType scaleType = getScaleType(); 662 | if (scaleType != null) { 663 | setScaleType(scaleType); 664 | } 665 | else { 666 | setScaleType(ScaleType.CENTER_INSIDE); 667 | } 668 | setImageDrawable(errorDrawable); 669 | if (errorDrawable instanceof AnimationDrawable) { 670 | ((AnimationDrawable) errorDrawable).start(); 671 | } 672 | } 673 | } 674 | 675 | /** 676 | * Sets the image from a byte array. 677 | * 678 | * @param image The image to set. 679 | */ 680 | private void setByteArray(final byte[] image) { 681 | if (image != null) { 682 | ScaleType scaleType = getScaleType(); 683 | if (scaleType != null) { 684 | setScaleType(scaleType); 685 | } 686 | setSource(image); 687 | } 688 | } 689 | 690 | /** 691 | * Returns the maximum number of concurrent worker threads 692 | * used to get images from cache/network. 693 | * 694 | * @return Maximum number of concurrent threads. 695 | */ 696 | public static int getMaximumNumberOfThreads() { 697 | return mConcurrentThreads; 698 | } 699 | 700 | /** 701 | * Define the maximum number of concurrent worker threads 702 | * used to get images from cache/network. 703 | * By default only 10 concurrent worker threads are used at 704 | * the same time. 705 | * The value will be set once and for all when the first 706 | * ImageViewNext is instantiated. Calling this function again 707 | * after an ImageViewNext is instantiated will have no effect. 708 | * 709 | * @param concurrentThreads The number of concurrent threads. 710 | */ 711 | public static void setMaximumNumberOfThreads(int concurrentThreads) { 712 | mConcurrentThreads = concurrentThreads; 713 | } 714 | 715 | /** 716 | * Operation listener for the disk cache retrieval operation. 717 | * 718 | * @author Francesco Pontillo 719 | */ 720 | private class ImageDiskCacheListener extends ImageViewExRequestListener { 721 | 722 | public ImageDiskCacheListener(ImageViewNext imageViewNext) { 723 | super(imageViewNext); 724 | } 725 | 726 | @Override 727 | public void onRequestFinished(Request request, Bundle resultData) { 728 | byte[] image = 729 | resultData.getByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT); 730 | String url = 731 | resultData.getString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL); 732 | if (image == null) { 733 | handleMiss(); 734 | } 735 | else { 736 | mImageViewNext.onDiskCacheHit(image, url); 737 | } 738 | } 739 | 740 | @Override 741 | public void onRequestConnectionError(Request request, int statusCode) { 742 | handleMiss(); 743 | } 744 | 745 | @Override 746 | public void onRequestDataError(Request request) { 747 | handleMiss(); 748 | } 749 | 750 | @Override 751 | public void onRequestCustomError(Request request, Bundle resultData) { 752 | handleMiss(); 753 | } 754 | 755 | /** Generic function to handle the cache miss. */ 756 | private void handleMiss() { 757 | // Calls the class callback 758 | mImageViewNext.onDiskCacheMiss(); 759 | // Starts searching in the network 760 | getFromNetwork(mImageViewNext.getUrl()); 761 | } 762 | 763 | } 764 | 765 | /** 766 | * Operation listener for the network retrieval operation. 767 | * 768 | * @author Francesco Pontillo 769 | */ 770 | private class ImageDownloadListener extends ImageViewExRequestListener { 771 | 772 | public ImageDownloadListener(ImageViewNext imageViewNext) { 773 | super(imageViewNext); 774 | } 775 | 776 | @Override 777 | public void onRequestFinished(Request request, Bundle resultData) { 778 | byte[] image = 779 | resultData.getByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT); 780 | String url = 781 | resultData.getString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL); 782 | if (image == null || image.length == 0) { 783 | handleMiss(); 784 | } 785 | else { 786 | mImageViewNext.onNetworkHit(image, url); 787 | } 788 | } 789 | 790 | @Override 791 | public void onRequestConnectionError(Request request, int statusCode) { 792 | handleMiss(); 793 | } 794 | 795 | @Override 796 | public void onRequestDataError(Request request) { 797 | handleMiss(); 798 | } 799 | 800 | @Override 801 | public void onRequestCustomError(Request request, Bundle resultData) { 802 | handleMiss(); 803 | } 804 | 805 | /** Generic function to handle the network miss. */ 806 | private void handleMiss() { 807 | // Calls the class callback 808 | mImageViewNext.onNetworkMiss(); 809 | // Calss the final miss class callback 810 | mImageViewNext.onMiss(); 811 | } 812 | } 813 | 814 | /** A simple interface for image loading callbacks. */ 815 | public interface ImageLoadCompletionListener { 816 | 817 | /** 818 | * Loading of a resource has been started by invoking {@link #setUrl(String)}. 819 | * 820 | * @param v The ImageViewNext on which the loading has begun 821 | * @param level The cache level involved. You will receive a pair of calls, one 822 | * to onLoadStarted and one to onLoadCompleted or to onLoadError, 823 | * for each cache level, in this order: memory->disk->network 824 | * (for MISS on both memory and disk caches) 825 | */ 826 | public void onLoadStarted(ImageViewNext v, CacheLevel level); 827 | 828 | /** 829 | * Loading of a resource has been completed. This corresponds to a cache HIT 830 | * for the memory and disk cache levels, or a successful download from the net. 831 | * 832 | * @param v The ImageViewNext on which the loading has completed 833 | * @param level The cache level involved. You will receive a pair of calls, one 834 | * to onLoadStarted and one to onLoadCompleted or to onLoadError, 835 | * for each cache level, in this order: memory->disk->network 836 | * (for MISS on both memory and disk caches). 837 | */ 838 | public void onLoadCompleted(ImageViewNext v, CacheLevel level); 839 | 840 | /** 841 | * Loading of a resource has failed. This corresponds to a cache MISS 842 | * for the memory and disk cache levels, or a successful download from the net. 843 | * 844 | * @param v The ImageViewNext on which the loading has begun 845 | * @param level The cache level involved. You will receive a pair of calls, one 846 | * to onLoadStarted and one to onLoadCompleted or to onLoadError, 847 | * for each cache level, in this order: memory->disk->network 848 | * (for MISS on both memory and disk caches) 849 | */ 850 | public void onLoadError(ImageViewNext v, CacheLevel level); 851 | } 852 | } 853 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/broadcastreceiver/ConnectivityChangeBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.broadcastreceiver; 2 | 3 | import net.frakbot.imageviewex.ImageViewNext; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.net.ConnectivityManager; 8 | import android.net.NetworkInfo; 9 | 10 | /** 11 | * BroadcastReceiver for receiving information about the network state. 12 | * 13 | * @author Francesco Pontillo 14 | */ 15 | public class ConnectivityChangeBroadcastReceiver extends BroadcastReceiver { 16 | 17 | private ImageViewNext mImageViewNext; 18 | 19 | /** 20 | * Constructor, initializes the ImageViewNext to be used to retry the 21 | * network operation after the connection is restored. 22 | * 23 | * @param imageViewNext 24 | * The ImageViewNext instance. 25 | */ 26 | public ConnectivityChangeBroadcastReceiver(ImageViewNext imageViewNext) { 27 | mImageViewNext = imageViewNext; 28 | } 29 | 30 | @Override 31 | public void onReceive(Context context, Intent intent) { 32 | // Get the NetworkInfo Parcelable 33 | NetworkInfo networkInfo = intent 34 | .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); 35 | 36 | // Check for the connection 37 | boolean isConnected = networkInfo.isConnected(); 38 | if (isConnected) { 39 | mImageViewNext.retryFromNetworkIfPossible(); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/listener/ImageViewExRequestListener.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.listener; 2 | 3 | import net.frakbot.imageviewex.ImageViewNext; 4 | 5 | import com.foxykeep.datadroid.requestmanager.RequestManager.RequestListener; 6 | 7 | public abstract class ImageViewExRequestListener implements RequestListener { 8 | protected ImageViewNext mImageViewNext; 9 | 10 | public ImageViewExRequestListener(ImageViewNext imageViewNext) { 11 | this.mImageViewNext = imageViewNext; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/operation/ImageDiskCacheOperation.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.operation; 2 | 3 | import net.frakbot.cache.CacheHelper; 4 | import net.frakbot.imageviewex.Converters; 5 | import net.frakbot.imageviewex.ImageViewNext; 6 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 7 | import android.content.Context; 8 | import android.os.Bundle; 9 | import android.support.v4.util.LruCache; 10 | 11 | import com.foxykeep.datadroid.exception.ConnectionException; 12 | import com.foxykeep.datadroid.exception.CustomRequestException; 13 | import com.foxykeep.datadroid.exception.DataException; 14 | import com.foxykeep.datadroid.requestmanager.Request; 15 | import com.foxykeep.datadroid.service.RequestService.Operation; 16 | import com.jakewharton.disklrucache.DiskLruCache; 17 | import com.jakewharton.disklrucache.DiskLruCache.Snapshot; 18 | 19 | /** 20 | * Operation to search for an image in the disk cache. 21 | * Requested input: 22 | * - ImageMemCacheOperation.PARAM_IMAGE_URL, the URL of the image 23 | * Given output: 24 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, the byte array of the image 25 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, the requested URL of the image 26 | * 27 | * @author Francesco Pontillo 28 | * 29 | */ 30 | public class ImageDiskCacheOperation implements Operation { 31 | 32 | public static final String PARAM_IMAGE_URL = 33 | "net.frakbot.imageviewex.extra.url"; 34 | 35 | @Override 36 | public Bundle execute(Context context, Request request) 37 | throws ConnectionException, DataException, CustomRequestException { 38 | 39 | // Get the URL from the input Bundle 40 | String url = request.getString(PARAM_IMAGE_URL); 41 | if (url == null || url.equals("")) throw new DataException("No value for URL " + url); 42 | 43 | // Initializes the caches, if they're not initialized already 44 | ImageViewNext.initCaches(context); 45 | 46 | // Get the entry 47 | DiskLruCache diskCache = ImageViewNext.getDiskCache(); 48 | Snapshot cacheEntry = null; 49 | try { 50 | cacheEntry = diskCache.get(CacheHelper.UriToDiskLruCacheString(url)); 51 | } catch (Exception e) { 52 | throw new DataException("DISK CACHE: Error while getting value for URL " + url); 53 | } 54 | 55 | byte[] image = null; 56 | 57 | // If the object is not null, convert it 58 | if (cacheEntry != null) { 59 | // Convert the InputStream 60 | image = Converters.inputStreamToByteArray( 61 | cacheEntry.getInputStream(0), 62 | (int)cacheEntry.getLength(0)); 63 | 64 | // Saves the image in the in-memory cache 65 | LruCache memCache = ImageViewNext.getMemCache(); 66 | memCache.put(url, image); 67 | } 68 | 69 | Bundle b = new Bundle(); 70 | b.putByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, image); 71 | b.putString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, url); 72 | return b; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/operation/ImageDownloadOperation.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.operation; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v4.util.LruCache; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | import com.foxykeep.datadroid.exception.ConnectionException; 9 | import com.foxykeep.datadroid.exception.CustomRequestException; 10 | import com.foxykeep.datadroid.exception.DataException; 11 | import com.foxykeep.datadroid.requestmanager.Request; 12 | import com.foxykeep.datadroid.service.RequestService.Operation; 13 | import com.jakewharton.disklrucache.DiskLruCache; 14 | import com.jakewharton.disklrucache.DiskLruCache.Editor; 15 | import net.frakbot.cache.CacheHelper; 16 | import net.frakbot.imageviewex.ImageViewNext; 17 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 18 | import net.frakbot.remote.RemoteHelper; 19 | 20 | import java.io.IOException; 21 | 22 | /** 23 | * Operation to download an image from the network. 24 | * Requested input: 25 | * - ImageMemCacheOperation.PARAM_IMAGE_URL, the URL of the image 26 | * Given output: 27 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, the byte array of the image 28 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, the requested URL of the image 29 | * 30 | * @author Francesco Pontillo 31 | */ 32 | public class ImageDownloadOperation implements Operation { 33 | 34 | public static final String PARAM_IMAGE_URL = 35 | "net.frakbot.imageviewex.extra.url"; 36 | 37 | @Override 38 | public Bundle execute(Context context, Request request) 39 | throws ConnectionException, DataException, CustomRequestException { 40 | 41 | // Initializes the caches, if they're not initialized already 42 | ImageViewNext.initCaches(context); 43 | 44 | // Get the URL from the input Bundle 45 | String url = request.getString(PARAM_IMAGE_URL); 46 | if (TextUtils.isEmpty(url)) throw new DataException("No value for URL parameter"); 47 | 48 | byte[] image; 49 | try { 50 | image = RemoteHelper.download(url); 51 | } 52 | catch (IOException e) { 53 | throw new DataException("NETWORK: Error while getting value for URL " + url); 54 | } 55 | 56 | // If the object is not null 57 | if (image != null) { 58 | // Save into the disk cache 59 | DiskLruCache diskCache = ImageViewNext.getDiskCache(); 60 | try { 61 | Editor editor = diskCache.edit(CacheHelper.UriToDiskLruCacheString(url)); 62 | if (editor != null) { 63 | if (CacheHelper.writeByteArrayToEditor(image, editor)) { 64 | diskCache.flush(); 65 | editor.commit(); 66 | } 67 | else { 68 | editor.abort(); 69 | } 70 | } 71 | } 72 | catch (Exception e) { 73 | Log.w(ImageDownloadOperation.class.getSimpleName(), "Storage of image into the disk cache failed!"); 74 | } 75 | // Save into the memory cache 76 | LruCache memCache = ImageViewNext.getMemCache(); 77 | memCache.put(url, image); 78 | } 79 | 80 | Bundle b = new Bundle(); 81 | b.putByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, image); 82 | b.putString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, url); 83 | return b; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/operation/ImageMemCacheOperation.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.operation; 2 | 3 | import net.frakbot.imageviewex.ImageViewNext; 4 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 5 | import android.content.Context; 6 | import android.os.Bundle; 7 | import android.support.v4.util.LruCache; 8 | 9 | import com.foxykeep.datadroid.exception.ConnectionException; 10 | import com.foxykeep.datadroid.exception.CustomRequestException; 11 | import com.foxykeep.datadroid.exception.DataException; 12 | import com.foxykeep.datadroid.requestmanager.Request; 13 | import com.foxykeep.datadroid.service.RequestService.Operation; 14 | 15 | /** 16 | * Operation to search for an image in the in-memory cache. 17 | * Requested input: 18 | * - ImageMemCacheOperation.PARAM_IMAGE_URL, the URL of the image 19 | * Given output: 20 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, the byte array of the image 21 | * - ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, the requested URL of the image 22 | * 23 | * @deprecated Retrieving in an async way from the mem cache is slow. 24 | * @author Francesco Pontillo 25 | * 26 | */ 27 | public class ImageMemCacheOperation implements Operation { 28 | 29 | public static final String PARAM_IMAGE_URL = 30 | "net.frakbot.imageviewex.extra.url"; 31 | 32 | @Override 33 | public Bundle execute(Context context, Request request) 34 | throws ConnectionException, DataException, CustomRequestException { 35 | // Get the URL from the input Bundle 36 | String url = request.getString(PARAM_IMAGE_URL); 37 | if (url == null || url.equals("")) throw new DataException("MEM CACHE: Empty URL " + url); 38 | 39 | // Initializes the caches, if they're not initialized already 40 | ImageViewNext.initCaches(context); 41 | 42 | LruCache cache = ImageViewNext.getMemCache(); 43 | byte[] image = cache.get(url); 44 | 45 | Bundle b = new Bundle(); 46 | b.putByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT, image); 47 | b.putString(ImageViewExRequestFactory.BUNDLE_EXTRA_IMAGE_URL, url); 48 | return b; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/requestmanager/ImageViewExRequestFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2012 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package net.frakbot.imageviewex.requestmanager; 10 | 11 | import net.frakbot.imageviewex.operation.ImageDiskCacheOperation; 12 | import net.frakbot.imageviewex.operation.ImageDownloadOperation; 13 | import net.frakbot.imageviewex.operation.ImageMemCacheOperation; 14 | 15 | import com.foxykeep.datadroid.requestmanager.Request; 16 | 17 | /** 18 | * Class used to create the {@link Request}s. 19 | * 20 | * @author Foxykeep, Francesco Pontillo 21 | */ 22 | @SuppressWarnings("deprecation") 23 | public final class ImageViewExRequestFactory { 24 | // Request types 25 | public static final int REQUEST_TYPE_IMAGE_MEM_CACHE = 0; 26 | public static final int REQUEST_TYPE_IMAGE_DISK_CACHE = 1; 27 | public static final int REQUEST_TYPE_IMAGE_DOWNLOAD = 2; 28 | 29 | // Response data 30 | public static final String BUNDLE_EXTRA_OBJECT = 31 | "net.frakbot.imageviewex.extra.object"; 32 | public static final String BUNDLE_EXTRA_IMAGE_URL = 33 | "net.frakbot.imageviewex.extra.imageUrl"; 34 | 35 | private ImageViewExRequestFactory() { 36 | // no public constructor 37 | } 38 | 39 | /** 40 | * Create the request to get an image from the memory cache. 41 | * 42 | * @param url The URL of the image. 43 | * @return The request. 44 | */ 45 | public static Request getImageMemCacheRequest(String url) { 46 | Request request = new Request(REQUEST_TYPE_IMAGE_MEM_CACHE); 47 | request.put(ImageMemCacheOperation.PARAM_IMAGE_URL, url); 48 | request.setMemoryCacheEnabled(true); 49 | return request; 50 | } 51 | 52 | /** 53 | * Create the request to get an image from the the disk cache. 54 | * 55 | * @param url The URL of the image. 56 | * @return The request. 57 | */ 58 | public static Request getImageDiskCacheRequest(String url) { 59 | Request request = new Request(REQUEST_TYPE_IMAGE_DISK_CACHE); 60 | request.put(ImageDiskCacheOperation.PARAM_IMAGE_URL, url); 61 | request.setMemoryCacheEnabled(true); 62 | return request; 63 | } 64 | 65 | /** 66 | * Create the request to get an image from the network. 67 | * 68 | * @param url The URL of the image. 69 | * @return The request. 70 | */ 71 | public static Request getImageDownloaderRequest(String url) { 72 | Request request = new Request(REQUEST_TYPE_IMAGE_DOWNLOAD); 73 | request.put(ImageDownloadOperation.PARAM_IMAGE_URL, url); 74 | request.setMemoryCacheEnabled(true); 75 | return request; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/requestmanager/ImageViewExRequestManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package net.frakbot.imageviewex.requestmanager; 10 | 11 | import net.frakbot.imageviewex.service.ImageViewExService; 12 | 13 | import com.foxykeep.datadroid.requestmanager.RequestManager; 14 | 15 | import android.content.Context; 16 | 17 | /** 18 | * This class is used as a proxy to call the Service. It provides easy-to-use methods to call the 19 | * service and manages the Intent creation. It also assures that a request will not be sent again if 20 | * an exactly identical one is already in progress. 21 | * 22 | * @author Foxykeep, Francesco Pontillo 23 | */ 24 | public final class ImageViewExRequestManager extends RequestManager { 25 | 26 | // Singleton management 27 | private static ImageViewExRequestManager sInstance; 28 | 29 | public synchronized static ImageViewExRequestManager from(Context context) { 30 | if (sInstance == null) { 31 | sInstance = new ImageViewExRequestManager(context); 32 | } 33 | 34 | return sInstance; 35 | } 36 | 37 | private ImageViewExRequestManager(Context context) { 38 | super(context, ImageViewExService.class); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/net/frakbot/imageviewex/service/ImageViewExService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 2011 Foxykeep (http://datadroid.foxykeep.com) 3 | *

4 | * Licensed under the Beerware License :
5 | * As long as you retain this notice you can do whatever you want with this stuff. If we meet some 6 | * day, and you think this stuff is worth it, you can buy me a beer in return 7 | */ 8 | 9 | package net.frakbot.imageviewex.service; 10 | 11 | import net.frakbot.imageviewex.ImageViewNext; 12 | import net.frakbot.imageviewex.operation.ImageDiskCacheOperation; 13 | import net.frakbot.imageviewex.operation.ImageDownloadOperation; 14 | import net.frakbot.imageviewex.operation.ImageMemCacheOperation; 15 | import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory; 16 | import android.os.Bundle; 17 | 18 | import com.foxykeep.datadroid.exception.CustomRequestException; 19 | import com.foxykeep.datadroid.requestmanager.Request; 20 | import com.foxykeep.datadroid.service.RequestService; 21 | 22 | /** 23 | * This class is called by the {@link ImageViewExRequestManager} 24 | * through the {@link Intent} system. 25 | * 26 | * @author Foxykeep, Francesco Pontillo 27 | */ 28 | @SuppressWarnings("deprecation") 29 | public class ImageViewExService extends RequestService { 30 | 31 | @Override 32 | protected int getMaximumNumberOfThreads() { 33 | return ImageViewNext.getMaximumNumberOfThreads(); 34 | } 35 | 36 | @Override 37 | public Operation getOperationForType(int requestType) { 38 | switch (requestType) { 39 | case ImageViewExRequestFactory.REQUEST_TYPE_IMAGE_MEM_CACHE: 40 | return new ImageMemCacheOperation(); 41 | case ImageViewExRequestFactory.REQUEST_TYPE_IMAGE_DISK_CACHE: 42 | return new ImageDiskCacheOperation(); 43 | case ImageViewExRequestFactory.REQUEST_TYPE_IMAGE_DOWNLOAD: 44 | return new ImageDownloadOperation(); 45 | } 46 | return null; 47 | } 48 | 49 | @Override 50 | protected Bundle onCustomRequestException(Request request, CustomRequestException exception) { 51 | return super.onCustomRequestException(request, exception); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/net/frakbot/remote/RemoteHelper.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.remote; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.net.HttpURLConnection; 9 | import java.net.URL; 10 | 11 | import com.squareup.okhttp.OkHttpClient; 12 | 13 | /** 14 | * Helper class that exposes some utility methods for retrieving 15 | * objects from the network. 16 | * 17 | * @author Francesco Pontillo 18 | */ 19 | public class RemoteHelper { 20 | private static final String LOG_TAG = "Loader"; 21 | 22 | private static final int defaultBufferSize = 2048; 23 | 24 | /** 25 | * Download an object from the network. 26 | * 27 | * @param resourceUrl The URL of then rsource. 28 | * 29 | * @throws IOException If the connection cannot be established. 30 | * @return Byte array of the downloaded object. 31 | */ 32 | public static byte[] download(String resourceUrl) throws IOException { 33 | OkHttpClient client = new OkHttpClient(); 34 | URL url = new URL(resourceUrl); 35 | HttpURLConnection connection = client.open(url); 36 | 37 | final int responseCode = connection.getResponseCode(); 38 | if (responseCode != HttpURLConnection.HTTP_OK) { 39 | Log.w(LOG_TAG, "Downloading from URL " + resourceUrl + " failed with response code " + responseCode); 40 | return null; 41 | } 42 | 43 | // determine the image size and allocate a buffer 44 | int fileSize = connection.getContentLength(); 45 | Log.d(LOG_TAG, "fetching " + resourceUrl 46 | + " (" + (fileSize <= 0 ? "size unknown" : Integer.toString(fileSize)) + ")"); 47 | 48 | BufferedInputStream istream = new BufferedInputStream(connection.getInputStream()); 49 | 50 | try { 51 | if (fileSize <= 0) { 52 | Log.w(LOG_TAG, 53 | "Server did not set a Content-Length header, will default to buffer size of " 54 | + defaultBufferSize + " bytes"); 55 | ByteArrayOutputStream buf = new ByteArrayOutputStream(defaultBufferSize); 56 | byte[] buffer = new byte[defaultBufferSize]; 57 | int bytesRead = 0; 58 | while (bytesRead != -1) { 59 | bytesRead = istream.read(buffer, 0, defaultBufferSize); 60 | if (bytesRead > 0) { 61 | buf.write(buffer, 0, bytesRead); 62 | } 63 | } 64 | return buf.toByteArray(); 65 | } 66 | else { 67 | byte[] data = new byte[fileSize]; 68 | 69 | int bytesRead = 0; 70 | int offset = 0; 71 | while (bytesRead != -1 && offset < fileSize) { 72 | bytesRead = istream.read(data, offset, fileSize - offset); 73 | offset += bytesRead; 74 | } 75 | return data; 76 | } 77 | } 78 | finally { 79 | // clean up 80 | try { 81 | istream.close(); 82 | connection.disconnect(); 83 | } 84 | catch (Exception ignore) { 85 | } 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /test/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ImageViewEx-Test 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 | -------------------------------------------------------------------------------- /test/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /test/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /test/ImageViewEx-Test.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/assets/Awake.Finale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/assets/Awake.Finale.png -------------------------------------------------------------------------------- /test/assets/Episodes_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/assets/Episodes_thumb.png -------------------------------------------------------------------------------- /test/assets/Lost_anim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/assets/Lost_anim.gif -------------------------------------------------------------------------------- /test/assets/Lost_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/assets/Lost_thumb.png -------------------------------------------------------------------------------- /test/assets/Simpsons_anim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/assets/Simpsons_anim.gif -------------------------------------------------------------------------------- /test/assets/himym-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/assets/himym-banner.jpg -------------------------------------------------------------------------------- /test/assets/suicidiosenzafronzoli.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/assets/suicidiosenzafronzoli.gif -------------------------------------------------------------------------------- /test/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /test/project.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 edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | android.library.reference.1=..\\..\\ImageViewEx 16 | 17 | android.library.reference.2=../submods/DataDroid/DataDroid 18 | -------------------------------------------------------------------------------- /test/res/drawable-hdpi/empty_newsthumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/empty_newsthumb.png -------------------------------------------------------------------------------- /test/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /test/res/drawable-hdpi/loader_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/loader_0.png -------------------------------------------------------------------------------- /test/res/drawable-hdpi/loader_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/loader_1.png -------------------------------------------------------------------------------- /test/res/drawable-hdpi/loader_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/loader_2.png -------------------------------------------------------------------------------- /test/res/drawable-hdpi/loader_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/loader_3.png -------------------------------------------------------------------------------- /test/res/drawable-hdpi/loader_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/loader_4.png -------------------------------------------------------------------------------- /test/res/drawable-hdpi/loader_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/loader_5.png -------------------------------------------------------------------------------- /test/res/drawable-hdpi/loader_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/loader_6.png -------------------------------------------------------------------------------- /test/res/drawable-hdpi/loader_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-hdpi/loader_7.png -------------------------------------------------------------------------------- /test/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /test/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /test/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frapontillo/ImageViewEx/76431430682ceed2f5181ba13ac13ecd122a8b1c/test/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /test/res/drawable/loader.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 17 | 18 | 26 | 27 | 33 | 34 | 40 | 41 | 47 | 48 | 54 | 55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /test/res/layout/next.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 16 | 17 | 24 | 25 | 32 | 33 | 40 | 41 | 48 | 49 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /test/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World, ImageViewExActivity! 5 | ImageViewEx 6 | ImageViewNext 7 | 8 | -------------------------------------------------------------------------------- /test/src/net/frakbot/imageviewex/test/ImageViewExActivity.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.test; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.util.DisplayMetrics; 6 | import android.widget.TextView; 7 | import net.frakbot.imageviewex.Converters; 8 | import net.frakbot.imageviewex.ImageViewEx; 9 | 10 | public class ImageViewExActivity extends Activity { 11 | private ImageViewEx img1; 12 | private ImageViewEx img2; 13 | private ImageViewEx img3; 14 | private ImageViewEx img4; 15 | private ImageViewEx img5; 16 | private ImageViewEx img6; 17 | 18 | /** Called when the activity is first created. */ 19 | @Override 20 | public void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.main); 23 | 24 | // Displays some stats about density 25 | DisplayMetrics dm = new DisplayMetrics(); 26 | getWindowManager().getDefaultDisplay().getMetrics(dm); 27 | TextView textview = (TextView) findViewById(R.id.textview); 28 | textview.setText(String.format("Density: %f; DensityDpi: %d; ScaledDensity: %f; Pixel size: %d x %d", 29 | dm.density, dm.densityDpi, dm.scaledDensity, dm.widthPixels, dm.heightPixels)); 30 | 31 | // Disables animation, behaving like a regular ImageView, 32 | // except you can still set byte[] as the source 33 | // ImageViewEx.setCanAlwaysAnimate(false); 34 | 35 | // Sets a default density for all of the images in each ImageViewEx. 36 | // ImageViewEx.setClassLevelDensity(DisplayMetrics.DENSITY_MEDIUM); 37 | 38 | // Sets a density for the img5 only. 39 | // Changing the density after an object has been set will 40 | // do nothing, you will have to re-set the object. 41 | // img5.setInDensity(DisplayMetrics.DENSITY_LOW); 42 | 43 | img1 = (ImageViewEx) findViewById(R.id.imageViewEx1); 44 | img2 = (ImageViewEx) findViewById(R.id.imageViewEx2); 45 | img3 = (ImageViewEx) findViewById(R.id.imageViewEx3); 46 | img4 = (ImageViewEx) findViewById(R.id.imageViewEx4); 47 | img5 = (ImageViewEx) findViewById(R.id.imageViewEx5); 48 | img6 = (ImageViewEx) findViewById(R.id.imageViewEx6); 49 | 50 | // Sets the sources of ImageViewExs as byte arrays 51 | img1.setSource(Converters.assetToByteArray(getAssets(), "Episodes_thumb.png")); 52 | img2.setSource(Converters.assetToByteArray(getAssets(), "Lost_anim.gif")); 53 | img3.setSource(Converters.assetToByteArray(getAssets(), "Lost_thumb.png")); 54 | img4.setSource(Converters.assetToByteArray(getAssets(), "Simpsons_anim.gif")); 55 | img5.setSource(Converters.assetToByteArray(getAssets(), "suicidiosenzafronzoli.gif")); 56 | img6.setSource(Converters.assetToByteArray(getAssets(), "himym-banner.jpg")); 57 | 58 | img6.setFillDirection(ImageViewEx.FillDirection.HORIZONTAL); 59 | } 60 | } -------------------------------------------------------------------------------- /test/src/net/frakbot/imageviewex/test/ImageViewNextActivity.java: -------------------------------------------------------------------------------- 1 | package net.frakbot.imageviewex.test; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.util.DisplayMetrics; 6 | import android.widget.TextView; 7 | import net.frakbot.imageviewex.ImageAlign; 8 | import net.frakbot.imageviewex.ImageViewNext; 9 | 10 | public class ImageViewNextActivity extends Activity { 11 | private ImageViewNext img1; 12 | private ImageViewNext img2; 13 | private ImageViewNext img3; 14 | private ImageViewNext img4; 15 | private ImageViewNext img5; 16 | 17 | /** Called when the activity is first created. */ 18 | @Override 19 | public void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | 22 | // Set A LOT of maximum concurrent threads: 23 | // this is an exaggeration, please don't do this! :D 24 | ImageViewNext.setMaximumNumberOfThreads(100); 25 | 26 | setContentView(R.layout.next); 27 | 28 | // Displays some stats about density 29 | DisplayMetrics dm = new DisplayMetrics(); 30 | getWindowManager().getDefaultDisplay().getMetrics(dm); 31 | TextView textview = (TextView)findViewById(R.id.textview); 32 | textview.setText(String.format("Density: %f; DensityDpi: %d; ScaledDensity: %f; Pixel size: %d x %d", 33 | dm.density, dm.densityDpi, dm.scaledDensity, dm.widthPixels, dm.heightPixels)); 34 | 35 | // Sets the loading/error drawables (can be animated drawables!!!) for every instance 36 | // of the class. 37 | ImageViewNext.setClassErrorDrawable(R.drawable.empty_newsthumb); 38 | ImageViewNext.setClassLoadingDrawable(R.drawable.loader); 39 | 40 | img1 = (ImageViewNext)findViewById(R.id.imageViewNext1); 41 | img2 = (ImageViewNext)findViewById(R.id.imageViewNext2); 42 | img3 = (ImageViewNext)findViewById(R.id.imageViewNext3); 43 | img4 = (ImageViewNext)findViewById(R.id.imageViewNext4); 44 | img5 = (ImageViewNext)findViewById(R.id.imageViewNext5); 45 | 46 | // Sets the class density to HDPI 47 | // ImageViewNext.setClassLevelDensity(DisplayMetrics.DENSITY_HIGH); 48 | // Sets the first image density to medium (bigger than the others) 49 | img1.setDensity(DisplayMetrics.DENSITY_LOW); 50 | 51 | // Sets the sources of ImageViewNexts from URL 52 | img1.setUrl("https://api.italiansubs.net/api/rest/shows/2132/banner?apikey=9ec0a43e2690d09cb79a784459a0e044"); 53 | img2.setUrl("http://img.italiansubs.net/news2/data/The%20Simpsons/Stagione%2023/the.simpsons.s23e22.gif"); 54 | img3.setUrl("http://img.italiansubs.net/news2/data/Game%20of%20Thrones/Stagione%202/Game.of.Thrones.S02E08.gif"); 55 | img4.setUrl("http://www.italiansubs.net/forum/Smileys/default/suicidiosenzafronzoli.gif"); 56 | img5.setUrl("http://img.italiansubs.net/news2/data/Lost/Stagione%202/Lost.s02e04-05-06.gif"); 57 | 58 | // img1.setImageAlign(ImageAlign.TOP); 59 | } 60 | } --------------------------------------------------------------------------------