├── .classpath
├── .gitignore
├── .project
├── .settings
└── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── README.md
├── libs
└── android-support-v4.jar
├── pom.xml
├── proguard-project.txt
├── project.properties
├── res
├── drawable-hdpi
│ ├── empty_photo.png
│ ├── ic_launcher.png
│ ├── news_item_bg.9.png
│ └── scrollbar_handle_accelerated_anim2.9.png
├── drawable-ldpi
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ └── ic_launcher.png
├── drawable-xxhdpi
│ └── ic_launcher.png
├── layout
│ ├── activity_main.xml
│ └── infos_list.xml
├── menu
│ └── activity_main.xml
├── values-v11
│ └── styles.xml
├── values-v14
│ └── styles.xml
└── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
└── src
└── com
├── dodola
├── model
│ ├── DuitangInfo.java
│ └── Infos.java
└── waterex
│ ├── MainActivity.java
│ └── StaggeredAdapter.java
├── example
└── android
│ └── bitmapfun
│ └── util
│ ├── DiskLruCache.java
│ ├── Helper.java
│ ├── ImageCache.java
│ ├── ImageFetcher.java
│ ├── ImageResizer.java
│ ├── ImageWorker.java
│ ├── RetainFragment.java
│ └── Utils.java
└── origamilabs
└── library
└── views
├── FastScroller.java
├── ScrollerCompat.java
├── ScrollerCompatIcs.java
└── StaggeredGridView.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # Eclipse project files
19 | #.classpath
20 | #.project
21 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | WaterFallEx
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WaterFallExt
2 | ============
3 |
4 | 增强版的瀑布流
5 |
--------------------------------------------------------------------------------
/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | com.origamilabs.library
8 | StaggeredGridView
9 | 1.0
10 | StaggeredGridView
11 | apklib
12 |
13 |
14 | 1.6
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | com.google.android
28 | android
29 | provided
30 | 4.1.1.4
31 |
32 |
33 | android.support
34 | compatibility-v4
35 | 11
36 |
37 |
38 |
39 |
40 | src
41 |
42 |
43 |
44 |
45 | org.apache.maven.plugins
46 | maven-compiler-plugin
47 | 2.5
48 |
49 | ${java.version}
50 | ${java.version}
51 |
52 |
53 |
54 |
55 | com.jayway.maven.plugins.android.generation2
56 | android-maven-plugin
57 | 3.4.1
58 |
59 |
60 | 16
61 |
62 |
63 |
64 |
65 |
66 | com.jayway.maven.plugins.android.generation2
67 | android-maven-plugin
68 | 3.4.1
69 | true
70 |
71 | ignored
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/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=false
16 | android.library.reference.1=../StaggeredGridView
17 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/empty_photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/res/drawable-hdpi/empty_photo.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/news_item_bg.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/res/drawable-hdpi/news_item_bg.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/scrollbar_handle_accelerated_anim2.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/res/drawable-hdpi/scrollbar_handle_accelerated_anim2.9.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
16 |
17 |
--------------------------------------------------------------------------------
/res/layout/infos_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
22 |
23 |
30 |
31 |
--------------------------------------------------------------------------------
/res/menu/activity_main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #FF000000
5 |
6 |
--------------------------------------------------------------------------------
/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 1dp
3 |
4 |
5 | 84dp
6 |
7 | 63dp
8 |
10 | 48dip
11 | 64dip
12 |
13 | 25dip
14 |
15 | 104dp
16 |
17 | 64dp
18 |
19 | 52dp
20 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 瀑布流Ex
5 | Hello world!
6 | Settings
7 |
8 |
--------------------------------------------------------------------------------
/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/src/com/dodola/model/DuitangInfo.java:
--------------------------------------------------------------------------------
1 | package com.dodola.model;
2 |
3 | public class DuitangInfo {
4 |
5 | private int height;
6 | private String albid = "";
7 | private String msg = "";
8 | private String isrc = "";
9 |
10 | public int getWidth(){
11 | return 200;
12 | }
13 | public String getAlbid() {
14 | return albid;
15 | }
16 |
17 | public void setAlbid(String albid) {
18 | this.albid = albid;
19 | }
20 |
21 | public String getMsg() {
22 | return msg;
23 | }
24 |
25 | public void setMsg(String msg) {
26 | this.msg = msg;
27 | }
28 |
29 | public String getIsrc() {
30 | return isrc;
31 | }
32 |
33 | public void setIsrc(String isrc) {
34 | this.isrc = isrc;
35 | }
36 |
37 | public int getHeight() {
38 | return height;
39 | }
40 |
41 | public void setHeight(int height) {
42 | this.height = height;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/com/dodola/model/Infos.java:
--------------------------------------------------------------------------------
1 | package com.dodola.model;
2 |
3 | import java.util.List;
4 |
5 | public class Infos {
6 | private String newsLast = "0";
7 | private int type = 0;
8 | private List newsInfos;
9 |
10 | public String getNewsLast() {
11 | return newsLast;
12 | }
13 |
14 | public void setNewsLast(String newsLast) {
15 | this.newsLast = newsLast;
16 | }
17 |
18 | public int getType() {
19 | return type;
20 | }
21 |
22 | public void setType(int type) {
23 | this.type = type;
24 | }
25 |
26 | public List getNewsInfos() {
27 | return newsInfos;
28 | }
29 |
30 | public void setNewsInfos(List newsInfos) {
31 | this.newsInfos = newsInfos;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/com/dodola/waterex/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.dodola.waterex;
2 |
3 | import java.io.IOException;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | import org.json.JSONArray;
8 | import org.json.JSONException;
9 | import org.json.JSONObject;
10 |
11 | import android.app.Activity;
12 | import android.content.Context;
13 | import android.os.AsyncTask;
14 | import android.os.Bundle;
15 | import android.os.AsyncTask.Status;
16 | import android.util.Log;
17 | import android.view.Menu;
18 |
19 | import com.dodola.model.DuitangInfo;
20 | import com.dodola.waterex.R;
21 | import com.example.android.bitmapfun.util.Helper;
22 | import com.example.android.bitmapfun.util.ImageFetcher;
23 | import com.origamilabs.library.views.StaggeredGridView;
24 |
25 | /** This will not work so great since the heights of the imageViews are
26 | * calculated on the iamgeLoader callback ruining the offsets. To fix this try
27 | * to get the (intrinsic) image width and height and set the views height
28 | * manually. I will look into a fix once I find extra time.
29 | *
30 | * @author Maurycy Wojtowicz */
31 | public class MainActivity extends Activity {
32 | private ImageFetcher mImageFetcher;
33 | private StaggeredAdapter mAdapter;
34 | private ContentTask task = new ContentTask(this, 2);
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.activity_main);
40 | mImageFetcher = new ImageFetcher(this, 240);
41 | mImageFetcher.setLoadingImage(R.drawable.empty_photo);
42 | StaggeredGridView gridView = (StaggeredGridView) this.findViewById(R.id.staggeredGridView1);
43 |
44 | int margin = getResources().getDimensionPixelSize(R.dimen.margin);
45 |
46 | gridView.setFastScrollEnabled(true);
47 |
48 | mAdapter = new StaggeredAdapter(MainActivity.this, mImageFetcher);
49 | gridView.setAdapter(mAdapter);
50 | mAdapter.notifyDataSetChanged();
51 | AddItemToContainer(1, 1);
52 | AddItemToContainer(2, 1);
53 | AddItemToContainer(3, 1);
54 |
55 | }
56 |
57 | private void AddItemToContainer(int pageindex, int type) {
58 | if (task.getStatus() != Status.RUNNING) {
59 | String url = "http://www.duitang.com/album/1733789/masn/p/" + pageindex + "/24/";
60 | Log.d("MainActivity", "current url:" + url);
61 | ContentTask task = new ContentTask(this, type);
62 | task.execute(url);
63 |
64 | }
65 | }
66 |
67 | @Override
68 | public boolean onCreateOptionsMenu(Menu menu) {
69 | getMenuInflater().inflate(R.menu.activity_main, menu);
70 | return true;
71 | }
72 |
73 | private class ContentTask extends AsyncTask> {
74 |
75 | private Context mContext;
76 | private int mType = 1;
77 |
78 | public ContentTask(Context context, int type) {
79 | super();
80 | mContext = context;
81 | mType = type;
82 | }
83 |
84 | @Override
85 | protected List doInBackground(String... params) {
86 | try {
87 | return parseNewsJSON(params[0]);
88 | } catch (IOException e) {
89 | e.printStackTrace();
90 | }
91 | return null;
92 | }
93 |
94 | @Override
95 | protected void onPostExecute(List result) {
96 | if (mType == 1) {
97 |
98 | mAdapter.addItemTop(result);
99 | mAdapter.notifyDataSetChanged();
100 |
101 | } else if (mType == 2) {
102 | mAdapter.addItemLast(result);
103 | mAdapter.notifyDataSetChanged();
104 |
105 | }
106 |
107 | }
108 |
109 | @Override
110 | protected void onPreExecute() {
111 | }
112 |
113 | public List parseNewsJSON(String url) throws IOException {
114 | List duitangs = new ArrayList();
115 | String json = "";
116 | if (Helper.checkConnection(mContext)) {
117 | try {
118 | json = Helper.getStringFromUrl(url);
119 |
120 | } catch (IOException e) {
121 | Log.e("IOException is : ", e.toString());
122 | e.printStackTrace();
123 | return duitangs;
124 | }
125 | }
126 | Log.d("MainActiivty", "json:" + json);
127 |
128 | try {
129 | if (null != json) {
130 | JSONObject newsObject = new JSONObject(json);
131 | JSONObject jsonObject = newsObject.getJSONObject("data");
132 | JSONArray blogsJson = jsonObject.getJSONArray("blogs");
133 |
134 | for (int i = 0; i < blogsJson.length(); i++) {
135 | JSONObject newsInfoLeftObject = blogsJson.getJSONObject(i);
136 | DuitangInfo newsInfo1 = new DuitangInfo();
137 | newsInfo1.setAlbid(newsInfoLeftObject.isNull("albid") ? "" : newsInfoLeftObject.getString("albid"));
138 | newsInfo1.setIsrc(newsInfoLeftObject.isNull("isrc") ? "" : newsInfoLeftObject.getString("isrc"));
139 | newsInfo1.setMsg(newsInfoLeftObject.isNull("msg") ? "" : newsInfoLeftObject.getString("msg"));
140 | newsInfo1.setHeight(newsInfoLeftObject.getInt("iht"));
141 | duitangs.add(newsInfo1);
142 | }
143 | }
144 | } catch (JSONException e) {
145 | e.printStackTrace();
146 | }
147 | return duitangs;
148 | }
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/src/com/dodola/waterex/StaggeredAdapter.java:
--------------------------------------------------------------------------------
1 | package com.dodola.waterex;
2 |
3 | import java.util.LinkedList;
4 | import java.util.List;
5 |
6 | import android.content.Context;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.BaseAdapter;
11 | import android.widget.ImageView;
12 | import android.widget.LinearLayout;
13 | import android.widget.TextView;
14 |
15 | import com.dodola.model.DuitangInfo;
16 | import com.dodola.waterex.R;
17 | import com.example.android.bitmapfun.util.ImageFetcher;
18 |
19 | public class StaggeredAdapter extends BaseAdapter {
20 | private LinkedList mInfos;
21 | ImageFetcher mImageFetcher;
22 |
23 | public StaggeredAdapter(Context context, ImageFetcher f) {
24 | mInfos = new LinkedList();
25 | mImageFetcher = f;
26 | }
27 |
28 | @Override
29 | public View getView(int position, View convertView, ViewGroup parent) {
30 |
31 | ViewHolder holder;
32 | DuitangInfo duitangInfo = mInfos.get(position);
33 |
34 | if (convertView == null) {
35 | LayoutInflater layoutInflator = LayoutInflater.from(parent.getContext());
36 | convertView = layoutInflator.inflate(R.layout.infos_list, null);
37 | holder = new ViewHolder();
38 | holder.imageView = (ImageView) convertView.findViewById(R.id.news_pic);
39 | holder.contentView = (TextView) convertView.findViewById(R.id.news_title);
40 | convertView.setTag(holder);
41 | }
42 | holder = (ViewHolder) convertView. getTag();
43 |
44 |
45 | // float iHeight = ((float) 200 / 183 * duitangInfo.getHeight());
46 | holder.imageView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) duitangInfo.getHeight()));
47 |
48 | holder.contentView.setText(duitangInfo.getMsg());
49 | mImageFetcher.loadImage(duitangInfo.getIsrc(), holder.imageView);
50 | return convertView;
51 | }
52 |
53 | class ViewHolder {
54 | ImageView imageView;
55 | TextView contentView;
56 | TextView timeView;
57 | }
58 |
59 | @Override
60 | public int getCount() {
61 | return mInfos.size();
62 | }
63 |
64 | @Override
65 | public Object getItem(int arg0) {
66 | return mInfos.get(arg0);
67 | }
68 |
69 | @Override
70 | public long getItemId(int arg0) {
71 | return 0;
72 | }
73 |
74 | public void addItemLast(List datas) {
75 | mInfos.addAll(datas);
76 | }
77 |
78 | public void addItemTop(List datas) {
79 | for (DuitangInfo info : datas) {
80 | mInfos.addFirst(info);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/com/example/android/bitmapfun/util/DiskLruCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.bitmapfun.util;
18 |
19 | import android.content.Context;
20 | import android.graphics.Bitmap;
21 | import android.graphics.Bitmap.CompressFormat;
22 | import android.graphics.BitmapFactory;
23 | import android.os.Environment;
24 | import android.util.Log;
25 |
26 | import java.io.BufferedOutputStream;
27 | import java.io.File;
28 | import java.io.FileNotFoundException;
29 | import java.io.FileOutputStream;
30 | import java.io.FilenameFilter;
31 | import java.io.IOException;
32 | import java.io.OutputStream;
33 | import java.io.UnsupportedEncodingException;
34 | import java.net.URLEncoder;
35 | import java.util.Collections;
36 | import java.util.LinkedHashMap;
37 | import java.util.Map;
38 | import java.util.Map.Entry;
39 |
40 | import com.dodola.waterex.BuildConfig;
41 |
42 | /**
43 | * A simple disk LRU bitmap cache to illustrate how a disk cache would be used
44 | * for bitmap caching. A much more robust and efficient disk LRU cache solution
45 | * can be found in the ICS source code
46 | * (libcore/luni/src/main/java/libcore/io/DiskLruCache.java) and is preferable
47 | * to this simple implementation.
48 | */
49 | public class DiskLruCache {
50 | private static final String TAG = "DiskLruCache";
51 | private static final String CACHE_FILENAME_PREFIX = "cache_";
52 | private static final int MAX_REMOVALS = 4;
53 | private static final int INITIAL_CAPACITY = 32;
54 | private static final float LOAD_FACTOR = 0.75f;
55 |
56 | private final File mCacheDir;
57 | private int cacheSize = 0;
58 | private int cacheByteSize = 0;
59 | private final int maxCacheItemSize = 64; // 64 item default
60 | private long maxCacheByteSize = 1024 * 1024 * 5; // 5MB default
61 | private CompressFormat mCompressFormat = CompressFormat.JPEG;
62 | private int mCompressQuality = 70;
63 |
64 | private final Map mLinkedHashMap = Collections.synchronizedMap(new LinkedHashMap(INITIAL_CAPACITY,
65 | LOAD_FACTOR, true));
66 |
67 | /**
68 | * A filename filter to use to identify the cache filenames which have
69 | * CACHE_FILENAME_PREFIX prepended.
70 | */
71 | private static final FilenameFilter cacheFileFilter = new FilenameFilter() {
72 | @Override
73 | public boolean accept(File dir, String filename) {
74 | return filename.startsWith(CACHE_FILENAME_PREFIX);
75 | }
76 | };
77 |
78 | /**
79 | * Used to fetch an instance of DiskLruCache.
80 | *
81 | * @param context
82 | * @param cacheDir
83 | * @param maxByteSize
84 | * @return
85 | */
86 | public static DiskLruCache openCache(Context context, File cacheDir, long maxByteSize) {
87 | if (!cacheDir.exists()) {
88 | cacheDir.mkdir();
89 | }
90 |
91 | if (cacheDir.isDirectory() && cacheDir.canWrite() && Utils.getUsableSpace(cacheDir) > maxByteSize) {
92 | return new DiskLruCache(cacheDir, maxByteSize);
93 | }
94 |
95 | return null;
96 | }
97 |
98 | /**
99 | * Constructor that should not be called directly, instead use
100 | * {@link DiskLruCache#openCache(Context, File, long)} which runs some extra
101 | * checks before creating a DiskLruCache instance.
102 | *
103 | * @param cacheDir
104 | * @param maxByteSize
105 | */
106 | private DiskLruCache(File cacheDir, long maxByteSize) {
107 | mCacheDir = cacheDir;
108 | maxCacheByteSize = maxByteSize;
109 | }
110 |
111 | /**
112 | * Add a bitmap to the disk cache.
113 | *
114 | * @param key
115 | * A unique identifier for the bitmap.
116 | * @param data
117 | * The bitmap to store.
118 | */
119 | public void put(String key, Bitmap data) {
120 | synchronized (mLinkedHashMap) {
121 | if (mLinkedHashMap.get(key) == null) {
122 | try {
123 | final String file = createFilePath(mCacheDir, key);
124 | if (writeBitmapToFile(data, file)) {
125 | put(key, file);
126 | flushCache();
127 | }
128 | } catch (final FileNotFoundException e) {
129 | Log.e(TAG, "Error in put: " + e.getMessage());
130 | } catch (final IOException e) {
131 | Log.e(TAG, "Error in put: " + e.getMessage());
132 | }
133 | }
134 | }
135 | }
136 |
137 | private void put(String key, String file) {
138 | mLinkedHashMap.put(key, file);
139 | cacheSize = mLinkedHashMap.size();
140 | cacheByteSize += new File(file).length();
141 | }
142 |
143 | /**
144 | * Flush the cache, removing oldest entries if the total size is over the
145 | * specified cache size. Note that this isn't keeping track of stale files
146 | * in the cache directory that aren't in the HashMap. If the images and keys
147 | * in the disk cache change often then they probably won't ever be removed.
148 | */
149 | private void flushCache() {
150 | Entry eldestEntry;
151 | File eldestFile;
152 | long eldestFileSize;
153 | int count = 0;
154 |
155 | while (count < MAX_REMOVALS && (cacheSize > maxCacheItemSize || cacheByteSize > maxCacheByteSize)) {
156 | eldestEntry = mLinkedHashMap.entrySet().iterator().next();
157 | eldestFile = new File(eldestEntry.getValue());
158 | eldestFileSize = eldestFile.length();
159 | mLinkedHashMap.remove(eldestEntry.getKey());
160 | eldestFile.delete();
161 | cacheSize = mLinkedHashMap.size();
162 | cacheByteSize -= eldestFileSize;
163 | count++;
164 | if (BuildConfig.DEBUG) {
165 | Log.d(TAG, "flushCache - Removed cache file, " + eldestFile + ", " + eldestFileSize);
166 | }
167 | }
168 | }
169 |
170 | /**
171 | * Get an image from the disk cache.
172 | *
173 | * @param key
174 | * The unique identifier for the bitmap
175 | * @return The bitmap or null if not found
176 | */
177 | public Bitmap get(String key) {
178 | synchronized (mLinkedHashMap) {
179 | final String file = mLinkedHashMap.get(key);
180 | if (file != null) {
181 | if (BuildConfig.DEBUG) {
182 | Log.d(TAG, "Disk cache hit");
183 | }
184 | return BitmapFactory.decodeFile(file);
185 | } else {
186 | final String existingFile = createFilePath(mCacheDir, key);
187 | if (new File(existingFile).exists()) {
188 | put(key, existingFile);
189 | if (BuildConfig.DEBUG) {
190 | Log.d(TAG, "Disk cache hit (existing file)");
191 | }
192 | return BitmapFactory.decodeFile(existingFile);
193 | }
194 | }
195 | return null;
196 | }
197 | }
198 |
199 | /**
200 | * Checks if a specific key exist in the cache.
201 | *
202 | * @param key
203 | * The unique identifier for the bitmap
204 | * @return true if found, false otherwise
205 | */
206 | public boolean containsKey(String key) {
207 | // See if the key is in our HashMap
208 | if (mLinkedHashMap.containsKey(key)) {
209 | return true;
210 | }
211 |
212 | // Now check if there's an actual file that exists based on the key
213 | final String existingFile = createFilePath(mCacheDir, key);
214 | if (new File(existingFile).exists()) {
215 | // File found, add it to the HashMap for future use
216 | put(key, existingFile);
217 | return true;
218 | }
219 | return false;
220 | }
221 |
222 | /**
223 | * Removes all disk cache entries from this instance cache dir
224 | */
225 | public void clearCache() {
226 | DiskLruCache.clearCache(mCacheDir);
227 | }
228 |
229 | /**
230 | * Removes all disk cache entries from the application cache directory in
231 | * the uniqueName sub-directory.
232 | *
233 | * @param context
234 | * The context to use
235 | * @param uniqueName
236 | * A unique cache directory name to append to the app cache
237 | * directory
238 | */
239 | public static void clearCache(Context context, String uniqueName) {
240 | File cacheDir = getDiskCacheDir(context, uniqueName);
241 | clearCache(cacheDir);
242 | }
243 |
244 | /**
245 | * Removes all disk cache entries from the given directory. This should not
246 | * be called directly, call {@link DiskLruCache#clearCache(Context, String)}
247 | * or {@link DiskLruCache#clearCache()} instead.
248 | *
249 | * @param cacheDir
250 | * The directory to remove the cache files from
251 | */
252 | private static void clearCache(File cacheDir) {
253 | final File[] files = cacheDir.listFiles(cacheFileFilter);
254 | for (int i = 0; i < files.length; i++) {
255 | files[i].delete();
256 | }
257 | }
258 |
259 | /**
260 | * Get a usable cache directory (external if available, internal otherwise).
261 | *
262 | * @param context
263 | * The context to use
264 | * @param uniqueName
265 | * A unique directory name to append to the cache dir
266 | * @return The cache dir
267 | */
268 | public static File getDiskCacheDir(Context context, String uniqueName) {
269 |
270 | // Check if media is mounted or storage is built-in, if so, try and use
271 | // external cache dir
272 | // otherwise use internal cache dir
273 | final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED || !Utils.isExternalStorageRemovable() ? Utils
274 | .getExternalCacheDir(context).getPath() : context.getCacheDir().getPath();
275 |
276 | return new File(cachePath + File.separator + uniqueName);
277 | }
278 |
279 | /**
280 | * Creates a constant cache file path given a target cache directory and an
281 | * image key.
282 | *
283 | * @param cacheDir
284 | * @param key
285 | * @return
286 | */
287 | public static String createFilePath(File cacheDir, String key) {
288 | try {
289 | // Use URLEncoder to ensure we have a valid filename, a tad hacky
290 | // but it will do for
291 | // this example
292 | return cacheDir.getAbsolutePath() + File.separator + CACHE_FILENAME_PREFIX + URLEncoder.encode(key.replace("*", ""), "UTF-8");
293 | } catch (final UnsupportedEncodingException e) {
294 | Log.e(TAG, "createFilePath - " + e);
295 | }
296 |
297 | return null;
298 | }
299 |
300 | /**
301 | * Create a constant cache file path using the current cache directory and
302 | * an image key.
303 | *
304 | * @param key
305 | * @return
306 | */
307 | public String createFilePath(String key) {
308 | return createFilePath(mCacheDir, key);
309 | }
310 |
311 | /**
312 | * Sets the target compression format and quality for images written to the
313 | * disk cache.
314 | *
315 | * @param compressFormat
316 | * @param quality
317 | */
318 | public void setCompressParams(CompressFormat compressFormat, int quality) {
319 | mCompressFormat = compressFormat;
320 | mCompressQuality = quality;
321 | }
322 |
323 | /**
324 | * Writes a bitmap to a file. Call
325 | * {@link DiskLruCache#setCompressParams(CompressFormat, int)} first to set
326 | * the target bitmap compression and format.
327 | *
328 | * @param bitmap
329 | * @param file
330 | * @return
331 | */
332 | private boolean writeBitmapToFile(Bitmap bitmap, String file) throws IOException, FileNotFoundException {
333 |
334 | OutputStream out = null;
335 | try {
336 | out = new BufferedOutputStream(new FileOutputStream(file), Utils.IO_BUFFER_SIZE);
337 | return bitmap.compress(mCompressFormat, mCompressQuality, out);
338 | } finally {
339 | if (out != null) {
340 | out.close();
341 | }
342 | }
343 | }
344 | }
345 |
--------------------------------------------------------------------------------
/src/com/example/android/bitmapfun/util/Helper.java:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dodola/WaterFallExt/f24dcf8a55867497e94f71bcb572b35d28169dd9/src/com/example/android/bitmapfun/util/Helper.java
--------------------------------------------------------------------------------
/src/com/example/android/bitmapfun/util/ImageCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.bitmapfun.util;
18 |
19 | import android.content.Context;
20 | import android.graphics.Bitmap;
21 | import android.graphics.Bitmap.CompressFormat;
22 | import android.support.v4.app.FragmentActivity;
23 | import android.support.v4.util.LruCache;
24 | import android.util.Log;
25 |
26 |
27 | import java.io.File;
28 |
29 | import com.dodola.waterex.BuildConfig;
30 |
31 |
32 | /**
33 | * This class holds our bitmap caches (memory and disk).
34 | */
35 | public class ImageCache {
36 | private static final String TAG = "ImageCache";
37 |
38 | // Default memory cache size
39 | private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 1024 * 5; // 5MB
40 |
41 | // Default disk cache size
42 | private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
43 |
44 | // Compression settings when writing images to disk cache
45 | private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG;
46 | private static final int DEFAULT_COMPRESS_QUALITY = 70;
47 |
48 | // Constants to easily toggle various caches
49 | private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
50 | private static final boolean DEFAULT_DISK_CACHE_ENABLED = true;
51 | private static final boolean DEFAULT_CLEAR_DISK_CACHE_ON_START = false;
52 |
53 | private DiskLruCache mDiskCache;
54 | private LruCache mMemoryCache;
55 |
56 | /**
57 | * Creating a new ImageCache object using the specified parameters.
58 | *
59 | * @param context The context to use
60 | * @param cacheParams The cache parameters to use to initialize the cache
61 | */
62 | public ImageCache(Context context, ImageCacheParams cacheParams) {
63 | init(context, cacheParams);
64 | }
65 |
66 | /**
67 | * Creating a new ImageCache object using the default parameters.
68 | *
69 | * @param context The context to use
70 | * @param uniqueName A unique name that will be appended to the cache directory
71 | */
72 | public ImageCache(Context context, String uniqueName) {
73 | init(context, new ImageCacheParams(uniqueName));
74 | }
75 |
76 | /**
77 | * Find and return an existing ImageCache stored in a {@link RetainFragment}, if not found a new
78 | * one is created with defaults and saved to a {@link RetainFragment}.
79 | *
80 | * @param activity The calling {@link FragmentActivity}
81 | * @param uniqueName A unique name to append to the cache directory
82 | * @return An existing retained ImageCache object or a new one if one did not exist.
83 | */
84 | public static ImageCache findOrCreateCache(
85 | final FragmentActivity activity, final String uniqueName) {
86 | return findOrCreateCache(activity, new ImageCacheParams(uniqueName));
87 | }
88 |
89 | /**
90 | * Find and return an existing ImageCache stored in a {@link RetainFragment}, if not found a new
91 | * one is created using the supplied params and saved to a {@link RetainFragment}.
92 | *
93 | * @param activity The calling {@link FragmentActivity}
94 | * @param cacheParams The cache parameters to use if creating the ImageCache
95 | * @return An existing retained ImageCache object or a new one if one did not exist
96 | */
97 | public static ImageCache findOrCreateCache(
98 | final FragmentActivity activity, ImageCacheParams cacheParams) {
99 |
100 | // Search for, or create an instance of the non-UI RetainFragment
101 | final RetainFragment mRetainFragment = RetainFragment.findOrCreateRetainFragment(
102 | activity.getSupportFragmentManager());
103 |
104 | // See if we already have an ImageCache stored in RetainFragment
105 | ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
106 |
107 | // No existing ImageCache, create one and store it in RetainFragment
108 | if (imageCache == null) {
109 | imageCache = new ImageCache(activity, cacheParams);
110 | mRetainFragment.setObject(imageCache);
111 | }
112 |
113 | return imageCache;
114 | }
115 |
116 | /**
117 | * Initialize the cache, providing all parameters.
118 | *
119 | * @param context The context to use
120 | * @param cacheParams The cache parameters to initialize the cache
121 | */
122 | private void init(Context context, ImageCacheParams cacheParams) {
123 | final File diskCacheDir = DiskLruCache.getDiskCacheDir(context, cacheParams.uniqueName);
124 |
125 | // Set up disk cache
126 | if (cacheParams.diskCacheEnabled) {
127 | mDiskCache = DiskLruCache.openCache(context, diskCacheDir, cacheParams.diskCacheSize);
128 | mDiskCache.setCompressParams(cacheParams.compressFormat, cacheParams.compressQuality);
129 | if (cacheParams.clearDiskCacheOnStart) {
130 | mDiskCache.clearCache();
131 | }
132 | }
133 |
134 | // Set up memory cache
135 | if (cacheParams.memoryCacheEnabled) {
136 | mMemoryCache = new LruCache(cacheParams.memCacheSize) {
137 | /**
138 | * Measure item size in bytes rather than units which is more practical for a bitmap
139 | * cache
140 | */
141 | @Override
142 | protected int sizeOf(String key, Bitmap bitmap) {
143 | return Utils.getBitmapSize(bitmap);
144 | }
145 | };
146 | }
147 | }
148 |
149 | public void addBitmapToCache(String data, Bitmap bitmap) {
150 | if (data == null || bitmap == null) {
151 | return;
152 | }
153 |
154 | // Add to memory cache
155 | if (mMemoryCache != null && mMemoryCache.get(data) == null) {
156 | mMemoryCache.put(data, bitmap);
157 | }
158 |
159 | // Add to disk cache
160 | if (mDiskCache != null && !mDiskCache.containsKey(data)) {
161 | mDiskCache.put(data, bitmap);
162 | }
163 | }
164 |
165 | /**
166 | * Get from memory cache.
167 | *
168 | * @param data Unique identifier for which item to get
169 | * @return The bitmap if found in cache, null otherwise
170 | */
171 | public Bitmap getBitmapFromMemCache(String data) {
172 | if (mMemoryCache != null) {
173 | final Bitmap memBitmap = mMemoryCache.get(data);
174 | if (memBitmap != null) {
175 | if (BuildConfig.DEBUG) {
176 | Log.d(TAG, "Memory cache hit");
177 | }
178 | return memBitmap;
179 | }
180 | }
181 | return null;
182 | }
183 |
184 | /**
185 | * Get from disk cache.
186 | *
187 | * @param data Unique identifier for which item to get
188 | * @return The bitmap if found in cache, null otherwise
189 | */
190 | public Bitmap getBitmapFromDiskCache(String data) {
191 | if (mDiskCache != null) {
192 | return mDiskCache.get(data);
193 | }
194 | return null;
195 | }
196 |
197 | public void clearCaches() {
198 | mDiskCache.clearCache();
199 | mMemoryCache.evictAll();
200 | }
201 |
202 | /**
203 | * A holder class that contains cache parameters.
204 | */
205 | public static class ImageCacheParams {
206 | public String uniqueName;
207 | public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
208 | public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
209 | public CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
210 | public int compressQuality = DEFAULT_COMPRESS_QUALITY;
211 | public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
212 | public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
213 | public boolean clearDiskCacheOnStart = DEFAULT_CLEAR_DISK_CACHE_ON_START;
214 |
215 | public ImageCacheParams(String uniqueName) {
216 | this.uniqueName = uniqueName;
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/src/com/example/android/bitmapfun/util/ImageFetcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.bitmapfun.util;
18 |
19 | import android.content.Context;
20 | import android.graphics.Bitmap;
21 | import android.net.ConnectivityManager;
22 | import android.net.NetworkInfo;
23 | import android.util.Log;
24 | import android.widget.Toast;
25 |
26 |
27 | import java.io.BufferedInputStream;
28 | import java.io.BufferedOutputStream;
29 | import java.io.File;
30 | import java.io.FileOutputStream;
31 | import java.io.IOException;
32 | import java.io.InputStream;
33 | import java.net.HttpURLConnection;
34 | import java.net.URL;
35 |
36 | import com.dodola.waterex.BuildConfig;
37 |
38 |
39 | /**
40 | * A simple subclass of {@link ImageResizer} that fetches and resizes images fetched from a URL.
41 | */
42 | public class ImageFetcher extends ImageResizer {
43 | private static final String TAG = "ImageFetcher";
44 | private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
45 | public static final String HTTP_CACHE_DIR = "http";
46 |
47 | /**
48 | * Initialize providing a target image width and height for the processing images.
49 | *
50 | * @param context
51 | * @param imageWidth
52 | * @param imageHeight
53 | */
54 | public ImageFetcher(Context context, int imageWidth, int imageHeight) {
55 | super(context);
56 | init(context);
57 | }
58 |
59 | /**
60 | * Initialize providing a single target image size (used for both width and height);
61 | *
62 | * @param context
63 | * @param imageSize
64 | */
65 | public ImageFetcher(Context context, int imageSize) {
66 | super(context);
67 | init(context);
68 | }
69 |
70 | private void init(Context context) {
71 | checkConnection(context);
72 | }
73 |
74 | /**
75 | * Simple network connection check.
76 | *
77 | * @param context
78 | */
79 | private void checkConnection(Context context) {
80 | final ConnectivityManager cm =
81 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
82 | final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
83 | if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) {
84 | Toast.makeText(context, "No network connection found.", Toast.LENGTH_LONG).show();
85 | Log.e(TAG, "checkConnection - no connection found");
86 | }
87 | }
88 |
89 | /**
90 | * The main process method, which will be called by the ImageWorker in the AsyncTask background
91 | * thread.
92 | *
93 | * @param data The data to load the bitmap, in this case, a regular http URL
94 | * @return The downloaded and resized bitmap
95 | */
96 | private Bitmap processBitmap(String data) {
97 | if (BuildConfig.DEBUG) {
98 | // Log.d(TAG, "processBitmap - " + data);
99 | }
100 |
101 | // Download a bitmap, write it to a file
102 | final File f = downloadBitmap(mContext, data);
103 |
104 | if (f != null) {
105 | // Return a sampled down version
106 | return decodeSampledBitmapFromFile(f.toString());
107 | }
108 |
109 | return null;
110 | }
111 |
112 | @Override
113 | protected Bitmap processBitmap(Object data) {
114 | return processBitmap(String.valueOf(data));
115 | }
116 |
117 | /**
118 | * Download a bitmap from a URL, write it to a disk and return the File pointer. This
119 | * implementation uses a simple disk cache.
120 | *
121 | * @param context The context to use
122 | * @param urlString The URL to fetch
123 | * @return A File pointing to the fetched bitmap
124 | */
125 | public static File downloadBitmap(Context context, String urlString) {
126 | final File cacheDir = DiskLruCache.getDiskCacheDir(context, HTTP_CACHE_DIR);
127 |
128 | final DiskLruCache cache =
129 | DiskLruCache.openCache(context, cacheDir, HTTP_CACHE_SIZE);
130 |
131 | final File cacheFile = new File(cache.createFilePath(urlString));
132 |
133 | if (cache.containsKey(urlString)) {
134 | if (BuildConfig.DEBUG) {
135 | // Log.d(TAG, "downloadBitmap - found in http cache - " + urlString);
136 | }
137 | return cacheFile;
138 | }
139 |
140 | if (BuildConfig.DEBUG) {
141 | // Log.d(TAG, "downloadBitmap - downloading - " + urlString);
142 | }
143 |
144 | Utils.disableConnectionReuseIfNecessary();
145 | HttpURLConnection urlConnection = null;
146 | BufferedOutputStream out = null;
147 |
148 | try {
149 | final URL url = new URL(urlString);
150 | urlConnection = (HttpURLConnection) url.openConnection();
151 | final InputStream in =
152 | new BufferedInputStream(urlConnection.getInputStream(), Utils.IO_BUFFER_SIZE);
153 | out = new BufferedOutputStream(new FileOutputStream(cacheFile), Utils.IO_BUFFER_SIZE);
154 |
155 | int b;
156 | while ((b = in.read()) != -1) {
157 | out.write(b);
158 | }
159 |
160 | return cacheFile;
161 |
162 | } catch (final IOException e) {
163 | Log.e(TAG, "Error in downloadBitmap - " + e);
164 | } finally {
165 | if (urlConnection != null) {
166 | urlConnection.disconnect();
167 | }
168 | if (out != null) {
169 | try {
170 | out.close();
171 | } catch (final IOException e) {
172 | Log.e(TAG, "Error in downloadBitmap - " + e);
173 | }
174 | }
175 | }
176 |
177 | return null;
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/com/example/android/bitmapfun/util/ImageResizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.bitmapfun.util;
18 |
19 | import com.dodola.waterex.BuildConfig;
20 |
21 | import android.content.Context;
22 | import android.content.res.Resources;
23 | import android.graphics.Bitmap;
24 | import android.graphics.BitmapFactory;
25 | import android.util.Log;
26 |
27 | /**
28 | * A simple subclass of {@link ImageWorker} that resizes images from resources
29 | * given a target width and height. Useful for when the input images might be
30 | * too large to simply load directly into memory.
31 | */
32 | public class ImageResizer extends ImageWorker {
33 | private static final String TAG = "ImageWorker";
34 |
35 | /**
36 | * Initialize providing a single target image size (used for both width and
37 | * height);
38 | *
39 | * @param context
40 | * @param imageWidth
41 | * @param imageHeight
42 | */
43 | public ImageResizer(Context context) {
44 | super(context);
45 | }
46 |
47 | /**
48 | * The main processing method. This happens in a background task. In this
49 | * case we are just sampling down the bitmap and returning it from a
50 | * resource.
51 | *
52 | * @param resId
53 | * @return
54 | */
55 | private Bitmap processBitmap(int resId) {
56 | if (BuildConfig.DEBUG) {
57 | Log.d(TAG, "processBitmap - " + resId);
58 | }
59 | return decodeSampledBitmapFromResource(mContext.getResources(), resId);
60 | }
61 |
62 | @Override
63 | protected Bitmap processBitmap(Object data) {
64 | return processBitmap(Integer.parseInt(String.valueOf(data)));
65 | }
66 |
67 | /**
68 | * Decode and sample down a bitmap from resources to the requested width and
69 | * height.
70 | *
71 | * @param res
72 | * The resources object containing the image data
73 | * @param resId
74 | * The resource id of the image data
75 | * @param reqWidth
76 | * The requested width of the resulting bitmap
77 | * @param reqHeight
78 | * The requested height of the resulting bitmap
79 | * @return A bitmap sampled down from the original with the same aspect
80 | * ratio and dimensions that are equal to or greater than the
81 | * requested width and height
82 | */
83 | public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId) {
84 |
85 | // First decode with inJustDecodeBounds=true to check dimensions
86 | final BitmapFactory.Options options = new BitmapFactory.Options();
87 | options.inJustDecodeBounds = true;
88 | BitmapFactory.decodeResource(res, resId, options);
89 |
90 | // Calculate inSampleSize
91 | options.inSampleSize = 1;
92 |
93 | // Decode bitmap with inSampleSize set
94 | options.inJustDecodeBounds = false;
95 | return BitmapFactory.decodeResource(res, resId, options);
96 | }
97 |
98 | /**
99 | * Decode and sample down a bitmap from a file to the requested width and
100 | * height.
101 | *
102 | * @param filename
103 | * The full path of the file to decode
104 | * @param reqWidth
105 | * The requested width of the resulting bitmap
106 | * @param reqHeight
107 | * The requested height of the resulting bitmap
108 | * @return A bitmap sampled down from the original with the same aspect
109 | * ratio and dimensions that are equal to or greater than the
110 | * requested width and height
111 | */
112 | public static synchronized Bitmap decodeSampledBitmapFromFile(String filename) {
113 |
114 | // First decode with inJustDecodeBounds=true to check dimensions
115 | final BitmapFactory.Options options = new BitmapFactory.Options();
116 | options.inJustDecodeBounds = true;
117 | BitmapFactory.decodeFile(filename, options);
118 |
119 | // Calculate inSampleSize
120 | options.inSampleSize =1;
121 |
122 | // Decode bitmap with inSampleSize set
123 | options.inJustDecodeBounds = false;
124 | return BitmapFactory.decodeFile(filename, options);
125 | }
126 |
127 | /**
128 | * Calculate an inSampleSize for use in a {@link BitmapFactory.Options}
129 | * object when decoding bitmaps using the decode* methods from
130 | * {@link BitmapFactory}. This implementation calculates the closest
131 | * inSampleSize that will result in the final decoded bitmap having a width
132 | * and height equal to or larger than the requested width and height. This
133 | * implementation does not ensure a power of 2 is returned for inSampleSize
134 | * which can be faster when decoding but results in a larger bitmap which
135 | * isn't as useful for caching purposes.
136 | *
137 | * @param options
138 | * An options object with out* params already populated (run
139 | * through a decode* method with inJustDecodeBounds==true
140 | * @param reqWidth
141 | * The requested width of the resulting bitmap
142 | * @param reqHeight
143 | * The requested height of the resulting bitmap
144 | * @return The value to be used for inSampleSize
145 | */
146 | public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
147 | // Raw height and width of image
148 | final int height = options.outHeight;
149 | final int width = options.outWidth;
150 | int inSampleSize = 1;
151 |
152 | if (height > reqHeight || width > reqWidth) {
153 | if (width > height) {
154 | inSampleSize = Math.round((float) height / (float) reqHeight);
155 | } else {
156 | inSampleSize = Math.round((float) width / (float) reqWidth);
157 | }
158 |
159 | // This offers some additional logic in case the image has a strange
160 | // aspect ratio. For example, a panorama may have a much larger
161 | // width than height. In these cases the total pixels might still
162 | // end up being too large to fit comfortably in memory, so we should
163 | // be more aggressive with sample down the image (=larger
164 | // inSampleSize).
165 |
166 | final float totalPixels = width * height;
167 |
168 | // Anything more than 2x the requested pixels we'll sample down
169 | // further.
170 | final float totalReqPixelsCap = reqWidth * reqHeight * 2;
171 |
172 | while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
173 | inSampleSize++;
174 | }
175 | }
176 | return inSampleSize;
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/com/example/android/bitmapfun/util/ImageWorker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.bitmapfun.util;
18 |
19 | import android.content.Context;
20 | import android.content.res.Resources;
21 | import android.graphics.Bitmap;
22 | import android.graphics.BitmapFactory;
23 | import android.graphics.drawable.BitmapDrawable;
24 | import android.graphics.drawable.ColorDrawable;
25 | import android.graphics.drawable.Drawable;
26 | import android.graphics.drawable.TransitionDrawable;
27 | import android.os.AsyncTask;
28 | import android.util.Log;
29 | import android.widget.ImageView;
30 |
31 | import java.lang.ref.WeakReference;
32 |
33 | import com.dodola.waterex.BuildConfig;
34 |
35 | /**
36 | * This class wraps up completing some arbitrary long running work when loading
37 | * a bitmap to an ImageView. It handles things like using a memory and disk
38 | * cache, running the work in a background thread and setting a placeholder
39 | * image.
40 | */
41 | public abstract class ImageWorker {
42 | private static final String TAG = "ImageWorker";
43 | private static final int FADE_IN_TIME = 200;
44 |
45 | private ImageCache mImageCache;
46 | private Bitmap mLoadingBitmap;
47 | private boolean mFadeInBitmap = true;
48 | private boolean mExitTasksEarly = false;
49 |
50 | protected Context mContext;
51 | protected ImageWorkerAdapter mImageWorkerAdapter;
52 |
53 | protected ImageWorker(Context context) {
54 | mContext = context;
55 | }
56 |
57 | /**
58 | * Load an image specified by the data parameter into an ImageView (override
59 | * {@link ImageWorker#processBitmap(Object)} to define the processing
60 | * logic). A memory and disk cache will be used if an {@link ImageCache} has
61 | * been set using {@link ImageWorker#setImageCache(ImageCache)}. If the
62 | * image is found in the memory cache, it is set immediately, otherwise an
63 | * {@link AsyncTask} will be created to asynchronously load the bitmap.
64 | *
65 | * @param data
66 | * The URL of the image to download.
67 | * @param imageView
68 | * The ImageView to bind the downloaded image to.
69 | */
70 | public void loadImage(Object data, ImageView imageView) {
71 | Bitmap bitmap = null;
72 |
73 | if (mImageCache != null) {
74 | bitmap = mImageCache.getBitmapFromMemCache(String.valueOf(data));
75 | }
76 |
77 | if (bitmap != null) {
78 | // Bitmap found in memory cache
79 | imageView.setImageBitmap(bitmap);
80 | } else if (cancelPotentialWork(data, imageView)) {
81 | final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
82 | final AsyncDrawable asyncDrawable = new AsyncDrawable(mContext.getResources(), mLoadingBitmap, task);
83 | imageView.setImageDrawable(asyncDrawable);
84 | task.execute(data);
85 | }
86 | }
87 |
88 | /**
89 | * Load an image specified from a set adapter into an ImageView (override
90 | * {@link ImageWorker#processBitmap(Object)} to define the processing
91 | * logic). A memory and disk cache will be used if an {@link ImageCache} has
92 | * been set using {@link ImageWorker#setImageCache(ImageCache)}. If the
93 | * image is found in the memory cache, it is set immediately, otherwise an
94 | * {@link AsyncTask} will be created to asynchronously load the bitmap.
95 | * {@link ImageWorker#setAdapter(ImageWorkerAdapter)} must be called before
96 | * using this method.
97 | *
98 | * @param data
99 | * The URL of the image to download.
100 | * @param imageView
101 | * The ImageView to bind the downloaded image to.
102 | */
103 | public void loadImage(int num, ImageView imageView) {
104 | if (mImageWorkerAdapter != null) {
105 | loadImage(mImageWorkerAdapter.getItem(num), imageView);
106 | } else {
107 | throw new NullPointerException("Data not set, must call setAdapter() first.");
108 | }
109 | }
110 |
111 | /**
112 | * Set placeholder bitmap that shows when the the background thread is
113 | * running.
114 | *
115 | * @param bitmap
116 | */
117 | public void setLoadingImage(Bitmap bitmap) {
118 | mLoadingBitmap = bitmap;
119 | }
120 |
121 | /**
122 | * Set placeholder bitmap that shows when the the background thread is
123 | * running.
124 | *
125 | * @param resId
126 | */
127 | public void setLoadingImage(int resId) {
128 | mLoadingBitmap = BitmapFactory.decodeResource(mContext.getResources(), resId);
129 | }
130 |
131 | /**
132 | * Set the {@link ImageCache} object to use with this ImageWorker.
133 | *
134 | * @param cacheCallback
135 | */
136 | public void setImageCache(ImageCache cacheCallback) {
137 | mImageCache = cacheCallback;
138 | }
139 |
140 | public ImageCache getImageCache() {
141 | return mImageCache;
142 | }
143 |
144 | /**
145 | * If set to true, the image will fade-in once it has been loaded by the
146 | * background thread.
147 | *
148 | * @param fadeIn
149 | */
150 | public void setImageFadeIn(boolean fadeIn) {
151 | mFadeInBitmap = fadeIn;
152 | }
153 |
154 | public void setExitTasksEarly(boolean exitTasksEarly) {
155 | mExitTasksEarly = exitTasksEarly;
156 | }
157 |
158 | /**
159 | * Subclasses should override this to define any processing or work that
160 | * must happen to produce the final bitmap. This will be executed in a
161 | * background thread and be long running. For example, you could resize a
162 | * large bitmap here, or pull down an image from the network.
163 | *
164 | * @param data
165 | * The data to identify which image to process, as provided by
166 | * {@link ImageWorker#loadImage(Object, ImageView)}
167 | * @return The processed bitmap
168 | */
169 | protected abstract Bitmap processBitmap(Object data);
170 |
171 | public static void cancelWork(ImageView imageView) {
172 | final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
173 | if (bitmapWorkerTask != null) {
174 | bitmapWorkerTask.cancel(true);
175 | if (BuildConfig.DEBUG) {
176 | final Object bitmapData = bitmapWorkerTask.data;
177 | Log.d(TAG, "cancelWork - cancelled work for " + bitmapData);
178 | }
179 | }
180 | }
181 |
182 | /**
183 | * Returns true if the current work has been canceled or if there was no
184 | * work in progress on this image view. Returns false if the work in
185 | * progress deals with the same data. The work is not stopped in that case.
186 | */
187 | public static boolean cancelPotentialWork(Object data, ImageView imageView) {
188 | final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
189 |
190 | if (bitmapWorkerTask != null) {
191 | final Object bitmapData = bitmapWorkerTask.data;
192 | if (bitmapData == null || !bitmapData.equals(data)) {
193 | bitmapWorkerTask.cancel(true);
194 | if (BuildConfig.DEBUG) {
195 | Log.d(TAG, "cancelPotentialWork - cancelled work for " + data);
196 | }
197 | } else {
198 | // The same work is already in progress.
199 | return false;
200 | }
201 | }
202 | return true;
203 | }
204 |
205 | /**
206 | * @param imageView
207 | * Any imageView
208 | * @return Retrieve the currently active work task (if any) associated with
209 | * this imageView. null if there is no such task.
210 | */
211 | private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
212 | if (imageView != null) {
213 | final Drawable drawable = imageView.getDrawable();
214 | if (drawable instanceof AsyncDrawable) {
215 | final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
216 | return asyncDrawable.getBitmapWorkerTask();
217 | }
218 | }
219 | return null;
220 | }
221 |
222 | /**
223 | * The actual AsyncTask that will asynchronously process the image.
224 | */
225 | private class BitmapWorkerTask extends AsyncTask