Changed the imageload and displayer.

Now using Ion to load the images, with a custom file cache.
This allows downloads bigger than the available ram, and a progressbar.
The subsampling-scale-image-view allows huge images to be displayed, even when there's not a lot of ram.
captchafix
Florens Douwes 11 years ago
parent 0db08433c7
commit f8878f8fbb
  1. 1
      Clover/app/build.gradle
  2. 62
      Clover/app/src/main/assets/html/licences.html
  3. 55
      Clover/app/src/main/java/com/davemorrissey/labs/subscaleview/ImageViewState.java
  4. 1802
      Clover/app/src/main/java/com/davemorrissey/labs/subscaleview/SubsamplingScaleImageView.java
  5. 27
      Clover/app/src/main/java/org/floens/chan/ChanApplication.java
  6. 59
      Clover/app/src/main/java/org/floens/chan/core/net/GIFRequest.java
  7. 88
      Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java
  8. 3
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PinnedAdapter.java
  9. 25
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java
  10. 2
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java
  11. 37
      Clover/app/src/main/java/org/floens/chan/ui/view/GIFView.java
  12. 4
      Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java
  13. 287
      Clover/app/src/main/java/org/floens/chan/ui/view/ThumbnailImageView.java
  14. 86
      Clover/app/src/main/java/org/floens/chan/utils/FileCache.java
  15. 60
      Clover/app/src/main/java/uk/co/senab/photoview/Compat.java
  16. 98
      Clover/app/src/main/java/uk/co/senab/photoview/DefaultOnDoubleTapListener.java
  17. 260
      Clover/app/src/main/java/uk/co/senab/photoview/IPhotoView.java
  18. 256
      Clover/app/src/main/java/uk/co/senab/photoview/PhotoView.java
  19. 1085
      Clover/app/src/main/java/uk/co/senab/photoview/PhotoViewAttacher.java
  20. 143
      Clover/app/src/main/java/uk/co/senab/photoview/gestures/CupcakeGestureDetector.java
  21. 85
      Clover/app/src/main/java/uk/co/senab/photoview/gestures/EclairGestureDetector.java
  22. 63
      Clover/app/src/main/java/uk/co/senab/photoview/gestures/FroyoGestureDetector.java
  23. 28
      Clover/app/src/main/java/uk/co/senab/photoview/gestures/GestureDetector.java
  24. 27
      Clover/app/src/main/java/uk/co/senab/photoview/gestures/OnGestureListener.java
  25. 42
      Clover/app/src/main/java/uk/co/senab/photoview/gestures/VersionedGestureDetector.java
  26. 35
      Clover/app/src/main/java/uk/co/senab/photoview/log/LogManager.java
  27. 116
      Clover/app/src/main/java/uk/co/senab/photoview/log/Logger.java
  28. 76
      Clover/app/src/main/java/uk/co/senab/photoview/log/LoggerDefault.java
  29. 68
      Clover/app/src/main/java/uk/co/senab/photoview/scrollerproxy/GingerScroller.java
  30. 33
      Clover/app/src/main/java/uk/co/senab/photoview/scrollerproxy/IcsScroller.java
  31. 58
      Clover/app/src/main/java/uk/co/senab/photoview/scrollerproxy/PreGingerScroller.java
  32. 48
      Clover/app/src/main/java/uk/co/senab/photoview/scrollerproxy/ScrollerProxy.java
  33. BIN
      Clover/app/src/main/res/drawable-hdpi/progress_primary_holo_light.9.png
  34. BIN
      Clover/app/src/main/res/drawable-mdpi/progress_primary_holo_light.9.png
  35. BIN
      Clover/app/src/main/res/drawable-xhdpi/progress_primary_holo_light.9.png
  36. BIN
      Clover/app/src/main/res/drawable-xxhdpi/progress_primary_holo_light.9.png
  37. 24
      Clover/app/src/main/res/drawable/progressbar_no_bg.xml
  38. 1
      Clover/app/src/main/res/values/dimens.xml

@ -71,6 +71,7 @@ dependencies {
compile 'com.j256.ormlite:ormlite-core:4.48'
compile 'com.j256.ormlite:ormlite-android:4.48'
compile 'com.android.support:support-v13:18.0.+'
compile 'com.koushikdutta.ion:ion:1.+'
compile files('libs/httpclientandroidlib-1.2.1.jar')
}

@ -17,27 +17,6 @@
</style>
</head>
<body>
<h3>PhotoView</h3>
<a href="https://github.com/chrisbanes/PhotoView">https://github.com/chrisbanes/PhotoView</a>
<pre>
<code>
Copyright 2011, 2012 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.
</code>
</pre>
<br>
<h3>This software includes several Android classes from the Android Open Source Project.</h3>
<pre>
<code>
@ -141,5 +120,46 @@ limitations under the License.
</pre>
<br>
<h3>Ion</h3>
<a href="https://github.com/koush/ion">https://github.com/koush/ion</a>
<pre>
<code>
Copyright 2013 Koushik Dutta
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.
</code>
</pre>
<br>
<h3>subsampling-scale-image-view</h3>
<a href="https://github.com/davemorrissey/subsampling-scale-image-view">https://github.com/davemorrissey/subsampling-scale-image-view</a>
<pre>
<code>
Copyright 2014 David Morrissey
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.
</code>
</pre>
<br>
</body>
</html>

@ -0,0 +1,55 @@
/*
Copyright 2014 David Morrissey
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 com.davemorrissey.labs.subscaleview;
import android.graphics.PointF;
import java.io.Serializable;
/**
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
*/
public class ImageViewState implements Serializable {
private float scale;
private float centerX;
private float centerY;
private int orientation;
public ImageViewState(float scale, PointF center, int orientation) {
this.scale = scale;
this.centerX = center.x;
this.centerY = center.y;
this.orientation = orientation;
}
public float getScale() {
return scale;
}
public PointF getCenter() {
return new PointF(centerX, centerY);
}
public int getOrientation() {
return orientation;
}
}

@ -19,19 +19,20 @@ package org.floens.chan;
import android.app.Application;
import android.content.SharedPreferences;
import android.os.StrictMode;
import android.preference.PreferenceManager;
import android.view.ViewConfiguration;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
import com.koushikdutta.ion.Ion;
import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.manager.ReplyManager;
import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.net.BitmapLruImageCache;
import org.floens.chan.database.DatabaseManager;
import org.floens.chan.utils.FileCache;
import org.floens.chan.utils.IconCache;
import org.floens.chan.utils.Logger;
@ -43,13 +44,18 @@ import java.util.List;
public class ChanApplication extends Application {
private static final String TAG = "ChanApplication";
private static final long FILE_CACHE_DISK_SIZE = 50 * 1024 * 1024; // 50mb
private static final String FILE_CACHE_NAME = "filecache";
private static final int VOLLEY_LRU_CACHE_SIZE = 8 * 1024 * 1024; // 8mb
private static ChanApplication instance;
private static RequestQueue volleyRequestQueue;
private static ImageLoader imageLoader;
private static com.android.volley.toolbox.ImageLoader imageLoader;
private static BoardManager boardManager;
private static WatchManager watchManager;
private static ReplyManager replyManager;
private static DatabaseManager databaseManager;
private static FileCache fileCache;
private List<ForegroundChangedListener> foregroundChangedListeners = new ArrayList<>();
private int activityForegroundCounter = 0;
@ -66,7 +72,7 @@ public class ChanApplication extends Application {
return volleyRequestQueue;
}
public static ImageLoader getImageLoader() {
public static ImageLoader getVolleyImageLoader() {
return imageLoader;
}
@ -86,6 +92,10 @@ public class ChanApplication extends Application {
return databaseManager;
}
public static FileCache getFileCache() {
return fileCache;
}
public static SharedPreferences getPreferences() {
return PreferenceManager.getDefaultSharedPreferences(instance);
}
@ -113,9 +123,14 @@ public class ChanApplication extends Application {
IconCache.createIcons(this);
File cacheDir = new File(getExternalCacheDir() != null ? getExternalCacheDir() : getCacheDir(), Volley.DEFAULT_CACHE_DIR);
volleyRequestQueue = Volley.newRequestQueue(this, null, cacheDir);
imageLoader = new ImageLoader(volleyRequestQueue, new BitmapLruImageCache(1024 * 1024 * 8));
Ion.getDefault(this).getCache().setMaxSize(1 * 1024 * 1024);
File cacheDir = getExternalCacheDir() != null ? getExternalCacheDir() : getCacheDir();
volleyRequestQueue = Volley.newRequestQueue(this, null, new File(cacheDir, Volley.DEFAULT_CACHE_DIR));
imageLoader = new ImageLoader(volleyRequestQueue, new BitmapLruImageCache(VOLLEY_LRU_CACHE_SIZE));
fileCache = new FileCache(new File(cacheDir, FILE_CACHE_NAME), FILE_CACHE_DISK_SIZE);
databaseManager = new DatabaseManager(this);
boardManager = new BoardManager();

@ -1,59 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.net;
import android.content.Context;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.android.volley.toolbox.HttpHeaderParser;
import org.floens.chan.ui.view.GIFView;
public class GIFRequest extends Request<GIFView> {
protected final Listener<GIFView> listener;
private final Context context;
public GIFRequest(String url, Listener<GIFView> listener, ErrorListener errorListener, Context context) {
super(Method.GET, url, errorListener);
this.listener = listener;
this.context = context;
}
@Override
protected void deliverResponse(GIFView response) {
listener.onResponse(response);
}
@Override
protected Response<GIFView> parseNetworkResponse(NetworkResponse response) {
GIFView gifView = new GIFView(context);
boolean success = gifView.setData(response.data);
if (success) {
return Response.success(gifView, HttpHeaderParser.parseCacheHeaders(response));
} else {
return Response.error(new ParseError());
}
}
}

@ -24,7 +24,10 @@ import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import org.floens.chan.R;
import org.floens.chan.core.ChanPreferences;
@ -51,6 +54,8 @@ public class ImageViewActivity extends Activity implements ViewPager.OnPageChang
private ViewPager viewPager;
private ImageViewAdapter adapter;
private ProgressBar progressBar;
private int currentPosition;
/**
@ -72,36 +77,54 @@ public class ImageViewActivity extends Activity implements ViewPager.OnPageChang
super.onCreate(savedInstanceState);
if (postAdapter == null) {
Logger.e(TAG, "Posts in ImageViewActivity was null");
finish();
return;
}
ThemeHelper.setTheme(this);
if (postAdapter != null) {
// Get the posts with images
ArrayList<Post> imagePosts = new ArrayList<>();
for (Post post : postAdapter.getList()) {
if (post.hasImage) {
imagePosts.add(post);
}
progressBar = new ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal);
progressBar.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT));
progressBar.setProgressDrawable(getResources().getDrawable(R.drawable.progressbar_no_bg));
progressBar.setIndeterminate(false);
progressBar.setMax(1000000);
final FrameLayout decorView = (FrameLayout) getWindow().getDecorView();
decorView.addView(progressBar);
progressBar.post(new Runnable() {
@Override
public void run() {
View contentView = decorView.findViewById(android.R.id.content);
progressBar.setY(contentView.getY() - progressBar.getHeight() / 2);
}
});
// Setup our pages and adapter
setContentView(R.layout.image_pager);
viewPager = (ViewPager) findViewById(R.id.image_pager);
adapter = new ImageViewAdapter(getFragmentManager(), this);
adapter.setList(imagePosts);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(this);
// Select the right image
for (int i = 0; i < imagePosts.size(); i++) {
if (imagePosts.get(i).no == selectedId) {
viewPager.setCurrentItem(i);
onPageSelected(i);
break;
}
// Get the posts with images
ArrayList<Post> imagePosts = new ArrayList<>();
for (Post post : postAdapter.getList()) {
if (post.hasImage) {
imagePosts.add(post);
}
}
// Setup our pages and adapter
setContentView(R.layout.image_pager);
viewPager = (ViewPager) findViewById(R.id.image_pager);
adapter = new ImageViewAdapter(getFragmentManager(), this);
adapter.setList(imagePosts);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(this);
// Select the right image
for (int i = 0; i < imagePosts.size(); i++) {
if (imagePosts.get(i).no == selectedId) {
viewPager.setCurrentItem(i);
onPageSelected(i);
break;
}
} else {
Logger.e(TAG, "Posts in ImageViewActivity was null");
finish();
}
}
@ -154,13 +177,22 @@ public class ImageViewActivity extends Activity implements ViewPager.OnPageChang
invalidateOptionsMenu();
}
public void callOnSelect() {
public void updateActionBarIfSelected(ImageViewFragment targetFragment) {
ImageViewFragment fragment = getFragment(currentPosition);
if (fragment != null) {
if (fragment != null && fragment == targetFragment) {
fragment.onSelected(adapter, currentPosition);
}
}
public void setProgressBar(long current, long total, boolean done) {
if (done) {
progressBar.setVisibility(View.GONE);
} else {
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress((int) (((double) current / total) * progressBar.getMax()));
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
@ -206,7 +238,7 @@ public class ImageViewActivity extends Activity implements ViewPager.OnPageChang
public boolean onPrepareOptionsMenu(Menu menu) {
ImageViewFragment fragment = getFragment(currentPosition);
if (fragment != null) {
fragment.onPrepareOptionsMenu(currentPosition, adapter, menu);
fragment.onPrepareOptionsMenu(menu);
}
return super.onPrepareOptionsMenu(menu);

@ -25,7 +25,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@ -120,7 +119,7 @@ public class PinnedAdapter extends BaseAdapter {
imageView.setVisibility(View.VISIBLE);
imageView.setFadeIn(100);
if (imageView.getUrl() == null || !imageView.getUrl().equals(pin.opPost.thumbnailUrl)) {
imageView.setImageUrl(pin.opPost.thumbnailUrl, ChanApplication.getImageLoader());
imageView.setImageUrl(pin.opPost.thumbnailUrl, ChanApplication.getVolleyImageLoader());
}
} else {
imageView.setVisibility(View.GONE);

@ -41,15 +41,20 @@ public class ImageViewFragment extends Fragment implements ThumbnailImageViewCal
private Context context;
private ImageViewActivity activity;
private ThumbnailImageView imageView;
private Post post;
private boolean showProgressBar = true;
private ThumbnailImageView imageView;
private boolean isVideo = false;
private boolean videoVisible = false;
private boolean videoSetIconToPause = false;
private boolean tapToLoad = false;
private boolean loaded = false;
private long progressCurrent;
private long progressTotal;
private boolean progressDone;
public static ImageViewFragment newInstance(Post post, ImageViewActivity activity, int index) {
ImageViewFragment imageViewFragment = new ImageViewFragment();
imageViewFragment.post = post;
@ -68,9 +73,7 @@ public class ImageViewFragment extends Fragment implements ThumbnailImageViewCal
imageView = new ThumbnailImageView(context);
imageView.setCallback(this);
imageView.setLayoutParams(Utils.MATCH_PARAMS);
int padding = (int) context.getResources().getDimension(R.dimen.image_view_padding);
int padding = Utils.dp(8);
imageView.setPadding(padding, padding, padding, padding);
return imageView;
@ -180,6 +183,8 @@ public class ImageViewFragment extends Fragment implements ThumbnailImageViewCal
}
}
}
activity.setProgressBar(progressCurrent, progressTotal, progressDone);
}
public void onDeselected() {
@ -188,7 +193,7 @@ public class ImageViewFragment extends Fragment implements ThumbnailImageViewCal
}
}
public void onPrepareOptionsMenu(int position, ImageViewAdapter adapter, Menu menu) {
public void onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(R.id.action_image_play_state);
item.setVisible(isVideo);
item.setEnabled(isVideo);
@ -237,7 +242,7 @@ public class ImageViewFragment extends Fragment implements ThumbnailImageViewCal
public void showProgressBar(boolean e) {
showProgressBar = e;
activity.callOnSelect();
activity.updateActionBarIfSelected(this);
}
@Override
@ -258,6 +263,14 @@ public class ImageViewFragment extends Fragment implements ThumbnailImageViewCal
showProgressBar(progress);
}
@Override
public void setLinearProgress(long current, long total, boolean done) {
progressCurrent = current;
progressTotal = total;
progressDone = done;
activity.updateActionBarIfSelected(this);
}
@Override
public void onVideoLoaded() {
videoSetIconToPause = true;

@ -428,7 +428,7 @@ public class ReplyFragment extends DialogFragment {
String imageUrl = ChanUrls.getCaptchaImageUrl(challenge);
NetworkImageView captchaImage = new NetworkImageView(context);
captchaImage.setImageUrl(imageUrl, ChanApplication.getImageLoader());
captchaImage.setImageUrl(imageUrl, ChanApplication.getVolleyImageLoader());
captchaContainer.setView(captchaImage);
gettingCaptcha = false;

@ -26,7 +26,14 @@ import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.View;
import org.floens.chan.utils.Utils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class GIFView extends View {
private static final ExecutorService executor = Executors.newFixedThreadPool(1);
private Movie movie;
private long movieStart;
@ -51,20 +58,22 @@ public class GIFView extends View {
setLayerType(LAYER_TYPE_SOFTWARE, paint);
}
public boolean setData(byte[] array) {
Movie movie = Movie.decodeByteArray(array, 0, array.length);
return onMovieLoaded(movie);
}
private boolean onMovieLoaded(Movie movie) {
if (movie != null) {
this.movie = movie;
invalidate();
return true;
} else {
return false;
}
public void setPath(final String path) {
executor.submit(new Runnable() {
@Override
public void run() {
final Movie movie = Movie.decodeFile(path);
if (movie != null) {
Utils.runOnUiThread(new Runnable() {
@Override
public void run() {
GIFView.this.movie = movie;
invalidate();
}
});
}
}
});
}
@Override

@ -141,7 +141,7 @@ public class PostView extends LinearLayout implements View.OnClickListener {
if (post.hasImage) {
imageView.setVisibility(View.VISIBLE);
imageView.setImageUrl(post.thumbnailUrl, ChanApplication.getImageLoader());
imageView.setImageUrl(post.thumbnailUrl, ChanApplication.getVolleyImageLoader());
} else {
imageView.setVisibility(View.GONE);
imageView.setImageUrl(null, null);
@ -251,7 +251,7 @@ public class PostView extends LinearLayout implements View.OnClickListener {
deletedView.setVisibility(showDeletedIcon ? View.VISIBLE : View.GONE);
if (showCountryFlag) {
countryView.setVisibility(View.VISIBLE);
countryView.setImageUrl(ChanUrls.getCountryFlagUrl(post.country), ChanApplication.getImageLoader());
countryView.setImageUrl(ChanUrls.getCountryFlagUrl(post.country), ChanApplication.getVolleyImageLoader());
} else {
countryView.setVisibility(View.GONE);
countryView.setImageUrl(null, null);

@ -19,7 +19,6 @@ package org.floens.chan.ui.view;
import android.content.Context;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
@ -28,24 +27,23 @@ import android.widget.Toast;
import android.widget.VideoView;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader.ImageContainer;
import com.android.volley.toolbox.ImageLoader.ImageListener;
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.ion.Ion;
import com.koushikdutta.ion.ProgressCallback;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
import org.floens.chan.core.net.FileRequest;
import org.floens.chan.core.net.GIFRequest;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.Utils;
import java.io.File;
import java.util.concurrent.CancellationException;
import uk.co.senab.photoview.PhotoViewAttacher;
import uk.co.senab.photoview.PhotoViewAttacher.OnViewTapListener;
public class ThumbnailImageView extends LoadView implements OnViewTapListener, View.OnClickListener {
public class ThumbnailImageView extends LoadView implements View.OnClickListener {
private static final String TAG = "ThumbnailImageView";
private ThumbnailImageViewCallback callback;
@ -57,8 +55,8 @@ public class ThumbnailImageView extends LoadView implements OnViewTapListener, V
private boolean thumbnailNeeded = true;
private ImageContainer imageContainerRequest;
private Request<?> imageRequest;
private Future<?> ionRequest;
private VideoView videoView;
public ThumbnailImageView(Context context) {
@ -90,7 +88,8 @@ public class ThumbnailImageView extends LoadView implements OnViewTapListener, V
return;
}
ChanApplication.getImageLoader().get(thumbnailUrl, new ImageListener() {
// Also use volley for the thumbnails
ChanApplication.getVolleyImageLoader().get(thumbnailUrl, new com.android.volley.toolbox.ImageLoader.ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
onError();
@ -116,34 +115,53 @@ public class ThumbnailImageView extends LoadView implements OnViewTapListener, V
callback.setProgress(true);
// 4096 is the max GPU upload size
int maxWidth = Math.min((int) (getWidth() * maxScale), 4096);
int maxHeight = Math.min((int) (getHeight() * maxScale), 4096);
imageContainerRequest = ChanApplication.getImageLoader().get(imageUrl, new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
onError();
}
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
CleanupImageView image = new CleanupImageView(getContext());
image.setImageBitmap(response.getBitmap());
PhotoViewAttacher attacher = new PhotoViewAttacher(image);
attacher.setOnViewTapListener(ThumbnailImageView.this);
attacher.setMaximumScale(maxScale);
File file = ChanApplication.getFileCache().get(imageUrl);
if (file.exists()) {
onBigImage(file);
} else {
ionRequest = Ion.with(getContext())
.load(imageUrl)
.progress(new ProgressCallback() {
@Override
public void onProgress(final long downloaded, final long total) {
Utils.runOnUiThread(new Runnable() {
@Override
public void run() {
callback.setLinearProgress(downloaded, total, false);
}
});
}
})
.write(file)
.setCallback(new FutureCallback<File>() {
@Override
public void onCompleted(Exception e, File result) {
if (e != null || result == null || result.length() == 0) {
if (result != null) {
ChanApplication.getFileCache().delete(result);
}
if (e != null && !(e instanceof CancellationException)) {
onError();
e.printStackTrace();
}
} else {
ChanApplication.getFileCache().put(result);
onBigImage(result);
}
}
});
}
}
image.setAttacher(attacher);
private void onBigImage(File file) {
SubsamplingScaleImageView image = new SubsamplingScaleImageView(getContext());
image.setImageFile(file.getAbsolutePath());
image.setOnClickListener(this);
setView(image, !isImmediate);
callback.setProgress(false);
thumbnailNeeded = false;
}
}
}, maxWidth, maxHeight);
setView(image, false);
callback.setProgress(false);
callback.setLinearProgress(0, 0, true);
thumbnailNeeded = false;
}
public void setGif(String gifUrl) {
@ -154,68 +172,118 @@ public class ThumbnailImageView extends LoadView implements OnViewTapListener, V
callback.setProgress(true);
imageRequest = ChanApplication.getVolleyRequestQueue().add(
new GIFRequest(gifUrl, new Response.Listener<GIFView>() {
@Override
public void onResponse(GIFView view) {
view.setLayoutParams(Utils.MATCH_PARAMS);
setView(view, false);
callback.setProgress(false);
thumbnailNeeded = false;
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onError();
}
}, getContext())
);
File file = ChanApplication.getFileCache().get(gifUrl);
if (file.exists()) {
onGif(file);
} else {
ionRequest = Ion.with(getContext())
.load(gifUrl)
.progress(new ProgressCallback() {
@Override
public void onProgress(final long downloaded, final long total) {
Utils.runOnUiThread(new Runnable() {
@Override
public void run() {
callback.setLinearProgress(downloaded, total, false);
}
});
}
})
.write(file)
.setCallback(new FutureCallback<File>() {
@Override
public void onCompleted(Exception e, File result) {
if (e != null || result == null || result.length() == 0) {
if (result != null) {
ChanApplication.getFileCache().delete(result);
}
if (e != null && !(e instanceof CancellationException)) {
onError();
e.printStackTrace();
}
} else {
ChanApplication.getFileCache().put(result);
onGif(result);
}
}
});
}
}
private void onGif(File file) {
GIFView view = new GIFView(getContext());
view.setPath(file.getAbsolutePath());
view.setLayoutParams(Utils.MATCH_PARAMS);
setView(view, false);
callback.setProgress(false);
callback.setLinearProgress(0, 0, true);
}
public void setVideo(String videoUrl) {
callback.setProgress(true);
imageRequest = ChanApplication.getVolleyRequestQueue().add(
new FileRequest(videoUrl, new Response.Listener<File>() {
@Override
public void onResponse(final File file) {
if (file != null) {
videoView = new VideoView(getContext());
videoView.setZOrderOnTop(true);
videoView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
videoView.setLayoutParams(Utils.MATCH_PARAMS);
LayoutParams par = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
par.gravity = Gravity.CENTER;
videoView.setLayoutParams(par);
videoView.setOnPreparedListener(new OnPreparedListener() {
File file = ChanApplication.getFileCache().get(videoUrl);
if (file.exists()) {
onVideo(file);
} else {
ionRequest = Ion.with(getContext())
.load(videoUrl)
.progress(new ProgressCallback() {
@Override
public void onProgress(final long downloaded, final long total) {
Utils.runOnUiThread(new Runnable() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
callback.onVideoLoaded();
public void run() {
callback.setLinearProgress(downloaded, total, false);
}
});
videoView.setVideoPath(file.getAbsolutePath());
}
})
.write(file)
.setCallback(new FutureCallback<File>() {
@Override
public void onCompleted(Exception e, File result) {
if (e != null || result == null || result.length() == 0) {
if (result != null) {
ChanApplication.getFileCache().delete(result);
}
if (e != null && !(e instanceof CancellationException)) {
onError();
e.printStackTrace();
}
} else {
ChanApplication.getFileCache().put(result);
onVideo(result);
}
}
});
}
}
private void onVideo(File file) {
videoView = new VideoView(getContext());
videoView.setZOrderOnTop(true);
videoView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
videoView.setLayoutParams(Utils.MATCH_PARAMS);
LayoutParams par = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
par.gravity = Gravity.CENTER;
videoView.setLayoutParams(par);
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
callback.onVideoLoaded();
}
});
videoView.setVideoPath(file.getAbsolutePath());
setView(videoView, false);
callback.setProgress(false);
thumbnailNeeded = false;
setView(videoView, false);
callback.setProgress(false);
thumbnailNeeded = false;
videoView.start();
} else {
onError();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onError();
}
})
);
videoView.start();
}
@Override
@ -238,17 +306,11 @@ public class ThumbnailImageView extends LoadView implements OnViewTapListener, V
imageRequest = null;
}
if (imageContainerRequest != null) {
imageContainerRequest.cancelRequest();
imageContainerRequest = null;
if (ionRequest != null) {
ionRequest.cancel(true);
}
}
@Override
public void onViewTap(View view, float x, float y) {
callback.onTap();
}
@Override
public void onClick(View v) {
callback.onTap();
@ -259,35 +321,8 @@ public class ThumbnailImageView extends LoadView implements OnViewTapListener, V
public void setProgress(boolean progress);
public void onVideoLoaded();
}
private static class CleanupImageView extends ImageView {
private PhotoViewAttacher attacher;
public CleanupImageView(Context context) {
super(context);
}
public CleanupImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setLinearProgress(long current, long total, boolean done);
public CleanupImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setAttacher(PhotoViewAttacher attacher) {
this.attacher = attacher;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (attacher != null) {
attacher.cleanup();
}
}
public void onVideoLoaded();
}
}

@ -0,0 +1,86 @@
package org.floens.chan.utils;
import android.util.Log;
import java.io.File;
public class FileCache {
private static final String TAG = "FileCache";
private final File directory;
private final long maxSize;
private long size;
public FileCache(File directory, long maxSize) {
this.directory = directory;
this.maxSize = maxSize;
if (!directory.exists() && !directory.mkdirs()) {
Logger.e(TAG, "Unable to create file cache dir " + directory.getAbsolutePath());
}
calculateSize();
}
public File get(String key) {
return new File(directory, Integer.toString(key.hashCode()));
}
public void put(File file) {
size += file.length();
trim();
}
public boolean delete(File file) {
size -= file.length();
return file.delete();
}
private void trim() {
int tries = 0;
while (size > maxSize && tries++ < 10) {
File[] files = directory.listFiles();
if (files == null) {
break;
}
long age = Long.MAX_VALUE;
long last;
File oldest = null;
for (File file : files) {
last = file.lastModified();
if (last < age && last != 0L) {
age = last;
oldest = file;
}
}
if (oldest == null) {
Log.e(TAG, "No files to trim");
break;
} else {
Log.d(TAG, "Deleting " + oldest.getAbsolutePath());
if (!delete(oldest)) {
Log.e(TAG, "Cannot delete cache file");
calculateSize();
break;
}
}
calculateSize();
}
}
private void calculateSize() {
size = 0;
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
size += file.length();
}
}
}
}

@ -1,60 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.MotionEvent;
import android.view.View;
public class Compat {
private static final int SIXTY_FPS_INTERVAL = 1000 / 60;
public static void postOnAnimation(View view, Runnable runnable) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
postOnAnimationJellyBean(view, runnable);
} else {
view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
}
}
@TargetApi(16)
private static void postOnAnimationJellyBean(View view, Runnable runnable) {
view.postOnAnimation(runnable);
}
public static int getPointerIndex(int action) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
return getPointerIndexHoneyComb(action);
else
return getPointerIndexEclair(action);
}
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.ECLAIR)
private static int getPointerIndexEclair(int action) {
return (action & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static int getPointerIndexHoneyComb(int action) {
return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
}
}

@ -1,98 +0,0 @@
package uk.co.senab.photoview;
import android.graphics.RectF;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.ImageView;
/**
* Provided default implementation of GestureDetector.OnDoubleTapListener, to be overriden with custom behavior, if needed
* <p>&nbsp;</p>
* To be used via {@link uk.co.senab.photoview.PhotoViewAttacher#setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener)}
*/
public class DefaultOnDoubleTapListener implements GestureDetector.OnDoubleTapListener {
private PhotoViewAttacher photoViewAttacher;
/**
* Default constructor
*
* @param photoViewAttacher PhotoViewAttacher to bind to
*/
public DefaultOnDoubleTapListener(PhotoViewAttacher photoViewAttacher) {
setPhotoViewAttacher(photoViewAttacher);
}
/**
* Allows to change PhotoViewAttacher within range of single instance
*
* @param newPhotoViewAttacher PhotoViewAttacher to bind to
*/
public void setPhotoViewAttacher(PhotoViewAttacher newPhotoViewAttacher) {
this.photoViewAttacher = newPhotoViewAttacher;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (this.photoViewAttacher == null)
return false;
ImageView imageView = photoViewAttacher.getImageView();
if (null != photoViewAttacher.getOnPhotoTapListener()) {
final RectF displayRect = photoViewAttacher.getDisplayRect();
if (null != displayRect) {
final float x = e.getX(), y = e.getY();
// Check to see if the user tapped on the photo
if (displayRect.contains(x, y)) {
float xResult = (x - displayRect.left)
/ displayRect.width();
float yResult = (y - displayRect.top)
/ displayRect.height();
photoViewAttacher.getOnPhotoTapListener().onPhotoTap(imageView, xResult, yResult);
return true;
}
}
}
if (null != photoViewAttacher.getOnViewTapListener()) {
photoViewAttacher.getOnViewTapListener().onViewTap(imageView, e.getX(), e.getY());
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent ev) {
if (photoViewAttacher == null)
return false;
try {
float scale = photoViewAttacher.getScale();
float x = ev.getX();
float y = ev.getY();
if (scale < photoViewAttacher.getMediumScale()) {
photoViewAttacher.setScale(photoViewAttacher.getMediumScale(), x, y, true);
} else if (scale >= photoViewAttacher.getMediumScale() && scale < photoViewAttacher.getMaximumScale()) {
photoViewAttacher.setScale(photoViewAttacher.getMaximumScale(), x, y, true);
} else {
photoViewAttacher.setScale(photoViewAttacher.getMinimumScale(), x, y, true);
}
} catch (ArrayIndexOutOfBoundsException e) {
// Can sometimes happen when getX() and getY() is called
}
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
// Wait for the confirmed onDoubleTap() instead
return false;
}
}

@ -1,260 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.view.View;
import android.widget.ImageView;
public interface IPhotoView {
/**
* Returns true if the PhotoView is set to allow zooming of Photos.
*
* @return true if the PhotoView allows zooming.
*/
boolean canZoom();
/**
* Gets the Display Rectangle of the currently displayed Drawable. The
* Rectangle is relative to this View and includes all scaling and
* translations.
*
* @return - RectF of Displayed Drawable
*/
RectF getDisplayRect();
/**
* Sets the Display Matrix of the currently displayed Drawable. The
* Rectangle is considered relative to this View and includes all scaling and
* translations.
*
* @return - true if rectangle was applied successfully
*/
boolean setDisplayMatrix(Matrix finalMatrix);
/**
* Gets the Display Matrix of the currently displayed Drawable. The
* Rectangle is considered relative to this View and includes all scaling and
* translations.
*
* @return - true if rectangle was applied successfully
*/
Matrix getDisplayMatrix();
/**
* Use {@link #getMinimumScale()} instead, this will be removed in future release
*
* @return The current minimum scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
@Deprecated
float getMinScale();
/**
* @return The current minimum scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
float getMinimumScale();
/**
* Use {@link #getMediumScale()} instead, this will be removed in future release
*
* @return The current middle scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
@Deprecated
float getMidScale();
/**
* @return The current medium scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
float getMediumScale();
/**
* Use {@link #getMaximumScale()} instead, this will be removed in future release
*
* @return The current maximum scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
@Deprecated
float getMaxScale();
/**
* @return The current maximum scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
float getMaximumScale();
/**
* Returns the current scale value
*
* @return float - current scale value
*/
float getScale();
/**
* Return the current scale type in use by the ImageView.
*/
ImageView.ScaleType getScaleType();
/**
* Whether to allow the ImageView's parent to intercept the touch event when the photo is scroll to it's horizontal edge.
*/
void setAllowParentInterceptOnEdge(boolean allow);
/**
* Use {@link #setMinimumScale(float minimumScale)} instead, this will be removed in future release
* <p/>
* Sets the minimum scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
@Deprecated
void setMinScale(float minScale);
/**
* Sets the minimum scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
void setMinimumScale(float minimumScale);
/**
* Use {@link #setMediumScale(float mediumScale)} instead, this will be removed in future release
* <p/>
* Sets the middle scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
@Deprecated
void setMidScale(float midScale);
/*
* Sets the medium scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
void setMediumScale(float mediumScale);
/**
* Use {@link #setMaximumScale(float maximumScale)} instead, this will be removed in future release
* <p/>
* Sets the maximum scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
@Deprecated
void setMaxScale(float maxScale);
/**
* Sets the maximum scale level. What this value represents depends on the current {@link android.widget.ImageView.ScaleType}.
*/
void setMaximumScale(float maximumScale);
/**
* Register a callback to be invoked when the Photo displayed by this view is long-pressed.
*
* @param listener - Listener to be registered.
*/
void setOnLongClickListener(View.OnLongClickListener listener);
/**
* Register a callback to be invoked when the Matrix has changed for this
* View. An example would be the user panning or scaling the Photo.
*
* @param listener - Listener to be registered.
*/
void setOnMatrixChangeListener(PhotoViewAttacher.OnMatrixChangedListener listener);
/**
* Register a callback to be invoked when the Photo displayed by this View
* is tapped with a single tap.
*
* @param listener - Listener to be registered.
*/
void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener);
/**
* Returns a listener to be invoked when the Photo displayed by this View
* is tapped with a single tap.
*
* @return PhotoViewAttacher.OnPhotoTapListener currently set, may be null
*/
PhotoViewAttacher.OnPhotoTapListener getOnPhotoTapListener();
/**
* Register a callback to be invoked when the View is tapped with a single
* tap.
*
* @param listener - Listener to be registered.
*/
void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener);
/**
* Returns a callback listener to be invoked when the View is tapped with a single
* tap.
*
* @return PhotoViewAttacher.OnViewTapListener currently set, may be null
*/
PhotoViewAttacher.OnViewTapListener getOnViewTapListener();
/**
* Changes the current scale to the specified value.
*
* @param scale - Value to scale to
*/
void setScale(float scale);
/**
* Changes the current scale to the specified value.
*
* @param scale - Value to scale to
* @param animate - Whether to animate the scale
*/
void setScale(float scale, boolean animate);
/**
* Changes the current scale to the specified value, around the given focal point.
*
* @param scale - Value to scale to
* @param focalX - X Focus Point
* @param focalY - Y Focus Point
* @param animate - Whether to animate the scale
*/
void setScale(float scale, float focalX, float focalY, boolean animate);
/**
* Controls how the image should be resized or moved to match the size of
* the ImageView. Any scaling or panning will happen within the confines of
* this {@link android.widget.ImageView.ScaleType}.
*
* @param scaleType - The desired scaling mode.
*/
void setScaleType(ImageView.ScaleType scaleType);
/**
* Allows you to enable/disable the zoom functionality on the ImageView.
* When disable the ImageView reverts to using the FIT_CENTER matrix.
*
* @param zoomable - Whether the zoom functionality is enabled.
*/
void setZoomable(boolean zoomable);
/**
* Enables rotation via PhotoView internal functions.
* Name is chosen so it won't collide with View.setRotation(float) in API since 11
*
* @param rotationDegree - Degree to rotate PhotoView by, should be in range 0 to 360
*/
void setPhotoViewRotation(float rotationDegree);
/**
* Extracts currently visible area to Bitmap object, if there is no image loaded yet or the
* ImageView is already destroyed, returns {@code null}
*
* @return currently visible area as bitmap or null
*/
public Bitmap getVisibleRectangleBitmap();
}

@ -1,256 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview;
import uk.co.senab.photoview.PhotoViewAttacher.OnMatrixChangedListener;
import uk.co.senab.photoview.PhotoViewAttacher.OnPhotoTapListener;
import uk.co.senab.photoview.PhotoViewAttacher.OnViewTapListener;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.widget.ImageView;
public class PhotoView extends ImageView implements IPhotoView {
private final PhotoViewAttacher mAttacher;
private ScaleType mPendingScaleType;
public PhotoView(Context context) {
this(context, null);
}
public PhotoView(Context context, AttributeSet attr) {
this(context, attr, 0);
}
public PhotoView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
super.setScaleType(ScaleType.MATRIX);
mAttacher = new PhotoViewAttacher(this);
if (null != mPendingScaleType) {
setScaleType(mPendingScaleType);
mPendingScaleType = null;
}
}
@Override
public void setPhotoViewRotation(float rotationDegree) {
mAttacher.setPhotoViewRotation(rotationDegree);
}
@Override
public boolean canZoom() {
return mAttacher.canZoom();
}
@Override
public RectF getDisplayRect() {
return mAttacher.getDisplayRect();
}
@Override
public Matrix getDisplayMatrix() {
return mAttacher.getDrawMatrix();
}
@Override
public boolean setDisplayMatrix(Matrix finalRectangle) {
return mAttacher.setDisplayMatrix(finalRectangle);
}
@Override
@Deprecated
public float getMinScale() {
return getMinimumScale();
}
@Override
public float getMinimumScale() {
return mAttacher.getMinimumScale();
}
@Override
@Deprecated
public float getMidScale() {
return getMediumScale();
}
@Override
public float getMediumScale() {
return mAttacher.getMediumScale();
}
@Override
@Deprecated
public float getMaxScale() {
return getMaximumScale();
}
@Override
public float getMaximumScale() {
return mAttacher.getMaximumScale();
}
@Override
public float getScale() {
return mAttacher.getScale();
}
@Override
public ScaleType getScaleType() {
return mAttacher.getScaleType();
}
@Override
public void setAllowParentInterceptOnEdge(boolean allow) {
mAttacher.setAllowParentInterceptOnEdge(allow);
}
@Override
@Deprecated
public void setMinScale(float minScale) {
setMinimumScale(minScale);
}
@Override
public void setMinimumScale(float minimumScale) {
mAttacher.setMinimumScale(minimumScale);
}
@Override
@Deprecated
public void setMidScale(float midScale) {
setMediumScale(midScale);
}
@Override
public void setMediumScale(float mediumScale) {
mAttacher.setMediumScale(mediumScale);
}
@Override
@Deprecated
public void setMaxScale(float maxScale) {
setMaximumScale(maxScale);
}
@Override
public void setMaximumScale(float maximumScale) {
mAttacher.setMaximumScale(maximumScale);
}
@Override
// setImageBitmap calls through to this method
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
if (null != mAttacher) {
mAttacher.update();
}
}
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
if (null != mAttacher) {
mAttacher.update();
}
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
if (null != mAttacher) {
mAttacher.update();
}
}
@Override
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
mAttacher.setOnMatrixChangeListener(listener);
}
@Override
public void setOnLongClickListener(OnLongClickListener l) {
mAttacher.setOnLongClickListener(l);
}
@Override
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
mAttacher.setOnPhotoTapListener(listener);
}
@Override
public OnPhotoTapListener getOnPhotoTapListener() {
return mAttacher.getOnPhotoTapListener();
}
@Override
public void setOnViewTapListener(OnViewTapListener listener) {
mAttacher.setOnViewTapListener(listener);
}
@Override
public OnViewTapListener getOnViewTapListener() {
return mAttacher.getOnViewTapListener();
}
@Override
public void setScale(float scale) {
mAttacher.setScale(scale);
}
@Override
public void setScale(float scale, boolean animate) {
mAttacher.setScale(scale, animate);
}
@Override
public void setScale(float scale, float focalX, float focalY, boolean animate) {
mAttacher.setScale(scale, focalX, focalY, animate);
}
@Override
public void setScaleType(ScaleType scaleType) {
if (null != mAttacher) {
mAttacher.setScaleType(scaleType);
} else {
mPendingScaleType = scaleType;
}
}
@Override
public void setZoomable(boolean zoomable) {
mAttacher.setZoomable(zoomable);
}
@Override
public Bitmap getVisibleRectangleBitmap() {
return mAttacher.getVisibleRectangleBitmap();
}
@Override
protected void onDetachedFromWindow() {
mAttacher.cleanup();
super.onDetachedFromWindow();
}
}

@ -1,143 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.gestures;
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
public class CupcakeGestureDetector implements GestureDetector {
protected OnGestureListener mListener;
private static final String LOG_TAG = "CupcakeGestureDetector";
float mLastTouchX;
float mLastTouchY;
final float mTouchSlop;
final float mMinimumVelocity;
@Override
public void setOnGestureListener(OnGestureListener listener) {
this.mListener = listener;
}
public CupcakeGestureDetector(Context context) {
final ViewConfiguration configuration = ViewConfiguration
.get(context);
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mTouchSlop = configuration.getScaledTouchSlop();
}
private VelocityTracker mVelocityTracker;
private boolean mIsDragging;
float getActiveX(MotionEvent ev) {
return ev.getX();
}
float getActiveY(MotionEvent ev) {
return ev.getY();
}
@Override
public boolean isScaling() {
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
mVelocityTracker = VelocityTracker.obtain();
if (null != mVelocityTracker) {
mVelocityTracker.addMovement(ev);
} else {
Log.i(LOG_TAG, "Velocity tracker is null");
}
mLastTouchX = getActiveX(ev);
mLastTouchY = getActiveY(ev);
mIsDragging = false;
break;
}
case MotionEvent.ACTION_MOVE: {
final float x = getActiveX(ev);
final float y = getActiveY(ev);
final float dx = x - mLastTouchX, dy = y - mLastTouchY;
if (!mIsDragging) {
// Use Pythagoras to see if drag length is larger than
// touch slop
mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
}
if (mIsDragging) {
mListener.onDrag(dx, dy);
mLastTouchX = x;
mLastTouchY = y;
if (null != mVelocityTracker) {
mVelocityTracker.addMovement(ev);
}
}
break;
}
case MotionEvent.ACTION_CANCEL: {
// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
case MotionEvent.ACTION_UP: {
if (mIsDragging) {
if (null != mVelocityTracker) {
mLastTouchX = getActiveX(ev);
mLastTouchY = getActiveY(ev);
// Compute velocity within the last 1000ms
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000);
final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker
.getYVelocity();
// If the velocity is greater than minVelocity, call
// listener
if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
mListener.onFling(mLastTouchX, mLastTouchY, -vX,
-vY);
}
}
}
// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
}
return true;
}
}

@ -1,85 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.gestures;
import uk.co.senab.photoview.Compat;
import android.annotation.TargetApi;
import android.content.Context;
import android.view.MotionEvent;
@TargetApi(5)
public class EclairGestureDetector extends CupcakeGestureDetector {
private static final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private int mActivePointerIndex = 0;
public EclairGestureDetector(Context context) {
super(context);
}
@Override
float getActiveX(MotionEvent ev) {
try {
return ev.getX(mActivePointerIndex);
} catch (Exception e) {
return ev.getX();
}
}
@Override
float getActiveY(MotionEvent ev) {
try {
return ev.getY(mActivePointerIndex);
} catch (Exception e) {
return ev.getY();
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = ev.getPointerId(0);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mActivePointerId = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
// Ignore deprecation, ACTION_POINTER_ID_MASK and
// ACTION_POINTER_ID_SHIFT has same value and are deprecated
// You can have either deprecation or lint target api warning
final int pointerIndex = Compat.getPointerIndex(ev.getAction());
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
}
break;
}
mActivePointerIndex = ev
.findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId
: 0);
return super.onTouchEvent(ev);
}
}

@ -1,63 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.gestures;
import android.annotation.TargetApi;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
@TargetApi(8)
public class FroyoGestureDetector extends EclairGestureDetector {
protected final ScaleGestureDetector mDetector;
public FroyoGestureDetector(Context context) {
super(context);
ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
mListener.onScale(detector.getScaleFactor(),
detector.getFocusX(), detector.getFocusY());
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// NO-OP
}
};
mDetector = new ScaleGestureDetector(context, mScaleListener);
}
@Override
public boolean isScaling() {
return mDetector.isInProgress();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mDetector.onTouchEvent(ev);
return super.onTouchEvent(ev);
}
}

@ -1,28 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.gestures;
import android.view.MotionEvent;
public interface GestureDetector {
public boolean onTouchEvent(MotionEvent ev);
public boolean isScaling();
public void setOnGestureListener(OnGestureListener listener);
}

@ -1,27 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.gestures;
public interface OnGestureListener {
public void onDrag(float dx, float dy);
public void onFling(float startX, float startY, float velocityX,
float velocityY);
public void onScale(float scaleFactor, float focusX, float focusY);
}

@ -1,42 +0,0 @@
package uk.co.senab.photoview.gestures;
/*******************************************************************************
* Copyright 2011, 2012 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.
*******************************************************************************/
import android.content.Context;
import android.os.Build;
public final class VersionedGestureDetector {
public static GestureDetector newInstance(Context context,
OnGestureListener listener) {
final int sdkVersion = Build.VERSION.SDK_INT;
GestureDetector detector;
if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
detector = new CupcakeGestureDetector(context);
} else if (sdkVersion < Build.VERSION_CODES.FROYO) {
detector = new EclairGestureDetector(context);
} else {
detector = new FroyoGestureDetector(context);
}
detector.setOnGestureListener(listener);
return detector;
}
}

@ -1,35 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.log;
import android.util.Log;
/**
* class that holds the {@link Logger} for this library, defaults to {@link LoggerDefault} to send logs to android {@link Log}
*/
public final class LogManager {
private static Logger logger = new LoggerDefault();
public static void setLogger(Logger newLogger) {
logger = newLogger;
}
public static Logger getLogger() {
return logger;
}
}

@ -1,116 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.log;
/**
* interface for a logger class to replace the static calls to {@link android.util.Log}
*/
public interface Logger {
/**
* Send a {@link android.util.Log#VERBOSE} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int v(String tag, String msg);
/**
* Send a {@link android.util.Log#VERBOSE} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int v(String tag, String msg, Throwable tr);
/**
* Send a {@link android.util.Log#DEBUG} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int d(String tag, String msg);
/**
* Send a {@link android.util.Log#DEBUG} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int d(String tag, String msg, Throwable tr);
/**
* Send an {@link android.util.Log#INFO} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int i(String tag, String msg);
/**
* Send a {@link android.util.Log#INFO} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int i(String tag, String msg, Throwable tr);
/**
* Send a {@link android.util.Log#WARN} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int w(String tag, String msg);
/**
* Send a {@link android.util.Log#WARN} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int w(String tag, String msg, Throwable tr);
/**
* Send an {@link android.util.Log#ERROR} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int e(String tag, String msg);
/**
* Send a {@link android.util.Log#ERROR} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int e(String tag, String msg, Throwable tr);
}

@ -1,76 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.log;
import android.util.Log;
/**
* Helper class to redirect {@link LogManager#logger} to {@link Log}
*/
public class LoggerDefault implements Logger {
@Override
public int v(String tag, String msg) {
return Log.v(tag, msg);
}
@Override
public int v(String tag, String msg, Throwable tr) {
return Log.v(tag, msg, tr);
}
@Override
public int d(String tag, String msg) {
return Log.d(tag, msg);
}
@Override
public int d(String tag, String msg, Throwable tr) {
return Log.d(tag, msg, tr);
}
@Override
public int i(String tag, String msg) {
return Log.i(tag, msg);
}
@Override
public int i(String tag, String msg, Throwable tr) {
return Log.i(tag, msg, tr);
}
@Override
public int w(String tag, String msg) {
return Log.w(tag, msg);
}
@Override
public int w(String tag, String msg, Throwable tr) {
return Log.w(tag, msg, tr);
}
@Override
public int e(String tag, String msg) {
return Log.e(tag, msg);
}
@Override
public int e(String tag, String msg, Throwable tr) {
return Log.e(tag, msg, tr);
}
}

@ -1,68 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.scrollerproxy;
import android.annotation.TargetApi;
import android.content.Context;
import android.widget.OverScroller;
@TargetApi(9)
public class GingerScroller extends ScrollerProxy {
protected final OverScroller mScroller;
private boolean mFirstScroll = false;
public GingerScroller(Context context) {
mScroller = new OverScroller(context);
}
@Override
public boolean computeScrollOffset() {
// Workaround for first scroll returning 0 for the direction of the edge it hits.
// Simply recompute values.
if (mFirstScroll) {
mScroller.computeScrollOffset();
mFirstScroll = false;
}
return mScroller.computeScrollOffset();
}
@Override
public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
int overX, int overY) {
mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, overX, overY);
}
@Override
public void forceFinished(boolean finished) {
mScroller.forceFinished(finished);
}
@Override
public boolean isFinished() {
return mScroller.isFinished();
}
@Override
public int getCurrX() {
return mScroller.getCurrX();
}
@Override
public int getCurrY() {
return mScroller.getCurrY();
}
}

@ -1,33 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.scrollerproxy;
import android.annotation.TargetApi;
import android.content.Context;
@TargetApi(14)
public class IcsScroller extends GingerScroller {
public IcsScroller(Context context) {
super(context);
}
@Override
public boolean computeScrollOffset() {
return mScroller.computeScrollOffset();
}
}

@ -1,58 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.scrollerproxy;
import android.content.Context;
import android.widget.Scroller;
public class PreGingerScroller extends ScrollerProxy {
private final Scroller mScroller;
public PreGingerScroller(Context context) {
mScroller = new Scroller(context);
}
@Override
public boolean computeScrollOffset() {
return mScroller.computeScrollOffset();
}
@Override
public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
int overX, int overY) {
mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
}
@Override
public void forceFinished(boolean finished) {
mScroller.forceFinished(finished);
}
public boolean isFinished() {
return mScroller.isFinished();
}
@Override
public int getCurrX() {
return mScroller.getCurrX();
}
@Override
public int getCurrY() {
return mScroller.getCurrY();
}
}

@ -1,48 +0,0 @@
/*******************************************************************************
* Copyright 2011, 2012 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.senab.photoview.scrollerproxy;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
public abstract class ScrollerProxy {
public static ScrollerProxy getScroller(Context context) {
if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) {
return new PreGingerScroller(context);
} else if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
return new GingerScroller(context);
} else {
return new IcsScroller(context);
}
}
public abstract boolean computeScrollOffset();
public abstract void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY,
int maxY, int overX, int overY);
public abstract void forceFinished(boolean finished);
public abstract boolean isFinished();
public abstract int getCurrX();
public abstract int getCurrY();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/progress">
<scale
android:drawable="@drawable/progress_primary_holo_light"
android:scaleWidth="100%" />
</item>
</layer-list>

@ -17,5 +17,4 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<dimen name="image_view_padding">8dp</dimen>
</resources>

Loading…
Cancel
Save