Add fancy imageviewer transitions

tempwork
Floens 10 years ago
parent f8213fef73
commit 2ae40e97f7
  1. 52
      Clover/app/src/main/java/org/floens/chan/controller/Controller.java
  2. 7
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  3. 16
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  4. 247
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewController.java
  5. 52
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  6. 15
      Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java
  7. 251
      Clover/app/src/main/java/org/floens/chan/ui/layout/ImageViewLayout.java
  8. 11
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  9. 10
      Clover/app/src/main/java/org/floens/chan/ui/transition/ImageTransition.java
  10. 37
      Clover/app/src/main/java/org/floens/chan/ui/view/ClippingImageView.java
  11. 4
      Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java
  12. 17
      Clover/app/src/main/java/org/floens/chan/utils/AnimationUtils.java
  13. 29
      Clover/app/src/main/res/layout/controller_view_image.xml
  14. 28
      Clover/app/src/main/res/layout/image_view_layout.xml

@ -38,6 +38,7 @@ public abstract class Controller {
// Controller (for presenting) members // Controller (for presenting) members
public Controller presentingController; public Controller presentingController;
public Controller presentedController;
public Controller(Context context) { public Controller(Context context) {
this.context = context; this.context = context;
@ -67,32 +68,49 @@ public abstract class Controller {
} }
public void presentController(Controller controller) { public void presentController(Controller controller) {
presentController(controller, true);
}
public void presentController(Controller controller, boolean animated) {
ViewGroup contentView = ((BoardActivity) context).getContentView(); ViewGroup contentView = ((BoardActivity) context).getContentView();
presentedController = controller;
controller.presentingController = this; controller.presentingController = this;
ControllerTransition transition = new FadeInTransition(); if (animated) {
transition.setCallback(new ControllerTransition.Callback() { ControllerTransition transition = new FadeInTransition();
@Override transition.setCallback(new ControllerTransition.Callback() {
public void onControllerTransitionCompleted(ControllerTransition transition) { @Override
ControllerLogic.finishTransition(transition); public void onControllerTransitionCompleted(ControllerTransition transition) {
} ControllerLogic.finishTransition(transition);
}); }
ControllerLogic.startTransition(null, controller, false, true, contentView, transition); });
ControllerLogic.startTransition(null, controller, false, true, contentView, transition);
} else {
ControllerLogic.transition(null, controller, false, true, contentView);
}
((BoardActivity) context).addController(controller); ((BoardActivity) context).addController(controller);
} }
public void stopPresenting() { public void stopPresenting() {
stopPresenting(true);
}
public void stopPresenting(boolean animated) {
ViewGroup contentView = ((BoardActivity) context).getContentView(); ViewGroup contentView = ((BoardActivity) context).getContentView();
ControllerTransition transition = new FadeOutTransition(); if (animated) {
transition.setCallback(new ControllerTransition.Callback() { ControllerTransition transition = new FadeOutTransition();
@Override transition.setCallback(new ControllerTransition.Callback() {
public void onControllerTransitionCompleted(ControllerTransition transition) { @Override
ControllerLogic.finishTransition(transition); public void onControllerTransitionCompleted(ControllerTransition transition) {
} ControllerLogic.finishTransition(transition);
}); }
ControllerLogic.startTransition(this, null, true, false, contentView, transition); });
((BoardActivity) context).removeController(Controller.this); ControllerLogic.startTransition(this, null, true, false, contentView, transition);
} else {
ControllerLogic.transition(this, null, true, false, contentView);
}
((BoardActivity) context).removeController(this);
} }
public View inflateRes(int resId) { public View inflateRes(int resId) {

@ -19,6 +19,7 @@ package org.floens.chan.core.presenter;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
import android.widget.ImageView;
import com.android.volley.VolleyError; import com.android.volley.VolleyError;
@ -134,7 +135,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
} }
@Override @Override
public void onThumbnailClicked(Post post) { public void onThumbnailClicked(Post post, ImageView thumbnail) {
List<PostImage> images = new ArrayList<>(); List<PostImage> images = new ArrayList<>();
int index = -1; int index = -1;
for (int i = 0; i < chanLoader.getThread().posts.size(); i++) { for (int i = 0; i < chanLoader.getThread().posts.size(); i++) {
@ -147,7 +148,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
} }
} }
threadPresenterCallback.showImages(images, index); threadPresenterCallback.showImages(images, index, thumbnail);
} }
@Override @Override
@ -326,6 +327,6 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
public void showPostsPopup(Post forPost, List<Post> posts); public void showPostsPopup(Post forPost, List<Post> posts);
public void showImages(List<PostImage> images, int index); public void showImages(List<PostImage> images, int index, ImageView thumbnail);
} }
} }

@ -28,11 +28,9 @@ import android.widget.TextView;
import org.floens.chan.ChanApplication; import org.floens.chan.ChanApplication;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls; import org.floens.chan.chan.ChanUrls;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.Board; import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.layout.ThreadLayout; import org.floens.chan.ui.layout.ThreadLayout;
import org.floens.chan.ui.toolbar.ToolbarMenu; import org.floens.chan.ui.toolbar.ToolbarMenu;
import org.floens.chan.ui.toolbar.ToolbarMenuItem; import org.floens.chan.ui.toolbar.ToolbarMenuItem;
@ -43,14 +41,13 @@ import org.floens.chan.utils.AndroidUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class BrowseController extends Controller implements ToolbarMenuItem.ToolbarMenuItemCallback, ThreadLayout.ThreadLayoutCallback, FloatingMenu.FloatingMenuCallback, BoardManager.BoardChangeListener { public class BrowseController extends ThreadController implements ToolbarMenuItem.ToolbarMenuItemCallback, ThreadLayout.ThreadLayoutCallback, FloatingMenu.FloatingMenuCallback, BoardManager.BoardChangeListener {
private static final int REFRESH_ID = 1; private static final int REFRESH_ID = 1;
private static final int POST_ID = 2; private static final int POST_ID = 2;
private static final int SEARCH_ID = 101; private static final int SEARCH_ID = 101;
private static final int SHARE_ID = 102; private static final int SHARE_ID = 102;
private static final int SETTINGS_ID = 103; private static final int SETTINGS_ID = 103;
private ThreadLayout threadLayout;
private List<FloatingMenuItem> boardItems; private List<FloatingMenuItem> boardItems;
public BrowseController(Context context) { public BrowseController(Context context) {
@ -84,11 +81,6 @@ public class BrowseController extends Controller implements ToolbarMenuItem.Tool
overflow.setSubMenu(new FloatingMenu(context, overflow.getView(), items)); overflow.setSubMenu(new FloatingMenu(context, overflow.getView(), items));
threadLayout = new ThreadLayout(context);
threadLayout.setCallback(this);
view = threadLayout;
loadBoard(ChanApplication.getBoardManager().getSavedBoards().get(0)); loadBoard(ChanApplication.getBoardManager().getSavedBoards().get(0));
} }
@ -147,12 +139,6 @@ public class BrowseController extends Controller implements ToolbarMenuItem.Tool
navigationController.pushController(viewThreadController); navigationController.pushController(viewThreadController);
} }
@Override
public void showImages(List<PostImage> images, int index) {
ImageViewController imageViewController = new ImageViewController(context);
presentController(imageViewController);
}
@Override @Override
public void onBoardsChanged() { public void onBoardsChanged() {
loadBoards(); loadBoards();

@ -1,14 +1,40 @@
package org.floens.chan.ui.controller; package org.floens.chan.ui.controller;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.view.View; import android.view.View;
import android.widget.Button; import android.view.Window;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.Controller; import org.floens.chan.controller.Controller;
import org.floens.chan.ui.view.ClippingImageView;
import org.floens.chan.utils.AnimationUtils;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AnimationUtils.calculateBoundsAnimation;
public class ImageViewController extends Controller implements View.OnClickListener { public class ImageViewController extends Controller implements View.OnClickListener {
private Button button; private static final int DURATION = 165;
private static final float CLIP_DURATION_PERCENTAGE = 0.40f;
private static final float FINAL_ALPHA = 0.80f;
private ClippingImageView imageView;
private Callback callback;
private int statusBarColorPrevious;
private AnimatorSet startAnimation;
private AnimatorSet endAnimation;
public ImageViewController(Context context) { public ImageViewController(Context context) {
super(context); super(context);
@ -20,20 +46,227 @@ public class ImageViewController extends Controller implements View.OnClickListe
view = inflateRes(R.layout.controller_view_image); view = inflateRes(R.layout.controller_view_image);
button = (Button) view.findViewById(R.id.button); imageView = (ClippingImageView) view.findViewById(R.id.image);
button.setOnClickListener(this); view.setOnClickListener(this);
} }
@Override @Override
public boolean onBack() { public boolean onBack() {
stopPresenting(); removeImage();
return true; return true;
} }
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v == button) { removeImage();
stopPresenting(); }
public void setImage(Callback callback, final ImageView startImageView) {
this.callback = callback;
imageView.setImageDrawable(startImageView.getDrawable());
Rect startBounds = getStartImageViewBounds(startImageView);
final Rect endBounds = new Rect();
final Point globalOffset = new Point();
view.getGlobalVisibleRect(endBounds, globalOffset);
float startScale = calculateBoundsAnimation(startBounds, endBounds, globalOffset);
imageView.setPivotX(0f);
imageView.setPivotY(0f);
imageView.setX(startBounds.left);
imageView.setY(startBounds.top);
imageView.setScaleX(startScale);
imageView.setScaleY(startScale);
Rect clipStartBounds = new Rect(0, 0, (int) (startImageView.getWidth() / startScale), (int) (startImageView.getHeight() / startScale));
Window window = ((Activity) context).getWindow();
if (Build.VERSION.SDK_INT >= 21) {
statusBarColorPrevious = window.getStatusBarColor();
}
startAnimation(startBounds, endBounds, startScale, clipStartBounds);
}
public void removeImage() {
if (startAnimation != null || endAnimation != null) {
return;
} }
endAnimation();
// endAnimationEmpty();
}
private void startAnimation(Rect startBounds, Rect finalBounds, float startScale, final Rect clipStartBounds) {
startAnimation = new AnimatorSet();
ValueAnimator backgroundAlpha = ValueAnimator.ofFloat(0f, 1f);
backgroundAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setBackgroundAlpha((float) animation.getAnimatedValue());
}
});
final Rect clipRect = new Rect();
ValueAnimator clip = ValueAnimator.ofFloat(1f, 0f);
clip.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
AnimationUtils.getClippingBounds(clipStartBounds, imageView, clipRect, (float) animation.getAnimatedValue());
imageView.clip(clipRect);
}
});
startAnimation
.play(ObjectAnimator.ofFloat(imageView, View.X, startBounds.left, finalBounds.left).setDuration(DURATION))
.with(ObjectAnimator.ofFloat(imageView, View.Y, startBounds.top, finalBounds.top).setDuration(DURATION))
.with(ObjectAnimator.ofFloat(imageView, View.SCALE_X, startScale, 1f).setDuration(DURATION))
.with(ObjectAnimator.ofFloat(imageView, View.SCALE_Y, startScale, 1f).setDuration(DURATION))
.with(backgroundAlpha.setDuration(DURATION))
.with(clip.setDuration((long) (DURATION * CLIP_DURATION_PERCENTAGE)));
startAnimation.setInterpolator(new DecelerateInterpolator());
startAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
startAnimationEnd();
startAnimation = null;
}
});
startAnimation.start();
}
private void startAnimationEnd() {
imageView.setX(0f);
imageView.setY(0f);
imageView.setScaleX(1f);
imageView.setScaleY(1f);
}
private void endAnimation() {
ImageView startImage = getStartImageView();
Rect startBounds = null;
if (startImage != null) {
startBounds = getStartImageViewBounds(startImage);
}
if (startBounds == null) {
endAnimation = new AnimatorSet();
ValueAnimator backgroundAlpha = ValueAnimator.ofFloat(1f, 0f);
backgroundAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setBackgroundAlpha((float) animation.getAnimatedValue());
}
});
endAnimation
.play(ObjectAnimator.ofFloat(imageView, View.Y, imageView.getTop(), imageView.getTop() + dp(20)))
.with(ObjectAnimator.ofFloat(imageView, View.ALPHA, 1f, 0f))
.with(backgroundAlpha);
endAnimation.setDuration(DURATION);
endAnimation.setInterpolator(new DecelerateInterpolator());
endAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
endAnimationEnd();
}
});
endAnimation.start();
} else {
final Rect endBounds = new Rect();
final Point globalOffset = new Point();
view.getGlobalVisibleRect(endBounds, globalOffset);
float startScale = calculateBoundsAnimation(startBounds, endBounds, globalOffset);
endAnimation = new AnimatorSet();
ValueAnimator backgroundAlpha = ValueAnimator.ofFloat(1f, 0f);
backgroundAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setBackgroundAlpha((float) animation.getAnimatedValue());
}
});
final Rect clipStartBounds = new Rect(0, 0, (int) (startImage.getWidth() / startScale), (int) (startImage.getHeight() / startScale));
final Rect clipRect = new Rect();
ValueAnimator clip = ValueAnimator.ofFloat(0f, 1f);
clip.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
AnimationUtils.getClippingBounds(clipStartBounds, imageView, clipRect, (float) animation.getAnimatedValue());
imageView.clip(clipRect);
}
});
long clipDuration = (long) (DURATION * CLIP_DURATION_PERCENTAGE);
clip.setStartDelay(DURATION - clipDuration);
clip.setDuration(clipDuration);
endAnimation
.play(ObjectAnimator.ofFloat(imageView, View.X, startBounds.left).setDuration(DURATION))
.with(ObjectAnimator.ofFloat(imageView, View.Y, startBounds.top).setDuration(DURATION))
.with(ObjectAnimator.ofFloat(imageView, View.SCALE_X, 1f, startScale).setDuration(DURATION))
.with(ObjectAnimator.ofFloat(imageView, View.SCALE_Y, 1f, startScale).setDuration(DURATION))
.with(backgroundAlpha.setDuration(DURATION))
.with(clip);
endAnimation.setInterpolator(new DecelerateInterpolator());
endAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
endAnimationEnd();
}
});
endAnimation.start();
}
}
private void endAnimationEnd() {
Window window = ((Activity) context).getWindow();
if (Build.VERSION.SDK_INT >= 21) {
window.setStatusBarColor(statusBarColorPrevious);
}
callback.onImageViewLayoutDestroy(this);
stopPresenting(false);
}
private void setBackgroundAlpha(float alpha) {
alpha = alpha * FINAL_ALPHA;
view.setBackgroundColor(Color.argb((int) (alpha * 255f), 0, 0, 0));
if (Build.VERSION.SDK_INT >= 21) {
Window window = ((Activity) context).getWindow();
int r = (int) ((1f - alpha) * Color.red(statusBarColorPrevious));
int g = (int) ((1f - alpha) * Color.green(statusBarColorPrevious));
int b = (int) ((1f - alpha) * Color.blue(statusBarColorPrevious));
window.setStatusBarColor(Color.argb(255, r, g, b));
}
}
private Rect getStartImageViewBounds(ImageView image) {
Rect startBounds = new Rect();
if (image.getGlobalVisibleRect(startBounds)) {
AnimationUtils.adjustImageViewBoundsToDrawableBounds(image, startBounds);
return startBounds;
} else {
return null;
}
}
private ImageView getStartImageView() {
return callback.getImageView(this);
}
public interface Callback {
public ImageView getImageView(ImageViewController imageViewController);
public void onImageViewLayoutDestroy(ImageViewController imageViewController);
} }
} }

@ -0,0 +1,52 @@
package org.floens.chan.ui.controller;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.layout.ThreadLayout;
import org.floens.chan.utils.AndroidUtils;
import java.util.List;
public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewController.Callback {
protected ThreadLayout threadLayout;
private ImageView presentingImageView;
public ThreadController(Context context) {
super(context);
threadLayout = new ThreadLayout(context);
threadLayout.setCallback(this);
view = threadLayout;
}
@Override
public void showImages(List<PostImage> images, int index, final ImageView thumbnail) {
presentingImageView = thumbnail;
presentingImageView.setVisibility(View.INVISIBLE);
final ImageViewController imageViewController = new ImageViewController(context);
presentController(imageViewController, false);
AndroidUtils.waitForMeasure(imageViewController.view, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
imageViewController.setImage(ThreadController.this, thumbnail);
return true;
}
});
}
@Override
public ImageView getImageView(ImageViewController imageViewController) {
return presentingImageView;
}
@Override
public void onImageViewLayoutDestroy(ImageViewController imageViewController) {
presentingImageView.setVisibility(View.VISIBLE);
presentingImageView = null;
}
}

@ -22,15 +22,10 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.layout.ThreadLayout; import org.floens.chan.ui.layout.ThreadLayout;
import java.util.List; public class ViewThreadController extends ThreadController implements ThreadLayout.ThreadLayoutCallback {
public class ViewThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback {
private ThreadLayout threadLayout;
private Loadable loadable; private Loadable loadable;
public ViewThreadController(Context context) { public ViewThreadController(Context context) {
@ -45,9 +40,6 @@ public class ViewThreadController extends Controller implements ThreadLayout.Thr
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
threadLayout = new ThreadLayout(context);
threadLayout.setCallback(this);
view = threadLayout;
view.setBackgroundColor(0xffffffff); view.setBackgroundColor(0xffffffff);
threadLayout.getPresenter().bindLoadable(loadable); threadLayout.getPresenter().bindLoadable(loadable);
@ -71,9 +63,4 @@ public class ViewThreadController extends Controller implements ThreadLayout.Thr
.setMessage("/" + threadLoadable.board + "/" + threadLoadable.no) .setMessage("/" + threadLoadable.board + "/" + threadLoadable.no)
.show(); .show();
} }
@Override
public void showImages(List<PostImage> images, int index) {
}
} }

@ -1,251 +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.ui.layout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import org.floens.chan.R;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AnimationUtils.calculateBoundsAnimation;
public class ImageViewLayout extends FrameLayout implements View.OnClickListener {
private ImageView imageView;
private Callback callback;
private Drawable drawable;
private int statusBarColorPrevious;
private AnimatorSet startAnimation;
private AnimatorSet endAnimation;
public static ImageViewLayout attach(Window window) {
ImageViewLayout imageViewLayout = (ImageViewLayout) LayoutInflater.from(window.getContext()).inflate(R.layout.image_view_layout, null);
window.addContentView(imageViewLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return imageViewLayout;
}
public ImageViewLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
this.imageView = (ImageView) findViewById(R.id.image);
setOnClickListener(this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
return true;
}
@Override
public void onClick(View v) {
removeImage();
}
public void setImage(Callback callback, final Drawable drawable) {
this.callback = callback;
this.drawable = drawable;
this.imageView.setImageDrawable(drawable);
Rect startBounds = callback.getImageViewLayoutStartBounds();
final Rect endBounds = new Rect();
final Point globalOffset = new Point();
getGlobalVisibleRect(endBounds, globalOffset);
float startScale = calculateBoundsAnimation(startBounds, endBounds, globalOffset);
imageView.setPivotX(0f);
imageView.setPivotY(0f);
imageView.setX(startBounds.left);
imageView.setY(startBounds.top);
imageView.setScaleX(startScale);
imageView.setScaleY(startScale);
Window window = ((Activity) getContext()).getWindow();
if (Build.VERSION.SDK_INT >= 21) {
statusBarColorPrevious = window.getStatusBarColor();
}
startAnimation(startBounds, endBounds, startScale);
}
public void removeImage() {
if (startAnimation != null || endAnimation != null) {
return;
}
endAnimation();
// endAnimationEmpty();
}
private void startAnimation(Rect startBounds, Rect finalBounds, float startScale) {
startAnimation = new AnimatorSet();
ValueAnimator backgroundAlpha = ValueAnimator.ofFloat(0f, 1f);
backgroundAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setBackgroundAlpha((float) animation.getAnimatedValue());
}
});
startAnimation
.play(ObjectAnimator.ofFloat(imageView, View.X, startBounds.left, finalBounds.left))
.with(ObjectAnimator.ofFloat(imageView, View.Y, startBounds.top, finalBounds.top))
.with(ObjectAnimator.ofFloat(imageView, View.SCALE_X, startScale, 1f))
.with(ObjectAnimator.ofFloat(imageView, View.SCALE_Y, startScale, 1f))
.with(backgroundAlpha);
startAnimation.setDuration(200);
startAnimation.setInterpolator(new DecelerateInterpolator());
startAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
startAnimationEnd();
startAnimation = null;
}
});
startAnimation.start();
}
private void startAnimationEnd() {
imageView.setX(0f);
imageView.setY(0f);
imageView.setScaleX(1f);
imageView.setScaleY(1f);
// controller.setVisibility(false);
}
private void endAnimation() {
// controller.setVisibility(true);
Rect startBounds = callback.getImageViewLayoutStartBounds();
final Rect endBounds = new Rect();
final Point globalOffset = new Point();
getGlobalVisibleRect(endBounds, globalOffset);
float startScale = calculateBoundsAnimation(startBounds, endBounds, globalOffset);
endAnimation = new AnimatorSet();
ValueAnimator backgroundAlpha = ValueAnimator.ofFloat(1f, 0f);
backgroundAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setBackgroundAlpha((float) animation.getAnimatedValue());
}
});
endAnimation
.play(ObjectAnimator.ofFloat(imageView, View.X, startBounds.left))
.with(ObjectAnimator.ofFloat(imageView, View.Y, startBounds.top))
.with(ObjectAnimator.ofFloat(imageView, View.SCALE_X, 1f, startScale))
.with(ObjectAnimator.ofFloat(imageView, View.SCALE_Y, 1f, startScale))
.with(backgroundAlpha);
endAnimation.setDuration(200);
endAnimation.setInterpolator(new DecelerateInterpolator());
endAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
endAnimationEnd();
}
});
endAnimation.start();
}
private void endAnimationEmpty() {
endAnimation = new AnimatorSet();
ValueAnimator backgroundAlpha = ValueAnimator.ofFloat(1f, 0f);
backgroundAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setBackgroundAlpha((float) animation.getAnimatedValue());
}
});
endAnimation
.play(ObjectAnimator.ofFloat(imageView, View.Y, imageView.getTop(), imageView.getTop() + dp(20)))
.with(ObjectAnimator.ofFloat(imageView, View.ALPHA, 1f, 0f))
.with(backgroundAlpha);
endAnimation.setDuration(200);
endAnimation.setInterpolator(new DecelerateInterpolator());
endAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
endAnimationEnd();
}
});
endAnimation.start();
}
private void endAnimationEnd() {
Window window = ((Activity) getContext()).getWindow();
if (Build.VERSION.SDK_INT >= 21) {
window.setStatusBarColor(statusBarColorPrevious);
}
callback.onImageViewLayoutDestroy();
}
private void setBackgroundAlpha(float alpha) {
setBackgroundColor(Color.argb((int) (alpha * 255f), 0, 0, 0));
if (Build.VERSION.SDK_INT >= 21) {
Window window = ((Activity) getContext()).getWindow();
int r = (int) ((1f - alpha) * Color.red(statusBarColorPrevious));
int g = (int) ((1f - alpha) * Color.green(statusBarColorPrevious));
int b = (int) ((1f - alpha) * Color.blue(statusBarColorPrevious));
window.setStatusBarColor(Color.argb(255, r, g, b));
}
}
public interface Callback {
public Rect getImageViewLayoutStartBounds();
public void onImageViewLayoutDestroy();
}
}

@ -23,18 +23,19 @@ import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.Toast; import android.widget.Toast;
import com.android.volley.VolleyError; import com.android.volley.VolleyError;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.model.PostLinkable; import org.floens.chan.core.model.PostLinkable;
import org.floens.chan.core.presenter.ThreadPresenter; import org.floens.chan.core.presenter.ThreadPresenter;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.helper.PostPopupHelper; import org.floens.chan.ui.helper.PostPopupHelper;
import org.floens.chan.ui.view.LoadView; import org.floens.chan.ui.view.LoadView;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;
@ -163,8 +164,8 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
} }
@Override @Override
public void showImages(List<PostImage> images, int index) { public void showImages(List<PostImage> images, int index, ImageView thumbnail) {
callback.showImages(images, index); callback.showImages(images, index, thumbnail);
} }
private void switchVisible(boolean visible) { private void switchVisible(boolean visible) {
@ -177,6 +178,6 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
public interface ThreadLayoutCallback { public interface ThreadLayoutCallback {
public void openThread(Loadable threadLoadable); public void openThread(Loadable threadLoadable);
public void showImages(List<PostImage> images, int index); public void showImages(List<PostImage> images, int index, ImageView thumbnail);
} }
} }

@ -0,0 +1,10 @@
package org.floens.chan.ui.transition;
import org.floens.chan.controller.ControllerTransition;
public class ImageTransition extends ControllerTransition {
@Override
public void perform() {
}
}

@ -0,0 +1,37 @@
package org.floens.chan.ui.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.ImageView;
public class ClippingImageView extends ImageView {
private Rect clipRect = new Rect();
public ClippingImageView(Context context) {
super(context);
}
public ClippingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ClippingImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
if (!clipRect.isEmpty() && (clipRect.width() < getWidth() || clipRect.height() < getHeight())) {
canvas.clipRect(clipRect);
}
super.onDraw(canvas);
}
public void clip(Rect rect) {
clipRect.set(rect);
invalidate();
}
}

@ -323,7 +323,7 @@ public class PostView extends LinearLayout implements View.OnClickListener {
imageView.setOnClickListener(new View.OnClickListener() { imageView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
callback.onThumbnailClicked(post); callback.onThumbnailClicked(post, imageView);
} }
}); });
@ -491,7 +491,7 @@ public class PostView extends LinearLayout implements View.OnClickListener {
public void onPostClicked(Post post); public void onPostClicked(Post post);
public void onThumbnailClicked(Post post); public void onThumbnailClicked(Post post, ImageView thumbnail);
public void onShowPostReplies(Post post); public void onShowPostReplies(Post post);

@ -61,6 +61,23 @@ public class AnimationUtils {
return startScale; return startScale;
} }
public static void getClippingBounds(Rect clipStartBounds, ImageView view, Rect out, float progress) {
float[] f = new float[9];
view.getImageMatrix().getValues(f);
float imageWidth = view.getDrawable().getIntrinsicWidth() * f[Matrix.MSCALE_X];
float imageHeight = view.getDrawable().getIntrinsicHeight() * f[Matrix.MSCALE_Y];
float imageWidthDiff = (view.getWidth() - imageWidth) / 2f;
float imageHeightDiff = (view.getHeight() - imageHeight) / 2f;
float offsetWidth = ((imageWidth - clipStartBounds.right) / 2f) * progress;
float offsetHeight = ((imageHeight - clipStartBounds.bottom) / 2f) * progress;
out.set((int) (offsetWidth + imageWidthDiff),
(int) (offsetHeight + imageHeightDiff),
(int) (view.getWidth() - offsetWidth - imageWidthDiff),
(int) (view.getHeight() - offsetHeight - imageHeightDiff));
}
public static void adjustImageViewBoundsToDrawableBounds(ImageView imageView, Rect bounds) { public static void adjustImageViewBoundsToDrawableBounds(ImageView imageView, Rect bounds) {
float[] f = new float[9]; float[] f = new float[9];
imageView.getImageMatrix().getValues(f); imageView.getImageMatrix().getValues(f);

@ -1,29 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.floens.chan.ui.view.TouchBlockingFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="#ffffffff"
android:padding="16dp">
<LinearLayout <org.floens.chan.ui.view.ClippingImageView
android:id="@+id/image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
android:orientation="vertical">
<TextView </FrameLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, world!"
android:textColor="#dd000000"
android:textSize="20sp" />
<Button
android:id="@+id/button"
android:text="Back"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</org.floens.chan.ui.view.TouchBlockingFrameLayout>

@ -1,28 +0,0 @@
<?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/>.
-->
<org.floens.multipanetest.layout.ImageViewLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff000000">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</org.floens.multipanetest.layout.ImageViewLayout>
Loading…
Cancel
Save