Skip to content

Commit

Permalink
iOS version, more methods for Android PhotoView from default ImageView
Browse files Browse the repository at this point in the history
  • Loading branch information
alwx committed Jun 19, 2016
1 parent bd13c49 commit ed4ccc3
Show file tree
Hide file tree
Showing 11 changed files with 575 additions and 110 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Provides custom Image view for React Native that allows to perform
pinch-to-zoom on images.
*On iOS* it uses native Image wrapped in ScrollView;
*On Android* it uses PhotoDraweeView.

It uses [PhotoDraweeView](https://github.com/ongakuer/PhotoDraweeView).

## Installation

Expand Down
84 changes: 84 additions & 0 deletions android/src/main/java/com/reactnative/photoview/ImageEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

package com.reactnative.photoview;

import android.support.annotation.IntDef;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class ImageEvent extends Event<ImageEvent> {
@IntDef({ON_ERROR, ON_LOAD, ON_LOAD_END, ON_LOAD_START, ON_TAP, ON_VIEW_TAP, ON_SCALE})
@Retention(RetentionPolicy.SOURCE)
@interface ImageEventType {}

public static final int ON_ERROR = 1;
public static final int ON_LOAD = 2;
public static final int ON_LOAD_END = 3;
public static final int ON_LOAD_START = 4;
public static final int ON_TAP = 5;
public static final int ON_VIEW_TAP = 6;
public static final int ON_SCALE = 7;

private final int mEventType;
private WritableMap mMap;

public ImageEvent(int viewId, long timestampMs, @ImageEventType int eventType) {
super(viewId, timestampMs);
mEventType = eventType;
mMap = null;
}

public static String eventNameForType(@ImageEventType int eventType) {
switch(eventType) {
case ON_ERROR:
return "topError";
case ON_LOAD:
return "topLoad";
case ON_LOAD_END:
return "topLoadEnd";
case ON_LOAD_START:
return "topLoadStart";
case ON_TAP:
return "topTap";
case ON_VIEW_TAP:
return "topViewTap";
case ON_SCALE:
return "topScale";
default:
throw new IllegalStateException("Invalid image event: " + Integer.toString(eventType));
}
}

@Override
public String getEventName() {
return ImageEvent.eventNameForType(mEventType);
}

@Override
public short getCoalescingKey() {
// Intentionally casting mEventType because it is guaranteed to be small
// enough to fit into short.
return (short) mEventType;
}

@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mMap);
}

public ImageEvent setExtras(WritableMap map){
this.mMap = map;
return this;
}
}
220 changes: 220 additions & 0 deletions android/src/main/java/com/reactnative/photoview/PhotoView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package com.reactnative.photoview;

import android.content.Context;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.drawable.AutoRotateDrawable;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.SystemClock;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
import me.relex.photodraweeview.OnPhotoTapListener;
import me.relex.photodraweeview.OnScaleChangeListener;
import me.relex.photodraweeview.OnViewTapListener;
import me.relex.photodraweeview.PhotoDraweeView;

import javax.annotation.Nullable;

import static com.facebook.react.views.image.ReactImageView.REMOTE_IMAGE_FADE_DURATION_MS;

/**
* @author alwx (https://github.com/alwx)
* @version 1.0
*/
public class PhotoView extends PhotoDraweeView {
private Uri mUri;
private boolean mIsDirty;
private boolean mIsLocalImage;
private Drawable mLoadingImageDrawable;
private PipelineDraweeControllerBuilder mDraweeControllerBuilder;
private int mFadeDurationMs = -1;
private ControllerListener mControllerListener;

public PhotoView(Context context, GenericDraweeHierarchy hierarchy) {
super(context, hierarchy);
}

public PhotoView(Context context) {
super(context);
}

public PhotoView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public PhotoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public void setSource(@Nullable String source,
@NonNull PipelineDraweeControllerBuilder builder,
@NonNull ResourceDrawableIdHelper resourceDrawableIdHelper) {
mDraweeControllerBuilder = builder;
mUri = null;
if (source != null) {
try {
mUri = Uri.parse(source);
// Verify scheme is set, so that relative uri (used by static resources) are not handled.
if (mUri.getScheme() == null) {
mUri = null;
}
} catch (Exception e) {
// ignore malformed uri, then attempt to extract resource ID.
}
if (mUri == null) {
mUri = resourceDrawableIdHelper.getResourceDrawableUri(getContext(), source);
mIsLocalImage = true;
} else {
mIsLocalImage = false;
}
}
mIsDirty = true;
}

public void setLoadingIndicatorSource(@Nullable String name,
ResourceDrawableIdHelper resourceDrawableIdHelper) {
Drawable drawable = resourceDrawableIdHelper.getResourceDrawable(getContext(), name);
mLoadingImageDrawable =
drawable != null ? (Drawable) new AutoRotateDrawable(drawable, 1000) : null;
mIsDirty = true;
}

public void setFadeDuration(int durationMs) {
mFadeDurationMs = durationMs;
// no worth marking as dirty if it already rendered..
}

public void setShouldNotifyLoadEvents(boolean shouldNotify) {
if (!shouldNotify) {
mControllerListener = null;
} else {
final EventDispatcher eventDispatcher = ((ReactContext) getContext())
.getNativeModule(UIManagerModule.class).getEventDispatcher();
mControllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onSubmit(String id, Object callerContext) {
eventDispatcher.dispatchEvent(
new ImageEvent(getId(), SystemClock.nanoTime(), ImageEvent.ON_LOAD_START)
);
}

@Override
public void onFinalImageSet(
String id,
@Nullable final ImageInfo imageInfo,
@Nullable Animatable animatable) {
if (imageInfo != null) {
eventDispatcher.dispatchEvent(
new ImageEvent(getId(), SystemClock.nanoTime(), ImageEvent.ON_LOAD)
);
eventDispatcher.dispatchEvent(
new ImageEvent(getId(), SystemClock.nanoTime(), ImageEvent.ON_LOAD_END)
);
}
}

@Override
public void onFailure(String id, Throwable throwable) {
eventDispatcher.dispatchEvent(
new ImageEvent(getId(), SystemClock.nanoTime(), ImageEvent.ON_ERROR)
);
eventDispatcher.dispatchEvent(
new ImageEvent(getId(), SystemClock.nanoTime(), ImageEvent.ON_LOAD_END)
);
}
};
}
mIsDirty = true;
}

public void maybeUpdateView() {
if (!mIsDirty) {
return;
}

GenericDraweeHierarchy hierarchy = getHierarchy();
if (mLoadingImageDrawable != null) {
hierarchy.setPlaceholderImage(mLoadingImageDrawable, ScalingUtils.ScaleType.CENTER);
}
hierarchy.setFadeDuration(
mFadeDurationMs >= 0
? mFadeDurationMs
: mIsLocalImage ? 0 : REMOTE_IMAGE_FADE_DURATION_MS);

mDraweeControllerBuilder.setUri(mUri);
mDraweeControllerBuilder.setOldController(getController());
mDraweeControllerBuilder.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
super.onFinalImageSet(id, imageInfo, animatable);
if (imageInfo == null) {
return;
}
update(imageInfo.getWidth(), imageInfo.getHeight());
}
});

if (mControllerListener != null) {
mDraweeControllerBuilder.setControllerListener(mControllerListener);
}

setController(mDraweeControllerBuilder.build());
setViewCallbacks();

mIsDirty = false;
}

private void setViewCallbacks() {
final EventDispatcher eventDispatcher = ((ReactContext) getContext())
.getNativeModule(UIManagerModule.class).getEventDispatcher();

setOnPhotoTapListener(new OnPhotoTapListener() {
@Override
public void onPhotoTap(View view, float x, float y) {
WritableMap scaleChange = Arguments.createMap();
scaleChange.putDouble("x", x);
scaleChange.putDouble("y", y);
eventDispatcher.dispatchEvent(
new ImageEvent(getId(), SystemClock.nanoTime(), ImageEvent.ON_TAP).setExtras(scaleChange)
);
}
});

setOnScaleChangeListener(new OnScaleChangeListener() {
@Override
public void onScaleChange(float scaleFactor, float focusX, float focusY) {
WritableMap scaleChange = Arguments.createMap();
scaleChange.putDouble("scaleFactor", scaleFactor);
scaleChange.putDouble("focusX", focusX);
scaleChange.putDouble("focusY", focusY);
eventDispatcher.dispatchEvent(
new ImageEvent(getId(), SystemClock.nanoTime(), ImageEvent.ON_SCALE).setExtras(scaleChange)
);
}
});

setOnViewTapListener(new OnViewTapListener() {
@Override
public void onViewTap(View view, float x, float y) {
WritableMap scaleChange = Arguments.createMap();
scaleChange.putDouble("x", x);
scaleChange.putDouble("y", y);
eventDispatcher.dispatchEvent(
new ImageEvent(getId(), SystemClock.nanoTime(), ImageEvent.ON_TAP).setExtras(scaleChange)
);
}
});
}
}
Loading

0 comments on commit ed4ccc3

Please sign in to comment.