rendering

What? Non-invasive? What the hell is this? It is also very simple, just use it, do not rewrite or customize the layout for a certain effect.

When you look at the above renderings, the first thing that might come to mind is to rewrite the dispatchDraw method for viewGroups and add custom viewgroups to the XML. That’s one way to do it. The other is what I call the ‘non-invasive’ idea, which is to extract the animated View into a tool class that can be dynamically added and removed.

Ideas:

  1. Custom AnimationView, override onDraw method.
  2. Added to the top-level ViewGroup. (The top-level View is just thatDecorViewThat isFrameLayoutOverlay its View right on top.)
  3. Play specific animation

Before addView, you need to get the specific coordinate value of the target View to the screen, so that the animation can be displayed in the correct position

Get the top-level View:

Window window = activity.getWindow();
ViewGroup container = (ViewGroup) window.getDecorView();
Copy the code

Gets the coordinates of the target View to the window

int[] viewXY = new int[2];
targetView.getLocationInWindow(viewXY); // 0 indicates x, 1 indicates y=>(x,y)
Copy the code

Binding the View

Window window = activity.getWindow();
if (window == null || activity.isFinishing()) {
    return; } ViewGroup container = (ViewGroup) window.getDecorView(); .if (likeAnimateView == null) {
    likeAnimateView = new LikeAnimateView(window.getContext());
    int[] viewXY = new int[2];
    targetView.getLocationInWindow(viewXY);
    int centerX = viewXY[0] + targetView.getWidth() / 2;
    int centerY = viewXY[1] + targetView.getHeight() / 2;
    likeAnimateView.setTargetXY(centerX, centerY);
    ViewGroup.LayoutParams params = container.getLayoutParams();
    ViewGroup.LayoutParams animParams = newViewGroup.LayoutParams(params.width, params.height); likeAnimateView.setLayoutParams(animParams); . container.addView(likeAnimateView); }Copy the code

Get the absolute coordinates of the target view, calculate the center coordinates of the target view, pass in the AnimateView to be bound, set the size of the AnimateView to the size of the top view, and add a View


Specific animation implementation:

This animation is actually a divergent effect (the process of gradually spreading from the inner circle to the outer circle).

Any point on the inner circle to any point on the outer circle to the path is this animation

After analyzing a Path, you need to use the Path and PathMeasure classes together

  • PathSetting the circle path
this.mPath.addCircle(x, y, 20, Path.Direction.CW);
Copy the code
  • PathMeasurePath Coordinates tracing of waypoints
this.mPathMeasure.setPath(this.mPath, true);// The second parameter indicates whether to close the path
Copy the code
  • PathMeasure.getPosTan(dis,pos,null)To obtainPathCoordinates on the path
this.mPathMeasure.getPosTan(discount, point, null);
Copy the code

LikeInfo class structure

public static class LikeInfo {
    public Bitmap mBitmap;
    public int x;
    public int y;
    public int startX, startY;
    public Path path = new Path();
    public PathMeasure pathMeasure = new PathMeasure();
    public Paint paint = new Paint();
    public ValueAnimator valueAnimator = new ValueAnimator();
    public int zWidth = 0;
    public LikeInfo(a) {
        x = 0;
        y = 0;
        paint.setColor(Color.RED);
        paint.setStrokeWidth(1);
        paint.setStyle(Paint.Style.STROKE);
    }
    public void setBitmap(Bitmap bitmap) {
        this.mBitmap = BitmapUtil.bitmapScale(bitmap, 60.60); }}Copy the code

X and y represent the coordinates of a graph. The xy coordinates at the beginning of startX and startY are mainly used to calculate the distance between the current coordinate point and the starting coordinate point. The hypotenuse length a^2 + B ^2 = c^2 can be calculated according to the Pythagorean theorem and divide with the total path to obtain a proportion. Each LikeInfo class has its own animation control classes, ValueAnimator, Paint,Path, and PathMeasure. Each Bitmap needs to be the same size, with the image scaled.

Inner circle part code:

this.mPath = new Path();
this.mPath.addCircle(x, y, 20, Path.Direction.CW);
this.mPathMeasure = new PathMeasure();
this.mPathMeasure.setPath(this.mPath, true);
float len = this.mPathMeasure.getLength();
float[] point = new float[2];
int index = 0;
// The first initial circle
for (int i = 0; i < len; i += (len / LikeAnimationHelper.likesList.size())) {
    if (index < LikeAnimationHelper.likesList.size()) {
        this.mPathMeasure.getPosTan(i, point, null);
        LikeAnimationHelper.LikeInfo likeInfo = LikeAnimationHelper.likesList.get(index);
        likeInfo.x = (int) point[0];
        likeInfo.y = (int) point[1];
        likeInfo.startX = (int) point[0];
        likeInfo.startY = (int) point[1];
    }
    index++;
}
Copy the code

Within the circle part this. MPathMeasure. GetLength () for all points in the is a path of coordinate array length, point array is used to receive getPosTan access point specific coordinates. I + = (len/LikeAnimationHelper. LikesList. The size ()) are here mainly according to the average distribution of animation image list are coordinates, so that we can, in the circle the average Bitmap images drawn in the Canvas on the inner circle

Inner circle drawing effect

Outer circle part code:

        this.mPath.reset();
        this.mPath.addCircle(x, y, 250, Path.Direction.CW);
        this.mPathMeasure.setPath(this.mPath, false);
        len = this.mPathMeasure.getLength();
        for (int i = 0; i < len; i += (len / LikeAnimationHelper.likesList.size())) {
            this.mPathMeasure.getPosTan(i, point, null);
            if (index < LikeAnimationHelper.likesList.size()) {
                LikeAnimationHelper.LikeInfo likeInfo = LikeAnimationHelper.likesList.get(index);
                likeInfo.path.reset();/ / reset
                likeInfo.path.moveTo(likeInfo.x, likeInfo.y);
                likeInfo.path.lineTo(point[0], point[1]);
                likeInfo.zWidth = LikeAnimationHelper.computeDistance(likeInfo.x, likeInfo.y, point[0], point[1]);
                likeInfo.pathMeasure.setPath(likeInfo.path, false);
                likeInfo.valueAnimator.setFloatValues(0, likeInfo.pathMeasure.getLength());
                likeInfo.valueAnimator.setDuration(LikeAnimationHelper.getRandomInt(200.1000));
                likeInfo.valueAnimator.setInterpolator(new DecelerateInterpolator());
                likeInfo.valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float value = (float) animation.getAnimatedValue();
                        float[] pos = new float[2];
                        likeInfo.pathMeasure.getPosTan(value, pos, null);
                        likeInfo.x = (int) pos[0];
                        likeInfo.y = (int) pos[1];
                        int bW = LikeAnimationHelper.computeDistance(likeInfo.startX, likeInfo.startY, likeInfo.x, likeInfo.y);
                        // Current distance/Total distance = scale
                        float fit = (bW * 1.0 f) / (likeInfo.zWidth * 1.0 f);
                        // Ratio * 255 = transparency
                        int alpha = (int) Math.min((fit) * 255.255);
                        // Set the brush transparency
                        likeInfo.paint.setAlpha(255- alpha); invalidate(); }}); } index++; }Copy the code

The for loop calculates the distance for each animation object and sets the animation where moveTo and lineTo draw the path from a point on the inner circle to a point on the outer circle. ZWidth represents its total path length (the Pythagorean theorem can get the hyhyuse length), add the path to PathMeasure, then obtain each coordinate of the current path through getPosTan in the animation callback addUpdateListener, and then set the coordinates to X and y in LikeInfo successively. Complete the animation operation of the whole movement.

LikeInfo. ValueAnimator. SetDuration (LikeAnimationHelper getRandomInt (0) 200100) set setDuration here make a random execution time to deal with, Also in the animation can be seen as a scatter effect, if the fixed value is an inner circle diffusion effect

Renderings of outer circle and its path:

Question: How do I determine that all animations are executed?

ValueAnimator has an addListener method that has a callback onAnimationEnd

likeInfo.valueAnimator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {}@Override
    public void onAnimationEnd(Animator animation) {
        endIndex += 1;
    }

    @Override
    public void onAnimationCancel(Animator animation) {}@Override
    public void onAnimationRepeat(Animator animation) {}});Copy the code

When endIndex > = LikeAnimationHelper. LikesList. The size () said the animation has finished execution

private Thread mThread = new Thread() {
    @Override
    public void run(a) {
        super.run();

        while (endIndex < LikeAnimationHelper.likesList.size()) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        post(new Runnable() {
            @Override
            public void run(a) {
                // End the interface callbackiLikeAniationListener.onEnd(); }}); }};Copy the code

Start a thread and check every 100 milliseconds

The source address

  • LikeAnimationHelperUtility class methodsstartAnimation(Activity activity, View targetView)Parameter 1. Activity 2. Target View
  • LikeAnimateViewThe animation View