Basic changing of modes for MultiImageView

tempwork
Floens 10 years ago
parent 3d6fe34518
commit 639d5cd8d3
  1. 135
      Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java
  2. 80
      Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java
  3. 53
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  4. 14
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java
  5. 15
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  6. 5
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java
  7. 61
      Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java
  8. 43
      Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java
  9. 8
      Clover/app/src/main/java/org/floens/chan/ui/view/ViewPagerAdapter.java
  10. 3
      Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java
  11. 3
      Clover/app/src/main/res/layout/controller_image_viewer.xml

@ -1,40 +1,163 @@
package org.floens.chan.core.presenter;
import android.support.v4.view.ViewPager;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.view.MultiImageView;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ImageViewerPresenter {
public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.OnPageChangeListener {
private final Callback callback;
private final boolean imageAutoLoad = ChanSettings.imageAutoLoad.get();
private boolean entering = true;
private boolean exiting = false;
private List<PostImage> images;
private int selectedIndex;
private boolean initalLowResLoaded = false;
private boolean changeViewsOnInTransitionEnd = false;
public ImageViewerPresenter(Callback callback) {
this.callback = callback;
}
public void showImages(List<PostImage> images, int index) {
callback.startPreviewInTransition();
this.images = images;
selectedIndex = index;
// Do this before the view is measured, to avoid it to always loading the first two pages
callback.setPagerItems(images, selectedIndex);
callback.setImageMode(images.get(selectedIndex), MultiImageView.Mode.LOWRES);
}
public void onViewMeasured() {
// Pager is measured, but still invisible
callback.startPreviewInTransition();
}
public void onInTransitionEnd() {
entering = false;
// Depends on what onModeLoaded did
if (changeViewsOnInTransitionEnd) {
callback.setPreviewVisibility(false);
callback.setPagerVisiblity(true);
}
}
public void onExit() {
if (exiting) return;
if (entering || exiting) return;
exiting = true;
callback.setPagerVisiblity(false);
callback.setPreviewVisibility(true);
callback.startPreviewOutTransition();
}
public void onInTransitionEnd() {
PostImage image = images.get(selectedIndex);
@Override
public void onPageSelected(int position) {
selectedIndex = position;
if (initalLowResLoaded) {
for (PostImage other : getOther(selectedIndex)) {
callback.setImageMode(other, MultiImageView.Mode.LOWRES);
}
callback.setImageMode(images.get(selectedIndex), MultiImageView.Mode.LOWRES);
}
// onModeLoaded will handle the else case
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
@Override
public void onModeLoaded(MultiImageView multiImageView, MultiImageView.Mode mode) {
if (mode == MultiImageView.Mode.LOWRES) {
// lowres is requested at the beginning of the transition,
// the lowres is loaded before the in transition or after
if (!initalLowResLoaded) {
initalLowResLoaded = true;
if (!entering) {
// Entering transition was already ended, switch now
callback.setPreviewVisibility(false);
callback.setPagerVisiblity(true);
} else {
// Wait for enter animation to finish before changing views
changeViewsOnInTransitionEnd = true;
}
// Transition ended or not, request loading the other side views to lowres
for (PostImage other : getOther(selectedIndex)) {
callback.setImageMode(other, MultiImageView.Mode.LOWRES);
}
// selectedIndex can be different than the initial one because of page changes before onModeLoaded was called,
// request a load of the current selectedIndex one here
callback.setImageMode(images.get(selectedIndex), MultiImageView.Mode.LOWRES);
}
// Initial load or not, transitioning or not, load the high res when the user setting says so after the low res
if (imageAutoLoad) {
multiImageView.setMode(MultiImageView.Mode.BIGIMAGE);
}
}
}
@Override
public void onTap(MultiImageView multiImageView) {
onExit();
}
@Override
public void setProgress(MultiImageView multiImageView, boolean progress) {
}
@Override
public void setLinearProgress(MultiImageView multiImageView, long current, long total, boolean done) {
}
@Override
public void onVideoLoaded(MultiImageView multiImageView) {
}
@Override
public void onVideoError(MultiImageView multiImageView, File video) {
}
private List<PostImage> getOther(int position) {
List<PostImage> other = new ArrayList<>(2);
if (position - 1 >= 0) {
other.add(images.get(position - 1));
}
if (position + 1 < images.size()) {
other.add(images.get(position + 1));
}
return other;
}
public interface Callback {
public void startPreviewInTransition();
public void startPreviewOutTransition();
public void setPreviewVisibility(boolean visible);
public void setPagerVisiblity(boolean visible);
public void setPagerItems(List<PostImage> images, int initialIndex);
public void setImageMode(PostImage postImage, MultiImageView.Mode mode);
public MultiImageView.Mode getImageMode(PostImage postImage);
}
}

@ -2,18 +2,26 @@ package org.floens.chan.ui.adapter;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.view.MultiImageView;
import org.floens.chan.ui.view.ViewPagerAdapter;
import org.floens.chan.utils.Logger;
import java.util.ArrayList;
import java.util.List;
public class ImageViewerAdapter extends ViewPagerAdapter {
private static final String TAG = "ImageViewerAdapter";
private final Context context;
private final List<PostImage> images;
private final MultiImageView.Callback multiImageViewCallback;
private List<MultiImageView> loadedViews = new ArrayList<>(3);
private List<ModeChange> pendingModeChanges = new ArrayList<>();
public ImageViewerAdapter(Context context, List<PostImage> images, MultiImageView.Callback multiImageViewCallback) {
this.context = context;
this.images = images;
@ -22,14 +30,84 @@ public class ImageViewerAdapter extends ViewPagerAdapter {
@Override
public View getView(int position) {
PostImage postImage = images.get(position);
MultiImageView view = new MultiImageView(context);
view.bindPostImage(images.get(position), multiImageViewCallback);
view.bindPostImage(postImage, multiImageViewCallback);
loadedViews.add(view);
Logger.test("getView: " + postImage.imageUrl + " " + postImage.type.toString());
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
PostImage postImage = ((MultiImageView)object).getPostImage();
Logger.test("destroyView: " + postImage.imageUrl + " " + postImage.type.toString());
//noinspection SuspiciousMethodCalls
if (!loadedViews.remove((View) object)) {
Logger.test("Nope");
}
}
@Override
public int getCount() {
return images.size();
}
@Override
public void finishUpdate(ViewGroup container) {
for (ModeChange change : pendingModeChanges) {
MultiImageView view = find(change.postImage);
if (view == null) {
Logger.w(TAG, "finishUpdate setMode view still not found");
} else {
view.setMode(change.mode);
}
}
pendingModeChanges.clear();
}
public void setMode(final PostImage postImage, MultiImageView.Mode mode) {
MultiImageView view = find(postImage);
if (view == null) {
Logger.w(TAG, "setMode view not found, scheduling it");
pendingModeChanges.add(new ModeChange(mode, postImage));
} else {
view.setMode(mode);
}
}
public MultiImageView.Mode getMode(PostImage postImage) {
MultiImageView view = find(postImage);
if (view == null) {
Logger.w(TAG, "getMode view not found");
return null;
} else {
return view.getMode();
}
}
public MultiImageView find(PostImage postImage) {
for (MultiImageView view : loadedViews) {
if (view.getPostImage() == postImage) {
return view;
}
}
return null;
}
private static class ModeChange {
public MultiImageView.Mode mode;
public PostImage postImage;
private ModeChange(MultiImageView.Mode mode, PostImage postImage) {
this.mode = mode;
this.postImage = postImage;
}
}
}

@ -19,11 +19,17 @@ import android.widget.ImageView;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
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.ClippingImageView;
import org.floens.chan.ui.view.MultiImageView;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.AnimationUtils;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AnimationUtils.calculateBoundsAnimation;
@ -36,7 +42,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
private AnimatorSet startPreviewAnimation;
private AnimatorSet endAnimation;
private Callback callback;
private PreviewCallback previewCallback;
private ImageViewerPresenter presenter;
private final Toolbar toolbar;
@ -60,6 +66,15 @@ public class ImageViewerController extends Controller implements View.OnClickLis
view.setOnClickListener(this);
previewImage = (ClippingImageView) view.findViewById(R.id.preview_image);
pager = (ViewPager) view.findViewById(R.id.pager);
pager.setOnPageChangeListener(presenter);
AndroidUtils.waitForMeasure(view, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
presenter.onViewMeasured();
return true;
}
});
}
@Override
@ -73,16 +88,38 @@ public class ImageViewerController extends Controller implements View.OnClickLis
return true;
}
public void setCallback(Callback callback) {
this.callback = callback;
public void setPreviewCallback(PreviewCallback previewCallback) {
this.previewCallback = previewCallback;
}
public ImageViewerPresenter getPresenter() {
return presenter;
}
public void setPreviewVisibility(boolean visible) {
previewImage.setVisibility(visible ? View.VISIBLE : View.GONE);
}
public void setPagerVisiblity(boolean visible) {
pager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
public void setPagerItems(List<PostImage> images, int initialIndex) {
ImageViewerAdapter adapter = new ImageViewerAdapter(context, images, presenter);
pager.setAdapter(adapter);
pager.setCurrentItem(initialIndex);
}
public void setImageMode(PostImage postImage, MultiImageView.Mode mode) {
((ImageViewerAdapter) pager.getAdapter()).setMode(postImage, mode);
}
public MultiImageView.Mode getImageMode(PostImage postImage) {
return ((ImageViewerAdapter) pager.getAdapter()).getMode(postImage);
}
public void startPreviewInTransition() {
ImageView previewImageView = callback.getPreviewImageStartView(this);
ImageView previewImageView = previewCallback.getPreviewImageStartView(this);
previewImage.setImageDrawable(previewImageView.getDrawable());
Rect startBounds = getImageViewBounds(previewImageView);
@ -148,7 +185,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
startPreviewAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
callback.onPreviewCreate(ImageViewerController.this);
previewCallback.onPreviewCreate(ImageViewerController.this);
}
@Override
@ -247,7 +284,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
private void previewOutAnimationEnded() {
setBackgroundAlpha(0f);
callback.onPreviewDestroy(this);
previewCallback.onPreviewDestroy(this);
navigationController.stopPresenting(false);
}
@ -293,14 +330,14 @@ public class ImageViewerController extends Controller implements View.OnClickLis
}
private ImageView getStartImageView() {
return callback.getPreviewImageStartView(this);
return previewCallback.getPreviewImageStartView(this);
}
private Window getWindow() {
return ((Activity) context).getWindow();
}
public interface Callback {
public interface PreviewCallback {
public ImageView getPreviewImageStartView(ImageViewerController imageViewerController);
public void onPreviewCreate(ImageViewerController imageViewerController);

@ -1,14 +1,12 @@
package org.floens.chan.ui.controller;
import android.content.Context;
import android.view.View;
import android.widget.FrameLayout;
import org.floens.chan.R;
import org.floens.chan.controller.NavigationController;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.toolbar.Toolbar;
import org.floens.chan.utils.AndroidUtils;
import java.util.List;
@ -33,14 +31,8 @@ public class ImageViewerNavigationController extends NavigationController {
pushController(imageViewerController, false);
}
public void showImages(final List<PostImage> images, final int index, final ImageViewerController.Callback callback) {
AndroidUtils.waitForMeasure(imageViewerController.view, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
imageViewerController.setCallback(callback);
imageViewerController.getPresenter().showImages(images, index);
return true;
}
});
public void showImages(final List<PostImage> images, final int index, final ImageViewerController.PreviewCallback previewCallback) {
imageViewerController.setPreviewCallback(previewCallback);
imageViewerController.getPresenter().showImages(images, index);
}
}

@ -10,7 +10,7 @@ import org.floens.chan.ui.layout.ThreadLayout;
import java.util.List;
public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.Callback {
public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.PreviewCallback {
protected ThreadLayout threadLayout;
private ImageView presentingImageView;
@ -24,11 +24,14 @@ public abstract class ThreadController extends Controller implements ThreadLayou
@Override
public void showImages(List<PostImage> images, int index, final ImageView thumbnail) {
presentingImageView = thumbnail;
final ImageViewerNavigationController imageViewerNavigationController = new ImageViewerNavigationController(context);
presentController(imageViewerNavigationController, false);
imageViewerNavigationController.showImages(images, index, this);
// Just ignore the showImages request when the image is not loaded
if (thumbnail.getDrawable() != null) {
presentingImageView = thumbnail;
final ImageViewerNavigationController imageViewerNavigationController = new ImageViewerNavigationController(context);
presentController(imageViewerNavigationController, false);
imageViewerNavigationController.showImages(images, index, this);
}
}
@Override

@ -280,6 +280,11 @@ public class ImageViewFragment extends Fragment implements Callback {
}
}
@Override
public void onModeLoaded(MultiImageView multiImageView, MultiImageView.Mode mode) {
}
private void showVideoWarning() {
LinearLayout notice = new LinearLayout(context);
notice.setOrientation(LinearLayout.VERTICAL);

@ -81,43 +81,54 @@ public class LoadView extends FrameLayout {
newView = progressBar;
}
// Readded while still running a add/remove animation for the new view
// This also removes the new view from this view
AnimatorSet out = animatorsOut.remove(newView);
if (out != null) {
out.cancel();
}
AnimatorSet in = animatorsIn.remove(newView);
if (in != null) {
in.cancel();
}
if (animate) {
// Readded while still running a add/remove animation for the new view
// This also removes the new view from this view
AnimatorSet out = animatorsOut.remove(newView);
if (out != null) {
out.cancel();
}
// Add fade out animations for all remaining view
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child != null) {
AnimatorSet inSet = animatorsIn.remove(child);
if (inSet != null) {
inSet.cancel();
}
AnimatorSet in = animatorsIn.remove(newView);
if (in != null) {
in.cancel();
}
if (!animatorsOut.containsKey(child)) {
animateViewOut(child);
// Add fade out animations for all remaining view
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child != null) {
AnimatorSet inSet = animatorsIn.remove(child);
if (inSet != null) {
inSet.cancel();
}
if (!animatorsOut.containsKey(child)) {
animateViewOut(child);
}
}
}
}
addView(newView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(newView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
if (animate) {
// Fade view in
if (newView.getAlpha() == 1f) {
newView.setAlpha(0f);
}
animateViewIn(newView);
} else {
newView.setAlpha(1f);
for (AnimatorSet set : animatorsIn.values()) {
set.cancel();
}
animatorsIn.clear();
for (AnimatorSet set : animatorsOut.values()) {
set.cancel();
}
animatorsOut.clear();
removeAllViews();
addView(newView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
}

@ -49,14 +49,20 @@ import pl.droidsonroids.gif.GifDrawable;
import pl.droidsonroids.gif.GifImageView;
public class MultiImageView extends LoadView implements View.OnClickListener {
public enum Mode {
UNLOADED, LOWRES, BIGIMAGE
}
private static final String TAG = "MultiImageView";
private PostImage postImage;
private Callback callback;
private boolean thumbnailNeeded = true;
private Mode mode = Mode.UNLOADED;
private boolean thumbnailNeeded = true;
private Future<?> request;
private VideoView videoView;
public MultiImageView(Context context) {
@ -83,11 +89,36 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
this.callback = callback;
}
public void loadLowRes() {
public PostImage getPostImage() {
return postImage;
}
public void loadHighRes() {
public void setMode(Mode mode) {
if (this.mode != mode) {
this.mode = mode;
if (mode == Mode.LOWRES) {
Logger.d(TAG, "Changing mode to LOWRES for " + postImage.thumbnailUrl);
AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
setThumbnail(postImage.thumbnailUrl);
return false;
}
});
} else if (mode == Mode.BIGIMAGE) {
Logger.d(TAG, "Changing mode to BIGIMAGE for " + postImage.thumbnailUrl);
// Always done after at least LOWRES, so the view is measured
if (postImage.type == PostImage.Type.STATIC) {
setBigImage(postImage.imageUrl);
} else {
Logger.e(TAG, "postImage type not STATIC, not changing to BIGIMAGE mode!");
}
}
}
}
public Mode getMode() {
return mode;
}
public void setCallback(Callback callback) {
@ -114,6 +145,7 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
thumbnail.setImageBitmap(response.getBitmap());
thumbnail.setLayoutParams(AndroidUtils.MATCH_PARAMS);
setView(thumbnail, false);
callback.onModeLoaded(MultiImageView.this, Mode.LOWRES);
}
}
}, getWidth(), getHeight());
@ -121,7 +153,7 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
public void setBigImage(String imageUrl) {
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 big image");
return;
}
@ -166,6 +198,7 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
removeAllViews();
addView(image);
callback.setProgress(MultiImageView.this, false);
callback.onModeLoaded(MultiImageView.this, Mode.BIGIMAGE);
}
@Override
@ -347,6 +380,8 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
public void onVideoLoaded(MultiImageView multiImageView);
public void onVideoError(MultiImageView multiImageView, File video);
public void onModeLoaded(MultiImageView multiImageView, Mode mode);
}
public static class NoMusicServiceCommandContext extends ContextWrapper {

@ -9,12 +9,16 @@ import org.floens.chan.utils.AndroidUtils;
public abstract class ViewPagerAdapter extends PagerAdapter {
@Override
public Object instantiateItem(ViewGroup container, int position) {
return getView(position);
View view = getView(position);
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
AndroidUtils.removeFromParentView((View) object);
container.removeView((View)object);
}
public boolean isViewFromObject(View view, Object object) {

@ -201,11 +201,12 @@ public class AndroidUtils {
observer.removeOnPreDrawListener(this);
}
boolean ret = false;
boolean ret;
try {
ret = callback.onMeasured(view);
} catch (Exception e) {
Log.i("AndroidUtils", "Exception in onMeasured", e);
throw e;
}
return ret;

@ -8,8 +8,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ViewPager
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="match_parent" />

Loading…
Cancel
Save