From 52747782a71ea0f8b7e836923ceca5bb9e07e03c Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Wed, 24 Jun 2020 17:18:02 -0300 Subject: [PATCH] Full screen avatar circle to square shape transition. --- .../securesms/AvatarPreviewActivity.java | 67 +++++++++++---- .../CircleSquareImageViewTransition.java | 83 +++++++++++++++++++ .../CircleToSquareImageViewTransition.java | 15 ++++ .../SquareToCircleImageViewTransition.java | 15 ++++ .../layout/contact_photo_preview_activity.xml | 9 +- ...reen_avatar_image_enter_transition_set.xml | 11 +++ ...een_avatar_image_return_transition_set.xml | 11 +++ 7 files changed, 191 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleSquareImageViewTransition.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleToSquareImageViewTransition.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/animation/transitions/SquareToCircleImageViewTransition.java create mode 100644 app/src/main/res/transition-v21/full_screen_avatar_image_enter_transition_set.xml create mode 100644 app/src/main/res/transition-v21/full_screen_avatar_image_return_transition_set.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java index 966da14004..10cdb642de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java @@ -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() { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target 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 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target 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 target, DataSource dataSource, boolean isFirstResource) { + return false; + } + }) + .into(new CustomTarget() { + @Override + public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition 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)); }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleSquareImageViewTransition.java b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleSquareImageViewTransition.java new file mode 100644 index 0000000000..add2d2388e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleSquareImageViewTransition.java @@ -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 { + + 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; + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleToSquareImageViewTransition.java b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleToSquareImageViewTransition.java new file mode 100644 index 0000000000..4c07fa0d00 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleToSquareImageViewTransition.java @@ -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); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/SquareToCircleImageViewTransition.java b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/SquareToCircleImageViewTransition.java new file mode 100644 index 0000000000..42b5133966 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/SquareToCircleImageViewTransition.java @@ -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); + } +} diff --git a/app/src/main/res/layout/contact_photo_preview_activity.xml b/app/src/main/res/layout/contact_photo_preview_activity.xml index 7865e35961..1fed20c45f 100644 --- a/app/src/main/res/layout/contact_photo_preview_activity.xml +++ b/app/src/main/res/layout/contact_photo_preview_activity.xml @@ -1,6 +1,7 @@ @@ -8,8 +9,12 @@ + 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" /> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/transition-v21/full_screen_avatar_image_return_transition_set.xml b/app/src/main/res/transition-v21/full_screen_avatar_image_return_transition_set.xml new file mode 100644 index 0000000000..a7e3b90f5d --- /dev/null +++ b/app/src/main/res/transition-v21/full_screen_avatar_image_return_transition_set.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file