我正在尝试在两个活动之间进行共享元素转换。
第一个活动有一个圆形图像视图,第二个活动有一个矩形图像视图。我只是希望圆圈从第一个活动过渡到第二个活动,在那里它变成一个正方形,当我按下时回到圆圈。
我发现转换不是那么整洁 - 在下面的动画中,您可以看到矩形图像视图似乎减小了大小,直到它与圆的大小相匹配。方形图像视图会瞬间出现,然后出现圆圈。我想摆脱方形图像视图,以便圆圈成为过渡的终点。
我已经创建了一个小型测试仓库,您可以在此处下载:https://github.com/Winghin2517/TransitionTest
我的第一个活动的代码 - imageview位于我的第一个活动的MainFragment
内:
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;
}
}
这是我的第二个包含矩形图像视图的活动:
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();
}
}
这是我传入Picasso生成圆圈的PureCircleTransformation类:
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()";
}
}
我确实理解,在我的第一个活动中,通过应用毕加索变换类来“切割”圆圈,并且图像视图只是切出的方形布局,使其显示为圆形。也许这就是动画看起来像这样的原因,因为我正在从矩形过渡到正方形,但我真的希望过渡从矩形变为圆形。
我认为有办法做到这一点。在whatsapp应用程序中,我可以看到效果,但我似乎无法弄清楚他们是如何设法做到的 - 如果您点击whatsapp上朋友的个人资料图片,该应用程序会将圆形图像视图扩展为正方形。点击返回会将方块返回到圆圈。
答案 0 :(得分:26)
我提议创建一个自定义视图,它可以自动设置从圆形到矩形和背面的动画,然后通过添加移动动画来围绕它进行自定义过渡。
代码如下(有价值的部分)。
如需完整样本,请检查my 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();
}
已编辑:CircleToRectTransition
的上一个变体不是通用的,仅适用于特定情况。检查修改后的示例没有那个缺点
EDITED2 :事实证明您根本不需要自定义转换,只需从SecondActivity
中删除设置逻辑,它将以默认方式运行。使用此方法,您可以设置转换持续时间this way。
EDITED3 :为api&lt;提供后端口18
顺便说一句,您可以使用such technique将这些内容反向移植到棒棒糖前设备上。您已经创建了使用动画师的地方
答案 1 :(得分:5)
您需要添加一些代码:基本上您必须实现自定义转换。但是大多数代码都可以重用。我将推送github上的代码供您参考,但所需的步骤是:
SecondAcvitiy 创建自定义转换:
ALL_TABLES
CircularReveal 捕获视图边界(开始和结束值)并提供两个动画,第一个是您需要将圆形图像视图设置为大视图时的动画,第二个是相反的情况。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Transition transition = new CircularReveal();
transition.setInterpolator(new LinearInterpolator());
getWindow().setSharedElementEnterTransition(transition);
}