Ответ 1
Я предлагаю создать настраиваемое представление, которое может анимироваться от круга до прямой и обратно, а затем обменивать пользовательский переход вокруг него с добавлением движущейся анимации.
Код ниже (ценная часть).
Для полного образца, проверьте мой 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();
}
EDITED. Предыдущий вариант CircleToRectTransition
не был общим и работал только в конкретном случае. Проверьте измененный пример без этого недостатка
EDITED2. Оказывается, вам вообще не нужен настраиваемый переход, просто удалите логику установки из SecondActivity
и он будет работать по умолчанию. При таком подходе вы можете установить продолжительность перехода таким образом.
EDITED3: предоставлено backport для api < 18
Кстати, вы можете заархивировать этот материал на pre-lollipop-устройствах с помощью такой техники. Где вы можете использовать аниматоры уже созданы