Android ImageView变形:从Square到Circle(解决方案更新)

时间:2016-10-03 20:57:28

标签: android animation

我正在使用CircularReveal创建动画,将方形专辑艺术制作成圆圈。以下是一个简短的片段。

int cx = mImageView.getMeasuredWidth() / 2;
int cy = mImageView.getMeasuredHeight() / 2;

// get the initial radius for the clipping circle
int initialRadius = mImageView.getWidth() / 2;

// create the animation (the final radius is zero)
Animator anim = ViewAnimationUtils.createCircularReveal(mImageView, cx, cy, mImageView.getWidth(), initialRadius);
anim.setDuration(500);
anim.start();

问题是,在动画之后,图像不会保持圆形。我正在寻找类似Animation.fillAfter(boolean fillAfter)调用的东西,但动画师没有这个选项。

以下是当前(故障)行为。

enter image description here

在动画播放后修改图像的任何建议?

2 个答案:

答案 0 :(得分:11)

我通过使用CircularRevealView使用我的自定义GradientDrawable将此CardView完全替换为自定义掩码来解决此问题。

my xml(tmp_activity.xml)

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@color/background_button"
    tools:context=".TempActivity_">

    <com.myapp.customviews.AnimatableCardView
        android:id="@+id/base_view"
        android:layout_marginLeft="@dimen/margin_medium"
        android:layout_centerVertical="true"
        android:layout_width="@dimen/album_art_small"
        android:layout_height="@dimen/album_art_small"
        app:cardElevation="0dp">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_centerVertical="true"
                android:layout_centerHorizontal="true"
                android:src="@drawable/charlie"
                android:id="@+id/imageView2"/>

    </com.myapp.customviews.AnimatableCardView>
</RelativeLayout>

我的活动(请注意,我使用的是Android Annotations,而不是findViewById(..))

@EActivity(R.layout.tmp_activity)
public class TempActivity extends BaseActivity {
    @ViewById(R.id.base_view)
    ViewGroup mParent;

    @ViewById(R.id.imageView2)
    ImageView mImageView;

    GradientDrawable gradientDrawable;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    private volatile boolean isCircle = false;
    @Override
    protected void onViewsCreated() {
        super.onViewsCreated();

        gradientDrawable = new GradientDrawable();
        gradientDrawable.setCornerRadius(30.0f);
        gradientDrawable.setShape(GradientDrawable.RECTANGLE);
        mParent.setBackground(gradientDrawable);

        mImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (isCircle) {
                    makeSquare();
                }
                else {
                    makeCircle();
                }
                isCircle = !isCircle;
            }
        });
    }

    private void makeCircle() {
        ObjectAnimator cornerAnimation =
                ObjectAnimator.ofFloat(gradientDrawable, "cornerRadius", 30f, 200.0f);

        Animator shiftAnimation = AnimatorInflater.loadAnimator(this, R.animator.slide_right_down);
        shiftAnimation.setTarget(mParent);

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(500);
        animatorSet.playTogether(cornerAnimation, shiftAnimation);
        animatorSet.start();
    }

    private void makeSquare() {
        ObjectAnimator cornerAnimation =
                ObjectAnimator.ofFloat(gradientDrawable, "cornerRadius", 200.0f, 30f);

        Animator shiftAnimation = AnimatorInflater.loadAnimator(this, R.animator.slide_left_up);
        shiftAnimation.setTarget(mParent);

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(500);
        animatorSet.playTogether(cornerAnimation, shiftAnimation);
        animatorSet.start();
    }
}

我的自定义CardView(AnimatableCardView)

public class AnimatableCardView extends CardView {
    private float xFraction = 0;
    private float yFraction = 0;

    private ViewTreeObserver.OnPreDrawListener preDrawListener = null;

    public AnimatableCardView(Context context) {
        super(context);
    }

    public AnimatableCardView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AnimatableCardView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    // Note that fraction "0.0" is the starting point of the view. This includes margins.
    // If this view was placed in (200,300), moveTo="0.1" for xFraction will give you (220,300)
    public void setXFraction(float fraction) {
        this.xFraction = fraction;

        if (((ViewGroup) getParent()).getWidth() == 0) {
            if (preDrawListener == null) {
                preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
                    @Override
                    public boolean onPreDraw() {
                        getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
                        setXFraction(xFraction);
                        return true;
                    }
                };
                getViewTreeObserver().addOnPreDrawListener(preDrawListener);
            }
            return;
        }

        float translationX = Math.max(0, (((ViewGroup) getParent()).getWidth()) * fraction - (getWidth() * getScaleX() / 2));
        setTranslationX(translationX);
    }

    public float getXFraction() {
        return this.xFraction;
    }

    public void setYFraction(float fraction) {
        this.yFraction = fraction;

        if (((ViewGroup) getParent()).getHeight() == 0) {
            if (preDrawListener == null) {
                preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
                    @Override
                    public boolean onPreDraw() {
                        getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
                        setYFraction(yFraction);
                        return true;
                    }
                };
                getViewTreeObserver().addOnPreDrawListener(preDrawListener);
            }
            return;
        }

        float translationY = Math.max(0, (((ViewGroup) getParent()).getHeight()) * fraction - (getHeight() * getScaleY() / 2));
        setTranslationY(translationY);
    }

    public float getYFraction() {
        return this.yFraction;
    }
}

<强> slide_right_down.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:ordering="together">
        <objectAnimator
            android:interpolator="@android:anim/accelerate_decelerate_interpolator"
            android:propertyName="xFraction"
            android:duration="@android:integer/config_mediumAnimTime"
            android:valueFrom="0.0"
            android:valueTo="0.5"
            android:valueType="floatType"/>

        <objectAnimator
            android:interpolator="@android:anim/accelerate_decelerate_interpolator"
            android:propertyName="yFraction"
            android:duration="@android:integer/config_mediumAnimTime"
            android:valueFrom="0.0"
            android:valueTo="0.1"
            android:valueType="floatType"/>

        <objectAnimator
            android:duration="@android:integer/config_mediumAnimTime"
            android:propertyName="scaleX"
            android:valueFrom="1.0"
            android:valueTo="1.5"/>

        <objectAnimator
            android:duration="@android:integer/config_mediumAnimTime"
            android:propertyName="scaleY"
            android:valueFrom="1.0"
            android:valueTo="1.5"/>
</set>

<强> slide_left_up.xml

<?xml version="1.0" encoding="utf-8"?>
<set android:ordering="together"
     xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:propertyName="xFraction"
        android:duration="@android:integer/config_mediumAnimTime"
        android:valueFrom="0.5"
        android:valueTo="0.0"
        android:valueType="floatType"/>
    <objectAnimator
        android:propertyName="yFraction"
        android:duration="@android:integer/config_mediumAnimTime"
        android:valueFrom="0.1"
        android:valueTo="0.0"
        android:valueType="floatType"/>

    <objectAnimator
        android:duration="@android:integer/config_mediumAnimTime"
        android:propertyName="scaleX"
        android:valueFrom="1.5"
        android:valueTo="1.0"/>

    <objectAnimator
        android:duration="@android:integer/config_mediumAnimTime"
        android:propertyName="scaleY"
        android:valueFrom="1.5"
        android:valueTo="1.0"/>
</set>

这是结果(设备的速度更快,更顺畅)

enter image description here

答案 1 :(得分:1)

可以执行此操作的另一种方法是使用MotionLayout和ImageFilterView。 ConstraintLayout版本2.0中引入的ImageFilterView允许图像处理。它可以做令人惊奇的事情,例如淡化两个图像,但是它也可以修改给定图像的半径。我的示例没有被接受的答案所发布的那样小的反弹,但是使用KeyFrames可以很容易地添加它。

这是我的解决方案:VIDEO

这是实现该目标所需的文件

首先,您的活动/片段应如下所示(请注意,如果您希望将其合并到现有布局中,则该活动/片段也可以是subView

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity"
    app:layoutDescription="@xml/motion_scene">

    <androidx.constraintlayout.utils.widget.ImageFilterView
        android:id="@+id/puthPic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scaleType="centerCrop"
        android:src="@drawable/puth"/>

</androidx.constraintlayout.motion.widget.MotionLayout>

请注意,MotionLayout有一个名为app:layoutDescription的字段,它指向@ xml / motion_scene ...这就是motion_scene.xml布局的样子

<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@+id/start"
    motion:duration="500"
    motion:motionInterpolator="easeInOut">
    <OnClick
        motion:clickAction="toggle"
        motion:targetId="@+id/puthPic" />
</Transition>
<ConstraintSet android:id="@+id/start">
    <Constraint android:id="@id/puthPic">
        <Layout
            android:layout_width="128dp"
            android:layout_height="128dp"
            android:layout_marginStart="16dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <CustomAttribute
            motion:attributeName="roundPercent"
            motion:customFloatValue="1" />
    </Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
    <Constraint android:id="@id/puthPic">
        <Layout
            android:layout_width="128dp"
            android:layout_height="128dp"
            android:layout_marginEnd="16dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <CustomAttribute
            motion:attributeName="roundPercent"
            motion:customFloatValue="0.000001" />
    </Constraint>
</ConstraintSet>
</MotionScene>

仅需少量代码,motionLayout便会在位置和圆之间插值以为您平方!

您会注意到,我将motion:customFloatValue="0.000001"作为percentRound的结束场景值。这是因为如果将percentRound设置为0.0,则会存在一个导致图像保持矩形的现有错误。我已提交了此错误,如果需要,您可以here查看有关它的更多信息。

在那里,您可以轻松地在正方形和圆形视图之间进行动画制作!