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)
After mastering these, we can use attribute animation to complete some custom controls. Let's see how to implement them
GithubDownload address
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.