summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Robinson <joe@lc8n.com>2014-08-13 23:13:52 +0100
committerJoe Robinson <joe@lc8n.com>2014-08-13 23:23:22 +0100
commitd2f7c8b9fb56eadac1a6406af8b9cc43cb99d15b (patch)
tree2a0d4f494a0b5fadc3a64f393ef655a95aa916e2
parent276e27febe8d0e97ce80d97f06ca62d951d54606 (diff)
Added higher resolution image loading on zoom
-rw-r--r--.idea/libraries/Android_BitmapCache_2_3.xml9
-rw-r--r--.idea/libraries/disklrucache_2_0_2.xml11
-rw-r--r--.idea/libraries/library_1_2_3.xml10
-rw-r--r--.idea/libraries/picasso_2_3_3.xml11
-rw-r--r--.idea/libraries/universal_image_loader_1_9_2.xml11
-rw-r--r--app/app.iml5
-rw-r--r--app/build.gradle6
-rw-r--r--app/src/main/java/uk/co/blatech/blaupload/BlauploadApplication.java39
-rw-r--r--app/src/main/java/uk/co/blatech/blaupload/HomeScreenFragment.java4
-rw-r--r--app/src/main/java/uk/co/blatech/blaupload/ImagePager.java20
-rw-r--r--app/src/main/java/uk/co/blatech/blaupload/ui/ImagePagerAdapter.java118
-rw-r--r--app/src/main/java/uk/co/blatech/blaupload/ui/NetworkCacheableImageView.java271
-rw-r--r--app/src/main/java/uk/co/blatech/blaupload/ui/TouchImageView.java1276
-rw-r--r--app/src/main/java/uk/co/blatech/blaupload/util/BlaImageLoader.java (renamed from app/src/main/java/uk/co/blatech/blaupload/util/ImageLoader.java)8
-rw-r--r--app/src/main/res/layout/activity_image_pager.xml17
-rw-r--r--app/src/main/res/layout/image_pager_item.xml23
16 files changed, 1460 insertions, 379 deletions
diff --git a/.idea/libraries/Android_BitmapCache_2_3.xml b/.idea/libraries/Android_BitmapCache_2_3.xml
deleted file mode 100644
index 34c06f6..0000000
--- a/.idea/libraries/Android_BitmapCache_2_3.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<component name="libraryTable">
- <library name="Android-BitmapCache-2.3">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/app/libs/Android-BitmapCache-2.3.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
-</component> \ No newline at end of file
diff --git a/.idea/libraries/disklrucache_2_0_2.xml b/.idea/libraries/disklrucache_2_0_2.xml
deleted file mode 100644
index 45145e6..0000000
--- a/.idea/libraries/disklrucache_2_0_2.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="disklrucache-2.0.2">
- <CLASSES>
- <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.jakewharton/disklrucache/2.0.2/93b399470e72c0bc8cb52379943b63c9aa586cd8/disklrucache-2.0.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.jakewharton/disklrucache/2.0.2/a8b600c9a1d9f75cdc45bb1b24b4731845a7658b/disklrucache-2.0.2-sources.jar!/" />
- </SOURCES>
- </library>
-</component> \ No newline at end of file
diff --git a/.idea/libraries/library_1_2_3.xml b/.idea/libraries/library_1_2_3.xml
deleted file mode 100644
index 1c592d4..0000000
--- a/.idea/libraries/library_1_2_3.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<component name="libraryTable">
- <library name="library-1.2.3">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.github.chrisbanes.photoview/library/1.2.3/classes.jar!/" />
- <root url="file://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.github.chrisbanes.photoview/library/1.2.3/res" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
-</component> \ No newline at end of file
diff --git a/.idea/libraries/picasso_2_3_3.xml b/.idea/libraries/picasso_2_3_3.xml
new file mode 100644
index 0000000..cff7520
--- /dev/null
+++ b/.idea/libraries/picasso_2_3_3.xml
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+ <library name="picasso-2.3.3">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.squareup.picasso/picasso/2.3.3/1262995965440e28ba5c4d480c9c9ab4ccce84cc/picasso-2.3.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.squareup.picasso/picasso/2.3.3/c3f71fa5609cedc6951ba8c7a6639282ac9ae0e2/picasso-2.3.3-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/universal_image_loader_1_9_2.xml b/.idea/libraries/universal_image_loader_1_9_2.xml
new file mode 100644
index 0000000..be34e19
--- /dev/null
+++ b/.idea/libraries/universal_image_loader_1_9_2.xml
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+ <library name="universal-image-loader-1.9.2">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.nostra13.universalimageloader/universal-image-loader/1.9.2/9e025bc5b84968e7d6ac765e660a70b3e5a87432/universal-image-loader-1.9.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.nostra13.universalimageloader/universal-image-loader/1.9.2/e52a7a5d1dbd9f817f314aa28a037d546a9241c2/universal-image-loader-1.9.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/app/app.iml b/app/app.iml
index 1556047..a5e1ead 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -61,11 +61,10 @@
</content>
<orderEntry type="jdk" jdkName="Android API 17 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" exported="" name="library-1.2.3" level="project" />
<orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" />
- <orderEntry type="library" exported="" name="disklrucache-2.0.2" level="project" />
+ <orderEntry type="library" exported="" name="picasso-2.3.3" level="project" />
<orderEntry type="library" exported="" name="otto-1.3.5" level="project" />
- <orderEntry type="library" exported="" name="Android-BitmapCache-2.3" level="project" />
+ <orderEntry type="library" exported="" name="universal-image-loader-1.9.2" level="project" />
</component>
</module>
diff --git a/app/build.gradle b/app/build.gradle
index d099f61..a481a41 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -24,7 +24,7 @@ dependencies {
// You must install or update the Support Repository through the SDK manager to use this dependency.
compile 'com.android.support:support-v4:19.+'
compile 'com.squareup:otto:1.3.5'
- compile 'com.github.chrisbanes.photoview:library:1.2.3'
- compile 'com.jakewharton:disklrucache:2.0.2'
- compile files('libs/Android-BitmapCache-2.3.jar')
+ compile 'com.squareup.picasso:picasso:2.3.3'
+ compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.2'
+
}
diff --git a/app/src/main/java/uk/co/blatech/blaupload/BlauploadApplication.java b/app/src/main/java/uk/co/blatech/blaupload/BlauploadApplication.java
index 518eff8..9a5c5e2 100644
--- a/app/src/main/java/uk/co/blatech/blaupload/BlauploadApplication.java
+++ b/app/src/main/java/uk/co/blatech/blaupload/BlauploadApplication.java
@@ -1,50 +1,19 @@
package uk.co.blatech.blaupload;
import android.app.Application;
-import android.content.Context;
-import android.os.Environment;
-import android.util.Log;
-import java.io.File;
-
-import uk.co.senab.bitmapcache.BitmapLruCache;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
public class BlauploadApplication extends Application {
- private BitmapLruCache mCache;
-
@Override
public void onCreate() {
super.onCreate();
- File cacheLocation;
-
- // If we have external storage use it for the disk cache. Otherwise we use
- // the cache dir
- if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
- cacheLocation = new File(
- Environment.getExternalStorageDirectory() + "/blaupload");
- } else {
- cacheLocation = new File(getFilesDir() + "/blaupload");
- }
- cacheLocation.mkdirs();
-
- BitmapLruCache.Builder builder = new BitmapLruCache.Builder(this);
- builder.setMemoryCacheEnabled(true).setMemoryCacheMaxSizeUsingHeapSize();
- builder.setDiskCacheEnabled(true).setDiskCacheLocation(cacheLocation);
-
-
- mCache = builder.build();
-
- Log.d(BlauploadApplication.class.toString(), cacheLocation.toString());
- }
-
- public BitmapLruCache getBitmapCache() {
- return mCache;
- }
+ ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()).build();
+ ImageLoader.getInstance().init(config);
- public static BlauploadApplication getApplication(Context context) {
- return (BlauploadApplication) context.getApplicationContext();
}
} \ No newline at end of file
diff --git a/app/src/main/java/uk/co/blatech/blaupload/HomeScreenFragment.java b/app/src/main/java/uk/co/blatech/blaupload/HomeScreenFragment.java
index ee51908..88ed453 100644
--- a/app/src/main/java/uk/co/blatech/blaupload/HomeScreenFragment.java
+++ b/app/src/main/java/uk/co/blatech/blaupload/HomeScreenFragment.java
@@ -22,8 +22,8 @@ import java.util.ArrayList;
import uk.co.blatech.blaupload.data.ImageItem;
import uk.co.blatech.blaupload.ui.GridViewAdapter;
+import uk.co.blatech.blaupload.util.BlaImageLoader;
import uk.co.blatech.blaupload.util.EventBus;
-import uk.co.blatech.blaupload.util.ImageLoader;
import uk.co.blatech.blaupload.util.ImageLoaderResultEvent;
import uk.co.blatech.blaupload.util.JSONLoader;
import uk.co.blatech.blaupload.util.JSONLoaderResultEvent;
@@ -153,7 +153,7 @@ public class HomeScreenFragment extends Fragment {
//Load the thumbnail for each item on a thread
int id = 0;
for (String filename : filenames) {
- new ImageLoader(getResources()).execute("http://www.lc8n.com/blathumbs/", filename+".png", String.valueOf(id));
+ new BlaImageLoader(getResources()).execute("http://www.lc8n.com/bla100/", filename, String.valueOf(id));
id++;
}
diff --git a/app/src/main/java/uk/co/blatech/blaupload/ImagePager.java b/app/src/main/java/uk/co/blatech/blaupload/ImagePager.java
index 0191c52..f562e2f 100644
--- a/app/src/main/java/uk/co/blatech/blaupload/ImagePager.java
+++ b/app/src/main/java/uk/co/blatech/blaupload/ImagePager.java
@@ -2,12 +2,12 @@ package uk.co.blatech.blaupload;
import android.app.Activity;
import android.os.Bundle;
-import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
-import com.squareup.otto.Subscribe;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import org.json.JSONArray;
import org.json.JSONException;
@@ -15,24 +15,23 @@ import org.json.JSONObject;
import java.util.ArrayList;
-import uk.co.blatech.blaupload.R;
-import uk.co.blatech.blaupload.data.ImageItem;
import uk.co.blatech.blaupload.ui.ImagePagerAdapter;
+import uk.co.blatech.blaupload.ui.ZoomViewPager;
import uk.co.blatech.blaupload.util.EventBus;
-import uk.co.blatech.blaupload.util.ImageLoader;
-import uk.co.blatech.blaupload.util.ImageLoaderResultEvent;
public class ImagePager extends Activity {
- private ViewPager mViewPager;
+ private ZoomViewPager mViewPager;
private JSONArray json;
private ArrayList<String> urls;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mViewPager = new ViewPager(this);
- setContentView(mViewPager);
+ setContentView(R.layout.activity_image_pager);
+
+ mViewPager = (ZoomViewPager) findViewById(R.id.imagePager);
EventBus.getInstance().register(this);
urls = new ArrayList<String>();
String jsonText = getIntent().getStringExtra("json");
@@ -49,7 +48,8 @@ public class ImagePager extends Activity {
for (int i = 0; i < json.length(); i++) {
try {
JSONObject item = json.getJSONObject(i);
- urls.add("http://www.blaupload.co.uk/"+item.getString("filename"));
+ urls.add("http://www.lc8n.com/bla500/"+item.getString("filename"));
+
} catch (JSONException e) {
e.printStackTrace();
}
diff --git a/app/src/main/java/uk/co/blatech/blaupload/ui/ImagePagerAdapter.java b/app/src/main/java/uk/co/blatech/blaupload/ui/ImagePagerAdapter.java
index 4f5dba0..df55eda 100644
--- a/app/src/main/java/uk/co/blatech/blaupload/ui/ImagePagerAdapter.java
+++ b/app/src/main/java/uk/co/blatech/blaupload/ui/ImagePagerAdapter.java
@@ -16,41 +16,145 @@
package uk.co.blatech.blaupload.ui;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
import android.support.v4.view.PagerAdapter;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView.ScaleType;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
+import com.squareup.picasso.Picasso;
+import com.squareup.picasso.Target;
+
+import java.io.IOException;
import java.util.ArrayList;
+import uk.co.blatech.blaupload.R;
+
public class ImagePagerAdapter extends PagerAdapter {
private final ArrayList<String> mUrls;
private final Context mContext;
+ private float[] mWidths;
+
+ private float[] mHeights;
+ private int position;
+ private ViewGroup container;
+
+ private TouchImageView imageView;
+ private TouchImageView zoomImageView;
+ private ImageLoader imageLoader;
+ private ZoomViewPager mPager;
+ private ArrayList<String> filenames;
public ImagePagerAdapter(Context context, ArrayList<String> urls) {
mUrls = urls;
mContext = context;
+ mWidths = new float[mUrls.size()];
+
+ mHeights = new float[mUrls.size()];
+ filenames = new ArrayList<String>();
}
+
+ private Target target = new Target() {
+ @Override
+ public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
+ zoomImageView.setImageBitmap(bitmap);
+ zoomImageView.setVisibility(View.VISIBLE);
+ imageView.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onBitmapFailed(Drawable errorDrawable) {
+
+ }
+
+ @Override
+ public void onPrepareLoad(Drawable placeHolderDrawable) {
+
+ }
+
+
+ };
+
@Override
public int getCount() {
return null != mUrls ? mUrls.size() : 0;
}
@Override
- public View instantiateItem(ViewGroup container, int position) {
- NetworkCacheableImageView imageView = new NetworkCacheableImageView(mContext, null);
+ public View instantiateItem(ViewGroup con, int pos) {
+ container = con;
+ position = pos;
- String url = mUrls.get(position);
- imageView.loadImage(url, true, null);
+
+ LayoutInflater inflater = (LayoutInflater) container.getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.image_pager_item, container, false);
+
+
+ imageView = (TouchImageView) view.findViewById(R.id.imageView);
+ zoomImageView = (TouchImageView) view.findViewById(R.id.zoomImageView);
+ final String url = mUrls.get(position);
+ String[] parts = url.split("/");
+ final String filename = parts[parts.length-1];
+
+
+ imageLoader = ImageLoader.getInstance();
+ imageLoader.displayImage(url, imageView);
imageView.setScaleType(ScaleType.FIT_CENTER);
- container.addView(imageView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ zoomImageView.setScaleType(ScaleType.FIT_CENTER);
+ container.refreshDrawableState();
+
+ imageView.setTag("img"+pos);
+ zoomImageView.setTag("zoom"+pos);
+ mWidths[position] = 0;
+ mHeights[position] = 0;
+ filenames.add(pos, filename);
+ mPager = (ZoomViewPager) container.findViewById(R.id.imagePager);
+
+ imageView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ final TouchImageView thisImage = (TouchImageView) container.findViewWithTag("img" + mPager.getCurrentItem());
+ final TouchImageView zoomImage = (TouchImageView) container.findViewWithTag("zoom" + mPager.getCurrentItem());
+
+ if (thisImage.getCurrentZoom() > 1.0) {
+
+ imageLoader.displayImage("http://www.blaupload.co.uk/"+ filenames.get(mPager.getCurrentItem()), zoomImage, new SimpleImageLoadingListener() {
+ //
+ @Override
+ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
+
+ zoomImage.setZoom(thisImage.getCurrentZoom());
+
+ zoomImage.setVisibility(View.VISIBLE);
+ thisImage.setVisibility(View.GONE);
+ }
+ });
+
+ }
+ Log.d(ImagePagerAdapter.class.toString(), "Initial width: "+ mWidths[position] +", height: "+ mHeights[position]);
+ Log.d(ImagePagerAdapter.class.toString(), "Current width: " + thisImage.getMeasuredWidth() + ", height: " + thisImage.getMeasuredHeight());
+ Log.d(ImagePagerAdapter.class.toString(), "Other width: " + thisImage.getWidth() + ", height: " + thisImage.getHeight());
+ Log.d(ImagePagerAdapter.class.toString(), "Zoom: " + thisImage.getCurrentZoom());
+ return true;
+ }
+ });
+
+ container.addView(view, 0);
- return imageView;
+ return view;
}
@Override
diff --git a/app/src/main/java/uk/co/blatech/blaupload/ui/NetworkCacheableImageView.java b/app/src/main/java/uk/co/blatech/blaupload/ui/NetworkCacheableImageView.java
deleted file mode 100644
index 043caae..0000000
--- a/app/src/main/java/uk/co/blatech/blaupload/ui/NetworkCacheableImageView.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2013 Chris Banes.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- ******************************************************************************/
-
-package uk.co.blatech.blaupload.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
-import android.graphics.drawable.BitmapDrawable;
-import android.opengl.GLES10;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.ImageView;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.util.EntityUtils;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.concurrent.RejectedExecutionException;
-
-import javax.microedition.khronos.opengles.GL10;
-
-import uk.co.blatech.blaupload.BlauploadApplication;
-import uk.co.blatech.blaupload.R;
-import uk.co.blatech.blaupload.data.ImageItem;
-import uk.co.senab.bitmapcache.BitmapLruCache;
-import uk.co.senab.bitmapcache.CacheableBitmapDrawable;
-import uk.co.senab.bitmapcache.CacheableImageView;
-
-/**
- * Simple extension of CacheableImageView which allows downloading of Images of the Internet.
- *
- * This code isn't production quality, but works well enough for this sample.s
- *
- * @author Chris Banes
- */
-public class NetworkCacheableImageView extends CacheableImageView {
-
- public interface OnImageLoadedListener {
- void onImageLoaded(CacheableBitmapDrawable result);
- }
-
- /**
- * This task simply fetches an Bitmap from the specified URL and wraps it in a wrapper. This
- * implementation is NOT 'best practice' or production ready code.
- */
- private static class ImageUrlAsyncTask
- extends AsyncTask<String, Void, CacheableBitmapDrawable> {
-
- private final BitmapLruCache mCache;
-
- private final WeakReference<ImageView> mImageViewRef;
- private final OnImageLoadedListener mListener;
-
- private final BitmapFactory.Options mDecodeOpts;
-
- ImageUrlAsyncTask(ImageView imageView, BitmapLruCache cache,
- BitmapFactory.Options decodeOpts, OnImageLoadedListener listener) {
- mCache = cache;
- mImageViewRef = new WeakReference<ImageView>(imageView);
- mListener = listener;
- mDecodeOpts = decodeOpts;
- }
-
- @Override
- protected CacheableBitmapDrawable doInBackground(String... params) {
-
- // Return early if the ImageView has disappeared.
- if (null == mImageViewRef.get()) {
- return null;
- }
-
- final String url = params[0];
-
- // Now we're not on the main thread we can check all caches
- CacheableBitmapDrawable result = mCache.get(url);
-
- if (null == result) {
- Log.d("ImageUrlAsyncTask", "Downloading: " + url);
-
-// // The bitmap isn't cached so download from the web
-// HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
-// InputStream is = new BufferedInputStream(conn.getInputStream());
- Bitmap bmp = downloadImage(url);
- // Add to cache
- if (bmp != null) {
- result = mCache.put(url, bmp);
- }
- } else {
- Log.d("ImageUrlAsyncTask", "Got from Cache: " + url);
- }
-
- return result;
-
-
- }
-
- @Override
- protected void onPostExecute(CacheableBitmapDrawable result) {
- super.onPostExecute(result);
-
- ImageView iv = mImageViewRef.get();
- if (null != iv) {
- iv.setImageDrawable(result);
- }
-
- if (null != mListener) {
- mListener.onImageLoaded(result);
- }
- }
-
- private Bitmap downloadImage(String url) {
-
-
- DefaultHttpClient client = new DefaultHttpClient();
- HttpGet httpGet = new HttpGet(url);
- Bitmap bmp = null;
-
-
- int[] maxTextureSize = new int[1];
- maxTextureSize[0] = 4096;
- GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
- //TODO: Fix out of memory errors
- try {
- HttpResponse response = client.execute(httpGet);
- StatusLine statusLine = response.getStatusLine();
- int statusCode = statusLine.getStatusCode();
- String[] parts = url.split("\\.");
- String extension = parts[parts.length-1];
- //Create the Bitmap if the file exists
- if (statusCode == 200 && isImageExtension(extension)) {
- // If the thumbnail wasn't found, show a placeholder
- HttpEntity entity = response.getEntity();
- byte[] bytes = EntityUtils.toByteArray(entity);
- try {
- bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
- } catch (Exception e) {
- Log.e(NetworkCacheableImageView.class.toString(), "Failed decoding " + url);
- e.printStackTrace();
- return bmp;
- }
- int bmpHeight = bmp.getHeight();
- int bmpWidth = bmp.getWidth();
- if (bmpWidth > maxTextureSize[0]) {
-
- float ratio = (float)maxTextureSize[0]/(float)bmpWidth;
- Matrix matrix = new Matrix();
- bmpWidth = maxTextureSize[0];
- bmpHeight = Math.round(bmpHeight * ratio);
- matrix.postScale(ratio, ratio);
- bmp = Bitmap.createBitmap(bmp, 0, 0, bmpWidth, bmpHeight, matrix, false);
- } else if (bmpHeight > maxTextureSize[0]) {
- float ratio = (float)maxTextureSize[0]/(float)bmpHeight;
- bmpHeight = maxTextureSize[0];
- bmpWidth = Math.round(bmpWidth * ratio);
- Matrix matrix = new Matrix();
- matrix.postScale(ratio, ratio);
- bmp = Bitmap.createBitmap(bmp, 0, 0, bmpWidth, bmpHeight, matrix, false);
- }
- } else {
- //If the file doesn't exist, use the placeholder instead
-// bmp = BitmapFactory.decodeResource(res, R.drawable.x);
- return bmp;
- }
-
- //TODO: Error handling (Not sure how we get here)
- } catch (ClientProtocolException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- return bmp;
- }
-
- private boolean isImageExtension(String extension) {
- if (extension.equalsIgnoreCase("jpeg") || extension.equalsIgnoreCase("jpg") ||
- extension.equalsIgnoreCase("gif") || extension.equalsIgnoreCase("png")) {
- return true;
- } else {
- return false;
- }
- }
- }
-
- private final BitmapLruCache mCache;
-
- private ImageUrlAsyncTask mCurrentTask;
-
- public NetworkCacheableImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mCache = BlauploadApplication.getApplication(context).getBitmapCache();
- }
-
- /**
- * Loads the Bitmap.
- *
- * @param url - URL of image
- * @param fullSize - Whether the image should be kept at the original size
- * @return true if the bitmap was found in the cache
- */
- public boolean loadImage(String url, final boolean fullSize, OnImageLoadedListener listener) {
- // First check whether there's already a task running, if so cancel it
- if (null != mCurrentTask) {
- mCurrentTask.cancel(true);
- }
-
- // Check to see if the memory cache already has the bitmap. We can
- // safely do
- // this on the main thread.
- BitmapDrawable wrapper = mCache.getFromMemoryCache(url);
-
- if (null != wrapper) {
- // The cache has it, so just display it
- setImageDrawable(wrapper);
- return true;
- } else {
- // Memory Cache doesn't have the URL, do threaded request...
- setImageDrawable(null);
-
- BitmapFactory.Options decodeOpts = null;
-
- if (!fullSize) {
- //decodeOpts = new BitmapFactory.Options();
- //decodeOpts.inSampleSize = 2;
- }
-
- mCurrentTask = new ImageUrlAsyncTask(this, mCache, decodeOpts, listener);
-
- try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- mCurrentTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url);
- } else {
- mCurrentTask.execute(url);
- }
- } catch (RejectedExecutionException e) {
- // This shouldn't happen, but might.
- }
-
- return false;
- }
- }
-
-}
diff --git a/app/src/main/java/uk/co/blatech/blaupload/ui/TouchImageView.java b/app/src/main/java/uk/co/blatech/blaupload/ui/TouchImageView.java
new file mode 100644
index 0000000..45513c4
--- /dev/null
+++ b/app/src/main/java/uk/co/blatech/blaupload/ui/TouchImageView.java
@@ -0,0 +1,1276 @@
+/*
+ * TouchImageView.java
+ * By: Michael Ortiz
+ * Updated By: Patrick Lackemacher
+ * Updated By: Babay88
+ * Updated By: @ipsilondev
+ * Updated By: hank-cp
+ * Updated By: singpolyma
+ * -------------------
+ * Extends Android ImageView to include pinch zooming, panning, fling and double tap zoom.
+ */
+
+package uk.co.blatech.blaupload.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.OverScroller;
+import android.widget.Scroller;
+
+public class TouchImageView extends ImageView {
+
+ private static final String DEBUG = "DEBUG";
+
+ //
+ // SuperMin and SuperMax multipliers. Determine how much the image can be
+ // zoomed below or above the zoom boundaries, before animating back to the
+ // min/max zoom boundary.
+ //
+ private static final float SUPER_MIN_MULTIPLIER = .75f;
+ private static final float SUPER_MAX_MULTIPLIER = 1.25f;
+
+ //
+ // Scale of image ranges from minScale to maxScale, where minScale == 1
+ // when the image is stretched to fit view.
+ //
+ private float normalizedScale;
+
+ //
+ // Matrix applied to image. MSCALE_X and MSCALE_Y should always be equal.
+ // MTRANS_X and MTRANS_Y are the other values used. prevMatrix is the matrix
+ // saved prior to the screen rotating.
+ //
+ private Matrix matrix, prevMatrix;
+
+ private static enum State { NONE, DRAG, ZOOM, FLING, ANIMATE_ZOOM };
+ private State state;
+
+ private float minScale;
+ private float maxScale;
+ private float superMinScale;
+ private float superMaxScale;
+ private float[] m;
+
+ private Context context;
+ private Fling fling;
+
+ private ScaleType mScaleType;
+
+ private boolean imageRenderedAtLeastOnce;
+ private boolean onDrawReady;
+
+ private ZoomVariables delayedZoomVariables;
+
+ //
+ // Size of view and previous view size (ie before rotation)
+ //
+ private int viewWidth, viewHeight, prevViewWidth, prevViewHeight;
+
+ //
+ // Size of image when it is stretched to fit view. Before and After rotation.
+ //
+ private float matchViewWidth, matchViewHeight, prevMatchViewWidth, prevMatchViewHeight;
+
+ private ScaleGestureDetector mScaleDetector;
+ private GestureDetector mGestureDetector;
+ private GestureDetector.OnDoubleTapListener doubleTapListener = null;
+ private OnTouchListener userTouchListener = null;
+ private OnTouchImageViewListener touchImageViewListener = null;
+
+ public TouchImageView(Context context) {
+ super(context);
+ sharedConstructing(context);
+ }
+
+ public TouchImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ sharedConstructing(context);
+ }
+
+ public TouchImageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ sharedConstructing(context);
+ }
+
+ private void sharedConstructing(Context context) {
+ super.setClickable(true);
+ this.context = context;
+ mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
+ mGestureDetector = new GestureDetector(context, new GestureListener());
+ matrix = new Matrix();
+ prevMatrix = new Matrix();
+ m = new float[9];
+ normalizedScale = 1;
+ if (mScaleType == null) {
+ mScaleType = ScaleType.FIT_CENTER;
+ }
+ minScale = 1;
+ maxScale = 3;
+ superMinScale = SUPER_MIN_MULTIPLIER * minScale;
+ superMaxScale = SUPER_MAX_MULTIPLIER * maxScale;
+ setImageMatrix(matrix);
+ setScaleType(ScaleType.MATRIX);
+ setState(State.NONE);
+ onDrawReady = false;
+ super.setOnTouchListener(new PrivateOnTouchListener());
+ }
+
+ @Override
+ public void setOnTouchListener(View.OnTouchListener l) {
+ userTouchListener = l;
+ }
+
+ public void setOnTouchImageViewListener(OnTouchImageViewListener l) {
+ touchImageViewListener = l;
+ }
+
+ public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener l) {
+ doubleTapListener = l;
+ }
+
+ @Override
+ public void setImageResource(int resId) {
+ super.setImageResource(resId);
+ savePreviousImageValues();
+ fitImageToView();
+ }
+
+ @Override
+ public void setImageBitmap(Bitmap bm) {
+ super.setImageBitmap(bm);
+ savePreviousImageValues();
+ fitImageToView();
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+ savePreviousImageValues();
+ fitImageToView();
+ }
+
+ @Override
+ public void setImageURI(Uri uri) {
+ super.setImageURI(uri);
+ savePreviousImageValues();
+ fitImageToView();
+ }
+
+ @Override
+ public void setScaleType(ScaleType type) {
+ if (type == ScaleType.FIT_START || type == ScaleType.FIT_END) {
+ throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END");
+ }
+ if (type == ScaleType.MATRIX) {
+ super.setScaleType(ScaleType.MATRIX);
+
+ } else {
+ mScaleType = type;
+ if (onDrawReady) {
+ //
+ // If the image is already rendered, scaleType has been called programmatically
+ // and the TouchImageView should be updated with the new scaleType.
+ //
+ setZoom(this);
+ }
+ }
+ }
+
+ @Override
+ public ScaleType getScaleType() {
+ return mScaleType;
+ }
+
+ /**
+ * Returns false if image is in initial, unzoomed state. False, otherwise.
+ * @return true if image is zoomed
+ */
+ public boolean isZoomed() {
+ return normalizedScale != 1;
+ }
+
+ /**
+ * Return a Rect representing the zoomed image.
+ * @return rect representing zoomed image
+ */
+ public RectF getZoomedRect() {
+ if (mScaleType == ScaleType.FIT_XY) {
+ throw new UnsupportedOperationException("getZoomedRect() not supported with FIT_XY");
+ }
+ PointF topLeft = transformCoordTouchToBitmap(0, 0, true);
+ PointF bottomRight = transformCoordTouchToBitmap(viewWidth, viewHeight, true);
+
+ float w = getDrawable().getIntrinsicWidth();
+ float h = getDrawable().getIntrinsicHeight();
+ return new RectF(topLeft.x / w, topLeft.y / h, bottomRight.x / w, bottomRight.y / h);
+ }
+
+ /**
+ * Save the current matrix and view dimensions
+ * in the prevMatrix and prevView variables.
+ */
+ private void savePreviousImageValues() {
+ if (matrix != null && viewHeight != 0 && viewWidth != 0) {
+ matrix.getValues(m);
+ prevMatrix.setValues(m);
+ prevMatchViewHeight = matchViewHeight;
+ prevMatchViewWidth = matchViewWidth;
+ prevViewHeight = viewHeight;
+ prevViewWidth = viewWidth;
+ }
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("instanceState", super.onSaveInstanceState());
+ bundle.putFloat("saveScale", normalizedScale);
+ bundle.putFloat("matchViewHeight", matchViewHeight);
+ bundle.putFloat("matchViewWidth", matchViewWidth);
+ bundle.putInt("viewWidth", viewWidth);
+ bundle.putInt("viewHeight", viewHeight);
+ matrix.getValues(m);
+ bundle.putFloatArray("matrix", m);
+ bundle.putBoolean("imageRendered", imageRenderedAtLeastOnce);
+ return bundle;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (state instanceof Bundle) {
+ Bundle bundle = (Bundle) state;
+ normalizedScale = bundle.getFloat("saveScale");
+ m = bundle.getFloatArray("matrix");
+ prevMatrix.setValues(m);
+ prevMatchViewHeight = bundle.getFloat("matchViewHeight");
+ prevMatchViewWidth = bundle.getFloat("matchViewWidth");
+ prevViewHeight = bundle.getInt("viewHeight");
+ prevViewWidth = bundle.getInt("viewWidth");
+ imageRenderedAtLeastOnce = bundle.getBoolean("imageRendered");
+ super.onRestoreInstanceState(bundle.getParcelable("instanceState"));
+ return;
+ }
+
+ super.onRestoreInstanceState(state);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ onDrawReady = true;
+ imageRenderedAtLeastOnce = true;
+ if (delayedZoomVariables != null) {
+ setZoom(delayedZoomVariables.scale, delayedZoomVariables.focusX, delayedZoomVariables.focusY, delayedZoomVariables.scaleType);
+ delayedZoomVariables = null;
+ }
+ super.onDraw(canvas);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ savePreviousImageValues();
+ }
+
+ /**
+ * Get the max zoom multiplier.
+ * @return max zoom multiplier.
+ */
+ public float getMaxZoom() {
+ return maxScale;
+ }
+
+ /**
+ * Set the max zoom multiplier. Default value: 3.
+ * @param max max zoom multiplier.
+ */
+ public void setMaxZoom(float max) {
+ maxScale = max;
+ superMaxScale = SUPER_MAX_MULTIPLIER * maxScale;
+ }
+
+ /**
+ * Get the min zoom multiplier.
+ * @return min zoom multiplier.
+ */
+ public float getMinZoom() {
+ return minScale;
+ }
+
+ /**
+ * Get the current zoom. This is the zoom relative to the initial
+ * scale, not the original resource.
+ * @return current zoom multiplier.
+ */
+ public float getCurrentZoom() {
+ return normalizedScale;
+ }
+
+ /**
+ * Set the min zoom multiplier. Default value: 1.
+ * @param min min zoom multiplier.
+ */
+ public void setMinZoom(float min) {
+ minScale = min;
+ superMinScale = SUPER_MIN_MULTIPLIER * minScale;
+ }
+
+ /**
+ * Reset zoom and translation to initial state.
+ */
+ public void resetZoom() {
+ normalizedScale = 1;
+ fitImageToView();
+ }
+
+ /**
+ * Set zoom to the specified scale. Image will be centered by default.
+ * @param scale
+ */
+ public void setZoom(float scale) {
+ setZoom(scale, 0.5f, 0.5f);
+ }
+
+ /**
+ * Set zoom to the specified scale. Image will be centered around the point
+ * (focusX, focusY). These floats range from 0 to 1 and denote the focus point
+ * as a fraction from the left and top of the view. For example, the top left
+ * corner of the image would be (0, 0). And the bottom right corner would be (1, 1).
+ * @param scale
+ * @param focusX
+ * @param focusY
+ */
+ public void setZoom(float scale, float focusX, float focusY) {
+ setZoom(scale, focusX, focusY, mScaleType);
+ }
+
+ /**
+ * Set zoom to the specified scale. Image will be centered around the point
+ * (focusX, focusY). These floats range from 0 to 1 and denote the focus point
+ * as a fraction from the left and top of the view. For example, the top left
+ * corner of the image would be (0, 0). And the bottom right corner would be (1, 1).
+ * @param scale
+ * @param focusX
+ * @param focusY
+ * @param scaleType
+ */
+ public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) {
+ //
+ // setZoom can be called before the image is on the screen, but at this point,
+ // image and view sizes have not yet been calculated in onMeasure. Thus, we should
+ // delay calling setZoom until the view has been measured.
+ //
+ if (!onDrawReady) {
+ delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType);
+ return;
+ }
+
+ if (scaleType != mScaleType) {
+ setScaleType(scaleType);
+ }
+ resetZoom();
+ scaleImage(scale, viewWidth / 2, viewHeight / 2, true);
+ matrix.getValues(m);
+ m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f));
+ m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f));
+ matrix.setValues(m);
+ fixTrans();
+ setImageMatrix(matrix);
+ }
+
+ /**
+ * Set zoom parameters equal to another TouchImageView. Including scale, position,
+ * and ScaleType.
+ * @param img
+ */
+ public void setZoom(TouchImageView img) {
+ PointF center = img.getScrollPosition();
+ setZoom(img.getCurrentZoom(), center.x, center.y, img.getScaleType());
+ }
+
+ /**
+ * Return the point at the center of the zoomed image. The PointF coordinates range
+ * in value between 0 and 1 and the focus point is denoted as a fraction from the left
+ * and top of the view. For example, the top left corner of the image would be (0, 0).
+ * And the bottom right corner would be (1, 1).
+ * @return PointF representing the scroll position of the zoomed image.
+ */
+ public PointF getScrollPosition() {
+ Drawable drawable = getDrawable();
+ if (drawable == null) {
+ return null;
+ }
+ int drawableWidth = drawable.getIntrinsicWidth();
+ int drawableHeight = drawable.getIntrinsicHeight();
+
+ PointF point = transformCoordTouchToBitmap(viewWidth / 2, viewHeight / 2, true);
+ point.x /= drawableWidth;
+ point.y /= drawableHeight;
+ return point;
+ }
+
+ /**
+ * Set the focus point of the zoomed image. The focus points are denoted as a fraction from the
+ * left and top of the view. The focus points can range in value between 0 and 1.
+ * @param focusX
+ * @param focusY
+ */
+ public void setScrollPosition(float focusX, float focusY) {
+ setZoom(normalizedScale, focusX, focusY);
+ }
+
+ /**
+ * Performs boundary checking and fixes the image matrix if it
+ * is out of bounds.
+ */
+ private void fixTrans() {
+ matrix.getValues(m);
+ float transX = m[Matrix.MTRANS_X];
+ float transY = m[Matrix.MTRANS_Y];
+
+ float fixTransX = getFixTrans(transX, viewWidth, getImageWidth());
+ float fixTransY = getFixTrans(transY, viewHeight, getImageHeight());
+
+ if (fixTransX != 0 || fixTransY != 0) {
+ matrix.postTranslate(fixTransX, fixTransY);
+ }
+ }
+
+ /**
+ * When transitioning from zooming from focus to zoom from center (or vice versa)
+ * the image can become unaligned within the view. This is apparent when zooming
+ * quickly. When the content size is less than the view size, the content will often
+ * be centered incorrectly within the view. fixScaleTrans first calls fixTrans() and
+ * then makes sure the image is centered correctly within the view.
+ */
+ private void fixScaleTrans() {
+ fixTrans();
+ matrix.getValues(m);
+ if (getImageWidth() < viewWidth) {
+ m[Matrix.MTRANS_X] = (viewWidth - getImageWidth()) / 2;
+ }
+
+ if (getImageHeight() < viewHeight) {
+ m[Matrix.MTRANS_Y] = (viewHeight - getImageHeight()) / 2;
+ }
+ matrix.setValues(m);
+ }
+
+ private float getFixTrans(float trans, float viewSize, float contentSize) {
+ float minTrans, maxTrans;
+
+ if (contentSize <= viewSize) {
+ minTrans = 0;
+ maxTrans = viewSize - contentSize;
+
+ } else {
+ minTrans = viewSize - contentSize;
+ maxTrans = 0;
+ }
+
+ if (trans < minTrans)
+ return -trans + minTrans;
+ if (trans > maxTrans)
+ return -trans + maxTrans;
+ return 0;
+ }
+
+ private float getFixDragTrans(float delta, float viewSize, float contentSize) {
+ if (contentSize <= viewSize) {
+ return 0;
+ }
+ return delta;
+ }
+
+ private float getImageWidth() {
+ return matchViewWidth * normalizedScale;
+ }
+
+ private float getImageHeight() {
+ return matchViewHeight * normalizedScale;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Drawable drawable = getDrawable();
+ if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) {
+ setMeasuredDimension(0, 0);
+ return;
+ }
+
+ int drawableWidth = drawable.getIntrinsicWidth();
+ int drawableHeight = drawable.getIntrinsicHeight();
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ viewWidth = setViewSize(widthMode, widthSize, drawableWidth);
+ viewHeight = setViewSize(heightMode, heightSize, drawableHeight);
+
+ //
+ // Set view dimensions
+ //
+ setMeasuredDimension(viewWidth, viewHeight);
+
+ //
+ // Fit content within view
+ //
+ fitImageToView();
+ }
+
+ /**
+ * If the normalizedScale is equal to 1, then the image is made to fit the screen. Otherwise,
+ * it is made to fit the screen according to the dimensions of the previous image matrix. This
+ * allows the image to maintain its zoom after rotation.
+ */
+ private void fitImageToView() {
+ Drawable drawable = getDrawable();
+ if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) {
+ return;
+ }
+ if (matrix == null || prevMatrix == null) {
+ return;
+ }
+
+ int drawableWidth = drawable.getIntrinsicWidth();
+ int drawableHeight = drawable.getIntrinsicHeight();
+
+ //
+ // Scale image for view
+ //
+ float scaleX = (float) viewWidth / drawableWidth;
+ float scaleY = (float) viewHeight / drawableHeight;
+
+ switch (mScaleType) {
+ case CENTER:
+ scaleX = scaleY = 1;
+ break;
+
+ case CENTER_CROP:
+ scaleX = scaleY = Math.max(scaleX, scaleY);
+ break;
+
+ case CENTER_INSIDE:
+ scaleX = scaleY = Math.min(1, Math.min(scaleX, scaleY));
+
+ case FIT_CENTER:
+ scaleX = scaleY = Math.min(scaleX, scaleY);
+ break;
+
+ case FIT_XY:
+ break;
+
+ default:
+ //
+ // FIT_START and FIT_END not supported
+ //
+ throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END");
+
+ }
+
+ //
+ // Center the image
+ //
+ float redundantXSpace = viewWidth - (scaleX * drawableWidth);
+ float redundantYSpace = viewHeight - (scaleY * drawableHeight);
+ matchViewWidth = viewWidth - redundantXSpace;
+ matchViewHeight = viewHeight - redundantYSpace;
+ if (!isZoomed() && !imageRenderedAtLeastOnce) {
+ //
+ // Stretch and center image to fit view
+ //
+ matrix.setScale(scaleX, scaleY);
+ matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2);
+ normalizedScale = 1;
+
+ } else {
+ //
+ // These values should never be 0 or we will set viewWidth and viewHeight
+ // to NaN in translateMatrixAfterRotate. To avoid this, call savePreviousImageValues
+ // to set them equal to the current values.
+ //
+ if (prevMatchViewWidth == 0 || prevMatchViewHeight == 0) {
+ savePreviousImageValues();
+ }
+
+ prevMatrix.getValues(m);
+
+ //
+ // Rescale Matrix after rotation
+ //
+ m[Matrix.MSCALE_X] = matchViewWidth / drawableWidth * normalizedScale;
+ m[Matrix.MSCALE_Y] = matchViewHeight / drawableHeight * normalizedScale;
+
+ //
+ // TransX and TransY from previous matrix
+ //
+ float transX = m[Matrix.MTRANS_X];
+ float transY = m[Matrix.MTRANS_Y];
+
+ //
+ // Width
+ //
+ float prevActualWidth = prevMatchViewWidth * normalizedScale;
+ float actualWidth = getImageWidth();
+ translateMatrixAfterRotate(Matrix.MTRANS_X, transX, prevActualWidth, actualWidth, prevViewWidth, viewWidth, drawableWidth);
+
+ //
+ // Height
+ //
+ float prevActualHeight = prevMatchViewHeight * normalizedScale;
+ float actualHeight = getImageHeight();
+ translateMatrixAfterRotate(Matrix.MTRANS_Y, transY, prevActualHeight, actualHeight, prevViewHeight, viewHeight, drawableHeight);
+
+ //
+ // Set the matrix to the adjusted scale and translate values.
+ //
+ matrix.setValues(m);
+ }
+ fixTrans();
+ setImageMatrix(matrix);
+ }
+
+ /**
+ * Set view dimensions based on layout params
+ *
+ * @param mode
+ * @param size
+ * @param drawableWidth
+ * @return
+ */
+ private int setViewSize(int mode, int size, int drawableWidth) {
+ int viewSize;
+ switch (mode) {
+ case MeasureSpec.EXACTLY:
+ viewSize = size;
+ break;
+
+ case MeasureSpec.AT_MOST:
+ viewSize = Math.min(drawableWidth, size);
+ break;
+
+ case MeasureSpec.UNSPECIFIED:
+ viewSize = drawableWidth;
+ break;
+
+ default:
+ viewSize = size;
+ break;
+ }
+ return viewSize;
+ }
+
+ /**
+ * After rotating, the matrix needs to be translated. This function finds the area of image
+ * which was previously centered and adjusts translations so that is again the center, post-rotation.
+ *
+ * @param axis Matrix.MTRANS_X or Matrix.MTRANS_Y
+ * @param trans the value of trans in that axis before the rotation
+ * @param prevImageSize the width/height of the image before the rotation
+ * @param imageSize width/height of the image after rotation
+ * @param prevViewSize width/height of view before rotation
+ * @param viewSize width/height of view after rotation
+ * @param drawableSize width/height of drawable
+ */
+ private void translateMatrixAfterRotate(int axis, float trans, float prevImageSize, float imageSize, int prevViewSize, int viewSize, int drawableSize) {
+ if (imageSize < viewSize) {
+ //
+ // The width/height of image is less than the view's width/height. Center it.
+ //
+ m[axis] = (viewSize - (drawableSize * m[Matrix.MSCALE_X])) * 0.5f;
+
+ } else if (trans > 0) {
+ //
+ // The image is larger than the view, but was not before rotation. Center it.
+ //
+ m[axis] = -((imageSize - viewSize) * 0.5f);
+
+ } else {
+ //
+ // Find the area of the image which was previously centered in the view. Determine its distance
+ // from the left/top side of the view as a fraction of the entire image's width/height. Use that percentage
+ // to calculate the trans in the new view width/height.
+ //
+ float percentage = (Math.abs(trans) + (0.5f * prevViewSize)) / prevImageSize;
+ m[axis] = -((percentage * imageSize) - (viewSize * 0.5f));
+ }
+ }
+
+ private void setState(State state) {
+ this.state = state;
+ }
+
+ public boolean canScrollHorizontallyFroyo(int direction) {
+ return canScrollHorizontally(direction);
+ }
+
+ @Override
+ public boolean canScrollHorizontally(int direction) {
+ matrix.getValues(m);
+ float x = m[Matrix.MTRANS_X];
+
+ if (getImageWidth() < viewWidth) {
+ return false;
+
+ } else if (x >= -1 && direction < 0) {
+ return false;
+
+ } else if (Math.abs(x) + viewWidth + 1 >= getImageWidth() && direction > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gesture Listener detects a single click or long click and passes that on
+ * to the view's listener.
+ * @author Ortiz
+ *
+ */
+ private class GestureListener extends GestureDetector.SimpleOnGestureListener {
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e)
+ {
+ if(doubleTapListener != null) {
+ return doubleTapListener.onSingleTapConfirmed(e);
+ }
+ return performClick();
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e)
+ {
+ performLongClick();
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
+ {
+ if (fling != null) {
+ //
+ // If a previous fling is still active, it should be cancelled so that two flings
+ // are not run simultaenously.
+ //
+ fling.cancelFling();
+ }
+ fling = new Fling((int) velocityX, (int) velocityY);
+ compatPostOnAnimation(fling);
+ return super.onFling(e1, e2, velocityX, velocityY);
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ boolean consumed = false;
+ if(doubleTapListener != null) {
+ consumed = doubleTapListener.onDoubleTap(e);
+ }
+ if (state == State.NONE) {
+ float targetZoom = (normalizedScale == minScale) ? maxScale : minScale;
+ DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false);
+ compatPostOnAnimation(doubleTap);
+ consumed = true;
+ }
+ return consumed;
+ }
+
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ if(doubleTapListener != null) {
+ return doubleTapListener.onDoubleTapEvent(e);
+ }
+ return false;
+ }
+ }
+
+ public interface OnTouchImageViewListener {
+ public void onMove();
+ }
+
+ /**
+ * Responsible for all touch events. Handles the heavy lifting of drag and also sends
+ * touch events to Scale Detector and Gesture Detector.
+ * @author Ortiz
+ *
+ */
+ private class PrivateOnTouchListener implements OnTouchListener {
+
+ //
+ // Remember last point position for dragging
+ //
+ private PointF last = new PointF();
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ mScaleDetector.onTouchEvent(event);
+ mGestureDetector.onTouchEvent(event);
+ PointF curr = new PointF(event.getX(), event.getY());
+
+ if (state == State.NONE || state == State.DRAG || state == State.FLING) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ last.set(curr);
+ if (fling != null)
+ fling.cancelFling();
+ setState(State.DRAG);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (state == State.DRAG) {
+ float deltaX = curr.x - last.x;
+ float deltaY = curr.y - last.y;
+ float fixTransX = getFixDragTrans(deltaX, viewWidth, getImageWidth());
+ float fixTransY = getFixDragTrans(deltaY, viewHeight, getImageHeight());
+ matrix.postTranslate(fixTransX, fixTransY);
+ fixTrans();
+ last.set(curr.x, curr.y);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ setState(State.NONE);
+ break;
+ }
+ }
+
+ setImageMatrix(matrix);
+
+ //
+ // User-defined OnTouchListener
+ //
+ if(userTouchListener != null) {
+ userTouchListener.onTouch(v, event);
+ }
+
+ //
+ // OnTouchImageViewListener is set: TouchImageView dragged by user.
+ //
+ if (touchImageViewListener != null) {
+ touchImageViewListener.onMove();
+ }
+
+ //
+ // indicate event was handled
+ //
+ return true;
+ }
+ }
+
+ /**
+ * ScaleListener detects user two finger scaling and scales image.
+ * @author Ortiz
+ *
+ */
+ private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ setState(State.ZOOM);
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true);
+
+ //
+ // OnTouchImageViewListener is set: TouchImageView pinch zoomed by user.
+ //
+ if (touchImageViewListener != null) {
+ touchImageViewListener.onMove();
+ }
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ super.onScaleEnd(detector);
+ setState(State.NONE);
+ boolean animateToZoomBoundary = false;
+ float targetZoom = normalizedScale;
+ if (normalizedScale > maxScale) {
+ targetZoom = maxScale;
+ animateToZoomBoundary = true;
+
+ } else if (normalizedScale < minScale) {
+ targetZoom = minScale;
+ animateToZoomBoundary = true;
+ }
+
+ if (animateToZoomBoundary) {
+ DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true);
+ compatPostOnAnimation(doubleTap);
+ }
+ }
+ }
+
+ private void scaleImage(double deltaScale, float focusX, float focusY, boolean stretchImageToSuper) {
+
+ float lowerScale, upperScale;
+ if (stretchImageToSuper) {
+ lowerScale = superMinScale;
+ upperScale = superMaxScale;
+
+ } else {
+ lowerScale = minScale;
+ upperScale = maxScale;
+ }
+
+ float origScale = normalizedScale;
+ normalizedScale *= deltaScale;
+ if (normalizedScale > upperScale) {
+ normalizedScale = upperScale;
+ deltaScale = upperScale / origScale;
+ } else if (normalizedScale < lowerScale) {
+ normalizedScale = lowerScale;
+ deltaScale = lowerScale / origScale;
+ }
+
+ matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY);
+ fixScaleTrans();
+ }
+
+ /**
+ * DoubleTapZoom calls a series of runnables which apply
+ * an animated zoom in/out graphic to the image.
+ * @author Ortiz
+ *
+ */
+ private class DoubleTapZoom implements Runnable {
+
+ private long startTime;
+ private static final float ZOOM_TIME = 500;
+ private float startZoom, targetZoom;
+ private float bitmapX, bitmapY;
+ private boolean stretchImageToSuper;
+ private AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator();
+ private PointF startTouch;
+ private PointF endTouch;
+
+ DoubleTapZoom(float targetZoom, float focusX, float focusY, boolean stretchImageToSuper) {
+ setState(State.ANIMATE_ZOOM);
+ startTime = System.currentTimeMillis();
+ this.startZoom = normalizedScale;
+ this.targetZoom = targetZoom;
+ this.stretchImageToSuper = stretchImageToSuper;
+ PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY, false);
+ this.bitmapX = bitmapPoint.x;
+ this.bitmapY = bitmapPoint.y;
+
+ //
+ // Used for translating image during scaling
+ //
+ startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY);
+ endTouch = new PointF(viewWidth / 2, viewHeight / 2);
+ }
+
+ @Override
+ public void run() {
+ float t = interpolate();
+ double deltaScale = calculateDeltaScale(t);
+ scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper);
+ translateImageToCenterTouchPosition(t);
+ fixScaleTrans();
+ setImageMatrix(matrix);
+
+ //
+ // OnTouchImageViewListener is set: double tap runnable updates listener
+ // with every frame.
+ //
+ if (touchImageViewListener != null) {
+ touchImageViewListener.onMove();
+ }
+
+ if (t < 1f) {
+ //
+ // We haven't finished zooming
+ //
+ compatPostOnAnimation(this);
+
+ } else {
+ //
+ // Finished zooming
+ //
+ setState(State.NONE);
+ }
+ }
+
+ /**
+ * Interpolate between where the image should start and end in order to translate
+ * the image so that the point that is touched is what ends up centered at the end
+ * of the zoom.
+ * @param t
+ */
+ private void translateImageToCenterTouchPosition(float t) {
+ float targetX = startTouch.x + t * (endTouch.x - startTouch.x);
+ float targetY = startTouch.y + t * (endTouch.y - startTouch.y);
+ PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY);
+ matrix.postTranslate(targetX - curr.x, targetY - curr.y);
+ }
+
+ /**
+ * Use interpolator to get t
+ * @return
+ */
+ private float interpolate() {
+ long currTime = System.currentTimeMillis();
+ float elapsed = (currTime - startTime) / ZOOM_TIME;
+ elapsed = Math.min(1f, elapsed);
+ return interpolator.getInterpolation(elapsed);
+ }
+
+ /**
+ * Interpolate the current targeted zoom and get the delta
+ * from the current zoom.
+ * @param t
+ * @return
+ */
+ private double calculateDeltaScale(float t) {
+ double zoom = startZoom + t * (targetZoom - startZoom);
+ return zoom / normalizedScale;
+ }
+ }
+
+ /**
+ * This function will transform the coordinates in the touch event to the coordinate
+ * system of the drawable that the imageview contain
+ * @param x x-coordinate of touch event
+ * @param y y-coordinate of touch event
+ * @param clipToBitmap Touch event may occur within view, but outside image content. True, to clip return value
+ * to the bounds of the bitmap size.
+ * @return Coordinates of the point touched, in the coordinate system of the original drawable.
+ */
+ private PointF transformCoordTouchToBitmap(float x, float y, boolean clipToBitmap) {
+ matrix.getValues(m);
+ float origW = getDrawable().getIntrinsicWidth();
+ float origH = getDrawable().getIntrinsicHeight();
+ float transX = m[Matrix.MTRANS_X];
+ float transY = m[Matrix.MTRANS_Y];
+ float finalX = ((x - transX) * origW) / getImageWidth();
+ float finalY = ((y - transY) * origH) / getImageHeight();
+
+ if (clipToBitmap) {
+ finalX = Math.min(Math.max(finalX, 0), origW);
+ finalY = Math.min(Math.max(finalY, 0), origH);
+ }
+
+ return new PointF(finalX , finalY);
+ }
+
+ /**
+ * Inverse of transformCoordTouchToBitmap. This function will transform the coordinates in the
+ * drawable's coordinate system to the view's coordinate system.
+ * @param bx x-coordinate in original bitmap coordinate system
+ * @param by y-coordinate in original bitmap coordinate system
+ * @return Coordinates of the point in the view's coordinate system.
+ */
+ private PointF transformCoordBitmapToTouch(float bx, float by) {
+ matrix.getValues(m);
+ float origW = getDrawable().getIntrinsicWidth();
+ float origH = getDrawable().getIntrinsicHeight();
+ float px = bx / origW;
+ float py = by / origH;
+ float finalX = m[Matrix.MTRANS_X] + getImageWidth() * px;
+ float finalY = m[Matrix.MTRANS_Y] + getImageHeight() * py;
+ return new PointF(finalX , finalY);
+ }
+
+ /**
+ * Fling launches sequential runnables which apply
+ * the fling graphic to the image. The values for the translation
+ * are interpolated by the Scroller.
+ * @author Ortiz
+ *
+ */
+ private class Fling implements Runnable {
+
+ CompatScroller scroller;
+ int currX, currY;
+
+ Fling(int velocityX, int velocityY) {
+ setState(State.FLING);
+ scroller = new CompatScroller(context);
+ matrix.getValues(m);
+
+ int startX = (int) m[Matrix.MTRANS_X];
+ int startY = (int) m[Matrix.MTRANS_Y];
+ int minX, maxX, minY, maxY;
+
+ if (getImageWidth() > viewWidth) {
+ minX = viewWidth - (int) getImageWidth();
+ maxX = 0;
+
+ } else {
+ minX = maxX = startX;
+ }
+
+ if (getImageHeight() > viewHeight) {
+ minY = viewHeight - (int) getImageHeight();
+ maxY = 0;
+
+ } else {
+ minY = maxY = startY;
+ }
+
+ scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX,
+ maxX, minY, maxY);
+ currX = startX;
+ currY = startY;
+ }
+
+ public void cancelFling() {
+ if (scroller != null) {
+ setState(State.NONE);
+ scroller.forceFinished(true);
+ }
+ }
+
+ @Override
+ public void run() {
+
+ //
+ // OnTouchImageViewListener is set: TouchImageView listener has been flung by user.
+ // Listener runnable updated with each frame of fling animation.
+ //
+ if (touchImageViewListener != null) {
+ touchImageViewListener.onMove();
+ }
+
+ if (scroller.isFinished()) {
+ scroller = null;
+ return;
+ }
+
+ if (scroller.computeScrollOffset()) {
+ int newX = scroller.getCurrX();
+ int newY = scroller.getCurrY();
+ int transX = newX - currX;
+ int transY = newY - currY;
+ currX = newX;
+ currY = newY;
+ matrix.postTranslate(transX, transY);
+ fixTrans();
+ setImageMatrix(matrix);
+ compatPostOnAnimation(this);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.GINGERBREAD)
+ private class CompatScroller {
+ Scroller scroller;
+ OverScroller overScroller;
+ boolean isPreGingerbread;
+
+ public CompatScroller(Context context) {
+ if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) {
+ isPreGingerbread = true;
+ scroller = new Scroller(context);
+
+ } else {
+ isPreGingerbread = false;
+ overScroller = new OverScroller(context);
+ }
+ }
+
+ public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) {
+ if (isPreGingerbread) {
+ scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
+ } else {
+ overScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
+ }
+ }
+
+ public void forceFinished(boolean finished) {
+ if (isPreGingerbread) {
+ scroller.forceFinished(finished);
+ } else {
+ overScroller.forceFinished(finished);
+ }
+ }
+
+ public boolean isFinished() {
+ if (isPreGingerbread) {
+ return scroller.isFinished();
+ } else {
+ return overScroller.isFinished();
+ }
+ }
+
+ public boolean computeScrollOffset() {
+ if (isPreGingerbread) {
+ return scroller.computeScrollOffset();
+ } else {
+ overScroller.computeScrollOffset();
+ return overScroller.computeScrollOffset();
+ }
+ }
+
+ public int getCurrX() {
+ if (isPreGingerbread) {
+ return scroller.getCurrX();
+ } else {
+ return overScroller.getCurrX();
+ }
+ }
+
+ public int getCurrY() {
+ if (isPreGingerbread) {
+ return scroller.getCurrY();
+ } else {
+ return overScroller.getCurrY();
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ private void compatPostOnAnimation(Runnable runnable) {
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
+ postOnAnimation(runnable);
+
+ } else {
+ postDelayed(runnable, 1000/60);
+ }
+ }
+
+ private class ZoomVariables {
+ public float scale;
+ public float focusX;
+ public float focusY;
+ public ScaleType scaleType;
+
+ public ZoomVariables(float scale, float focusX, float focusY, ScaleType scaleType) {
+ this.scale = scale;
+ this.focusX = focusX;
+ this.focusY = focusY;
+ this.scaleType = scaleType;
+ }
+ }
+
+ private void printMatrixInfo() {
+ float[] n = new float[9];
+ matrix.getValues(n);
+ Log.d(DEBUG, "Scale: " + n[Matrix.MSCALE_X] + " TransX: " + n[Matrix.MTRANS_X] + " TransY: " + n[Matrix.MTRANS_Y]);
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/uk/co/blatech/blaupload/util/ImageLoader.java b/app/src/main/java/uk/co/blatech/blaupload/util/BlaImageLoader.java
index 4a2dbc5..58d1c14 100644
--- a/app/src/main/java/uk/co/blatech/blaupload/util/ImageLoader.java
+++ b/app/src/main/java/uk/co/blatech/blaupload/util/BlaImageLoader.java
@@ -1,13 +1,11 @@
package uk.co.blatech.blaupload.util;
-import android.annotation.TargetApi;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.opengl.GLES10;
import android.os.AsyncTask;
-import android.os.Build;
import android.util.Log;
import org.apache.http.HttpEntity;
@@ -30,12 +28,12 @@ import uk.co.blatech.blaupload.data.ImageItem;
*
* Created by joe on 02/08/14.
*/
-public class ImageLoader extends AsyncTask<String, Void, ImageItem> {
+public class BlaImageLoader extends AsyncTask<String, Void, ImageItem> {
private Resources res;
//Get the resources object from the UI thread so we can load a placeholder image
- public ImageLoader(Resources resources) {
+ public BlaImageLoader(Resources resources) {
this.res = resources;
}
@@ -82,7 +80,7 @@ public class ImageLoader extends AsyncTask<String, Void, ImageItem> {
try {
bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
} catch (Exception e) {
- Log.e(ImageLoader.class.toString(), "Failed decoding " + filename);
+ Log.e(BlaImageLoader.class.toString(), "Failed decoding " + filename);
e.printStackTrace();
bmp = BitmapFactory.decodeResource(res, R.drawable.x);
ImageItem image = new ImageItem(id, bmp, filename);
diff --git a/app/src/main/res/layout/activity_image_pager.xml b/app/src/main/res/layout/activity_image_pager.xml
index 74181fa..fc75d13 100644
--- a/app/src/main/res/layout/activity_image_pager.xml
+++ b/app/src/main/res/layout/activity_image_pager.xml
@@ -1,16 +1,7 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<uk.co.blatech.blaupload.ui.ZoomViewPager
+ xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/imagePager"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin"
- tools:context="uk.co.blatech.blaupload.ImagePager">
-
- <TextView
- android:text="@string/hello_world"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
-</RelativeLayout>
+ tools:context="uk.co.blatech.blaupload.ImagePager"/> \ No newline at end of file
diff --git a/app/src/main/res/layout/image_pager_item.xml b/app/src/main/res/layout/image_pager_item.xml
new file mode 100644
index 0000000..26d8083
--- /dev/null
+++ b/app/src/main/res/layout/image_pager_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="1dip" >
+ <uk.co.blatech.blaupload.ui.TouchImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/imageView"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true" />
+
+ <uk.co.blatech.blaupload.ui.TouchImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/zoomImageView"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:visibility="gone"/>
+
+</FrameLayout> \ No newline at end of file