Add transition view scale support, update ssiv

filtering
Floens 10 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. 40
      Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java
  6. 74
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  7. 83
      Clover/app/src/main/java/org/floens/chan/ui/view/CustomScaleImageView.java
  8. 129
      Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java
  9. 76
      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 'pl.droidsonroids.gif:android-gif-drawable:1.1.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')
}

@ -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 boolean imageAutoLoad = ChanSettings.imageAutoLoad.get();
private final boolean movieAutoLoad = imageAutoLoad && ChanSettings.videoAutoLoad.get();
private boolean entering = true;
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
private void onLowResInCenter() {
PostImage postImage = images.get(selectedPosition);
if (postImage.type == PostImage.Type.STATIC) {
callback.setImageMode(postImage, MultiImageView.Mode.BIGIMAGE);
} else {
// todo
if (imageAutoLoad) {
if (postImage.type == PostImage.Type.STATIC) {
callback.setImageMode(postImage, MultiImageView.Mode.BIGIMAGE);
} else if (postImage.type == PostImage.Type.GIF) {
callback.setImageMode(postImage, MultiImageView.Mode.GIF);
} else if (postImage.type == PostImage.Type.MOVIE && movieAutoLoad) {
callback.setImageMode(postImage, MultiImageView.Mode.MOVIE);
}
}
}
@Override
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) {
onExit();
PostImage postImage = images.get(selectedPosition);
if (imageAutoLoad) {
if (movieAutoLoad) {
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.Color;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.Log;
@ -20,6 +21,7 @@ import android.widget.ImageView;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.davemorrissey.labs.subscaleview.ImageViewState;
import org.floens.chan.ChanApplication;
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.ui.adapter.ImageViewerAdapter;
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.OptionalSwipeViewPager;
import org.floens.chan.ui.view.TransitionImageView;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
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 {
private static final String TAG = "ImageViewerController";
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 AnimatorSet startPreviewAnimation;
private AnimatorSet startAnimation;
private AnimatorSet endAnimation;
private PreviewCallback previewCallback;
@ -134,6 +138,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
ImageView startImageView = getTransitionImageView(postImage);
if (!setTransitionViewData(startImageView)) {
Logger.test("Oops");
return; // TODO
}
@ -141,7 +146,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
statusBarColorPrevious = getWindow().getStatusBarColor();
}
startPreviewAnimation = new AnimatorSet();
startAnimation = new AnimatorSet();
ValueAnimator progress = ValueAnimator.ofFloat(0f, 1f);
progress.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@ -152,10 +157,10 @@ public class ImageViewerController extends Controller implements View.OnClickLis
}
});
startPreviewAnimation.play(progress);
startPreviewAnimation.setDuration(TRANSITION_DURATION);
startPreviewAnimation.setInterpolator(new DecelerateInterpolator());
startPreviewAnimation.addListener(new AnimatorListenerAdapter() {
startAnimation.play(progress);
startAnimation.setDuration(TRANSITION_DURATION);
startAnimation.setInterpolator(new DecelerateInterpolator());
startAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
previewCallback.onPreviewCreate(ImageViewerController.this);
@ -163,15 +168,15 @@ public class ImageViewerController extends Controller implements View.OnClickLis
@Override
public void onAnimationEnd(Animator animation) {
startPreviewAnimation = null;
startAnimation = null;
presenter.onInTransitionEnd();
}
});
startPreviewAnimation.start();
startAnimation.start();
}
public void startPreviewOutTransition(final PostImage postImage) {
if (startPreviewAnimation != null || endAnimation != null) {
if (startAnimation != null || endAnimation != null) {
return;
}
@ -191,11 +196,22 @@ public class ImageViewerController extends Controller implements View.OnClickLis
}
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);
endAnimation = new AnimatorSet();
if (!setTransitionViewData(startImage) || bitmap == null) {
endAnimation = new AnimatorSet();
ValueAnimator backgroundAlpha = ValueAnimator.ofFloat(1f, 0f);
backgroundAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
@ -209,18 +225,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
.with(ObjectAnimator.ofFloat(previewImage, View.ALPHA, 1f, 0f))
.with(backgroundAlpha);
endAnimation.setDuration(TRANSITION_DURATION);
endAnimation.setInterpolator(new DecelerateInterpolator());
endAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
previewOutAnimationEnded();
}
});
endAnimation.start();
} else {
endAnimation = new AnimatorSet();
ValueAnimator progress = ValueAnimator.ofFloat(1f, 0f);
progress.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
@ -231,21 +236,16 @@ public class ImageViewerController extends Controller implements View.OnClickLis
});
endAnimation.play(progress);
endAnimation.setDuration(TRANSITION_DURATION);
endAnimation.setInterpolator(new DecelerateInterpolator());
endAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
previewCallback.onPreviewCreate(ImageViewerController.this);
}
@Override
public void onAnimationEnd(Animator animation) {
previewOutAnimationEnded();
}
});
endAnimation.start();
}
endAnimation.setDuration(TRANSITION_DURATION);
endAnimation.setInterpolator(new DecelerateInterpolator());
endAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
previewOutAnimationEnded();
}
});
endAnimation.start();
}
private void previewOutAnimationEnded() {

@ -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;
import android.content.Context;
@ -20,54 +5,72 @@ import android.util.AttributeSet;
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
public class CustomScaleImageView extends SubsamplingScaleImageView {
private InitedCallback initCallback;
private static final String TAG = "CustomScaleImageView";
private Callback callback;
public CustomScaleImageView(Context context, AttributeSet attr) {
super(context, attr);
init();
}
public CustomScaleImageView(Context context) {
super(context);
init();
}
public void setInitCallback(InitedCallback initCallback) {
this.initCallback = initCallback;
public void setCallback(Callback callback) {
this.callback = callback;
}
@Override
protected void onImageReady() {
super.onImageReady();
AndroidUtils.runOnUiThread(new Runnable() {
private void init() {
setOnImageEventListener(new OnImageEventListener() {
@Override
public void run() {
if (initCallback != null) {
initCallback.onInit();
public void onReady() {
float scale = Math.min(getWidth() / (float) getSWidth(), getHeight() / (float) getSHeight());
setMinScale(scale);
if (getMaxScale() < scale * 2f) {
setMaxScale(scale * 2f);
}
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM);
if (callback != null) {
callback.onReady();
}
}
});
}
@Override
protected void onOutOfMemory() {
super.onOutOfMemory();
@Override
public void onImageLoaded() {
}
AndroidUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
if (initCallback != null) {
initCallback.onOutOfMemory();
public void onPreviewLoadError(Exception e) {
}
@Override
public void onImageLoadError(Exception e) {
Logger.w(TAG, "onImageLoadError", e);
if (callback != null) {
callback.onError(true);
}
}
@Override
public void onTileLoadError(Exception e) {
Logger.w(TAG, "onTileLoadError", e);
if (callback != null) {
callback.onError(false);
}
}
});
}
public interface InitedCallback {
public void onInit();
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.toolbox.ImageLoader.ImageContainer;
import com.davemorrissey.labs.subscaleview.ImageSource;
import org.floens.chan.ChanApplication;
import org.floens.chan.R;
@ -51,7 +52,7 @@ import pl.droidsonroids.gif.GifImageView;
public class MultiImageView extends FrameLayout implements View.OnClickListener {
public enum Mode {
UNLOADED, LOWRES, BIGIMAGE
UNLOADED, LOWRES, BIGIMAGE, GIF, MOVIE
}
private static final String TAG = "MultiImageView";
@ -97,32 +98,31 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
return postImage;
}
public void setMode(Mode mode) {
if (this.mode != mode) {
final Mode previousTargetMode = this.mode;
this.mode = mode;
Logger.d(TAG, "Changing mode from " + previousTargetMode + "to " + mode + " for " + postImage.thumbnailUrl);
if (mode == Mode.LOWRES) {
AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
setThumbnail(postImage.thumbnailUrl);
return false;
}
});
} else if (mode == Mode.BIGIMAGE) {
if (postImage.type == PostImage.Type.STATIC) {
AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
public void setMode(final Mode newMode) {
if (this.mode != newMode) {
Logger.d(TAG, "Changing mode from " + this.mode + " to " + newMode + " for " + postImage.thumbnailUrl);
this.mode = newMode;
AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
switch (newMode) {
case LOWRES:
setThumbnail(postImage.thumbnailUrl);
break;
case BIGIMAGE:
setBigImage(postImage.imageUrl);
return false;
}
});
} else {
Logger.e(TAG, "postImage type not STATIC, not changing to BIGIMAGE mode!");
break;
case GIF:
setGif(postImage.imageUrl);
break;
case MOVIE:
setVideo(postImage.imageUrl);
break;
}
return false;
}
}
});
}
}
@ -134,6 +134,16 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
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) {
if (getWidth() == 0 || getHeight() == 0) {
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)) {
ImageView thumbnail = new ImageView(getContext());
thumbnail.setImageBitmap(response.getBitmap());
onModeLoaded(Mode.LOWRES, thumbnail);
}
}
@ -197,14 +208,14 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
public void setBigImageFile(File file) {
final CustomScaleImageView image = new CustomScaleImageView(getContext());
image.setImageFile(file.getAbsolutePath());
image.setImage(ImageSource.uri(file.getAbsolutePath()));
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
public void onInit() {
public void onReady() {
if (!hasContent || mode == Mode.BIGIMAGE) {
callback.setProgress(MultiImageView.this, false);
onModeLoaded(Mode.BIGIMAGE, image);
@ -212,8 +223,8 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
}
@Override
public void onOutOfMemory() {
onOutOfMemoryError();
public void onError(boolean wasInitial) {
onBigImageError(wasInitial);
}
});
}
@ -239,7 +250,9 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
@Override
public void onSuccess(File file) {
gifRequest = null;
setGifFile(file);
if (!hasContent || mode == Mode.GIF) {
setGifFile(file);
}
}
@Override
@ -271,8 +284,7 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
GifImageView view = new GifImageView(getContext());
view.setImageDrawable(drawable);
view.setLayoutParams(AndroidUtils.MATCH_PARAMS);
setView(view);
onModeLoaded(Mode.GIF, view);
}
public void setVideo(String videoUrl) {
@ -291,7 +303,9 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
@Override
public void onSuccess(File file) {
videoRequest = null;
setVideoFile(file);
if (!hasContent || mode == Mode.MOVIE) {
setVideoFile(file);
}
}
@Override
@ -316,6 +330,8 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
} catch (ActivityNotFoundException e) {
Toast.makeText(getContext(), R.string.open_link_failed, Toast.LENGTH_SHORT).show();
}
// TODO: check this
onModeLoaded(Mode.GIF, videoView);
} else {
Context proxyContext = new NoMusicServiceCommandContext(getContext());
@ -332,7 +348,7 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
@Override
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
callback.onVideoLoaded(MultiImageView.this);
onModeLoaded(Mode.MOVIE, videoView);
}
});
videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@ -346,7 +362,7 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
videoView.setVideoPath(file.getAbsolutePath());
setView(videoView);
addView(videoView, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER));
videoView.start();
}
@ -356,22 +372,32 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
return videoView;
}
public void onError() {
private void onError() {
Toast.makeText(getContext(), R.string.image_preview_failed, Toast.LENGTH_SHORT).show();
callback.setProgress(this, false);
}
public void onNotFoundError() {
private void onNotFoundError() {
callback.setProgress(this, false);
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();
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() {
if (thumbnailRequest != null) {
thumbnailRequest.cancelRequest();
}
if (bigImageRequest != null) {
bigImageRequest.cancel(true);
}
@ -394,14 +420,27 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
cancelLoad();
}
private void setView(View view) {
removeAllViews();
addView(view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
private void onModeLoaded(Mode mode) {
onModeLoaded(mode, null);
}
private void onModeLoaded(Mode mode, View view) {
removeAllViews();
addView(view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
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);
}
}
hasContent = true;
callback.onModeLoaded(this, mode);
}

@ -23,6 +23,10 @@ public class TransitionImageView extends View {
private PointF sourceOverlap = new PointF();
private RectF destClip = new RectF();
private float progress;
private float stateScale;
private float stateBitmapScaleDiff;
private PointF stateBitmapSize;
private PointF statePos;
public TransitionImageView(Context context) {
super(context);
@ -43,15 +47,19 @@ public class TransitionImageView extends View {
this.bitmap = bitmap;
bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
if (stateBitmapSize != null) {
stateBitmapScaleDiff = stateBitmapSize.x / bitmap.getWidth();
}
int[] myLoc = new int[2];
getLocationInWindow(myLoc);
float globalOffsetX = windowLocation.x - myLoc[0];
float globalOffsetY = windowLocation.y - myLoc[1];
// Get the coords in the image view with the center crop method
float scaleX = (float) viewSize.x / (float) bitmap.getWidth();
float scaleY = (float) viewSize.y / (float) bitmap.getHeight();
float scale = scaleX > scaleY ? scaleX : scaleY;
float scale = Math.max(
(float) viewSize.x / (float) bitmap.getWidth(),
(float) viewSize.y / (float) bitmap.getHeight());
float scaledX = bitmap.getWidth() * scale;
float scaledY = bitmap.getHeight() * scale;
float offsetX = (scaledX - viewSize.x) * 0.5f;
@ -66,30 +74,53 @@ public class TransitionImageView extends View {
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) {
this.progress = progress;
// Center inside method
float destScale = Math.min(
(float) getWidth() / (float) bitmap.getWidth(),
(float) getHeight() / (float) bitmap.getHeight());
float destOffsetX = (getWidth() - bitmap.getWidth() * destScale) * 0.5f;
float destOffsetY = (getHeight() - bitmap.getHeight() * destScale) * 0.5f;
float destRight = bitmap.getWidth() * destScale + destOffsetX;
float destBottom = bitmap.getHeight() * destScale + destOffsetY;
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
float selfWidth = getWidth();
float selfHeight = getHeight();
float destScale = Math.min(
selfWidth / (float) bitmap.getWidth(),
selfHeight / (float) bitmap.getHeight());
output = new RectF(
(selfWidth - bitmap.getWidth() * destScale) * 0.5f,
(selfHeight - bitmap.getHeight() * destScale) * 0.5f, 0, 0);
output.right = bitmap.getWidth() * destScale + output.left;
output.bottom = bitmap.getHeight() * destScale + output.top;
}
// 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);
float left = sourceImageRect.left + (destOffsetX - sourceImageRect.left) * progress;
float top = sourceImageRect.top + (destOffsetY - sourceImageRect.top) * progress;
float right = sourceImageRect.right + (destRight - sourceImageRect.right) * progress;
float bottom = sourceImageRect.bottom + (destBottom - sourceImageRect.bottom) * progress;
destRect.set(output);
destRect.set(left, top, right, bottom);
matrix.setRectToRect(bitmapRect, destRect, Matrix.ScaleToFit.FILL);
destClip.set(
left + sourceOverlap.x * (1f - progress),
top + sourceOverlap.y * (1f - progress),
right - sourceOverlap.x * (1f - progress),
bottom - sourceOverlap.y * (1f - progress)
output.left + sourceOverlap.x * (1f - progress),
output.top + sourceOverlap.y * (1f - progress),
output.right - sourceOverlap.x * (1f - progress),
output.bottom - sourceOverlap.y * (1f - progress)
);
invalidate();
@ -100,7 +131,6 @@ public class TransitionImageView extends View {
super.onDraw(canvas);
if (bitmap != null) {
matrix.setRectToRect(bitmapRect, destRect, Matrix.ScaleToFit.FILL);
canvas.save();
if (progress < 1f) {
canvas.clipRect(destClip);
@ -114,4 +144,8 @@ public class TransitionImageView extends View {
paint.setAntiAlias(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_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_open_failed">Failed to open image</string>

Loading…
Cancel
Save