Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.nextop.view.ImageView Maven / Gradle / Ivy
package io.nextop.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.*;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.net.Uri;
import android.util.AttributeSet;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import io.nextop.*;
import io.nextop.vm.ImageViewModel;
import rx.Observable;
import rx.Observer;
import rx.Subscription;
import rx.functions.Action1;
import rx.functions.Func2;
import rx.internal.util.SubscriptionList;
import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;
@Beta
public class ImageView extends android.widget.ImageView {
@Nullable
Nextop.LayersConfig layersConfig = null;
@Nullable
Source source = null;
@Nullable
Transition transition = null;
@Nullable
SubscriptionList loadSubscriptions = null;
@Nullable
Progress progress = null;
// CONFIG
private final Transition defaultTransition = new Transition(200, 200, false);
int transferQPx = 48;
float defaultMaxTransferMultiple = 2.f;
int defaultMinTransferPx = 48;
int downProgressTimeoutMs = 2000;
// DRAW STATE
Paint tempPaint = new Paint();
RectF tempRect = new RectF();
public ImageView(Context context) {
super(context);
}
public ImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@SuppressLint("NewApi")
public ImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private Nextop.LayersConfig createLayersConfig() {
Nextop.LayersConfig config;
if (null != layersConfig) {
config = layersConfig.copy();
} else {
config = createDefaultLayersConfig();
}
// now compute the target sizes for each bounds
int w = getWidth();
int h = getHeight();
// set the max values depending on the scale type
int maxW;
int maxH;
switch (getScaleType()) {
case CENTER:
case CENTER_CROP:
int s = Math.max(w, h);
maxW = s;
maxH = s;
break;
case CENTER_INSIDE:
case FIT_CENTER:
case FIT_END:
case FIT_START:
case FIT_XY:
maxW = w;
maxH = h;
break;
case MATRIX:
// use the scale values from the matrix
float[] mvalues = new float[9];
getImageMatrix().getValues(mvalues);
float sx = mvalues[Matrix.MSCALE_X];
float sy = mvalues[Matrix.MSCALE_Y];
maxW = Math.round(sx * w);
maxH = Math.round(sy * h);
break;
default:
throw new IllegalArgumentException();
}
maxW = Math.max(defaultMinTransferPx, maxW);
maxH = Math.max(defaultMinTransferPx, maxH);
// now quantize the max sizes to hit the same cache values in each band
// (round up)
maxW = ((maxW + transferQPx - 1) / transferQPx) * transferQPx;
maxH = ((maxH + transferQPx - 1) / transferQPx) * transferQPx;
for (Nextop.LayersConfig.Bound bound : config.receiveBounds) {
bound.maxWidth = w;
bound.maxHeight = h;
}
return config;
}
private Nextop.LayersConfig createDefaultLayersConfig() {
// 1. set the max transfer size based on the current view size (multiple e.g. 2x)
// 2. set the min transfer size of all but the last based on a fixed size (e.g. 48px)
// 3. use two quality steps (0.3 and 1.0)
int w = getWidth();
int h = getHeight();
Nextop.LayersConfig.Bound base = new Nextop.LayersConfig.Bound();
base.maxTransferWidth = Math.round(defaultMaxTransferMultiple * w);
base.maxTransferHeight = Math.round(defaultMaxTransferMultiple * h);
base.minTransferWidth = defaultMinTransferPx;
base.minTransferHeight = defaultMinTransferPx;
Nextop.LayersConfig.Bound a = base.copy();
a.quality = 30;
Nextop.LayersConfig.Bound b = base.copy();
b.quality = 100;
return Nextop.LayersConfig.receive(a, b);
}
// only the transfer properties are used in Bounds
// the display properties are set by the view
public void setLayersConfig(Nextop.LayersConfig layersConfig) {
this.layersConfig = layersConfig;
reload();
}
/////// SOURCE ///////
public void reset() {
setSource(null, Transition.instant());
}
@Override
public void setImageURI(Uri uri) {
setImageUri(uri);
}
public void setImageUri(Uri uri) {
setSource(Source.uri(uri));
}
// set to an image in the progress of uploading (the localId is the message that is sending the image)
public void setLocalImage(Id id) {
setSource(Source.local(id));
}
@Override
public void setImageBitmap(Bitmap bitmap) {
setSource(Source.memory(bitmap));
}
public void setSource(@Nullable Source source) {
setSource(source, null);
}
public void setSource(@Nullable Source source, @Nullable Transition transition) {
if (!Objects.equal(this.source, source)) {
Transition useTransition = null != transition ? transition : defaultTransition;
resetLoad();
if (!useTransition.hold) {
resetImage();
}
this.source = source;
this.transition = useTransition;
reload();
}
}
private void cancelLoadSubscriptions() {
if (null != loadSubscriptions) {
loadSubscriptions.unsubscribe();
loadSubscriptions = null;
}
}
private void resetLoad() {
cancelLoadSubscriptions();
// FIXME
// System.out.printf(" image progress reset load\n");
setProgress(null);
}
private void resetImage() {
setImageDrawable(null);
}
private void reload() {
cancelLoadSubscriptions();
// FIXME
// System.out.printf(" image progress reload\n");
setProgress(null);
if (null != source) {
switch (source.type) {
case URI: {
loadUri(source.uri);
break;
}
case LOCAL: {
loadLocal(source.localId);
break;
}
case MEMORY: {
loadMemory(source.bitmap);
break;
}
}
}
}
private void loadUri(Uri uri) {
assert null == loadSubscriptions;
@Nullable Nextop nextop = NextopAndroid.getActive(this);
if (null != nextop) {
loadSubscriptions = new SubscriptionList();
Message message = MessageAndroid.valueOf(Route.Method.GET, uri);
Observable downProgressSource = Observable.combineLatest(nextop.transferStatus(message.id), nextop.connectionStatus(),
new Func2() {
@Override
public Progress call(Nextop.TransferStatus transferStatus, Nextop.ConnectionStatus connectionStatus) {
return Progress.download(transferStatus.receive.asFloat(), connectionStatus.online);
}
}).delaySubscription(downProgressTimeoutMs, TimeUnit.MILLISECONDS);
// cancel this subscription on the first emitted layer (below)
final Subscription progressSubscription = downProgressSource.subscribe(new ProgressLoader());
loadSubscriptions.add(progressSubscription);
LayerLoader loader = new LayerLoader();
loader.immediate = true;
loadSubscriptions.add(nextop.send(Nextop.Layer.message(message),
createLayersConfig()).doOnNext(new Action1() {
@Override
public void call(Nextop.Layer layer) {
progressSubscription.unsubscribe();
}
}).subscribe(loader));
loader.immediate = false;
}
}
private void loadLocal(final Id id) {
assert null == loadSubscriptions;
@Nullable Nextop nextop = NextopAndroid.getActive(this);
if (null != nextop) {
loadSubscriptions = new SubscriptionList();
// FIXME
Observable upProgressSource = Observable.combineLatest(nextop.transferStatus(id), nextop.connectionStatus(),
new Func2() {
@Override
public Progress call(Nextop.TransferStatus transferStatus, Nextop.ConnectionStatus connectionStatus) {
return Progress.upload(transferStatus.send.asFloat(), connectionStatus.online);
}
});
// .doOnSubscribe(new Action0() {
// @Override
// public void call() {
// System.out.printf(" SUBSCRIBE to progress %s\n", id);
// }
// })
// .doOnUnsubscribe(new Action0() {
// @Override
// public void call() {
// System.out.printf(" UNSUBSCRIBE from progress %s\n", id);
// }
// });
// Observable upProgressSource = nextop.transferStatus(id).map(
// new Func1() {
// @Override
// public Progress call(Nextop.TransferStatus transferStatus) {
//// return Progress.upload(transferStatus.send.asFloat(), true);
// return Progress.upload(0.5f, true);
// }
// });
Subscription progressSubscription = upProgressSource.subscribe(new ProgressLoader());
loadSubscriptions.add(progressSubscription);
Message message = Message.newBuilder().setRoute(Message.echoRoute(id)).build();
LayerLoader loader = new LayerLoader();
loader.immediate = true;
loadSubscriptions.add(nextop.send(Nextop.Layer.message(message),
createLayersConfig()).subscribe(loader));
loader.immediate = false;
}
}
private void loadMemory(Bitmap bitmap) {
setImageDrawable(new BitmapDrawable(getResources(), bitmap));
}
/////// PROGRESS ///////
private void setProgress(@Nullable Progress progress) {
// FIXME
// System.out.printf(" image progress %s\n", progress);
if (!Objects.equal(this.progress, progress)) {
this.progress = progress;
invalidate();
}
}
/////// VIEW OVERRIDES ///////
@Override
public void setImageMatrix(Matrix matrix) {
super.setImageMatrix(matrix);
reload();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
reload();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
reload();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
resetLoad();
resetImage();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null != progress && progress.progress < 1.f) {
// TODO
// if online, filled grey with white pie
// if offline, outline grey with white pie
float w = getWidth();
float h = getHeight();
float s = 0.2f * Math.min(w, h);
tempPaint.setColor(Color.argb(128, 64, 64, 64));
if (progress.active) {
tempPaint.setStyle(Paint.Style.FILL);
} else {
tempPaint.setStyle(Paint.Style.STROKE);
}
tempRect.set(w / 2.f - s, h / 2.f - s, w / 2.f + s, h / 2.f + s);
canvas.drawArc(tempRect,
/* deg */ 360 * progress.progress, /* deg */ 360 * (1.f - progress.progress),
true, tempPaint);
if (progress.active) {
tempPaint.setColor(Color.argb(128, 255, 255, 255));
} else {
tempPaint.setColor(Color.argb(64, 255, 255, 255));
}
tempPaint.setStyle(Paint.Style.FILL);
tempRect.set(w / 2.f - s, h / 2.f - s, w / 2.f + s, h / 2.f + s);
canvas.drawArc(tempRect,
/* deg */ 0.f, /* deg */ 360 * progress.progress,
true, tempPaint);
}
}
private final class LayerLoader implements Observer {
/** if true, the layer should be immediately set into the view (no transition);
* otherwise, (if supported by the transition properties) do a fade in on a quantized schedule. */
boolean immediate = false;
int count = 0;
LayerLoader() {
}
private void set(Bitmap bitmap) {
++count;
Drawable d;
if (!immediate && 1 == count &&
null != transition && 0 < transition.fadeInMs) {
// FIXME use transitionQMs
TransitionDrawable td = new TransitionDrawable(new Drawable[]{
new ColorDrawable(Color.argb(0, 0, 0, 0)),
new BitmapDrawable(getResources(), bitmap)
});
td.startTransition(transition.fadeInMs);
d = td;
} else {
d = new BitmapDrawable(getResources(), bitmap);
}
setImageDrawable(d);
}
@Override
public void onNext(Nextop.Layer layer) {
if (null != layer.bitmap) {
set(layer.bitmap);
} // else ignore
}
@Override
public void onCompleted() {
// Do nothing
}
@Override
public void onError(Throwable e) {
// TODO
}
}
private final class ProgressLoader implements Observer {
@Override
public void onNext(Progress progress) {
// FIXME
// System.out.printf(" image progress loader next %s\n", progress);
setProgress(progress);
}
@Override
public void onCompleted() {
// FIXME
// System.out.printf(" image progress loader completed\n");
setProgress(null);
}
@Override
public void onError(Throwable e) {
// FIXME
// System.out.printf(" image progress loader error %s\n", e);
// if (null != e) {
// e.printStackTrace();
// }
setProgress(null);
}
}
public static final class Transition {
public static Transition instant() {
return new Transition(0, 0, false);
}
public static Transition instantHold() {
return new Transition(0, 0, true);
}
public final int fadeInMs;
/** align visual transitions on these boundaries */
public final int transitionQMs;
public final boolean hold;
public Transition(int fadeInMs, int transitionQMs, boolean hold) {
this.fadeInMs = fadeInMs;
this.transitionQMs = transitionQMs;
this.hold = hold;
}
}
public static final class Source {
static enum Type {
URI,
LOCAL,
MEMORY
}
@Nullable
public static Source uri(@Nullable Uri uri) {
if (null != uri) {
return new Source(Type.URI, uri, null, null);
} else {
return null;
}
}
@Nullable
public static Source local(@Nullable Id id) {
if (null != id) {
return new Source(Type.LOCAL, null, id, null);
} else {
return null;
}
}
@Nullable
public static Source memory(@Nullable Bitmap bitmap) {
if (null != bitmap) {
return new Source(Type.MEMORY, null, null, bitmap);
} else {
return null;
}
}
final Type type;
@Nullable
final Uri uri;
@Nullable
final Id localId;
@Nullable
final Bitmap bitmap;
Source(Type type, Uri uri, Id localId, Bitmap bitmap) {
this.type = type;
this.uri = uri;
this.localId = localId;
this.bitmap = bitmap;
}
@Override
public int hashCode() {
int c = type.hashCode();
c = 31 * c + Objects.hashCode(uri);
c = 31 * c + Objects.hashCode(localId);
c = 31 * c + (null != bitmap ? System.identityHashCode(bitmap) : 0);
return c;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Source)) {
return false;
}
Source b = (Source) o;
return type.equals(b.type)
&& Objects.equal(uri, b.uri)
&& Objects.equal(localId, b.localId)
&& bitmap == b.bitmap;
}
}
private static final class Progress {
static enum Type {
DOWNLOAD,
UPLOAD
}
static Progress download(float progress, boolean active) {
return create(Type.DOWNLOAD, progress, active);
}
static Progress upload(float progress, boolean active) {
return create(Type.UPLOAD, progress, active);
}
static Progress create(Type type, float progress, boolean active) {
float np = Math.max(0.f, Math.min(1.f, progress));
// normalize to percent
np = (int) (np * 100) / 100.f;
return new Progress(type, np, active);
}
final Type type;
final float progress;
final boolean active;
private Progress(Type type, float progress, boolean active) {
this.type = type;
this.progress = progress;
this.active = active;
}
@Override
public String toString() {
return String.format("%s %.2f %s", type, progress, active ? "active" : "-");
}
@Override
public int hashCode() {
int c = type.hashCode();
c = 31 * c + Float.floatToIntBits(progress);
c = 31 * c + (active ? 1 : 0);
return c;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Progress)) {
return false;
}
Progress b = (Progress) o;
return type.equals(b.type)
&& progress == b.progress
&& active == b.active;
}
}
public static final class Updater implements Observer {
private final ImageView imageView;
/** the last transition is held */
private final Transition[] transitions;
private int frameCount = 0;
public Updater(ImageView imageView, Transition ... transitions) {
this.imageView = imageView;
this.transitions = transitions;
}
@Override
public void onNext(final ImageViewModel imageVm) {
int index = frameCount++;
@Nullable Transition transition;
if (transitions.length <= 0) {
transition = null;
} else if (index < transitions.length) {
transition = transitions[index];
} else {
transition = transitions[transitions.length - 1];
}
if (null != imageVm.bitmap) {
imageView.setSource(Source.memory(imageVm.bitmap), transition);
} else if (null != imageVm.localId) {
imageView.setSource(Source.local(imageVm.localId), transition);
} else if (null != imageVm.uri) {
imageView.setSource(Source.uri(imageVm.uri), transition);
} else {
imageView.reset();
}
}
@Override
public void onCompleted() {
// Do nothing
}
@Override
public void onError(Throwable e) {
// Do nothing
}
}
}