blow your mind
The BYM series aims to share not only technology but also ideas, not just being a porter of code.
1. Background
The company’s business needs to do a shutter animation for the camera, looking for the whole network, few cases. It took me two days to create a shutter animation by hand with a custom View. It is also a small progress of my own. The difficulty of the whole project lies not in drawing, but in mathematics.
2. Review a few points
Canvas
Canvas is our Android canvas, just like any other language, as long as you have the following Paint you can Paint anything you want.
Paint
Paint is commonly used method setColor, setStyle setStrokeWidth
Path
When some basic graphics functions of Canvas cannot meet our needs, we can use path for combined drawing, and finally call Canvas. drawPath, path.close() can be used to close the graph. If you set path’s.setstyle (paint.style.fill), it will FILL the entire path.
Mathematics knowledge
Sines, sines, cosines and cosines, review them, help with homework. 😝
3. Demand effect
4. Requirements analysis
We extract three images of Gif for analysis. The graph is composed of two parts, one part is the outer circle, the other part is the dynamic irregular graph.
5
A. Find two points on the circle
First, six equilateral triangles, gradually converging toward the center of the inner circle, the equilateral triangle spacing is shrinking. So these six equilateral triangles are just at different angles, so when we start writing our code, we can draw one of them, to get an equilateral triangle converging to the center of the circle.
(√3/2/R,R/2)
float startX = 0;
float startY = -mRadius;
float stopX = (float) Math.sqrt(3) * mRadius / 2f;
float stopY = -mRadius / 2f;
Copy the code
B. Find the third TagPoint.
If it’s easy to do, we can think of it as a third point, right on the blue line. In this case, we only need to solve the position of the third point when the value of the Angle θ between the third point and the edge A composed of (0, -r) and the edge B composed of (√3/2/R,R/2) changes dynamically. So theta is the variable from 0 to 60 degrees.
C. Derive the formula to solve x and y.
I’m not going to draw the auxiliary line, but I’m going to give you the formula
The conversion to Java code is as follows
float tagX = (float) (Math.sin(Math.toRadians(60)) * mRadius
/ Math.cos(Math.toRadians(30 - swipeAgenl))
* Math.sin(Math.toRadians(60 - swipeAgenl)));
float tagY = (float) (Math.sin(Math.toRadians(60)) * mRadius
/ Math.cos(Math.toRadians(30 - swipeAgenl))
* Math.cos(Math.toRadians(60 - swipeAgenl))) - mRadius;
Copy the code
Run it and see if it works as expected
4. The arc
The point 0, -r, to square root of 3/2/R,R/2, where it intersects the circle is going to be an arc. This can be drawn using either the path. addArc or canvas.drawArc methods. There are two ways to pass in parameters, but essentially you pass in a rectangle and draw an arc inside the rectangle.
Path.java
public void addArc(RectF oval, float startAngle, float sweepAngle) {
addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle);
}
Canvas.java
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
@NonNull Paint paint) {
drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
paint);
}
Copy the code
StartAngle is the Angle at which the arc starts, and sweepAngle is the Angle at which the arc passes. The code is as follows:
RectF oval = new RectF(-mRadius, -mRadius, mRadius, mRadius);
mPaint.setStyle(Paint.Style.FILL);
path.addArc(oval, -90.60);
Copy the code
5. Look at the results
I’ve drawn one, so six are easy to draw, and I’ve made each one a different color for you to see.
Gee, something’s wrong. It’s not what I expected. We can look at the UI renderings again. Regardless of the rotation Angle of the graph, it can be observed that the graph is formed with the red border lengthening over time. The maximum length is R, the minimum length is 0, or whatever you start with, and the other thing to notice is that the Angle is always 60 degrees as the red line goes along.
6. Recalculate
Again, sine, cosine, figure out where the point is on the red line.
float L1 = (float) Math.sqrt(Math.pow(mRadius, 2.0) -
Math.pow(Math.sin(Math.toRadians(60)) * a, 2.0));
float consL1 = L1 / mRadius;
float sinL1 = (float) Math.sin(Math.toRadians(60)) * a / mRadius;
float L = (float) (L1 + Math.sin(Math.toRadians(30)) * a);
float tagX = L * sinL;
float tagY = L * consL - mRadius;
Copy the code
7. You’re done
/ * * *@authoer create by markfrain
* @githubhttps://github.com/furuiCQ * Gao Huai see physical and naive * time: 4/28/21 * description: ShootView * /
public class ShootView extends View {
private static final int DEGREE_60 = 60;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RectF mBounds = new RectF();
private int mRadius;
private int mCenterX;
private int mCenterY;
private ValueAnimator mPlayAnimator;
private float swipeAgenl = mRadius;// The extended distance
private float mShootLineTotalRotateAngle;// Rotate the Angle
private ValueAnimator mPreShootLineTotalRotateAnimator;
private float padding = 20;
private ValueAnimator paddingAnimator;
private float fix = 4;
private ValueAnimator fixAnimator;
private static final float SHOOT_LINE_ROTATE_END_RADIANS = (float) (Math.PI / 6.0);// Rotate the end radian
private static final float SHOOT_LINE_ROTATE_START_RADIANS = (float) (Math.PI / 2.0);// Rotate the starting radian
private static final float SHOOT_LINE_ROTATE_START_DEGREE =
(float) Math.toDegrees(SHOOT_LINE_ROTATE_END_RADIANS);// Rotate the starting Angle
private static final int PRE_SHOOT_LINE_TOTAL_ROTATE_DURATION = 1000;
public static final Property<ShootView, Float> SHOOT_LINE_TOTAL_ROTATE_DEGREE =
new Property<ShootView, Float>(Float.class, null) {
@Override
public Float get(ShootView object) {
return object.mShootLineTotalRotateAngle;
}
@Override
public void set(ShootView object, Float value) { object.mShootLineTotalRotateAngle = value; object.invalidate(); }};public ShootView(Context context) {
this(context, null);
}
public ShootView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ShootView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.parseColor("#ffc6c6c6"));
paddingAnimator = ValueAnimator.ofFloat(20.0);
paddingAnimator.setInterpolator(new LinearInterpolator());
paddingAnimator.setDuration(PRE_SHOOT_LINE_TOTAL_ROTATE_DURATION);
paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
padding = (float) animation.getAnimatedValue(); invalidate(); }}); fixAnimator = ValueAnimator.ofFloat(4.0);
fixAnimator.setInterpolator(new LinearInterpolator());
fixAnimator.setDuration(1200);
fixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
fix = (float) animation.getAnimatedValue(); invalidate(); }}); mPreShootLineTotalRotateAnimator = ValueAnimator.ofFloat(-(SHOOT_LINE_ROTATE_START_DEGREE /2.0 f) - 240.0 f,
-(SHOOT_LINE_ROTATE_START_DEGREE / 2.0 f) - 120.0 f);
mPreShootLineTotalRotateAnimator.setInterpolator(new LinearInterpolator());
mPreShootLineTotalRotateAnimator.setDuration(PRE_SHOOT_LINE_TOTAL_ROTATE_DURATION);
mPreShootLineTotalRotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mShootLineTotalRotateAngle = (float) animation.getAnimatedValue(); invalidate(); }}); }@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// drawTest(canvas);
drawArc(canvas);
drawCircle(canvas);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBounds.set(0 + getPaddingLeft(), 0 + getPaddingTop(), w - getPaddingRight(),
h - getPaddingBottom());
mRadius = (int) (Math.min(mBounds.width(), mBounds.height()) / 2);
mCenterX = (int) mBounds.centerX();
mCenterY = (int) mBounds.centerY();
swipeAgenl = mRadius * 2 / 3f;
}
float mStartX;
float mStartY;
int colors[] = {Color.RED, Color.GREEN, Color.YELLOW, Color.MAGENTA, Color.CYAN, Color.GRAY};
private void drawTest(Canvas canvas) {
drawCircle(canvas);
canvas.save();
canvas.translate(mCenterX, mCenterY);
int i = 0;
canvas.save();
canvas.rotate(-DEGREE_60 * i);
Path path = new Path();
float stopX = (float) Math.sqrt(3) * mRadius / 2f;
float stopY = -mRadius / 2f;
canvas.drawLine(0, -mRadius, stopX, stopY, mPaint);
mPaint.setColor(Color.RED);
canvas.drawPoint(stopX, stopY, mPaint);
mPaint.setColor(Color.RED);
//canvas.drawLine(0, 0, stopX, stopY, mPaint);
RectF oval = new RectF(-mRadius, -mRadius, mRadius, mRadius);
RectF rectF = new RectF(-mRadius, -2 * mRadius, mRadius, 0);
mPaint.setStyle(Paint.Style.FILL);
path.addArc(oval, -90.60);
// Calculate the direct point
float tagX = (float) (Math.sin(Math.toRadians(60)) * mRadius / Math.cos(Math.toRadians(30 - swipeAgenl)) * Math.sin(Math.toRadians(60 - swipeAgenl)));
float tagY = (float) (Math.sin(Math.toRadians(60)) * mRadius / Math.cos(Math.toRadians(30 - swipeAgenl)) * Math.cos(Math.toRadians(60 - swipeAgenl))) - mRadius;
// float tagX = math.sqrt (math.pow (mRadius,2.0) - math.pow (math.sin (math.toradians (60) * swipeAgenl), 2.0));
// float L = (float) math.sqrt (math.pow (mRadius, 2.0) - math.pow (math.sin (math.toradians (60)) * swipeAgenl, 2.0)); // float L = (float) math.pow (math.pow (mRadius, 2.0) - math.pow (math.toradians (60)) * swipeAgenl);
// float consL = L / mRadius;
// float sinL = (float) Math.sin(Math.toRadians(60)) * swipeAgenl / mRadius;
// float LL = (float) (L + Math.sin(Math.toRadians(30)) * swipeAgenl);
//
// float tagX = LL * sinL;
// float tagY = LL * consL - mRadius;
canvas.drawPoint(tagX, tagY, mPaint);
// path.moveTo(stopX, stopY);
// path.lineTo(tagX, tagY);
// path.lineTo(0, -mRadius);
canvas.drawPath(path, mPaint);
/ /}
}
private void drawArc(Canvas canvas) {
canvas.save();
canvas.translate(mCenterX, mCenterY);
canvas.rotate(mShootLineTotalRotateAngle);
for (int i = 0; i < 6; i++) {
canvas.save();
canvas.rotate(-DEGREE_60 * i);
Path path = new Path();
mPaint.setColor(colors[i]);
RectF oval = new RectF(-mRadius, -mRadius, mRadius, mRadius);
mPaint.setStyle(Paint.Style.FILL);
// mRadius- padding
double asin = Math.toDegrees(Math.asin((mRadius - padding) / mRadius / 2)) * 2;
path.addArc(oval, -90, (float) asin);
float stopX = (float) Math.sqrt(3) * mRadius / 2f;
float stopY = -mRadius / 2f;
float L = (float) Math.sqrt(Math.pow(mRadius, 2.0) - Math.pow(Math.sin(Math.toRadians(60)) * swipeAgenl, 2.0));
float consL = L / mRadius;
float sinL = (float) Math.sin(Math.toRadians(60)) * swipeAgenl / mRadius;
float LL = (float) (L + Math.sin(Math.toRadians(30)) * swipeAgenl);
float tagX = LL * sinL;
float tagY = LL * consL - mRadius;
path.moveTo(stopX - padding, stopY - padding - fix);
path.lineTo(tagX - padding, tagY - padding);
path.lineTo(0, -mRadius);
canvas.drawPath(path, mPaint);
canvas.restore();
}
canvas.restore();
}
private void drawCircle(Canvas canvas) {
mPaint.setColor(colors[1]);
mPaint.setStyle(Paint.Style.STROKE);
canvas.save();
canvas.translate(mCenterX, mCenterY);
canvas.drawCircle(0.0 f.0.0 f, mRadius, mPaint);
canvas.restore();
}
public void startPlay(a) {
mPlayAnimator = ValueAnimator.ofFloat(mRadius, mRadius / 5f);
mPlayAnimator.setDuration(PRE_SHOOT_LINE_TOTAL_ROTATE_DURATION);
mPlayAnimator.setInterpolator(new LinearInterpolator());
mPlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
swipeAgenl = (float) animation.getAnimatedValue();
Log.i("swipeAgenl".""+ swipeAgenl); invalidate(); }}); mPlayAnimator.start(); mPreShootLineTotalRotateAnimator.start(); paddingAnimator.start(); fixAnimator.start(); }}Copy the code
Source code for making
reference
- HenCoder Android development advanced: custom View 1-1 drawing basis
- Github ShootRefreshView