gpt4 book ai didi

Android 共享元素转换 : Transforming an ImageView from a circle to a rectangle and back again

转载 作者:IT老高 更新时间:2023-10-28 21:56:02 24 4
gpt4 key购买 nike

我正在尝试在两个 Activity 之间进行共享元素转换。

第一个 Activity 有一个圆形 ImageView ,第二个 Activity 有一个矩形 ImageView 。我只希望圆圈从第一个 Activity 过渡到第二个 Activity ,当我按回时它变成一个正方形并返回到圆圈。

我发现过渡不是那么整齐——在下面的动画中,你可以看到矩形 imageview 的大小似乎在缩小,直到它与圆的大小相匹配。方形 ImageView 出现片刻,然后出现圆圈。我想摆脱方形 ImageView ,使圆圈成为过渡的终点。

有人知道这是怎么做到的吗? enter image description here

我创建了一个小型测试存储库,您可以在此处下载:https://github.com/Winghin2517/TransitionTest

我的第一个 Activity 的代码 - ImageView 位于我的第一个 Activity 的 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;
}
}

这是我的第二个包含矩形 ImageView 的 Activity :

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()";
}
}

我明白,在我的第一个 Activity 中,圆圈只是通过应用 picasso 转换类被“剪切”出来的,并且 ImageView 只是一个方形布局,因此它显示为一个圆形。也许这就是我从矩形过渡到正方形时动画看起来像这样的原因,但我真的希望从矩形过渡到圆形。

我认为有办法做到这一点。在 whatsapp 应用程序中,我可以看到效果,但我似乎无法弄清楚他们是如何做到的 - 如果您在 whatsapp 上单击您 friend 的个人资料图片,该应用程序会将圆形 ImageView 扩展为正方形。单击返回将使正方形返回圆形。

enter image description here

最佳答案

我提议创建一个自定义 View ,它可以动画自己从圆形到矩形并返回,然后通过添加移动动画将自定义过渡包裹在它周围。

它的样子:
Circle to rect transition gif

代码如下(有值(value)的部分)。
如需完整 sample ,请查看 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 < 18

提供反向移植

顺便说一句,您可以使用 such technique 将这些东西反向移植到 Lollipop 之前的设备上。 .已经创建了可以使用动画师的地方

关于Android 共享元素转换 : Transforming an ImageView from a circle to a rectangle and back again,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39749404/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com