Full screen avatar circle to square shape transition.

master
Alan Evans 2020-06-24 17:18:02 -03:00 committed by Greyson Parrelli
parent 66f2668326
commit 52747782a7
7 changed files with 191 additions and 20 deletions

View File

@ -3,8 +3,12 @@ package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.transition.TransitionInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
@ -14,12 +18,15 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
@ -58,7 +65,15 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
setTheme(R.style.TextSecure_MediaPreview);
setContentView(R.layout.contact_photo_preview_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
if (Build.VERSION.SDK_INT >= 21) {
postponeEnterTransition();
TransitionInflater inflater = TransitionInflater.from(this);
getWindow().setSharedElementEnterTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_enter_transition_set));
getWindow().setSharedElementReturnTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_return_transition_set));
}
Toolbar toolbar = findViewById(R.id.toolbar);
ImageView avatar = findViewById(R.id.avatar);
setSupportActionBar(toolbar);
@ -79,24 +94,40 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
: recipient.getFallbackContactPhoto();
GlideApp.with(this).load(contactPhoto)
.fallback(fallbackPhoto.asCallCard(this))
.error(fallbackPhoto.asCallCard(this))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
Log.w(TAG, "Unable to load avatar, or avatar removed, closing");
finish();
return false;
}
Resources resources = this.getResources();
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
})
.into(avatar);
GlideApp.with(this)
.asBitmap()
.load(contactPhoto)
.fallback(fallbackPhoto.asCallCard(this))
.error(fallbackPhoto.asCallCard(this))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.addListener(new RequestListener<Bitmap>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
Log.w(TAG, "Unable to load avatar, or avatar removed, closing");
finish();
return false;
}
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
})
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
avatar.setImageDrawable(RoundedBitmapDrawableFactory.create(resources, resource));
if (Build.VERSION.SDK_INT >= 21) {
startPostponedEnterTransition();
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
toolbar.setTitle(recipient.getDisplayName(context));
});

View File

@ -0,0 +1,83 @@
package org.thoughtcrime.securesms.animation.transitions;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.graphics.drawable.Drawable;
import android.transition.Transition;
import android.transition.TransitionValues;
import android.util.Property;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
@TargetApi(21)
abstract class CircleSquareImageViewTransition extends Transition {
private static final String CIRCLE_RATIO = "CIRCLE_RATIO";
private final boolean toCircle;
CircleSquareImageViewTransition(boolean toCircle) {
this.toCircle = toCircle;
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
View view = transitionValues.view;
if (view instanceof ImageView) {
transitionValues.values.put(CIRCLE_RATIO, toCircle ? 0f : 1f);
}
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
View view = transitionValues.view;
if (view instanceof ImageView) {
transitionValues.values.put(CIRCLE_RATIO, toCircle ? 1f : 0f);
}
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
ImageView endImageView = (ImageView) endValues.view;
float start = (float) startValues.values.get(CIRCLE_RATIO);
float end = (float) endValues.values.get(CIRCLE_RATIO);
return ObjectAnimator.ofFloat(endImageView, new RadiusRatioProperty(), start, end);
}
static final class RadiusRatioProperty extends Property<ImageView, Float> {
private float ratio;
RadiusRatioProperty() {
super(Float.class, "circle_ratio");
}
@Override
final public void set(ImageView imageView, Float ratio) {
this.ratio = ratio;
Drawable imageViewDrawable = imageView.getDrawable();
if (imageViewDrawable instanceof RoundedBitmapDrawable) {
RoundedBitmapDrawable drawable = (RoundedBitmapDrawable) imageViewDrawable;
if (ratio > 0.95) {
drawable.setCircular(true);
} else {
drawable.setCornerRadius(Math.min(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()) * ratio * 0.5f);
}
}
}
@Override
public Float get(ImageView object) {
return ratio;
}
}
}

View File

@ -0,0 +1,15 @@
package org.thoughtcrime.securesms.animation.transitions;
import android.annotation.TargetApi;
import android.content.Context;
import android.util.AttributeSet;
/**
* Will only transition {@link android.widget.ImageView}s that contain a {@link androidx.core.graphics.drawable.RoundedBitmapDrawable}.
*/
@TargetApi(21)
public final class CircleToSquareImageViewTransition extends CircleSquareImageViewTransition {
public CircleToSquareImageViewTransition(Context context, AttributeSet attrs) {
super(false);
}
}

View File

@ -0,0 +1,15 @@
package org.thoughtcrime.securesms.animation.transitions;
import android.annotation.TargetApi;
import android.content.Context;
import android.util.AttributeSet;
/**
* Will only transition {@link android.widget.ImageView}s that contain a {@link androidx.core.graphics.drawable.RoundedBitmapDrawable}.
*/
@TargetApi(21)
public final class SquareToCircleImageViewTransition extends CircleSquareImageViewTransition {
public SquareToCircleImageViewTransition(Context context, AttributeSet attrs) {
super(true);
}
}

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/core_grey_95">
@ -8,8 +9,12 @@
<ImageView
android:id="@+id/avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionName="avatar" />
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:transitionName="avatar"
tools:src="@drawable/ic_signal_downloading" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/toolbar_layout"

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:transitionOrdering="together">
<changeImageTransform />
<changeBounds />
<transition class="org.thoughtcrime.securesms.animation.transitions.CircleToSquareImageViewTransition" />
</transitionSet>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:transitionOrdering="together">
<changeImageTransform />
<changeBounds />
<transition class="org.thoughtcrime.securesms.animation.transitions.SquareToCircleImageViewTransition" />
</transitionSet>