-1

I am creating a video recording app and I want to create a record button similar to TikTok. I have created the pre-record button but I have to animate the record button on long press. This is the code for the pre-record button:

<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>

I need to increase the padding and change the inside oval to a rounded rectangle on long press, like the picture below:

enter image description here

How to achieve this? Any help would be appreciated. Regards.

[![enter image description here][2]][2]

WebDiva
  • 133
  • 3
  • 23
  • I would more likely suggest to use a custom view which draws you the oval and the record button. Therefore the animation of the stroke of the oval would be much easier. First result on stackoverflow for an animated circle (you have to adjust the code to animate the stroke width and not the angle): https://stackoverflow.com/questions/29381474/how-to-draw-a-circle-with-animation-in-android-with-circle-size-based-on-a-value – Mert Dec 28 '20 at 10:19
  • Thank You. I am really new to android and I haven't made any custom view with android. It'd be great if you can help. Regards. – WebDiva Dec 28 '20 at 10:22

2 Answers2

1

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

Use it like:

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

as per @Mert suggested answer this is how you should modify the code to get the desired view. It is just to get you started.

kelvin
  • 1,480
  • 1
  • 14
  • 28
  • It'd be really helpful if you could add the full implementation. I haven't made any custom views yet so this seems difficult for me. Regards. – WebDiva Dec 29 '20 at 00:50
1

Not sure my logic is correct or not but it's working fine :P

The animation will start on on tap of the circle and be continue until finger up

here is my 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>

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

and this is 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;
    }
}
Priyanka
  • 3,369
  • 1
  • 10
  • 33
  • Thank you! It's working perfect. But could you tell me how to make the outer ring expand and contract on long press? (as in the gif). It's just a request. Once again, Thank You! – WebDiva Dec 29 '20 at 14:54
  • Also the button is not in a normal position when the activity starts but it gets normal once i press it once. Please help if you can. – WebDiva Dec 29 '20 at 15:10
  • did you try to long press on the black button? have you seen what happens? if it is not your requirements than describe me in detail what you need – Priyanka Dec 30 '20 at 04:06
  • `Also the button is not in a normal position when the activity starts` in which device you are testing? – Priyanka Dec 30 '20 at 04:07
  • I fixed the button issue. The problem was that I was calling the runnable on activity start. I did the long press and it's working fine. What I meant was that, if you look at the tiktok gif, when there is a long press, the outer ring is expanded. This is working fine in your code. What I am asking for is that, the outer ring shoud expand and contract on long press, as in tiktok. In your code it expands on long press but is static and stays the same, until I leave my thumb. But anyway it's not necessary but I would really appreciate if you could help. Thank You. – WebDiva Dec 30 '20 at 06:16
  • in my emulator, expand and contract on long press is working same as gif. did you change anything? – Priyanka Dec 30 '20 at 06:21
  • No. Wait, I will send you the gif of the current animation. – WebDiva Dec 30 '20 at 06:23
  • I have added the current animation. Please compare the two's long press animation. You'll get what I'm saying – WebDiva Dec 30 '20 at 06:32
  • ohk, compare your `runnable` with mine. I think there is something missing. and in `resetAnimation()` you can uncomment lines and test that it is working or not – Priyanka Dec 30 '20 at 06:36
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/226591/discussion-between-webdiva-and-priyankagb). – WebDiva Dec 30 '20 at 06:45