颜色流经管道动画效果在android中

时间:2014-10-30 18:41:53

标签: android animation android-animation

我想创建如下所示的动画: enter image description here

请告诉我如何达到这个效果。

这只是我想要创建的实际动画的开始。在最终动画中,将有许多不同形状的管道和不同的颜色浮动它们。

示例形状:

enter image description here

1 个答案:

答案 0 :(得分:5)

要显示一些自定义drawable,最简单的方法是创建一个扩展View并覆盖onDraw(Canvas canvas)方法的类,其中所有绘图魔法都会发生。

为了制作弯曲线,我使用了二次Bézier curve。它使用起点,终点和一个手柄点以某种方式弯曲线。

要画出弯曲的线条,它实际上是一系列很多短直线,从前一条直线开始。

不确定如何处理定期更新,我敢打赌,有一些比目前实施更好的方法。这种方式有效,但我不知道你未来的计划是什么,所以......

此实施的示例屏幕截图

enter image description here

代码

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;

import android.graphics.*;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(new BezierProgressBar(this));
    }

    private static class BezierProgressBar extends View {

        private final List<Point> points;
        private final Paint backgroundPaint;
        private final Paint progressPaint;

        private double progress;
        private double progressLength;

        final Handler viewHandler;
        final Runnable updateView;

        public BezierProgressBar(Context context) {
            super(context);

            // how long the 'liquid' part should be
            progressLength = 0.3;
            // where the 'liquid' part is currently positioned
            progress = -progressLength;

            // our points that will be used to build a bezier curve
            // the first and the last points are start and the end of the curve
            // the middle point is used as an anchor to curve the line
            points = new ArrayList<Point>();
            points.add(new Point(-200, 100));
            points.add(new Point(0, -100));
            points.add(new Point(200, 100));

            // the style of the background of our pipe
            backgroundPaint = new Paint();
            backgroundPaint.setStyle(Paint.Style.STROKE);
            backgroundPaint.setAntiAlias(true);
            backgroundPaint.setStrokeWidth(10);
            backgroundPaint.setStrokeCap(Paint.Cap.ROUND);
            backgroundPaint.setColor(Color.BLACK);

            // the style of the 'liquid' of our pipe
            progressPaint = new Paint();
            progressPaint.setStyle(Paint.Style.STROKE);
            progressPaint.setAntiAlias(true);
            progressPaint.setStrokeWidth(8);
            progressPaint.setStrokeCap(Paint.Cap.ROUND);
            progressPaint.setColor(Color.WHITE);

            // this will ensure the animation will loop
            // even though it works I believe there must be better way to do it and I would
            // recommend to look for better solution for periodic updates
            viewHandler = new Handler();
            updateView = new Runnable(){
                @Override
                public void run(){
                    progress += 0.01;
                    if (progress > 1.0) {
                        progress = -progressLength;
                    }

                    BezierProgressBar.this.invalidate();
                    viewHandler.postDelayed(updateView, 1000/60);
                }
            };
            viewHandler.post(updateView);
        }

        private PointF getBezierPoint(double progress, List<Point> points) {
            int size = points.size();
            // this is some magic I have done some time ago
            // don't really remember the logic behind, but there are many tutorials about this on web
            double[] t = new double[points.size()];
            double[] u = new double[points.size()];
            for (int i = 0; i < size; i++) {
                t[i] = 1;
                u[i] = 1;
                for (int j = 0; j < i; j++) {
                    t[i] *= progress;
                    u[i] *= (1-progress);
                }
            }
            // coefficients {1, 2, 1} will work for 3 points = quadratic bezier curve
            // for higher number of points, such as cubic bezier curve, we would need {1, 3, 3, 1}
            // etc. --> Pascal's triangle
            int[] coefficient = {1, 2, 1};
            double x = 0;
            double y = 0;
            for (int i = 0; i < size; i++) {
                x += coefficient[i] * t[i] * u[size-1-i] * points.get(i).x;
                y += coefficient[i] * t[i] * u[size-1-i] * points.get(i).y;
            }
            return new PointF((float)x, (float)y);
        }

        private Path getBezierPath(List<Point> points, double startProgress, double endProgress) {
            // higher segment count means more curvy line, but slower computation
            final int SEGMENT_COUNT = 200;

            // clamp values from 0 to 1, else it will draw outside
            if (startProgress < 0.0)
                startProgress = 0.0;
            if (endProgress > 1.0)
                endProgress = 1.0;

            Path path = new Path();

            // compute starting point of the progress / background
            PointF currentPoint = getBezierPoint(startProgress, points);

            // move to the starting point
            path.moveTo(currentPoint.x, currentPoint.y);

            // loop over all segments on our bezier line
            for (int i = (int) (startProgress*SEGMENT_COUNT); i <= endProgress*SEGMENT_COUNT; i++) {

                double progress = i / (double) SEGMENT_COUNT;

                // compute next point on the line
                currentPoint = getBezierPoint(progress, points);

                // draw line from the last point to the next point
                path.lineTo(currentPoint.x, currentPoint.y);
            }

            return path;
        }

        @Override
        protected void onDraw(Canvas canvas) {

            // we want to show background from starting point (0.0) to finish point (1.0)
            Path backgroundPath = getBezierPath(points, 0.0, 1.0);
            Path progressPath = getBezierPath(points, progress, progress + progressLength);

            // get the center of the view
            float centerWidth = canvas.getWidth() / 2;
            float centerHeight = canvas.getHeight() / 2;

            // set our pipe in the middle of the view
            backgroundPath.offset(centerWidth, centerHeight);
            progressPath.offset(centerWidth, centerHeight);

            // draw the pipe on the screen
            canvas.drawPath(backgroundPath, backgroundPaint);
            canvas.drawPath(progressPath, progressPaint);
        }

    }

}