package kr.kdev.dg1s.biowiki.widgets;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AlphaAnimation;
import android.widget.ImageView;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import kr.kdev.dg1s.biowiki.BioWiki;
import kr.kdev.dg1s.biowiki.R;
import kr.kdev.dg1s.biowiki.util.ReaderVideoUtils;
import kr.kdev.dg1s.biowiki.util.SysUtils;
/**
* Created by nbradbury on 7/30/13.
* most of the code below is from Volley's NetworkImageView, but it's modified to support:
* (1) fading in downloaded images
* (2) manipulating images before display
* (3) automatically retrieving the thumbnail for YouTube & Vimeo videos
* (4) adding a listener to determine when image has completed downloading (or failed)
*/
public class BWNetworkImageView extends ImageView {
private static final int FADE_TRANSITION = 250;
private ImageType mImageType = ImageType.PHOTO;
private String mUrl;
private ImageLoader.ImageContainer mImageContainer;
private ImageListener mImageListener;
public BWNetworkImageView(Context context) {
super(context);
}
public BWNetworkImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BWNetworkImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setImageUrl(String url, ImageType imageType) {
setImageUrl(url, imageType, null);
}
public void setImageUrl(String url, ImageType imageType, ImageListener imageListener) {
mUrl = url;
mImageType = imageType;
mImageListener = imageListener;
if (TextUtils.isEmpty(mUrl)) {
showErrorImage(mImageType);
} else {
// The URL has potentially changed. See if we need to load it.
loadImageIfNecessary(false);
}
}
/*
* retrieves and displays the thumbnail for the passed video
*/
public void setVideoUrl(final long postId, final String videoUrl) {
mImageType = ImageType.VIDEO;
if (TextUtils.isEmpty(videoUrl)) {
showDefaultImage(ImageType.VIDEO);
return;
}
showDefaultImage(ImageType.VIDEO);
// vimeo videos require network request to get thumbnail
if (ReaderVideoUtils.isVimeoLink(videoUrl)) {
ReaderVideoUtils.requestVimeoThumbnail(videoUrl, new ReaderVideoUtils.VideoThumbnailListener() {
@Override
public void onResponse(boolean successful, String thumbnailUrl) {
if (successful) {
setImageUrl(thumbnailUrl, ImageType.VIDEO);
}
}
});
}
}
/**
* Loads the image for the view if it isn't already loaded.
*
* @param isInLayoutPass True if this was invoked from a layout pass, false otherwise.
*/
private void loadImageIfNecessary(final boolean isInLayoutPass) {
int width = getWidth();
int height = getHeight();
boolean isFullyWrapContent = getLayoutParams() != null
&& getLayoutParams().height == LayoutParams.WRAP_CONTENT
&& getLayoutParams().width == LayoutParams.WRAP_CONTENT;
// if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content
// view, hold off on loading the image.
if (width == 0 && height == 0 && !isFullyWrapContent) {
return;
}
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
// currently loaded image.
if (TextUtils.isEmpty(mUrl)) {
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
showErrorImage(mImageType);
return;
}
// if there was an old request in this view, check if it needs to be canceled.
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
if (mImageContainer.getRequestUrl().equals(mUrl)) {
// if the request is from the same URL, return.
return;
} else {
// if there is a pre-existing request, cancel it if it's fetching a different URL.
mImageContainer.cancelRequest();
showDefaultImage(mImageType);
}
}
// The pre-existing content of this view didn't match the current URL. Load the new image
// from the network.
ImageLoader.ImageContainer newContainer = BioWiki.imageLoader.get(mUrl,
new ImageLoader.ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
showErrorImage(mImageType);
if (mImageListener != null)
mImageListener.onImageLoaded(false);
}
@Override
public void onResponse(final ImageLoader.ImageContainer response, boolean isImmediate) {
// If this was an immediate response that was delivered inside of a layout
// pass do not set the image immediately as it will trigger a requestLayout
// inside of a layout. Instead, defer setting the image by posting back to
// the main thread.
if (isImmediate && isInLayoutPass) {
post(new Runnable() {
@Override
public void run() {
// don't fade in the image since we know it's cached
handleResponse(response, true, false);
}
});
} else {
handleResponse(response, isImmediate, true);
}
}
}
);
// update the ImageContainer to be the new bitmap container.
mImageContainer = newContainer;
}
private void handleResponse(ImageLoader.ImageContainer response,
boolean isCached,
boolean allowFadeIn) {
if (response.getBitmap() != null) {
setImageBitmap(response.getBitmap());
// fade in photos/videos if not cached (not used for other image types since animation can be expensive)
if (!isCached && allowFadeIn && (mImageType == ImageType.PHOTO || mImageType == ImageType.VIDEO))
fadeIn();
if (mImageListener != null)
mImageListener.onImageLoaded(true);
} else {
showDefaultImage(mImageType);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
loadImageIfNecessary(true);
}
@Override
protected void onDetachedFromWindow() {
if (mImageContainer != null) {
// If the view was bound to an image request, cancel it and clear
// out the image from the view.
mImageContainer.cancelRequest();
setImageDrawable(null);
// also clear out the container so we can reload the image if necessary.
mImageContainer = null;
}
super.onDetachedFromWindow();
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
invalidate();
}
private int getColorRes(int resId) {
return getContext().getResources().getColor(resId);
}
private void showErrorImage(ImageType imageType) {
switch (imageType) {
case PHOTO_FULL:
// null default for full-screen photos
setImageDrawable(null);
break;
case AVATAR:
// "mystery man" for failed avatars
setImageResource(R.drawable.placeholder);
break;
default:
// medium grey box for all others
setImageDrawable(new ColorDrawable(getColorRes(R.color.grey_medium)));
break;
}
}
private void showDefaultImage(ImageType imageType) {
switch (imageType) {
case PHOTO_FULL:
// null default for full-screen photos
setImageDrawable(null);
break;
default:
// light grey box for all others
setImageDrawable(new ColorDrawable(getColorRes(R.color.grey_light)));
break;
}
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mImageType == ImageType.VIDEO)
drawVideoOverlay(canvas);
}
private void drawVideoOverlay(Canvas canvas) {
if (canvas == null)
return;
Bitmap overlay = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_reader_video_overlay, null);
int overlaySize = getContext().getResources().getDimensionPixelSize(R.dimen.reader_video_overlay_size);
// use the size of the view rather than the canvas
int srcWidth = this.getWidth();
int srcHeight = this.getHeight();
// skip if overlay is larger than source image
if (overlaySize > srcWidth || overlaySize > srcHeight)
return;
final int left = (srcWidth / 2) - (overlaySize / 2);
final int top = (srcHeight / 2) - (overlaySize / 2);
final Rect rcDst = new Rect(left, top, left + overlaySize, top + overlaySize);
canvas.drawBitmap(overlay, null, rcDst, new Paint(Paint.FILTER_BITMAP_FLAG));
overlay.recycle();
}
@SuppressLint("NewApi")
private void fadeIn() {
// use faster property animation if device supports it
if (SysUtils.isGteAndroid4()) {
ObjectAnimator alpha = ObjectAnimator.ofFloat(this, View.ALPHA, 0.25f, 1f);
alpha.setDuration(FADE_TRANSITION);
alpha.start();
} else {
AlphaAnimation animation = new AlphaAnimation(0.25f, 1f);
animation.setDuration(FADE_TRANSITION);
this.startAnimation(animation);
}
}
// --------------------------------------------------------------------------------------------------
public static enum ImageType {
PHOTO,
PHOTO_FULL,
VIDEO,
AVATAR
}
public interface ImageListener {
public void onImageLoaded(boolean succeeded);
}
}