Transition d'élément partagé Android: transformer une ImageView d'un cercle en rectangle et revenir

J'essaie de faire un élément partagé de transition entre deux activités.

La première activité a un cercle imageview et la seconde activité a une image rectangulaire. Je veux simplement que le cercle passe de la première activité à la deuxième activité où elle devient un carré et retourne au cercle lorsque je presse.

  • Emulateur Eclipse AVD Nexus 5 ne fonctionne pas
  • Socialauth-android obtient oAuth-token et utilise les informations de compte sur l'appareil
  • Ne peut pas accéder à adb dans OS X via Terminal, "commande introuvable"
  • Android - suppression de l'élément de ListView sur un long clic
  • Construire une unité de soins intensifs avec NDK
  • Modifiez ou supprimez complètement la couleur de surbrillance d'un InfoWindow de Google Maps v2 sur Android
  • Je trouve que la transition n'est pas si soignée – dans l'animation ci-dessous, vous pouvez voir que la vue d'image rectangulaire semble diminuer en taille jusqu'à ce qu'elle corresponde à la taille du cercle. La vue d'image carrée apparaît pour une fraction de seconde et ensuite le cercle apparaît. Je veux me débarrasser de la vue d'image carrée afin que le cercle devienne le point final de la transition.

    Quelqu'un sait comment cela se fait? Entrez la description de l'image ici

    J'ai créé un petit compte de test que vous pouvez télécharger ici: https://github.com/Winghin2517/TransitionTest

    Le code de ma première activité – la vue d'image se trouve dans le MainFragment de ma première activité:

     public class MainFragment extends android.support.v4.app.Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_view, container,false); final ImageView dot = (ImageView) view.findViewById(R.id.image_circle); Picasso.with(getContext()).load(R.drawable.snow).transform(new PureCircleTransformation()).into(dot); dot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent i = new Intent(getContext(), SecondActivity.class); View sharedView = dot; String transitionName = getString(R.string.blue_name); ActivityOptionsCompat transitionActivityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), sharedView, transitionName); startActivity(i, transitionActivityOptions.toBundle()); } }); return view; } } 

    C'est ma deuxième activité qui contient la vision d'image rectangulaire:

     public class SecondActivity extends AppCompatActivity { ImageView backdrop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); backdrop = (ImageView) findViewById(R.id.picture); backdrop.setBackground(ContextCompat.getDrawable(this, R.drawable.snow)); } @Override public void onBackPressed() { supportFinishAfterTransition(); super.onBackPressed(); } } 

    C'est la classe PureCircleTransformation que je passe dans Picasso pour générer le cercle:

     package test.com.transitiontest; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Paint; import com.squareup.picasso.Transformation; public class PureCircleTransformation implements Transformation { private static final int STROKE_WIDTH = 6; @Override public Bitmap transform(Bitmap source) { int size = Math.min(source.getWidth(), source.getHeight()); int x = (source.getWidth() - size) / 2; int y = (source.getHeight() - size) / 2; Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); if (squaredBitmap != source) { source.recycle(); } Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig()); Canvas canvas = new Canvas(bitmap); Paint avatarPaint = new Paint(); BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP); avatarPaint.setShader(shader); float r = size / 2f; canvas.drawCircle(r, r, r, avatarPaint); squaredBitmap.recycle(); return bitmap; } @Override public String key() { return "circleTransformation()"; } } 

    Je comprends que dans ma première activité, le cercle est simplement «coupé» en appliquant la classe de transformation de Picasso et que la vision d'image est juste une disposition carrée découpée afin qu'elle apparaisse comme un cercle. C'est peut-être la raison pour laquelle l'animation ressemble à cela alors que je passe d'un rectangle à un carré, mais je veux vraiment que la transition passe du rectangle à un cercle.

    Je pense qu'il existe un moyen de le faire. Dans l'application whatsapp, je peux voir l'effet, mais je ne peux pas sembler comprendre comment ils ont réussi à le faire – Si vous cliquez sur l'image de profil de vos amis sur whatsapp, l'application élargit l'image de cercle à un carré. En cliquant sur le dos, le carré sera remis au cercle.

    Entrez la description de l'image ici

  • Barre d'outils Android Lollipop vs Vue personnalisée
  • L'orientation de Camerainfo donne une mauvaise réponse
  • Clarification concernant Android "L'élévation d'attribut n'est utilisée que dans le niveau API 21 et supérieur"
  • PréférenceActivité: enregistrer la valeur en entier
  • Rassemblez la liste de l'arrayliste des objets
  • Valeurs EditText dans la gamme
  • 2 Solutions collect form web for “Transition d'élément partagé Android: transformer une ImageView d'un cercle en rectangle et revenir”

    J'offre de créer une vue personnalisée, qui peut s'organiser en cercle vers l'arrière et vers l'arrière, puis enrouler une transition personnalisée autour d'elle avec l'ajout d'animation en mouvement.

    Comment ressemble-t-il:
    Cercle à la transition droite gif

    Le code est ci-dessous (partie précieuse).
    Pour un échantillon complet, vérifiez mon github .

    CircleRectView.java:

     public class CircleRectView extends ImageView { private int circleRadius; private float cornerRadius; private RectF bitmapRect; private Path clipPath; private void init(TypedArray a) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { setLayerType(LAYER_TYPE_SOFTWARE, null); } if (a.hasValue(R.styleable.CircleRectView_circleRadius)) { circleRadius = a.getDimensionPixelSize(R.styleable.CircleRectView_circleRadius, 0); cornerRadius = circleRadius; } clipPath = new Path(); a.recycle(); } public Animator animator(int startHeight, int startWidth, int endHeight, int endWidth) { AnimatorSet animatorSet = new AnimatorSet(); ValueAnimator heightAnimator = ValueAnimator.ofInt(startHeight, endHeight); ValueAnimator widthAnimator = ValueAnimator.ofInt(startWidth, endWidth); heightAnimator.addUpdateListener(valueAnimator -> { int val = (Integer) valueAnimator.getAnimatedValue(); ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.height = val; setLayoutParams(layoutParams); requestLayoutSupport(); }); widthAnimator.addUpdateListener(valueAnimator -> { int val = (Integer) valueAnimator.getAnimatedValue(); ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.width = val; setLayoutParams(layoutParams); requestLayoutSupport(); }); ValueAnimator radiusAnimator; if (startWidth < endWidth) { radiusAnimator = ValueAnimator.ofFloat(circleRadius, 0); } else { radiusAnimator = ValueAnimator.ofFloat(cornerRadius, circleRadius); } radiusAnimator.setInterpolator(new AccelerateInterpolator()); radiusAnimator.addUpdateListener(animator -> cornerRadius = (float) (Float) animator.getAnimatedValue()); animatorSet.playTogether(heightAnimator, widthAnimator, radiusAnimator); return animatorSet; } /** * this needed because of that somehow {@link #onSizeChanged} NOT CALLED when requestLayout while activity transition end is running */ private void requestLayoutSupport() { View parent = (View) getParent(); int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.EXACTLY); parent.measure(widthSpec, heightSpec); parent.layout(parent.getLeft(), parent.getTop(), parent.getRight(), parent.getBottom()); } @Override public void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //This event-method provides the real dimensions of this custom view. Log.d("size changed", "w = " + w + " h = " + h); bitmapRect = new RectF(0, 0, w, h); } @Override protected void onDraw(Canvas canvas) { Drawable drawable = getDrawable(); if (drawable == null) { return; } if (getWidth() == 0 || getHeight() == 0) { return; } clipPath.reset(); clipPath.addRoundRect(bitmapRect, cornerRadius, cornerRadius, Path.Direction.CW); canvas.clipPath(clipPath); super.onDraw(canvas); } 

    }

     @TargetApi(Build.VERSION_CODES.KITKAT) public class CircleToRectTransition extends Transition { private static final String TAG = CircleToRectTransition.class.getSimpleName(); private static final String BOUNDS = "viewBounds"; private static final String[] PROPS = {BOUNDS}; @Override public String[] getTransitionProperties() { return PROPS; } private void captureValues(TransitionValues transitionValues) { View view = transitionValues.view; Rect bounds = new Rect(); bounds.left = view.getLeft(); bounds.right = view.getRight(); bounds.top = view.getTop(); bounds.bottom = view.getBottom(); transitionValues.values.put(BOUNDS, bounds); } @Override public void captureStartValues(TransitionValues transitionValues) { captureValues(transitionValues); } @Override public void captureEndValues(TransitionValues transitionValues) { captureValues(transitionValues); } @Override public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null) { return null; } if (!(startValues.view instanceof CircleRectView)) { Log.w(CircleToRectTransition.class.getSimpleName(), "transition view should be CircleRectView"); return null; } CircleRectView view = (CircleRectView) (startValues.view); Rect startRect = (Rect) startValues.values.get(BOUNDS); final Rect endRect = (Rect) endValues.values.get(BOUNDS); Animator animator; //scale animator animator = view.animator(startRect.height(), startRect.width(), endRect.height(), endRect.width()); //movement animators below //if some translation not performed fully, use it instead of start coordinate float startX = startRect.left + view.getTranslationX(); float startY = startRect.top + view.getTranslationY(); //somehow end rect returns needed value minus translation in case not finished transition available float moveXTo = endRect.left + Math.round(view.getTranslationX()); float moveYTo = endRect.top + Math.round(view.getTranslationY()); Animator moveXAnimator = ObjectAnimator.ofFloat(view, "x", startX, moveXTo); Animator moveYAnimator = ObjectAnimator.ofFloat(view, "y", startY, moveYTo); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(animator, moveXAnimator, moveYAnimator); //prevent blinking when interrupt animation return new NoPauseAnimator(animatorSet); } 

    MainActivity.java:

      view.setOnClickListener(v -> { Intent intent = new Intent(this, SecondActivity.class); ActivityOptionsCompat transitionActivityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this, view, getString(R.string.circle)); ActivityCompat.startActivity(MainActivity.this, intent , transitionActivityOptions.toBundle()); }); 

    SecondActivity.java:

     @Override protected void onCreate(Bundle savedInstanceState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().setSharedElementEnterTransition(new CircleToRectTransition().setDuration(1500)); getWindow().setSharedElementExitTransition(new CircleToRectTransition().setDuration(1500)); } super.onCreate(savedInstanceState); ... } @Override public void onBackPressed() { supportFinishAfterTransition(); } 

    ÉDITION : La variante précédente de CircleToRectTransition n'était pas générale et ne fonctionnait que dans des cas spécifiques. Vérifier l'exemple modifié sans cet inconvénient

    EDITED2 : Il s'avère que vous n'avez pas besoin d'une transition personnalisée, supprimez simplement la logique d'installation de SecondActivity et fonctionnera par défaut. Avec cette approche, vous pouvez définir la durée de transition de cette façon .

    EDITED3 : fourni un backport pour api <18

    Soit dit en passant, vous pouvez transporter ce matériel sur des appareils pré-lollipop en utilisant une telle technique . Où vous pouvez utiliser les animateurs ont déjà été créés

    Il y a un code que vous devez ajouter: il faut essentiellement implémenter une transition personnalisée. Mais la plupart du code peut être réutilisé. Je vais pousser le code sur github pour votre référence, mais les étapes nécessaires sont:

    SecondAcvitiy Créez votre transition personnalisée:

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Transition transition = new CircularReveal(); transition.setInterpolator(new LinearInterpolator()); getWindow().setSharedElementEnterTransition(transition); } 

    CircularReveal capture les limites de vue (valeurs de début et de fin) et fournit deux animations, la première lorsque vous devez animer la vue d'image circulaire vers la grande, la seconde pour l'inverse.

     @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class CircularReveal extends Transition { private static final String BOUNDS = "viewBounds"; private static final String[] PROPS = {BOUNDS}; @Override public void captureStartValues(TransitionValues transitionValues) { captureValues(transitionValues); } @Override public void captureEndValues(TransitionValues transitionValues) { captureValues(transitionValues); } private void captureValues(TransitionValues values) { View view = values.view; Rect bounds = new Rect(); bounds.left = view.getLeft(); bounds.right = view.getRight(); bounds.top = view.getTop(); bounds.bottom = view.getBottom(); values.values.put(BOUNDS, bounds); } @Override public String[] getTransitionProperties() { return PROPS; } @Override public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null) { return null; } Rect startRect = (Rect) startValues.values.get(BOUNDS); final Rect endRect = (Rect) endValues.values.get(BOUNDS); final View view = endValues.view; Animator circularTransition; if (isReveal(startRect, endRect)) { circularTransition = createReveal(view, startRect, endRect); return new NoPauseAnimator(circularTransition); } else { layout(startRect, view); circularTransition = createConceal(view, startRect, endRect); circularTransition.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { Rect bounds = endRect; bounds.left -= view.getLeft(); bounds.top -= view.getTop(); bounds.right -= view.getLeft(); bounds.bottom -= view.getTop(); outline.setOval(bounds); view.setClipToOutline(true); } }); } }); return new NoPauseAnimator(circularTransition); } } private void layout(Rect startRect, View view) { view.layout(startRect.left, startRect.top, startRect.right, startRect.bottom); } private Animator createReveal(View view, Rect from, Rect to) { int centerX = from.centerX(); int centerY = from.centerY(); float finalRadius = (float) Math.hypot(to.width(), to.height()); return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, from.width()/2, finalRadius); } private Animator createConceal(View view, Rect from, Rect to) { int centerX = to.centerX(); int centerY = to.centerY(); float initialRadius = (float) Math.hypot(from.width(), from.height()); return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, initialRadius, to.width()/2); } private boolean isReveal(Rect startRect, Rect endRect) { return startRect.width() < endRect.width(); } } 
    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.