Using attribute animation to realize the animation effect of starlight

Keywords: Android Attribute less ButterKnife

In Android, attribute animation is a very interesting function to control the animation effect of parameter transformation Compared with gif image, animation controls need to save space and increase response speed

main points: (1) Use PercentLayout to set the size of custom controls (2) The two important functions of attribute animation are median and mapping (3) Erase brush( PorterDuff.Mode.CLEAR )How to use (4) Use the Arg evaluator to control the color transformation (5) Custom control's postinvalidate method (6) Setting and use of property < view, float > (7) Use mapping function to control changing rhythm (8) How to use interpolator (9) How to use animatorset (10) Click on the settings for the event (ontouch event)

picture

After mastering these, we can use attribute animation to complete some custom controls. Let's see how to implement them

GithubDownload address

animation

1. Home page

The home page loads the custom star control, and all the logic is implemented in the control

logic

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

layout

<FrameLayout
    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"
    tools:context="clwang.chunyu.me.wcl_like_anim_demo.MainActivity">

    <clwang.chunyu.me.wcl_like_anim_demo.LikeButtonView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"/>

</FrameLayout>

2. Star control

The control is mainly divided into three parts:
(1) The explosive effect of a ring
(2) Dot like scattering effect
(3) The light and shade of the stars

Layout, using PercentLayout, rings and stars account for 40% of the control width, with the same height and width

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:app="http://schemas.android.com/apk/res-auto"
             android:layout_width="match_parent"
             android:layout_height="match_parent">

    <clwang.chunyu.me.wcl_like_anim_demo.DotsView
        android:id="@+id/like_button_dv_dots"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"/>

    <android.support.percent.PercentRelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">

        <clwang.chunyu.me.wcl_like_anim_demo.CircleView
            android:id="@+id/like_button_cv_circle"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_aspectRatio="100%"
            app:layout_widthPercent="40%"/>

    </android.support.percent.PercentRelativeLayout>

    <android.support.percent.PercentRelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">

        <ImageView
            android:id="@+id/like_button_iv_star"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_gravity="center"
            android:contentDescription="@null"
            android:src="@drawable/ic_star_rate_off"
            app:layout_aspectRatio="100%"
            app:layout_widthPercent="40%"/>

    </android.support.percent.PercentRelativeLayout>

</FrameLayout>

Attribute animation commonly used tool class, mapping function and median function

/**
 * Tools
 * <p>
 * Created by wangchenlong on 16/1/5.
 */
public class Utils {
    // Map to next domain
    public static double mapValueFromRangeToRange(double value, double fromLow, double fromHigh, double toLow, double toHigh) {
        return toLow + ((value - fromLow) / (fromHigh - fromLow) * (toHigh - toLow));
    }

    // Middle value, value < low, return low, value > high, return high
    public static double clamp(double value, double low, double high) {
        return Math.min(Math.max(value, low), high);
    }
}

3. Ring

Circle to achieve the explosion effect. The outer circle is a solid circle, color gradient; the inner circle is the erase effect

logic

/**
 * Circle view, outer circle is a solid circle, color gradient; inner circle is erase effect
 * <p>
 * Created by wangchenlong on 16/1/5.
 */
public class CircleView extends View {

    private static final int START_COLOR = 0xFFFF5722;
    private static final int END_COLOR = 0xFFFFC107;

    private ArgbEvaluator mArgbEvaluator; // Argb estimator

    private Paint mCirclePaint; // Circular view
    private Paint mMaskPaint; // Mask view

    private Canvas mTempCanvas; // Middle canvas
    private Bitmap mTempBitmap; // Middle picture

    private int mMaxCircleSize; // Maximum circle size

    private float mOuterCircleRadiusProgress; // Controller of outer circle
    private float mInnerCircleRadiusProgress; // Controller of inner circle

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

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

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public CircleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    // initialization
    private void init() {
        mCirclePaint = new Paint();
        mCirclePaint.setStyle(Paint.Style.FILL);

        mMaskPaint = new Paint(); // The effect of disappearing
        mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

        mArgbEvaluator = new ArgbEvaluator();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mMaxCircleSize = w / 2; // Half of the current canvas
        mTempBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
        mTempCanvas = new Canvas(mTempBitmap); // Initialize canvas
    }

    @Override protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mTempCanvas.drawColor(0xffffff, PorterDuff.Mode.CLEAR); // Clear color, set to white
        // Draw a circle in the center of the control. The width is half of the current view width (it will change with the control), and the radius will be larger and larger
        mTempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mOuterCircleRadiusProgress * mMaxCircleSize, mCirclePaint);
        mTempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mInnerCircleRadiusProgress * mMaxCircleSize, mMaskPaint);
        canvas.drawBitmap(mTempBitmap, 0, 0, null); // The canvas draws two circles
    }

    public float getOuterCircleRadiusProgress() {
        return mOuterCircleRadiusProgress;
    }

    public void setOuterCircleRadiusProgress(float outerCircleRadiusProgress) {
        mOuterCircleRadiusProgress = outerCircleRadiusProgress;
        updateCircleColor();
        postInvalidate(); // Delay redraw
    }

    // Update color changes for circles
    private void updateCircleColor() {
        // 0.5 to 1 color gradient
        float colorProgress = (float) Utils.clamp(mOuterCircleRadiusProgress, 0.5, 1);
        // Transform mapping control
        colorProgress = (float) Utils.mapValueFromRangeToRange(colorProgress, 0.5f, 1f, 0f, 1f);
        mCirclePaint.setColor((Integer) mArgbEvaluator.evaluate(colorProgress, START_COLOR, END_COLOR));
    }

    public float getInnerCircleRadiusProgress() {
        return mInnerCircleRadiusProgress;
    }

    public void setInnerCircleRadiusProgress(float innerCircleRadiusProgress) {
        mInnerCircleRadiusProgress = innerCircleRadiusProgress;
        postInvalidate(); // Delay redraw
    }

    // Inner circle processing
    public static final Property<CircleView, Float> INNER_CIRCLE_RADIUS_PROGRESS =
            new Property<CircleView, Float>(Float.class, "innerCircleRadiusProgress") {
                @Override
                public Float get(CircleView object) {
                    return object.getInnerCircleRadiusProgress();
                }

                @Override
                public void set(CircleView object, Float value) {
                    object.setInnerCircleRadiusProgress(value);
                }
            };

    // Outer circle processing
    public static final Property<CircleView, Float> OUTER_CIRCLE_RADIUS_PROGRESS =
            new Property<CircleView, Float>(Float.class, "outerCircleRadiusProgress") {
                @Override
                public Float get(CircleView object) {
                    return object.getOuterCircleRadiusProgress();
                }

                @Override
                public void set(CircleView object, Float value) {
                    object.setOuterCircleRadiusProgress(value);
                }
            };
}

Initialize. The outer circle uses a solid brush and the inner circle uses an erase brush. The ArgbEvaluator controls the color transformation

    // initialization
    private void init() {
        mCirclePaint = new Paint(); // Black circle
        mCirclePaint.setStyle(Paint.Style.FILL);

        mMaskPaint = new Paint(); // Vanishing effect
        mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

        mArgbEvaluator = new ArgbEvaluator();
    }

Circles, inner circles and outer circles are drawn at the center of the canvas, with a radius half the current width of the canvas

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mMaxCircleSize = w / 2; // Half of the current canvas
        mTempBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
        mTempCanvas = new Canvas(mTempBitmap); // Initialize canvas
    }

    @Override protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mTempCanvas.drawColor(0xffffff, PorterDuff.Mode.CLEAR); // Clear color, set to white
        // Draw a circle at the center of the control, half the width of the current view (as the control changes)
        mTempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mOuterCircleRadiusProgress * mMaxCircleSize, mCirclePaint);
        mTempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mInnerCircleRadiusProgress * mMaxCircleSize, mMaskPaint);
        canvas.drawBitmap(mTempBitmap, 0, 0, null); // The canvas draws two circles
    }

Control the radius of inner and outer circles by controlling Progress

    public float getOuterCircleRadiusProgress() {
        return mOuterCircleRadiusProgress;
    }

    public void setOuterCircleRadiusProgress(float outerCircleRadiusProgress) {
        mOuterCircleRadiusProgress = outerCircleRadiusProgress;
        updateCircleColor();
        postInvalidate(); // Delay redraw
    }

    // Update circle
    private void updateCircleColor() {
        // 0.5 to 1 color gradient
        float colorProgress = (float) Utils.clamp(mOuterCircleRadiusProgress, 0.5, 1);
        // Transform mapping control        
        colorProgress = (float) Utils.mapValueFromRangeToRange(colorProgress, 0.5f, 1f, 0f, 1f);
        mCirclePaint.setColor((Integer) mArgbEvaluator.evaluate(colorProgress, START_COLOR, END_COLOR));
    }

    public float getInnerCircleRadiusProgress() {
        return mInnerCircleRadiusProgress;
    }

    public void setInnerCircleRadiusProgress(float innerCircleRadiusProgress) {
        mInnerCircleRadiusProgress = innerCircleRadiusProgress;
        postInvalidate(); // Delay redraw
    }

postInvalidate() delays redrawing, and does not block UI threads

Set the animation attribute, change the size and color of the circle by setting the progress value

    // Inner circle processing
    public static final Property<CircleView, Float> INNER_CIRCLE_RADIUS_PROGRESS =
            new Property<CircleView, Float>(Float.class, "innerCircleRadiusProgress") {
                @Override
                public Float get(CircleView object) {
                    return object.getInnerCircleRadiusProgress();
                }

                @Override
                public void set(CircleView object, Float value) {
                    object.setInnerCircleRadiusProgress(value);
                }
            };

    // Outer circle processing
    public static final Property<CircleView, Float> OUTER_CIRCLE_RADIUS_PROGRESS =
            new Property<CircleView, Float>(Float.class, "outerCircleRadiusProgress") {
                @Override
                public Float get(CircleView object) {
                    return object.getOuterCircleRadiusProgress();
                }

                @Override
                public void set(CircleView object, Float value) {
                    object.setOuterCircleRadiusProgress(value);
                }
            };

4. Scatter point

Scattering points are composed of large points and small points. The two kinds of points are staggered in arrangement and color. The speed is slow first and then fast

/**
 * Diverging points are composed of large points and small points. The two types of points are staggered in arrangement and color. The speed is slow first and then fast
 * <p>
 * Created by wangchenlong on 16/1/6.
 */
public class DotsView extends View {
    private static final int DOTS_COUNT = 7; // 7 lattice
    private static final int OUTER_DOTS_POSITION_ANGLE = 51; // 51 degrees per origin

    private static final int COLOR_1 = 0xFFFFC107;
    private static final int COLOR_2 = 0xFFFF9800;
    private static final int COLOR_3 = 0xFFFF5722;
    private static final int COLOR_4 = 0xFFF44336;

    private final Paint[] mCirclePaints = new Paint[4]; // 4 types of circles

    // Center of image
    private int mCenterX;
    private int mCenterY;

    private float mMaxOuterDotsRadius; // Radius of maximum outer ring
    private float mMaxInnerDotsRadius; // Radius of maximum inner ring
    private float mMaxDotSize; // Maximum size of circle

    private float mCurrentProgress = 0; // Current progress, core parameters

    private float mCurrentRadius1 = 0; // Radius of outer circle point
    private float mCurrentDotSize1 = 0; // Size of outer circle points

    private float mCurrentDotSize2 = 0; // Radius of inner circle point
    private float mCurrentRadius2 = 0; // Size of inner circle point

    private ArgbEvaluator argbEvaluator = new ArgbEvaluator();

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

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

    public DotsView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public DotsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        // Fill in the circle
        for (int i = 0; i < mCirclePaints.length; i++) {
            mCirclePaints[i] = new Paint();
            mCirclePaints[i].setStyle(Paint.Style.FILL);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCenterX = w / 2;
        mCenterY = h / 2;
        mMaxDotSize = 20; // Point size
        mMaxOuterDotsRadius = w / 2 - mMaxDotSize * 2; // Maximum outer ring
        mMaxInnerDotsRadius = 0.8f * mMaxOuterDotsRadius; // Maximum inner ring
    }

    @Override
    protected void onDraw(Canvas canvas) {
        drawOuterDotsFrame(canvas);
        drawInnerDotsFrame(canvas);
    }

    // Outer circle points, draw several points, use different colors, center position CurrentRadius and point size CurrentDotSize are variables
    private void drawOuterDotsFrame(Canvas canvas) {
        for (int i = 0; i < DOTS_COUNT; i++) {
            int cX = (int) (mCenterX + mCurrentRadius1 * Math.cos(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
            int cY = (int) (mCenterY + mCurrentRadius1 * Math.sin(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
            canvas.drawCircle(cX, cY, mCurrentDotSize1, mCirclePaints[i % mCirclePaints.length]);
        }
    }

    // The inner circle point is staggered with the outer circle point by 10, and the color is also staggered with the outer circle point. The center position CurrentRadius and the point size CurrentDotSize are variables
    private void drawInnerDotsFrame(Canvas canvas) {
        for (int i = 0; i < DOTS_COUNT; i++) {
            int cX = (int) (mCenterX + mCurrentRadius2 * Math.cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
            int cY = (int) (mCenterY + mCurrentRadius2 * Math.sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));

            // i+1 make sure the colors are different
            canvas.drawCircle(cX, cY, mCurrentDotSize2, mCirclePaints[(i + 1) % mCirclePaints.length]);
        }
    }

    // Setting the current progress updates the size and track
    public void setCurrentProgress(float currentProgress) {
        mCurrentProgress = currentProgress;

        // Update location
        updateInnerDotsPosition();
        updateOuterDotsPosition();

        updateDotsPaints(); // Update color
        updateDotsAlpha(); // Update transparency

        postInvalidate(); // Delay redraw every time
    }

    public float getCurrentProgress() {
        return mCurrentProgress;
    }

    // Update internal points
    private void updateInnerDotsPosition() {
        // 0.3 or more
        if (mCurrentProgress < 0.3f) {
            this.mCurrentRadius2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0, 0.3f, 0.f, mMaxInnerDotsRadius);
        } else {
            this.mCurrentRadius2 = mMaxInnerDotsRadius;
        }

        // Shrinking speed of point
        if (mCurrentProgress < 0.2) {
            this.mCurrentDotSize2 = mMaxDotSize;
        } else if (mCurrentProgress < 0.5) {
            this.mCurrentDotSize2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.2f, 0.5f, mMaxDotSize, 0.3 * mMaxDotSize);
        } else {
            this.mCurrentDotSize2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.5f, 1f, mMaxDotSize * 0.3f, 0);
        }

    }

    // Transform the position of the outer point
    private void updateOuterDotsPosition() {
        // The radius is fast at first and slow at last
        if (mCurrentProgress < 0.3f) {
            mCurrentRadius1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.0f, 0.3f, 0, mMaxOuterDotsRadius * 0.8f);
        } else {
            mCurrentRadius1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.3f, 1f, 0.8f * mMaxOuterDotsRadius, mMaxOuterDotsRadius);
        }

        // The size of the point, less than 0.7 is the maximum point, and more than 0.7 is gradually 0
        if (mCurrentProgress < 0.7f) {
            mCurrentDotSize1 = mMaxDotSize;
        } else {
            mCurrentDotSize1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.7f, 1f, mMaxDotSize, 0);
        }
    }

    // Change color
    private void updateDotsPaints() {
        if (mCurrentProgress < 0.5f) {
            float progress = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0f, 0.5f, 0, 1f);
            mCirclePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
            mCirclePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
            mCirclePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
            mCirclePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
        } else {
            float progress = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.5f, 1f, 0, 1f);
            mCirclePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
            mCirclePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
            mCirclePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
            mCirclePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
        }
    }

    // Transparency of change
    private void updateDotsAlpha() {
        float progress = (float) Utils.clamp(mCurrentProgress, 0.6f, 1f); // 0.6 min, 1 Max
        int alpha = (int) Utils.mapValueFromRangeToRange(progress, 0.6f, 1f, 255, 0); // Until it disappears
        mCirclePaints[0].setAlpha(alpha);
        mCirclePaints[1].setAlpha(alpha);
        mCirclePaints[2].setAlpha(alpha);
        mCirclePaints[3].setAlpha(alpha);
    }

    public static final Property<DotsView, Float> DOTS_PROGRESS = new Property<DotsView, Float>(Float.class, "dotsProgress") {
        @Override
        public Float get(DotsView object) {
            return object.getCurrentProgress();
        }

        @Override
        public void set(DotsView object, Float value) {
            object.setCurrentProgress(value);
        }
    };
}

Initialize the circle, set the size and radius of the points, and arrange the size points staggered

    private void init() {
        // Fill in the circle
        for (int i = 0; i < mCirclePaints.length; i++) {
            mCirclePaints[i] = new Paint();
            mCirclePaints[i].setStyle(Paint.Style.FILL);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCenterX = w / 2;
        mCenterY = h / 2;
        mMaxDotSize = 20; // Point size
        mMaxOuterDotsRadius = w / 2 - mMaxDotSize * 2; // Maximum outer ring
        mMaxInnerDotsRadius = 0.8f * mMaxOuterDotsRadius; // Maximum inner ring
    }

    @Override
    protected void onDraw(Canvas canvas) {
        drawOuterDotsFrame(canvas);
        drawInnerDotsFrame(canvas);
    }

    // Outer circle points, draw several points, use different colors, center position CurrentRadius and point size CurrentDotSize are variables
    private void drawOuterDotsFrame(Canvas canvas) {
        for (int i = 0; i < DOTS_COUNT; i++) {
            int cX = (int) (mCenterX + mCurrentRadius1 * Math.cos(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
            int cY = (int) (mCenterY + mCurrentRadius1 * Math.sin(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
            canvas.drawCircle(cX, cY, mCurrentDotSize1, mCirclePaints[i % mCirclePaints.length]);
        }
    }

    // The inner circle point is staggered with the outer circle point by 10, and the color is also staggered with the outer circle point. The center position CurrentRadius and the point size CurrentDotSize are variables
    private void drawInnerDotsFrame(Canvas canvas) {
        for (int i = 0; i < DOTS_COUNT; i++) {
            int cX = (int) (mCenterX + mCurrentRadius2 * Math.cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
            int cY = (int) (mCenterY + mCurrentRadius2 * Math.sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));

            // i+1 make sure the colors are different
            canvas.drawCircle(cX, cY, mCurrentDotSize2, mCirclePaints[(i + 1) % mCirclePaints.length]);
        }
    }

Set point size and radius, color change, transparency, use mapping function to control speed change

    // Setting the current progress updates the size and track
    public void setCurrentProgress(float currentProgress) {
        mCurrentProgress = currentProgress;

        // Update location
        updateInnerDotsPosition();
        updateOuterDotsPosition();

        updateDotsPaints(); // Update color
        updateDotsAlpha(); // Update transparency

        postInvalidate(); // Delay redraw every time
    }

    public float getCurrentProgress() {
        return mCurrentProgress;
    }

    // Update internal points
    private void updateInnerDotsPosition() {
        // 0.3 or more
        if (mCurrentProgress < 0.3f) {
            this.mCurrentRadius2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0, 0.3f, 0.f, mMaxInnerDotsRadius);
        } else {
            this.mCurrentRadius2 = mMaxInnerDotsRadius;
        }

        // Shrinking speed of point
        if (mCurrentProgress < 0.2) {
            this.mCurrentDotSize2 = mMaxDotSize;
        } else if (mCurrentProgress < 0.5) {
            this.mCurrentDotSize2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.2f, 0.5f, mMaxDotSize, 0.3 * mMaxDotSize);
        } else {
            this.mCurrentDotSize2 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.5f, 1f, mMaxDotSize * 0.3f, 0);
        }

    }

    // Transform the position of the outer point
    private void updateOuterDotsPosition() {
        // The radius is fast at first and slow at last
        if (mCurrentProgress < 0.3f) {
            mCurrentRadius1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.0f, 0.3f, 0, mMaxOuterDotsRadius * 0.8f);
        } else {
            mCurrentRadius1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.3f, 1f, 0.8f * mMaxOuterDotsRadius, mMaxOuterDotsRadius);
        }

        // The size of the point, less than 0.7 is the maximum point, and more than 0.7 is gradually 0
        if (mCurrentProgress < 0.7f) {
            mCurrentDotSize1 = mMaxDotSize;
        } else {
            mCurrentDotSize1 = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.7f, 1f, mMaxDotSize, 0);
        }
    }

    // Change color
    private void updateDotsPaints() {
        if (mCurrentProgress < 0.5f) {
            float progress = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0f, 0.5f, 0, 1f);
            mCirclePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
            mCirclePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
            mCirclePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
            mCirclePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
        } else {
            float progress = (float) Utils.mapValueFromRangeToRange(mCurrentProgress, 0.5f, 1f, 0, 1f);
            mCirclePaints[0].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));
            mCirclePaints[1].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_3, COLOR_4));
            mCirclePaints[2].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_4, COLOR_1));
            mCirclePaints[3].setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
        }
    }

    // Transparency of change
    private void updateDotsAlpha() {
        float progress = (float) Utils.clamp(mCurrentProgress, 0.6f, 1f); // 0.6 min, 1 Max
        int alpha = (int) Utils.mapValueFromRangeToRange(progress, 0.6f, 1f, 255, 0); // Until it disappears
        mCirclePaints[0].setAlpha(alpha);
        mCirclePaints[1].setAlpha(alpha);
        mCirclePaints[2].setAlpha(alpha);
        mCirclePaints[3].setAlpha(alpha);
    }

Add attribute

    public static final Property<DotsView, Float> DOTS_PROGRESS = new Property<DotsView, Float>(Float.class, "dotsProgress") {
        @Override
        public Float get(DotsView object) {
            return object.getCurrentProgress();
        }

        @Override
        public void set(DotsView object, Float value) {
            object.setCurrentProgress(value);
        }
    };

5. Control logic

Like the button, through the property value change (0-1), set the state of the sub control, use the interpolator to control the speed change

/**
 * Like the button, through the property value change (0-1) to set the state of the sub control, using different interpolators to control the speed
 * <p>
 * Created by wangchenlong on 16/1/5.
 */
public class LikeButtonView extends FrameLayout {

    @Bind(R.id.like_button_cv_circle) CircleView mCvCircle; // circular
    @Bind(R.id.like_button_iv_star) ImageView mIvStar; // stars
    @Bind(R.id.like_button_dv_dots) DotsView mDvDots; // circle

    private DecelerateInterpolator mDecelerate; // Deceleration interpolation
    private OvershootInterpolator mOvershoot; // Beyond interpolation
    private AccelerateDecelerateInterpolator mAccelerateDecelerate; // Acceleration deceleration interpolation
    private AnimatorSet mAnimatorSet; // Animation Collection

    private boolean mIsChecked; // Click status

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

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

    public LikeButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public LikeButtonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    // Initialize view
    private void init() {
        isInEditMode();

        LayoutInflater.from(getContext()).inflate(R.layout.view_like_button, this, true);
        ButterKnife.bind(this);

        mDecelerate = new DecelerateInterpolator(); // Retarder interpolator
        mOvershoot = new OvershootInterpolator(4); // Out of interpolator
        mAccelerateDecelerate = new AccelerateDecelerateInterpolator(); // Acceleration and deceleration interpolator

        setOnClickListener(this::clickView);
    }

    // Click View
    private void clickView(View view) {
        mIsChecked = !mIsChecked;
        mIvStar.setImageResource(mIsChecked ? R.drawable.ic_star_rate_on : R.drawable.ic_star_rate_off);

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

        if (mIsChecked) {
            mIvStar.animate().cancel();
            mIvStar.setScaleX(0);
            mIvStar.setScaleY(0);
            mCvCircle.setInnerCircleRadiusProgress(0);
            mCvCircle.setOuterCircleRadiusProgress(0);
            mDvDots.setCurrentProgress(0);

            mAnimatorSet = new AnimatorSet();

            ObjectAnimator outerCircleAnimator = ObjectAnimator.ofFloat(mCvCircle, CircleView.OUTER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
            outerCircleAnimator.setDuration(250);
            outerCircleAnimator.setInterpolator(mDecelerate);

            ObjectAnimator innerCircleAnimator = ObjectAnimator.ofFloat(mCvCircle, CircleView.INNER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
            innerCircleAnimator.setDuration(200);
            innerCircleAnimator.setStartDelay(200);
            innerCircleAnimator.setInterpolator(mDecelerate);

            ObjectAnimator starScaleYAnimator = ObjectAnimator.ofFloat(mIvStar, ImageView.SCALE_Y, 0.2f, 1f);
            starScaleYAnimator.setDuration(350);
            starScaleYAnimator.setStartDelay(250);
            starScaleYAnimator.setInterpolator(mOvershoot);

            ObjectAnimator starScaleXAnimator = ObjectAnimator.ofFloat(mIvStar, ImageView.SCALE_X, 0.2f, 1f);
            starScaleXAnimator.setDuration(350);
            starScaleXAnimator.setStartDelay(250);
            starScaleXAnimator.setInterpolator(mOvershoot);

            ObjectAnimator dotsAnimator = ObjectAnimator.ofFloat(mDvDots, DotsView.DOTS_PROGRESS, 0, 1f);
            dotsAnimator.setDuration(900);
            dotsAnimator.setStartDelay(50);
            dotsAnimator.setInterpolator(mAccelerateDecelerate);

            mAnimatorSet.playTogether(
                    outerCircleAnimator,
                    innerCircleAnimator,
                    starScaleYAnimator,
                    starScaleXAnimator,
                    dotsAnimator
            );


            mAnimatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationCancel(Animator animation) {
                    mIvStar.setScaleX(1);
                    mIvStar.setScaleY(1);
                    mCvCircle.setInnerCircleRadiusProgress(0);
                    mCvCircle.setOuterCircleRadiusProgress(0);
                    mDvDots.setCurrentProgress(0);
                }
            });

            mAnimatorSet.start();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mIvStar.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(mDecelerate);
                setPressed(true);
                break;

            case MotionEvent.ACTION_MOVE:
                float x = event.getX();
                float y = event.getY();
                boolean isInside = (x > 0 && x < getWidth() && y > 0 && y < getHeight());
                if (isPressed() != isInside) {
                    setPressed(isInside);
                }
                break;

            case MotionEvent.ACTION_UP:
                mIvStar.animate().scaleX(1).scaleY(1).setInterpolator(mDecelerate);
                if (isPressed()) {
                    performClick();
                    setPressed(false);
                }
                break;
        }
        return true;
    }
}

Initialization, decelerating interpolator for circle control, overrunning interpolator for star picture, accelerating and decelerating interpolator for scatter point

    // Initialize view
    private void init() {
        isInEditMode();

        LayoutInflater.from(getContext()).inflate(R.layout.view_like_button, this, true);
        ButterKnife.bind(this);

        mDecelerate = new DecelerateInterpolator(); // Retarder interpolator
        mOvershoot = new OvershootInterpolator(4); // Beyond interpolator, picture zooms in and out
        mAccelerateDecelerate = new AccelerateDecelerateInterpolator(); // Acceleration and deceleration interpolator

        setOnClickListener(this::clickView);
    }

Design the animation of three controls in turn, put them into the animation set, set the initial state, and start

    // Click View
    private void clickView(View view) {
        mIsChecked = !mIsChecked;
        mIvStar.setImageResource(mIsChecked ? R.drawable.ic_star_rate_on : R.drawable.ic_star_rate_off);

        // Situation status
        if (mAnimatorSet != null) {
            mAnimatorSet.cancel();
        }

        if (mIsChecked) {
            mIvStar.animate().cancel();
            mIvStar.setScaleX(0);
            mIvStar.setScaleY(0);
            mCvCircle.setInnerCircleRadiusProgress(0);
            mCvCircle.setOuterCircleRadiusProgress(0);
            mDvDots.setCurrentProgress(0);

            mAnimatorSet = new AnimatorSet();

            ObjectAnimator outerCircleAnimator = ObjectAnimator.ofFloat(mCvCircle, CircleView.OUTER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
            outerCircleAnimator.setDuration(250);
            outerCircleAnimator.setInterpolator(mDecelerate);

            // Delay erase
            ObjectAnimator innerCircleAnimator = ObjectAnimator.ofFloat(mCvCircle, CircleView.INNER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
            innerCircleAnimator.setDuration(200);
            innerCircleAnimator.setStartDelay(200);
            innerCircleAnimator.setInterpolator(mDecelerate);

            // Zoom in and out vertically and horizontally
            ObjectAnimator starScaleYAnimator = ObjectAnimator.ofFloat(mIvStar, ImageView.SCALE_Y, 0.2f, 1f);
            starScaleYAnimator.setDuration(350);
            starScaleYAnimator.setStartDelay(250);
            starScaleYAnimator.setInterpolator(mOvershoot);

            ObjectAnimator starScaleXAnimator = ObjectAnimator.ofFloat(mIvStar, ImageView.SCALE_X, 0.2f, 1f);
            starScaleXAnimator.setDuration(350);
            starScaleXAnimator.setStartDelay(250);
            starScaleXAnimator.setInterpolator(mOvershoot);

            // First fast, then slow
            ObjectAnimator dotsAnimator = ObjectAnimator.ofFloat(mDvDots, DotsView.DOTS_PROGRESS, 0, 1f);
            dotsAnimator.setDuration(900);
            dotsAnimator.setStartDelay(50);
            dotsAnimator.setInterpolator(mAccelerateDecelerate);

            // Put in Animation Collection
            mAnimatorSet.playTogether(
                    outerCircleAnimator,
                    innerCircleAnimator,
                    starScaleYAnimator,
                    starScaleXAnimator,
                    dotsAnimator
            );

            // Animation Collection Monitoring
            mAnimatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationCancel(Animator animation) {
                    // Initial value
                    mIvStar.setScaleX(1);
                    mIvStar.setScaleY(1);
                    mCvCircle.setInnerCircleRadiusProgress(0);
                    mCvCircle.setOuterCircleRadiusProgress(0);
                    mDvDots.setCurrentProgress(0);
                }
            });

            mAnimatorSet.start();
        }
    }

Click event

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mIvStar.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(mDecelerate);
                setPressed(true);
                break;

            // Move in the control and click
            case MotionEvent.ACTION_MOVE:
                float x = event.getX();
                float y = event.getY();
                boolean isInside = (x > 0 && x < getWidth() && y > 0 && y < getHeight());
                if (isPressed() != isInside) {
                    setPressed(isInside);
                }
                break;

            case MotionEvent.ACTION_UP:
                mIvStar.animate().scaleX(1).scaleY(1).setInterpolator(mDecelerate);
                if (isPressed()) {
                    performClick();
                    setPressed(false);
                }
                break;
        }
        return true;
    }


Although attribute animation is a little complicated, it is very interesting to master its principle, which is similar to mathematical formula and exercise intelligence

OK, that's all! Enjoy it!



By spike King
Link: http://www.jianshu.com/p/520a0400aa7c
Source: Jianshu
The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source.

Posted by mauri_gato on Sun, 31 May 2020 09:14:52 -0700