像抖音一样的长按录制按钮动画(XML)?

Long press record button animation like TikTok (XML)?

我正在创建一个视频录制应用程序,我想创建一个类似于 TikTok 的录制按钮。我已经创建了预录按钮,但我必须在长按时为录制按钮制作动画。这是预录按钮的代码:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape
            android:shape="oval">

            <padding
                android:bottom="15dp"
                android:left="15dp"
                android:right="15dp"
                android:top="15dp"/>
            <stroke
                android:width="5dp"
                android:color="#CCFF0000"/>
        </shape>
    </item>
    <item>
        <shape
            android:shape="oval">
            <solid
                android:color="#CCFF0000"/>

            <size
                android:width="150dp"
                android:height="150dp"/>

        </shape>

    </item>
</layer-list>

我需要增加内边距,并在长按时将内部椭圆更改为圆角矩形,如下图所示:

如何实现?任何帮助,将不胜感激。问候。

[![在此处输入图片描述][2]][2]

Circle.java

public class Circle extends View {

    private static final int START_ANGLE_POINT = 90;

    private final Paint paint;
    private RectF rect;
    int strokeWidth = 0;
    private float angle=360;
    int height=0,width=0;
    public Circle(Context context, AttributeSet attrs) {
        super(context, attrs);


        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(getResources().getColor(R.color.red));

        rect = new RectF(strokeWidth/2, strokeWidth/2, width - strokeWidth, height - strokeWidth);

        //size 200x200 example

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        height = getHeight();
        width = getWidth();

        Log.d("height", String.valueOf(height));
        Log.d("width", String.valueOf(width));
        paint.setStrokeWidth(strokeWidth);
        rect.set(strokeWidth/2, strokeWidth/2, width - strokeWidth, height - strokeWidth);
        canvas.drawArc(rect, START_ANGLE_POINT, angle, false, paint);

    }

    public float getAngle() {
        return angle;
    }

    public void setAngle(float angle) {
        this.angle = angle;
    }

    public int getStokeWidth() {
        return strokeWidth;
    }
    public void setStokeWidth(int strokeWidth) {
        this.strokeWidth= strokeWidth;
        Log.d("strokeWidth", String.valueOf(strokeWidth));
    }
}

CircleAngleAnimation.java

public class CircleAngleAnimation extends Animation {

    private Circle circle;

    private float oldAngle;
    private float newAngle;

    public CircleAngleAnimation(Circle circle, int newAngle) {
        this.oldAngle = circle.getStokeWidth();
        this.newAngle = newAngle;
        this.circle = circle;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation transformation) {
        this.getDuration();

        int angle;
        if(interpolatedTime<0.5) {
             angle = Math.round(oldAngle + ((newAngle - oldAngle) * interpolatedTime));
        }else{
            angle = Math.round(oldAngle + ((newAngle - oldAngle) * (1-interpolatedTime)));
        }
        Log.d("angle", String.valueOf(this.getDuration())+" "+interpolatedTime);
        circle.setStokeWidth(angle);
        circle.requestLayout();
    }
}

像这样使用它:

Circle circle = (Circle) findViewById(R.id.circle);
CircleAngleAnimation animation = new CircleAngleAnimation(circle, 80);
animation.setDuration(5000);
circle.startAnimation(animation);

根据@Mert 建议的答案,您应该如何修改代码以获得所需的视图。它只是为了让你开始。

不确定我的逻辑是否正确,但它工作正常:P

动画将在点击圆圈时开始,一直持续到手指抬起

这是我的 xml

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

    <com.myapplication.systemupdate.CircleView
        android:id="@+id/voiceView"
        android:layout_width="150dp"
        android:layout_height="150dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.cardview.widget.CardView
        android:id="@+id/iv_square"
        android:layout_width="80dp"
        android:layout_height="80dp"
        app:cardBackgroundColor="@color/black"
        app:cardCornerRadius="40dp"
        app:cardElevation="0dp"
        app:contentPadding="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

这是CircleView.java

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.DecelerateInterpolator;

import java.util.Objects;


/**
 * https://github.com/kyze8439690/AndroidVoiceAnimation
 */
public class CircleView extends View {

    private Paint mPaint;
    private AnimatorSet mAnimatorSet = new AnimatorSet();

    private float mMinRadius;
    private float mMaxRadius;
    private float mCurrentRadius;
    private float mCurrentStroke;
    private float mMinStroke;
    private float mMaxStroke;

    public CircleView(Context context) {
        super(context);
        init();
    }

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

    public float getmMinRadius() {
        return mMinRadius;
    }

    public float getmMaxRadius() {
        return mMaxRadius;
    }

    public float getmMinStroke() {
        return mMinStroke;
    }

    public float getmMaxStroke() {
        return mMaxStroke;
    }

    private void init() {
        mMinRadius = dpToPx(getContext(), 45);
        mMinStroke = 15;
        mCurrentRadius = mMinRadius;
        mCurrentStroke = mMinStroke;

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(mMinStroke);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.rgb(255, 0, 0));


    }

    @Override
    protected void onSizeChanged(int w, int h, int oldWidth, int oldHeight) {
        super.onSizeChanged(w, h, oldWidth, oldHeight);
        mMaxRadius = (float) (Math.min(w, h) / 2.2);
        mMaxStroke = 60;
    }

    public int dpToPx(Context context, float valueInDp) {
        DisplayMetrics metrics = Objects.requireNonNull(context).getResources().getDisplayMetrics();
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float width = getWidth();
        float height = getHeight();

        float radius = Math.max(mCurrentRadius, mMinRadius);
        float stroke = Math.max(mCurrentStroke, mMinStroke);
        mPaint.setStrokeWidth(stroke);
        canvas.drawCircle(width / 2, height / 2, radius, mPaint);
    }

    public void animateRadius(float radius, float stroke) {

        if (stroke > mMaxStroke) {
            stroke = mMaxStroke;
        } else if (stroke < mMinStroke) {
            stroke = mMinStroke;
        }


        if (mAnimatorSet.isRunning()) {
            mAnimatorSet.cancel();
        }
        mAnimatorSet = new AnimatorSet();
        mAnimatorSet.play(ObjectAnimator.ofFloat(this, "CurrentRadius", getCurrentRadius(), (float) (radius - (stroke / 2.2))))
                .with(ObjectAnimator.ofFloat(this, "CurrentStroke", getCurrentStroke(), stroke));
        mAnimatorSet.setDuration(80);
        mAnimatorSet.setInterpolator(new DecelerateInterpolator());
        mAnimatorSet.start();
        invalidate();
    }

    public float getCurrentRadius() {
        return mCurrentRadius;
    }

    /**
     * required this method to set ObjectAnimator
     *
     * @param currentRadius current radius
     */
    public void setCurrentRadius(float currentRadius) {
        mCurrentRadius = currentRadius;
        invalidate();
    }

    public float getCurrentStroke() {
        return mCurrentStroke;
    }

    public void setCurrentStroke(float mCurrentStroke) {
        this.mCurrentStroke = mCurrentStroke;
        invalidate();
    }

    public void endAnimation() {
        if (mAnimatorSet != null) mAnimatorSet.end();
    }
}

这是MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import androidx.transition.ChangeBounds;
import androidx.transition.TransitionManager;
import androidx.transition.TransitionSet;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private CircleView circleView;
    private CardView cardView;
    private Handler handler;
    private Runnable runnable;

    private int i = 0;
    private final ArrayList<Integer> al = new ArrayList<>();
    private final ArrayList<Integer> al2 = new ArrayList<>();

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        circleView = findViewById(R.id.voiceView);
        cardView = findViewById(R.id.iv_square);

        cardView.setOnTouchListener((view, motionEvent) -> {
            switch (motionEvent.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    startAnimationOfSquare();
                    circleView.animateRadius(circleView.getmMaxRadius(), circleView.getmMinStroke());
                    handler.postDelayed(runnable, 80);
                    return true;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    circleView.animateRadius(circleView.getmMinRadius(), circleView.getmMinStroke());
                    stopAnimationOfSquare();
                    handler.removeCallbacks(runnable);
                    resetAnimation();
                    return true;
            }
            return true;
        });
        resetAnimation();

        handler = new Handler();
        runnable = () -> {
            //to make smooth stroke width animation I increase and decrease value step by step
            int random;
            if (!al.isEmpty()) {
                random = al.get(i++);

                if (i >= al.size()) {
                    for (int j = al.size() - 1; j >= 0; j--) {
                        al2.add(al.get(j));
                    }
                    al.clear();
                    i = 0;
                }
            } else {
                random = al2.get(i++);

                if (i >= al2.size()) {
                    for (int j = al2.size() - 1; j >= 0; j--) {
                        al.add(al2.get(j));
                    }
                    al2.clear();
                    i = 0;
                }
            }
            circleView.animateRadius(circleView.getmMaxRadius(), random);

            handler.postDelayed(runnable, 130);
        };

    }

    private void resetAnimation() {
        i = 0;
        al.clear();
        al2.clear();
        al.add(25);
        al.add(30);
        al.add(35);
        al.add(40);
        al.add(45);
//        al.add(50);
//        al.add(55);
//        al.add(60);

        circleView.endAnimation();
    }

    public int dpToPx(float valueInDp) {
        DisplayMetrics metrics = getResources().getDisplayMetrics();
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
    }

    private AnimatorSet currentAnimator;
    private int settingPopupVisibilityDuration;

    private void startAnimationOfSquare() {
        settingPopupVisibilityDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
        if (currentAnimator != null) {
            currentAnimator.cancel();
        }
        Rect finalBounds = new Rect();
        final Point globalOffset = new Point();

        circleView.getGlobalVisibleRect(finalBounds, globalOffset);


        TransitionManager.beginDelayedTransition(cardView, new TransitionSet()
                .addTransition(new ChangeBounds()).setDuration(settingPopupVisibilityDuration));

        ViewGroup.LayoutParams params = cardView.getLayoutParams();
        params.height = dpToPx(40);
        params.width = dpToPx(40);
        cardView.setLayoutParams(params);

        AnimatorSet set = new AnimatorSet();
        set.play(ObjectAnimator.ofFloat(cardView, "radius", dpToPx(8)));
        set.setDuration(settingPopupVisibilityDuration);
        set.setInterpolator(new DecelerateInterpolator());
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                finishAnimation();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                finishAnimation();
            }

            private void finishAnimation() {
                currentAnimator = null;
            }

        });
        set.start();
        currentAnimator = set;
    }

    public void stopAnimationOfSquare() {

        if (currentAnimator != null) {
            currentAnimator.cancel();
        }

        TransitionManager.beginDelayedTransition(cardView, new TransitionSet()
                .addTransition(new ChangeBounds()).setDuration(settingPopupVisibilityDuration));

        ViewGroup.LayoutParams params = cardView.getLayoutParams();
        params.width = dpToPx(80);
        params.height = dpToPx(80);
        cardView.setLayoutParams(params);

        AnimatorSet set1 = new AnimatorSet();
        set1.play(ObjectAnimator.ofFloat(cardView, "radius", dpToPx(40)));//radius = height/2 to make it round
        set1.setDuration(settingPopupVisibilityDuration);
        set1.setInterpolator(new DecelerateInterpolator());
        set1.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                finishAnimation();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                finishAnimation();
            }

            private void finishAnimation() {
                currentAnimator = null;
            }
        });
        set1.start();
        currentAnimator = set1;
    }
}