-
-
Save aitorvs/fb6812eec9205d4ec48efd5b515b5dbe to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.aitorvs.android.rsspace.ui.activity; | |
import android.app.Activity; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.graphics.Bitmap; | |
import android.graphics.drawable.BitmapDrawable; | |
import android.os.Bundle; | |
import android.support.annotation.NonNull; | |
import android.support.v4.content.ContextCompat; | |
import android.support.v7.app.AppCompatActivity; | |
import android.support.v7.graphics.Palette; | |
import android.text.TextUtils; | |
import android.view.View; | |
import android.view.ViewTreeObserver; | |
import android.view.animation.AccelerateDecelerateInterpolator; | |
import android.widget.FrameLayout; | |
import android.widget.ImageView; | |
import com.aitorvs.android.rsspace.MyApplication; | |
import com.aitorvs.android.rsspace.R; | |
import com.aitorvs.android.rsspace.util.ImageViewUtil; | |
import com.squareup.picasso.Callback; | |
import com.squareup.picasso.Picasso; | |
import java.util.Locale; | |
import javax.inject.Inject; | |
import timber.log.Timber; | |
public class ArticleImageActivity extends AppCompatActivity { | |
private static final String IMAGE_URL_EXTRA = "IMAGE_URL_EXTRA"; | |
private static final String VIEW_INFO_EXTRA = "VIEW_INFO_EXTRA"; | |
// Animation constants | |
private static final AccelerateDecelerateInterpolator DEFAULT_INTERPOLATOR = new AccelerateDecelerateInterpolator(); | |
private static final int DEFAULT_DURATION = 300; | |
// reference to the image vie | |
private ImageView mDestinationView; | |
// reference to the image view container | |
private FrameLayout mImageViewContainer; | |
// image URL | |
private String mImageUrl; | |
// Bundle that will contain the transition start values | |
private Bundle mStartValues; | |
// Bundle that will contain the transition end values | |
final private Bundle mEndValues = new Bundle(); | |
// Picasso object | |
@Inject | |
protected Picasso mPicasso; | |
// transition properties | |
private static String PROPNAME_SCREENLOCATION_LEFT = "rsspace:location:left"; | |
private static String PROPNAME_SCREENLOCATION_TOP = "rsspace:location:top"; | |
private static String PROPNAME_WIDTH = "rsspace:width"; | |
private static String PROPNAME_HEIGHT = "rsspace:height"; | |
private Palette mImagePalette; | |
public static void startActivityFromImage( | |
@NonNull Context context, | |
@NonNull String imageUrl, | |
@NonNull View originView) { | |
//noinspection ConstantConditions | |
if (context == null || !(context instanceof Activity) || TextUtils.isEmpty(imageUrl) || originView == null) { | |
Timber.e("Invalid params"); | |
return; | |
} | |
Intent intent = new Intent(context, ArticleImageActivity.class); | |
intent.putExtra(IMAGE_URL_EXTRA, imageUrl); | |
intent.putExtra(VIEW_INFO_EXTRA, /* start values */ captureValues(originView)); | |
context.startActivity(intent); | |
((Activity) context).overridePendingTransition(0, 0); | |
} | |
/** | |
* Helper method to capture the view values to animate | |
* | |
* @param view target view | |
* @return Bundle with the captured values | |
*/ | |
private static Bundle captureValues(@NonNull View view) { | |
Bundle b = new Bundle(); | |
captureScaleValues(b, view); | |
captureScreenLocationValues(b, view); | |
return b; | |
} | |
private static void captureScaleValues(@NonNull Bundle b, @NonNull View view) { | |
if (view instanceof ImageView) { | |
int[] size = ImageViewUtil.getDisplayedImageLocation((ImageView) view); | |
b.putInt(PROPNAME_WIDTH, size[2]); | |
b.putInt(PROPNAME_HEIGHT, size[3]); | |
} else { | |
b.putInt(PROPNAME_WIDTH, view.getWidth()); | |
b.putInt(PROPNAME_HEIGHT, view.getHeight()); | |
} | |
} | |
private static void captureScreenLocationValues(@NonNull Bundle b, @NonNull View view) { | |
if (view instanceof ImageView) { | |
int[] size = ImageViewUtil.getDisplayedImageLocation((ImageView) view); | |
b.putInt(PROPNAME_SCREENLOCATION_LEFT, size[0]); | |
b.putInt(PROPNAME_SCREENLOCATION_TOP, size[1]); | |
} else { | |
int[] screenLocation = new int[2]; | |
view.getLocationOnScreen(screenLocation); | |
b.putInt(PROPNAME_SCREENLOCATION_LEFT, screenLocation[0]); | |
b.putInt(PROPNAME_SCREENLOCATION_TOP, screenLocation[1]); | |
} | |
} | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_article_image); | |
// inject dependencies | |
MyApplication.getComponent().inject(this); | |
// make sure we're not visible yet | |
mDestinationView = (ImageView) findViewById(R.id.article_image); | |
if (mDestinationView != null) { | |
// we need to make INVISIBLE so that we can get the view coordinates | |
mDestinationView.setVisibility(View.INVISIBLE); | |
} | |
mImageViewContainer = (FrameLayout) findViewById(R.id.image_container); | |
// extract all the info from the bundle | |
extractViewInfoFromBundle(getIntent()); | |
// only now we load the image | |
mPicasso.load(mImageUrl) | |
.into(mDestinationView, new Callback() { | |
@Override | |
public void onSuccess() { | |
// get the image palette | |
Bitmap bitmap = ((BitmapDrawable) mDestinationView.getDrawable()).getBitmap(); | |
mImagePalette = Palette.from(bitmap).generate(); | |
// we've got the image loaded, start the animation | |
onUiReady(); | |
} | |
@Override | |
public void onError() { | |
// no-op | |
} | |
}); | |
} | |
@Override | |
public void onBackPressed() { | |
// run the exit animation | |
runExitAnimation(); | |
} | |
/** | |
* Call this method to notify the view has all elements ready (e.g. images fetched etc) | |
*/ | |
private void onUiReady() { | |
mDestinationView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { | |
@Override | |
public boolean onPreDraw() { | |
// remove previous listener | |
mDestinationView.getViewTreeObserver().removeOnPreDrawListener(this); | |
// prep the scene | |
prepareScene(); | |
// run the animation | |
runEnterAnimation(); | |
return true; | |
} | |
}); | |
} | |
/** | |
* This method preps the scene. Captures the end values, calculates deltas with start values and | |
* reposition the view in the target layout | |
*/ | |
private void prepareScene() { | |
// do the first capture to scale the image | |
captureScaleValues(mEndValues, mDestinationView); | |
// calculate the scale factors | |
float scaleX = scaleDelta(mStartValues, mEndValues, PROPNAME_WIDTH); | |
float scaleY = scaleDelta(mStartValues, mEndValues, PROPNAME_HEIGHT); | |
// scale the image | |
mDestinationView.setScaleX(scaleX); | |
mDestinationView.setScaleY(scaleY); | |
// as scaling the image will change the top and left coordinates, we need to re-capture | |
// the values to proper figure out the translation deltas w.r.t. to start view | |
captureScreenLocationValues(mEndValues, mDestinationView); | |
int deltaX = translationDelta(mStartValues, mEndValues, PROPNAME_SCREENLOCATION_LEFT); | |
int deltaY = translationDelta(mStartValues, mEndValues, PROPNAME_SCREENLOCATION_TOP); | |
// finally, translate the end view to where the start view was | |
mDestinationView.setTranslationX(deltaX); | |
mDestinationView.setTranslationY(deltaY); | |
} | |
/** | |
* This method will run the entry animation | |
*/ | |
private void runEnterAnimation() { | |
// We can now make it visible | |
mDestinationView.setVisibility(View.VISIBLE); | |
// set the image container background using palette | |
mImageViewContainer.setBackgroundColor(mImagePalette.getDarkVibrantColor(ContextCompat.getColor(this, android.R.color.black))); | |
// finally, run the animation | |
mDestinationView.animate() | |
.setDuration(DEFAULT_DURATION) | |
.setInterpolator(DEFAULT_INTERPOLATOR) | |
.scaleX(1f) | |
.scaleY(1f) | |
.translationX(0) | |
.translationY(0) | |
.start(); | |
} | |
/** | |
* Call this method to run the exit transition | |
*/ | |
private void runExitAnimation() { | |
// re-calculate deltas | |
int deltaX = translationDelta(mStartValues, mEndValues, PROPNAME_SCREENLOCATION_LEFT); | |
int deltaY = translationDelta(mStartValues, mEndValues, PROPNAME_SCREENLOCATION_TOP); | |
float scaleX = scaleDelta(mStartValues, mEndValues, PROPNAME_WIDTH); | |
float scaleY = scaleDelta(mStartValues, mEndValues, PROPNAME_HEIGHT); | |
mDestinationView.animate() | |
.setDuration(DEFAULT_DURATION) | |
.setInterpolator(DEFAULT_INTERPOLATOR) | |
.scaleX(scaleX) | |
.scaleY(scaleY) | |
.translationX(deltaX) | |
.translationY(deltaY) | |
.withEndAction(new Runnable() { | |
@Override | |
public void run() { | |
finish(); | |
overridePendingTransition(0, 0); | |
} | |
}).start(); | |
} | |
private void extractViewInfoFromBundle(Intent intent) { | |
mImageUrl = intent.getStringExtra(IMAGE_URL_EXTRA); | |
mStartValues = intent.getBundleExtra(VIEW_INFO_EXTRA); | |
} | |
/** | |
* Helper method to calculate the scale delta given start and end values | |
* | |
* @param startValues start values {@link Bundle} | |
* @param endValues end values {@link Bundle} | |
* @param propertyName property name | |
* @return scale delta value | |
*/ | |
private float scaleDelta( | |
@NonNull Bundle startValues, | |
@NonNull Bundle endValues, | |
@NonNull String propertyName) { | |
int startValue = startValues.getInt(propertyName); | |
int endValue = endValues.getInt(propertyName); | |
float delta = (float) startValue / endValue; | |
Timber.d(String.format(Locale.US, "%s: startValue = %d, endValue = %d, delta = %f", propertyName, startValue, endValue, delta)); | |
return delta; | |
} | |
/** | |
* Helper method to calculate the translation deltas given start and end values | |
* | |
* @param startValues start values {@link Bundle} | |
* @param endValues end values {@link Bundle} | |
* @param propertyName property name | |
* @return translation delta between start and end values | |
*/ | |
private int translationDelta( | |
@NonNull Bundle startValues, | |
@NonNull Bundle endValues, | |
@NonNull String propertyName) { | |
int startValue = startValues.getInt(propertyName); | |
int endValue = endValues.getInt(propertyName); | |
int delta = startValue - endValue; | |
Timber.d(String.format(Locale.US, "%s: startValue = %d, endValue = %d, delta = %d", propertyName, startValue, endValue, delta)); | |
return delta; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment