android - todas - no puedo ver fotos en messenger
Transición de elementos compartidos de Android: transformación de un ImageView de un círculo a un rectángulo y viceversa (2)
Hay algunos códigos que necesita agregar: básicamente, tiene que implementar una transición personalizada. Pero la mayor parte del código puede ser reutilizado. Voy a presionar el código en github para su referencia, pero los pasos necesarios son:
SecondAcvitiy Crea tu transición personalizada:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Transition transition = new CircularReveal();
transition.setInterpolator(new LinearInterpolator());
getWindow().setSharedElementEnterTransition(transition);
}
CircularReveal captura los límites de la vista (valores de inicio y final) y proporciona dos animaciones, la primera cuando necesitas animar la vista de la imagen circular a la grande, la segunda para el caso inverso.
@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();
}
}
Estoy tratando de hacer una transición de elemento compartido entre dos actividades.
La primera actividad tiene una vista de imagen circular y la segunda actividad tiene una vista de imagen rectangular. Solo quiero que el círculo pase de la primera actividad a la segunda actividad, donde se convierte en un cuadrado y vuelve al círculo cuando presiono atrás.
Me parece que la transición no es tan clara: en la animación a continuación, puede ver que la vista de imagen rectangular parece reducir en tamaño hasta que coincida con el tamaño del círculo. La vista de imagen cuadrada aparece durante una fracción de segundo y luego aparece el círculo. Quiero deshacerme de la vista de imagen cuadrada para que el círculo se convierta en el punto final de la transición.
Alguien sabe como se hace esto?
He creado un pequeño repositorio de prueba que puede descargar aquí: https://github.com/Winghin2517/TransitionTest
El código para mi primera actividad: la vista de imagen se encuentra dentro del MainFragment
de mi primera actividad:
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;
}
}
Esta es mi segunda actividad que contiene la vista de imagen rectangular:
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();
}
}
Esta es la clase PureCircleTransformation que paso en Picasso para generar el círculo:
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()";
}
}
Entiendo que en mi primera actividad, el círculo es simplemente ''cortado'' aplicando la clase de transformación de Picasso y que la vista de la imagen es solo un recorte cuadrado para que aparezca como un círculo. Tal vez esta es la razón por la que la animación se ve así cuando estoy pasando de una forma rectangular a una cuadrada, pero realmente quiero que la transición pase de la forma rectangular a un círculo.
Creo que hay una manera de hacer esto. En la aplicación WhatsApp, puedo ver el efecto, pero parece que no consigo entender cómo lograron hacerlo. Si haces clic en la imagen de perfil de tus amigos en WhatsApp, la aplicación amplía la vista de la imagen del círculo a un cuadrado. Haciendo clic atrás volverá el cuadrado al círculo.
Ofrezco crear una vista personalizada, que puede animarse a sí misma de un círculo a otro y luego a la vuelta y luego envolver la transición personalizada a su alrededor con la adición de animación en movimiento.
El código está debajo (parte valiosa).
Para muestra completa, compruebe mi 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();
}
EDITADO : La variante anterior de CircleToRectTransition
no era general y funcionaba solo en casos específicos. Ver ejemplo modificado sin esa desventaja.
EDITED2 : Resulta que no necesita una transición personalizada en absoluto, simplemente elimine la lógica de configuración de SecondActivity
y funcionará de forma predeterminada. Con este enfoque podría establecer la duración de la transición de esta manera .
EDITED3 : Proporcionó backport para api <18
Por cierto, puedes hacer backport estas cosas en dispositivos pre-lollipop con el uso de dicha técnica . Donde se pueden usar los animadores ya se han creado.