preface
Through the custom control, intended to imitate Twitter like effect. It mainly involves: 1. Application of cubic Bezier curve; 2. Comprehensive application of attribute animation; 3. Customize the View flow.
Disassemble the original effect
Let’s take a look at what the original Twitter look like.
Enlarged:
All right! The original speed is not very clear, after frame by frame delay:
Because this effect needs to be mixed with multiple animations, in order to get a more accurate proportion of each sub-animation stage, it is better to use photoshop to open it, and determine how to allocate the animation time according to the number of frames and the total number of frames in this stage.
implementation
1. Animation control
Here we use ValueAnimator and set the interpolator to LinearInterpolator to get progressively larger integer values proportional to time. This integer value has three functions here.
- Redraw the View every time it hears an integer change. There are five states.
- Draw a heart with shrinking and color gradient.
- Draw a circle with enlargement and color gradient.
- Draw a circle with enlargement and color gradient.
- The circle disappears, the heart enlarges, and fourteen dots surround it.
- The fourteen circles move outward and shrink, their opacity gradually fade out.
- The different stages are divided according to the size range of integer values.
- Implement other animation effects based on integer values to avoid numerous ObjectAnimators.
/** * display the effect of View click */
private void startViewMotion() {
if(animatorTime ! =null && animatorTime.isRunning())
return;
resetState();
animatorTime = ValueAnimator.ofInt(0.1200);
animatorTime.setDuration(mCycleTime);
animatorTime.setInterpolator(new LinearInterpolator());// Need constant velocity with time
animatorTime.start();
animatorTime.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int animatedValue = (int) animation.getAnimatedValue();
if (animatedValue == 0) {
if (animatorArgb == null| |! animatorArgb.isRunning()) { animatorArgb = ofArgb(mDefaultColor,0Xfff74769.0Xffde7bcc);
animatorArgb.setDuration(mCycleTime * 28 / 120);
animatorArgb.setInterpolator(newLinearInterpolator()); animatorArgb.start(); }}else if (animatedValue <= 100) {
float percent = calcPercent(0f, 100f, animatedValue);
mCurrentRadius = (int) (mRadius - mRadius * percent);
if(animatorArgb ! =null && animatorArgb.isRunning())
mCurrentColor = (int) animatorArgb.getAnimatedValue();
mCurrentState = HEART_VIEW;
invalidate();
} else if (animatedValue <= 280) {
float percent = calcPercent(100f, 340f, animatedValue);// The maximum radius is not reached at this stage
mCurrentRadius = (int) (2 * mRadius * percent);
if(animatorArgb ! =null && animatorArgb.isRunning())
mCurrentColor = (int) animatorArgb.getAnimatedValue();
mCurrentState = CIRCLE_VIEW;
invalidate();
} else if (animatedValue <= 340) {
float percent = calcPercent(100f, 340f, animatedValue);// The radius increases at the next stage, when the outer ring radius has reached its maximum value
mCurrentPercent = 1f - percent + 0.2f > 1f ? 1f : 1f - percent + 0.2f;// It is used to calculate the width of the circle. The minimum is 0.2, which is negatively correlated with the animation progress
mCurrentRadius = (int) (2 * mRadius * percent);
if(animatorArgb ! =null && animatorArgb.isRunning())
mCurrentColor = (int) animatorArgb.getAnimatedValue();
mCurrentState = RING_VIEW;
invalidate();
} else if (animatedValue <= 480) {
float percent = calcPercent(340f, 480f, animatedValue);// The radius of the inner ring increases until it dies
mCurrentPercent = percent;
mCurrentRadius = (int) (2 * mRadius);// The radius of the outer ring does not change
mCurrentState = RING_DOT__HEART_VIEW;
invalidate();
} else if (animatedValue <= 1200) {
float percent = calcPercent(480f, 1200f, animatedValue);
mCurrentPercent = percent;
mCurrentState = DOT__HEART_VIEW;
if (animatedValue == 1200) {
animatorTime.cancel();
animatorTime.removeAllListeners();
state = true; } invalidate(); }}}); }Copy the code
2. Graph drawing
heart
Here bezier curves are used to draw hearts, which are fitted by changing four sets of control points. Of course, you can use images in your project to make it easier to draw here.
// Draw a heart
private void drawHeart(Canvas canvas, int radius, int color) {
initControlPoints(radius);
mPaint.setColor(color);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
Path path = new Path();
path.moveTo(tPointB.x, tPointB.y);
path.cubicTo(tPointC.x, tPointC.y, rPointA.x, rPointA.y, rPointB.x, rPointB.y);
path.cubicTo(rPointC.x, rPointC.y, bPointC.x, bPointC.y, bPointB.x, bPointB.y);
path.cubicTo(bPointA.x, bPointA.y, lPointC.x, lPointC.y, lPointB.x, lPointB.y);
path.cubicTo(lPointA.x, lPointA.y, tPointA.x, tPointA.y, tPointB.x, tPointB.y);
canvas.drawPath(path, mPaint);
}Copy the code
other
There are also some circles, dots and circles that are easy to draw, which are not listed here. The emphasis is on the animation changes of the overlapping of these figures.
3. Click events
Provides external click event listener to handle like and unlike logic.
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (x + getLeft() < getRight() && y + getTop() < getBottom()) {// Click in the View area
if (state) {
deselectLike();
} else {
startViewMotion();
}
if(mListener ! =null)
mListener.onClick(this);
}
break;
}
return true;
}
Copy the code
Provides methods for setting listening externally
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
mListener = l;
}Copy the code
4. The end result
conclusion
This is roughly Twitter like. Although the animation should be allocated according to the scale of the original effect image frame, slowing down the observation is still not ideal. Other states that can’t be determined whether the color gradient or opacity changes, or whether scaling is accompanied by movement when critical disappearance, are simplified.
It is important to note that the color gradient animation is used, and the method system is provided by API21. The ArgbEvaluator is copied directly from the system source code into the project, which is equivalent to the property animation custom TypeEvaluator.
Source: github.com/qkxyjren/Li…
Welcome to point out mistakes