Add transition view scale support, update ssiv

filtering
Floens 11 years ago
parent 463b7e5291
commit 4a948c591f
  1. 1
      Clover/app/build.gradle
  2. 55
      Clover/app/src/main/java/com/davemorrissey/labs/subscaleview/ImageViewState.java
  3. 1523
      Clover/app/src/main/java/com/davemorrissey/labs/subscaleview/ScaleImageView.java
  4. 1842
      Clover/app/src/main/java/com/davemorrissey/labs/subscaleview/SubsamplingScaleImageView.java
  5. 34
      Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java
  6. 58
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  7. 77
      Clover/app/src/main/java/org/floens/chan/ui/view/CustomScaleImageView.java
  8. 111
      Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java
  9. 72
      Clover/app/src/main/java/org/floens/chan/ui/view/TransitionImageView.java
  10. 1
      Clover/app/src/main/res/values/strings.xml

@ -80,6 +80,7 @@ dependencies {
compile 'com.j256.ormlite:ormlite-android:4.48' compile 'com.j256.ormlite:ormlite-android:4.48'
compile 'pl.droidsonroids.gif:android-gif-drawable:1.1.0' compile 'pl.droidsonroids.gif:android-gif-drawable:1.1.0'
compile 'com.squareup.okhttp:okhttp:2.2.0' compile 'com.squareup.okhttp:okhttp:2.2.0'
compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.1.3'
compile files('libs/httpclientandroidlib-1.2.1.jar') compile files('libs/httpclientandroidlib-1.2.1.jar')
} }

@ -1,55 +0,0 @@
/*
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;
}
}

@ -18,6 +18,7 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.
private final Callback callback; private final Callback callback;
private final boolean imageAutoLoad = ChanSettings.imageAutoLoad.get(); private final boolean imageAutoLoad = ChanSettings.imageAutoLoad.get();
private final boolean movieAutoLoad = imageAutoLoad && ChanSettings.videoAutoLoad.get();
private boolean entering = true; private boolean entering = true;
private boolean exiting = false; private boolean exiting = false;
@ -137,18 +138,45 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.
// onModeLoaded when a unloaded image was swiped to the center earlier // onModeLoaded when a unloaded image was swiped to the center earlier
private void onLowResInCenter() { private void onLowResInCenter() {
PostImage postImage = images.get(selectedPosition); PostImage postImage = images.get(selectedPosition);
if (imageAutoLoad) {
if (postImage.type == PostImage.Type.STATIC) { if (postImage.type == PostImage.Type.STATIC) {
callback.setImageMode(postImage, MultiImageView.Mode.BIGIMAGE); callback.setImageMode(postImage, MultiImageView.Mode.BIGIMAGE);
} else { } else if (postImage.type == PostImage.Type.GIF) {
// todo callback.setImageMode(postImage, MultiImageView.Mode.GIF);
} else if (postImage.type == PostImage.Type.MOVIE && movieAutoLoad) {
callback.setImageMode(postImage, MultiImageView.Mode.MOVIE);
}
} }
} }
@Override @Override
public void onTap(MultiImageView multiImageView) { public void onTap(MultiImageView multiImageView) {
// Don't mistake a swipe from a user when the pager is disabled as a tap // Don't mistake a swipe when the pager is disabled as a tap
if (viewPagerVisible) { if (viewPagerVisible) {
PostImage postImage = images.get(selectedPosition);
if (imageAutoLoad) {
if (movieAutoLoad) {
onExit(); onExit();
} else {
if (postImage.type == PostImage.Type.MOVIE) {
callback.setImageMode(postImage, MultiImageView.Mode.MOVIE);
} else {
onExit();
}
}
} else {
MultiImageView.Mode currentMode = callback.getImageMode(postImage);
if (postImage.type == PostImage.Type.STATIC && currentMode != MultiImageView.Mode.BIGIMAGE) {
callback.setImageMode(postImage, MultiImageView.Mode.BIGIMAGE);
} else if (postImage.type == PostImage.Type.GIF && currentMode != MultiImageView.Mode.GIF) {
callback.setImageMode(postImage, MultiImageView.Mode.GIF);
} else if (postImage.type == PostImage.Type.MOVIE && currentMode != MultiImageView.Mode.MOVIE) {
callback.setImageMode(postImage, MultiImageView.Mode.MOVIE);
} else {
onExit();
}
}
} }
} }

@ -10,6 +10,7 @@ import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
@ -20,6 +21,7 @@ import android.widget.ImageView;
import com.android.volley.VolleyError; import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader; import com.android.volley.toolbox.ImageLoader;
import com.davemorrissey.labs.subscaleview.ImageViewState;
import org.floens.chan.ChanApplication; import org.floens.chan.ChanApplication;
import org.floens.chan.R; import org.floens.chan.R;
@ -28,10 +30,12 @@ import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.presenter.ImageViewerPresenter; import org.floens.chan.core.presenter.ImageViewerPresenter;
import org.floens.chan.ui.adapter.ImageViewerAdapter; import org.floens.chan.ui.adapter.ImageViewerAdapter;
import org.floens.chan.ui.toolbar.Toolbar; import org.floens.chan.ui.toolbar.Toolbar;
import org.floens.chan.ui.view.CustomScaleImageView;
import org.floens.chan.ui.view.MultiImageView; import org.floens.chan.ui.view.MultiImageView;
import org.floens.chan.ui.view.OptionalSwipeViewPager; import org.floens.chan.ui.view.OptionalSwipeViewPager;
import org.floens.chan.ui.view.TransitionImageView; import org.floens.chan.ui.view.TransitionImageView;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
import java.util.List; import java.util.List;
@ -40,10 +44,10 @@ import static org.floens.chan.utils.AndroidUtils.dp;
public class ImageViewerController extends Controller implements View.OnClickListener, ImageViewerPresenter.Callback { public class ImageViewerController extends Controller implements View.OnClickListener, ImageViewerPresenter.Callback {
private static final String TAG = "ImageViewerController"; private static final String TAG = "ImageViewerController";
private static final int TRANSITION_DURATION = 200; private static final int TRANSITION_DURATION = 200;
private static final float TRANSITION_FINAL_ALPHA = 0.80f; private static final float TRANSITION_FINAL_ALPHA = 0.85f;
private int statusBarColorPrevious; private int statusBarColorPrevious;
private AnimatorSet startPreviewAnimation; private AnimatorSet startAnimation;
private AnimatorSet endAnimation; private AnimatorSet endAnimation;
private PreviewCallback previewCallback; private PreviewCallback previewCallback;
@ -134,6 +138,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
ImageView startImageView = getTransitionImageView(postImage); ImageView startImageView = getTransitionImageView(postImage);
if (!setTransitionViewData(startImageView)) { if (!setTransitionViewData(startImageView)) {
Logger.test("Oops");
return; // TODO return; // TODO
} }
@ -141,7 +146,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
statusBarColorPrevious = getWindow().getStatusBarColor(); statusBarColorPrevious = getWindow().getStatusBarColor();
} }
startPreviewAnimation = new AnimatorSet(); startAnimation = new AnimatorSet();
ValueAnimator progress = ValueAnimator.ofFloat(0f, 1f); ValueAnimator progress = ValueAnimator.ofFloat(0f, 1f);
progress.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { progress.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@ -152,10 +157,10 @@ public class ImageViewerController extends Controller implements View.OnClickLis
} }
}); });
startPreviewAnimation.play(progress); startAnimation.play(progress);
startPreviewAnimation.setDuration(TRANSITION_DURATION); startAnimation.setDuration(TRANSITION_DURATION);
startPreviewAnimation.setInterpolator(new DecelerateInterpolator()); startAnimation.setInterpolator(new DecelerateInterpolator());
startPreviewAnimation.addListener(new AnimatorListenerAdapter() { startAnimation.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationStart(Animator animation) { public void onAnimationStart(Animator animation) {
previewCallback.onPreviewCreate(ImageViewerController.this); previewCallback.onPreviewCreate(ImageViewerController.this);
@ -163,15 +168,15 @@ public class ImageViewerController extends Controller implements View.OnClickLis
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
startPreviewAnimation = null; startAnimation = null;
presenter.onInTransitionEnd(); presenter.onInTransitionEnd();
} }
}); });
startPreviewAnimation.start(); startAnimation.start();
} }
public void startPreviewOutTransition(final PostImage postImage) { public void startPreviewOutTransition(final PostImage postImage) {
if (startPreviewAnimation != null || endAnimation != null) { if (startAnimation != null || endAnimation != null) {
return; return;
} }
@ -191,11 +196,22 @@ public class ImageViewerController extends Controller implements View.OnClickLis
} }
private void doPreviewOutAnimation(PostImage postImage, Bitmap bitmap) { private void doPreviewOutAnimation(PostImage postImage, Bitmap bitmap) {
// Find translation and scale if the current displayed image was a bigimage
MultiImageView multiImageView = ((ImageViewerAdapter) pager.getAdapter()).find(postImage);
CustomScaleImageView customScaleImageView = multiImageView.findScaleImageView();
if (customScaleImageView != null) {
ImageViewState state = customScaleImageView.getState();
if (state != null) {
PointF p = customScaleImageView.viewToSourceCoord(0f, 0f);
PointF bitmapSize = new PointF(customScaleImageView.getSWidth(), customScaleImageView.getSHeight());
previewImage.setState(state.getScale(), p, bitmapSize);
}
}
ImageView startImage = getTransitionImageView(postImage); ImageView startImage = getTransitionImageView(postImage);
if (!setTransitionViewData(startImage) || bitmap == null) {
endAnimation = new AnimatorSet(); endAnimation = new AnimatorSet();
if (!setTransitionViewData(startImage) || bitmap == null) {
ValueAnimator backgroundAlpha = ValueAnimator.ofFloat(1f, 0f); ValueAnimator backgroundAlpha = ValueAnimator.ofFloat(1f, 0f);
backgroundAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { backgroundAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override @Override
@ -209,18 +225,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
.with(ObjectAnimator.ofFloat(previewImage, View.ALPHA, 1f, 0f)) .with(ObjectAnimator.ofFloat(previewImage, View.ALPHA, 1f, 0f))
.with(backgroundAlpha); .with(backgroundAlpha);
endAnimation.setDuration(TRANSITION_DURATION);
endAnimation.setInterpolator(new DecelerateInterpolator());
endAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
previewOutAnimationEnded();
}
});
endAnimation.start();
} else { } else {
endAnimation = new AnimatorSet();
ValueAnimator progress = ValueAnimator.ofFloat(1f, 0f); ValueAnimator progress = ValueAnimator.ofFloat(1f, 0f);
progress.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { progress.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override @Override
@ -231,14 +236,10 @@ public class ImageViewerController extends Controller implements View.OnClickLis
}); });
endAnimation.play(progress); endAnimation.play(progress);
}
endAnimation.setDuration(TRANSITION_DURATION); endAnimation.setDuration(TRANSITION_DURATION);
endAnimation.setInterpolator(new DecelerateInterpolator()); endAnimation.setInterpolator(new DecelerateInterpolator());
endAnimation.addListener(new AnimatorListenerAdapter() { endAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
previewCallback.onPreviewCreate(ImageViewerController.this);
}
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
previewOutAnimationEnded(); previewOutAnimationEnded();
@ -246,7 +247,6 @@ public class ImageViewerController extends Controller implements View.OnClickLis
}); });
endAnimation.start(); endAnimation.start();
} }
}
private void previewOutAnimationEnded() { private void previewOutAnimationEnded() {
setBackgroundAlpha(0f); setBackgroundAlpha(0f);

@ -1,18 +1,3 @@
/**
* Copyright (C) 2013 The Android Open Source Project
*
* 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 org.floens.chan.ui.view; package org.floens.chan.ui.view;
import android.content.Context; import android.content.Context;
@ -20,54 +5,72 @@ import android.util.AttributeSet;
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.Logger;
public class CustomScaleImageView extends SubsamplingScaleImageView { public class CustomScaleImageView extends SubsamplingScaleImageView {
private InitedCallback initCallback; private static final String TAG = "CustomScaleImageView";
private Callback callback;
public CustomScaleImageView(Context context, AttributeSet attr) { public CustomScaleImageView(Context context, AttributeSet attr) {
super(context, attr); super(context, attr);
init();
} }
public CustomScaleImageView(Context context) { public CustomScaleImageView(Context context) {
super(context); super(context);
init();
} }
public void setInitCallback(InitedCallback initCallback) { public void setCallback(Callback callback) {
this.initCallback = initCallback; this.callback = callback;
} }
private void init() {
setOnImageEventListener(new OnImageEventListener() {
@Override @Override
protected void onImageReady() { public void onReady() {
super.onImageReady(); float scale = Math.min(getWidth() / (float) getSWidth(), getHeight() / (float) getSHeight());
setMinScale(scale);
AndroidUtils.runOnUiThread(new Runnable() { if (getMaxScale() < scale * 2f) {
@Override setMaxScale(scale * 2f);
public void run() {
if (initCallback != null) {
initCallback.onInit();
} }
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM);
if (callback != null) {
callback.onReady();
} }
});
} }
@Override @Override
protected void onOutOfMemory() { public void onImageLoaded() {
super.onOutOfMemory(); }
AndroidUtils.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void onPreviewLoadError(Exception e) {
if (initCallback != null) {
initCallback.onOutOfMemory();
} }
@Override
public void onImageLoadError(Exception e) {
Logger.w(TAG, "onImageLoadError", e);
if (callback != null) {
callback.onError(true);
} }
});
} }
public interface InitedCallback { @Override
public void onInit(); public void onTileLoadError(Exception e) {
Logger.w(TAG, "onTileLoadError", e);
if (callback != null) {
callback.onError(false);
}
}
});
}
public void onOutOfMemory(); public interface Callback {
public void onReady();
public void onError(boolean wasInitial);
} }
} }

@ -33,6 +33,7 @@ import android.widget.VideoView;
import com.android.volley.VolleyError; import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader.ImageContainer; import com.android.volley.toolbox.ImageLoader.ImageContainer;
import com.davemorrissey.labs.subscaleview.ImageSource;
import org.floens.chan.ChanApplication; import org.floens.chan.ChanApplication;
import org.floens.chan.R; import org.floens.chan.R;
@ -51,7 +52,7 @@ import pl.droidsonroids.gif.GifImageView;
public class MultiImageView extends FrameLayout implements View.OnClickListener { public class MultiImageView extends FrameLayout implements View.OnClickListener {
public enum Mode { public enum Mode {
UNLOADED, LOWRES, BIGIMAGE UNLOADED, LOWRES, BIGIMAGE, GIF, MOVIE
} }
private static final String TAG = "MultiImageView"; private static final String TAG = "MultiImageView";
@ -97,32 +98,31 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
return postImage; return postImage;
} }
public void setMode(Mode mode) { public void setMode(final Mode newMode) {
if (this.mode != mode) { if (this.mode != newMode) {
final Mode previousTargetMode = this.mode; Logger.d(TAG, "Changing mode from " + this.mode + " to " + newMode + " for " + postImage.thumbnailUrl);
this.mode = mode; this.mode = newMode;
Logger.d(TAG, "Changing mode from " + previousTargetMode + "to " + mode + " for " + postImage.thumbnailUrl);
if (mode == Mode.LOWRES) {
AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() { AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() {
@Override @Override
public boolean onMeasured(View view) { public boolean onMeasured(View view) {
switch (newMode) {
case LOWRES:
setThumbnail(postImage.thumbnailUrl); setThumbnail(postImage.thumbnailUrl);
return false; break;
} case BIGIMAGE:
});
} else if (mode == Mode.BIGIMAGE) {
if (postImage.type == PostImage.Type.STATIC) {
AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
setBigImage(postImage.imageUrl); setBigImage(postImage.imageUrl);
break;
case GIF:
setGif(postImage.imageUrl);
break;
case MOVIE:
setVideo(postImage.imageUrl);
break;
}
return false; return false;
} }
}); });
} else {
Logger.e(TAG, "postImage type not STATIC, not changing to BIGIMAGE mode!");
}
}
} }
} }
@ -134,6 +134,16 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
this.callback = callback; this.callback = callback;
} }
public CustomScaleImageView findScaleImageView() {
CustomScaleImageView bigImage = null;
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i) instanceof CustomScaleImageView) {
bigImage = (CustomScaleImageView) getChildAt(i);
}
}
return bigImage;
}
public void setThumbnail(String thumbnailUrl) { public void setThumbnail(String thumbnailUrl) {
if (getWidth() == 0 || getHeight() == 0) { if (getWidth() == 0 || getHeight() == 0) {
Logger.e(TAG, "getWidth() or getHeight() returned 0, not loading"); Logger.e(TAG, "getWidth() or getHeight() returned 0, not loading");
@ -154,6 +164,7 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
if (response.getBitmap() != null && (!hasContent || mode == Mode.LOWRES)) { if (response.getBitmap() != null && (!hasContent || mode == Mode.LOWRES)) {
ImageView thumbnail = new ImageView(getContext()); ImageView thumbnail = new ImageView(getContext());
thumbnail.setImageBitmap(response.getBitmap()); thumbnail.setImageBitmap(response.getBitmap());
onModeLoaded(Mode.LOWRES, thumbnail); onModeLoaded(Mode.LOWRES, thumbnail);
} }
} }
@ -197,14 +208,14 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
public void setBigImageFile(File file) { public void setBigImageFile(File file) {
final CustomScaleImageView image = new CustomScaleImageView(getContext()); final CustomScaleImageView image = new CustomScaleImageView(getContext());
image.setImageFile(file.getAbsolutePath()); image.setImage(ImageSource.uri(file.getAbsolutePath()));
image.setOnClickListener(MultiImageView.this); image.setOnClickListener(MultiImageView.this);
addView(image); addView(image, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
image.setInitCallback(new CustomScaleImageView.InitedCallback() { image.setCallback(new CustomScaleImageView.Callback() {
@Override @Override
public void onInit() { public void onReady() {
if (!hasContent || mode == Mode.BIGIMAGE) { if (!hasContent || mode == Mode.BIGIMAGE) {
callback.setProgress(MultiImageView.this, false); callback.setProgress(MultiImageView.this, false);
onModeLoaded(Mode.BIGIMAGE, image); onModeLoaded(Mode.BIGIMAGE, image);
@ -212,8 +223,8 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
} }
@Override @Override
public void onOutOfMemory() { public void onError(boolean wasInitial) {
onOutOfMemoryError(); onBigImageError(wasInitial);
} }
}); });
} }
@ -239,8 +250,10 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
@Override @Override
public void onSuccess(File file) { public void onSuccess(File file) {
gifRequest = null; gifRequest = null;
if (!hasContent || mode == Mode.GIF) {
setGifFile(file); setGifFile(file);
} }
}
@Override @Override
public void onFail(boolean notFound) { public void onFail(boolean notFound) {
@ -271,8 +284,7 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
GifImageView view = new GifImageView(getContext()); GifImageView view = new GifImageView(getContext());
view.setImageDrawable(drawable); view.setImageDrawable(drawable);
view.setLayoutParams(AndroidUtils.MATCH_PARAMS); onModeLoaded(Mode.GIF, view);
setView(view);
} }
public void setVideo(String videoUrl) { public void setVideo(String videoUrl) {
@ -291,8 +303,10 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
@Override @Override
public void onSuccess(File file) { public void onSuccess(File file) {
videoRequest = null; videoRequest = null;
if (!hasContent || mode == Mode.MOVIE) {
setVideoFile(file); setVideoFile(file);
} }
}
@Override @Override
public void onFail(boolean notFound) { public void onFail(boolean notFound) {
@ -316,6 +330,8 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
} catch (ActivityNotFoundException e) { } catch (ActivityNotFoundException e) {
Toast.makeText(getContext(), R.string.open_link_failed, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), R.string.open_link_failed, Toast.LENGTH_SHORT).show();
} }
// TODO: check this
onModeLoaded(Mode.GIF, videoView);
} else { } else {
Context proxyContext = new NoMusicServiceCommandContext(getContext()); Context proxyContext = new NoMusicServiceCommandContext(getContext());
@ -332,7 +348,7 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
@Override @Override
public void onPrepared(MediaPlayer mp) { public void onPrepared(MediaPlayer mp) {
mp.setLooping(true); mp.setLooping(true);
callback.onVideoLoaded(MultiImageView.this); onModeLoaded(Mode.MOVIE, videoView);
} }
}); });
videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@ -346,7 +362,7 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
videoView.setVideoPath(file.getAbsolutePath()); videoView.setVideoPath(file.getAbsolutePath());
setView(videoView); addView(videoView, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER));
videoView.start(); videoView.start();
} }
@ -356,22 +372,32 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
return videoView; return videoView;
} }
public void onError() { private void onError() {
Toast.makeText(getContext(), R.string.image_preview_failed, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), R.string.image_preview_failed, Toast.LENGTH_SHORT).show();
callback.setProgress(this, false); callback.setProgress(this, false);
} }
public void onNotFoundError() { private void onNotFoundError() {
callback.setProgress(this, false); callback.setProgress(this, false);
Toast.makeText(getContext(), R.string.image_not_found, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), R.string.image_not_found, Toast.LENGTH_SHORT).show();
} }
public void onOutOfMemoryError() { private void onOutOfMemoryError() {
Toast.makeText(getContext(), R.string.image_preview_failed_oom, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), R.string.image_preview_failed_oom, Toast.LENGTH_SHORT).show();
callback.setProgress(this, false); callback.setProgress(this, false);
} }
private void onBigImageError(boolean wasInitial) {
if (wasInitial) {
Toast.makeText(getContext(), R.string.image_failed_big_image, Toast.LENGTH_SHORT).show();
callback.setProgress(this, false);
}
}
public void cancelLoad() { public void cancelLoad() {
if (thumbnailRequest != null) {
thumbnailRequest.cancelRequest();
}
if (bigImageRequest != null) { if (bigImageRequest != null) {
bigImageRequest.cancel(true); bigImageRequest.cancel(true);
} }
@ -394,14 +420,27 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
cancelLoad(); cancelLoad();
} }
private void setView(View view) { private void onModeLoaded(Mode mode) {
removeAllViews(); onModeLoaded(mode, null);
addView(view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
} }
private void onModeLoaded(Mode mode, View view) { private void onModeLoaded(Mode mode, View view) {
removeAllViews(); if (view != null) {
// Remove all other views
boolean alreadyAttached = false;
for (int i = getChildCount() - 1; i >= 0; i--) {
if (getChildAt(i) != view) {
removeViewAt(i);
} else {
alreadyAttached = true;
}
}
if (!alreadyAttached) {
addView(view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); addView(view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
}
hasContent = true; hasContent = true;
callback.onModeLoaded(this, mode); callback.onModeLoaded(this, mode);
} }

@ -23,6 +23,10 @@ public class TransitionImageView extends View {
private PointF sourceOverlap = new PointF(); private PointF sourceOverlap = new PointF();
private RectF destClip = new RectF(); private RectF destClip = new RectF();
private float progress; private float progress;
private float stateScale;
private float stateBitmapScaleDiff;
private PointF stateBitmapSize;
private PointF statePos;
public TransitionImageView(Context context) { public TransitionImageView(Context context) {
super(context); super(context);
@ -43,15 +47,19 @@ public class TransitionImageView extends View {
this.bitmap = bitmap; this.bitmap = bitmap;
bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
if (stateBitmapSize != null) {
stateBitmapScaleDiff = stateBitmapSize.x / bitmap.getWidth();
}
int[] myLoc = new int[2]; int[] myLoc = new int[2];
getLocationInWindow(myLoc); getLocationInWindow(myLoc);
float globalOffsetX = windowLocation.x - myLoc[0]; float globalOffsetX = windowLocation.x - myLoc[0];
float globalOffsetY = windowLocation.y - myLoc[1]; float globalOffsetY = windowLocation.y - myLoc[1];
// Get the coords in the image view with the center crop method // Get the coords in the image view with the center crop method
float scaleX = (float) viewSize.x / (float) bitmap.getWidth(); float scale = Math.max(
float scaleY = (float) viewSize.y / (float) bitmap.getHeight(); (float) viewSize.x / (float) bitmap.getWidth(),
float scale = scaleX > scaleY ? scaleX : scaleY; (float) viewSize.y / (float) bitmap.getHeight());
float scaledX = bitmap.getWidth() * scale; float scaledX = bitmap.getWidth() * scale;
float scaledY = bitmap.getHeight() * scale; float scaledY = bitmap.getHeight() * scale;
float offsetX = (scaledX - viewSize.x) * 0.5f; float offsetX = (scaledX - viewSize.x) * 0.5f;
@ -66,30 +74,53 @@ public class TransitionImageView extends View {
scaledY - offsetY + globalOffsetY); scaledY - offsetY + globalOffsetY);
} }
public void setState(float stateScale, PointF statePos, PointF stateBitmapSize) {
this.stateScale = stateScale;
this.statePos = statePos;
this.stateBitmapSize = stateBitmapSize;
}
public void setProgress(float progress) { public void setProgress(float progress) {
this.progress = progress; this.progress = progress;
RectF output;
if (statePos != null) {
// Use scale and translate from ssiv
output = new RectF(-statePos.x * stateScale, -statePos.y * stateScale, 0, 0);
output.right = output.left + bitmap.getWidth() * stateBitmapScaleDiff * stateScale;
output.bottom = output.top + bitmap.getHeight() * stateBitmapScaleDiff * stateScale;
} else {
// Center inside method // Center inside method
float selfWidth = getWidth();
float selfHeight = getHeight();
float destScale = Math.min( float destScale = Math.min(
(float) getWidth() / (float) bitmap.getWidth(), selfWidth / (float) bitmap.getWidth(),
(float) getHeight() / (float) bitmap.getHeight()); selfHeight / (float) bitmap.getHeight());
float destOffsetX = (getWidth() - bitmap.getWidth() * destScale) * 0.5f;
float destOffsetY = (getHeight() - bitmap.getHeight() * destScale) * 0.5f; output = new RectF(
float destRight = bitmap.getWidth() * destScale + destOffsetX; (selfWidth - bitmap.getWidth() * destScale) * 0.5f,
float destBottom = bitmap.getHeight() * destScale + destOffsetY; (selfHeight - bitmap.getHeight() * destScale) * 0.5f, 0, 0);
float left = sourceImageRect.left + (destOffsetX - sourceImageRect.left) * progress; output.right = bitmap.getWidth() * destScale + output.left;
float top = sourceImageRect.top + (destOffsetY - sourceImageRect.top) * progress; output.bottom = bitmap.getHeight() * destScale + output.top;
float right = sourceImageRect.right + (destRight - sourceImageRect.right) * progress; }
float bottom = sourceImageRect.bottom + (destBottom - sourceImageRect.bottom) * progress;
destRect.set(left, top, right, bottom); // Linear interpolate between start bounds and calculated final bounds
output.left = lerp(sourceImageRect.left, output.left, progress);
output.top = lerp(sourceImageRect.top, output.top, progress);
output.right = lerp(sourceImageRect.right, output.right, progress);
output.bottom = lerp(sourceImageRect.bottom, output.bottom, progress);
destRect.set(output);
matrix.setRectToRect(bitmapRect, destRect, Matrix.ScaleToFit.FILL);
destClip.set( destClip.set(
left + sourceOverlap.x * (1f - progress), output.left + sourceOverlap.x * (1f - progress),
top + sourceOverlap.y * (1f - progress), output.top + sourceOverlap.y * (1f - progress),
right - sourceOverlap.x * (1f - progress), output.right - sourceOverlap.x * (1f - progress),
bottom - sourceOverlap.y * (1f - progress) output.bottom - sourceOverlap.y * (1f - progress)
); );
invalidate(); invalidate();
@ -100,7 +131,6 @@ public class TransitionImageView extends View {
super.onDraw(canvas); super.onDraw(canvas);
if (bitmap != null) { if (bitmap != null) {
matrix.setRectToRect(bitmapRect, destRect, Matrix.ScaleToFit.FILL);
canvas.save(); canvas.save();
if (progress < 1f) { if (progress < 1f) {
canvas.clipRect(destClip); canvas.clipRect(destClip);
@ -114,4 +144,8 @@ public class TransitionImageView extends View {
paint.setAntiAlias(true); paint.setAntiAlias(true);
paint.setFilterBitmap(true); paint.setFilterBitmap(true);
} }
private float lerp(float a, float b, float x) {
return a + (b - a) * x;
}
} }

@ -67,6 +67,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="image_preview_failed">Failed to show image</string> <string name="image_preview_failed">Failed to show image</string>
<string name="image_preview_failed_oom">Failed to show image, out of memory</string> <string name="image_preview_failed_oom">Failed to show image, out of memory</string>
<string name="image_failed_big_image">Deepzoom loading failed</string>
<string name="image_not_found">Image not found</string> <string name="image_not_found">Image not found</string>
<string name="image_open_failed">Failed to open image</string> <string name="image_open_failed">Failed to open image</string>

Loading…
Cancel
Save