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 |
--------------------------------------------------------------------------------
/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 | *
433 | *
No requests are pending for this instance.
434 | *
A previous download failed.
435 | *
The instance-level or class-level auto retry is set to true, with
436 | * this priority.
437 | *
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 |
18 |
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 | }
--------------------------------------------------------------------------------