我想创建如下所示的动画:
请告诉我如何达到这个效果。
这只是我想要创建的实际动画的开始。在最终动画中,将有许多不同形状的管道和不同的颜色浮动它们。
示例形状:
答案 0 :(得分:5)
要显示一些自定义drawable,最简单的方法是创建一个扩展View
并覆盖onDraw(Canvas canvas)
方法的类,其中所有绘图魔法都会发生。
为了制作弯曲线,我使用了二次Bézier curve。它使用起点,终点和一个手柄点以某种方式弯曲线。
要画出弯曲的线条,它实际上是一系列很多短直线,从前一条直线开始。
不确定如何处理定期更新,我敢打赌,有一些比目前实施更好的方法。这种方式有效,但我不知道你未来的计划是什么,所以......
此实施的示例屏幕截图:
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);
}
}
}