I am trying to modify a little this library: https://github.com/Shinelw/ColorArcProgressBar
And this is what i've done so far, without the purple circle indicator:
arc progress bar
My question is: how can i animate the purple circle along with the progress(as shown in the image)?
Inside ColorArcProgressBar class that extends View, at onDraw method, this is how the progress is draw: canvas.drawArc(bgRect, startAngle, currentAngle, false, progressPaint);
and the animation is
private void setAnimation(float last, float current, int length) {
progressAnimator = ValueAnimator.ofFloat(last, current);
progressAnimator.setDuration(length);
progressAnimator.setTarget(currentAngle);
progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
currentAngle = (float) animation.getAnimatedValue();
curValues = currentAngle / k;
}
});
progressAnimator.start();
}
I managed to position the purple bitmat at the start of the progress like this:
canvas.drawBitmap(mIcon,bgRect.left+mIcon.getWidth()/2 +30, bgRect.bottom - 40 +mIcon.getHeight()/2 , null);
Now, how can I animate it along the arc like the progress?
Didn't try this out, if it's not right it's close...
// since currentAngle is a "sweep" angle, the
// final angle should be current + start
float thetaD = startAngle + currentAngle;
if (thetaD > 360F) {
thetaD -= 360F;
}
// convert degrees to radians
float theta = (float) Math.toRadians(thetaD);
// polar to Cartesian coordinates
float x = (float) (Math.cos(theta) * bgRect.width() / 2) + bgRect.centerX();
float y = (float) (Math.sin(theta) * bgRect.height() / 2) + bgRect.centerY();
canvas.drawBitmap(mIcon, x - mIcon.getWidth()/2, y - mIcon.getHeight()/2 , null);
It helped that your bitmap is circular, so we didn't have to rotate that...
Related
I need to make lucky wheel like view, and there texts are drawn regarding the radius, as you can see image below. ( there you can see red line, I want to draw texts along that red line )
This is the result I want to achieve but, right now with my method, it just draws text based on circle like below
private void drawText(Canvas canvas, float tempAngle, float sweepAngle, String text) {
Path path = new Path();
Log.d("mytag", "tempAngle = " + tempAngle + ", sweepAngle = " + sweepAngle);
path.addArc(range, tempAngle, sweepAngle);
float textWidth = textPaint.measureText(text);
int hOffset = (int) (radius * Math.PI / mWheelItems.size() / 2 - textWidth / 2);
int vOffset = (radius / 2 / 3) - 15; // here 15 is distance from image
canvas.drawTextOnPath(text, path, hOffset, vOffset, textPaint);
}
Instead of creating an arc to draw the text on, you need a path from the outside of the circle to the center. I think you need something like this
int radius = 180;
float centerX = width/2f;
float centerY = height/2f
double radians = radius * Math.PI / 180;
float x = (float)Math.sin(radians);
float y = (float)-Math.cos(radians);
Path path = new Path();
path.moveTo(x,y);
path.lineTo(centerX, centerY);
canvas.drawTextOnPath(text, path, hOffset, vOffset, textPaint);
First, find the slice centre points.
Kotlin sample snippet
val arcCenter = startAngle + (arcProgress / 2)
//middle point radius is half of the radius of the circle
val pointRadius = size.width / 4
/*Calculate the x & y coordinates
* find points using angle and radius
*https://en.wikipedia.org/wiki/Polar_coordinate_system#Converting_between_polar_and_Cartesian_coordinates
* */
val x =
(pointRadius * cos(Math.toRadians(arcCenter.toDouble()))) +
size.center.x
val y =
(pointRadius * sin(Math.toRadians(arcCenter.toDouble()))) +
size.center.y
Then Rotate your canvas to the arch centre angle, Draw your text and restore the canvas to a normal position
it.nativeCanvas.rotate( arcCenter, x, y)
it.nativeCanvas.drawText(...)
it.nativeCanvas.rotate( -arcCenter, x, y)
I know how to use CircularReveal to reveal a view, so I'm looking for a way to do something like "CircularHide". In other words, I want to invisible a view by a circular animation (increasing radius) after making it visible. How can I do that?
I've written this to reveal:
private void startCircularReveal() {
RelativeLayout changeableLayout = findViewById(R.id.layoutChangeable);
int centerX = (likeButton.getRight() + likeButton.getLeft()) / 2;
int centerY = (likeButton.getBottom() + likeButton.getTop()) / 2;
float endRadius = (float) Math.hypot(changeableLayout.getWidth(), changeableLayout.getHeight());
changeableLayout.setVisibility(View.VISIBLE);
Animator revealAnimator = ViewAnimationUtils.createCircularReveal(changeableLayout,
centerX, centerY, 0, endRadius);
revealAnimator.setDuration(200).start();
}
I am trying to create a pad-like view in android. I got a circle that follows user's gestures and I am using distance to keep the circle of going outside the main circle of the pad control.
My problem is I want the circle to keep following the gesture, but to stay inside of the main circle. I am using the formula for finding a point using angle and radius, but it does some funky stuff.
I am translating the canvas, so that the center of the main circle is at 0, 0.
Here is the code:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(this.mainRadius, this.mainRadius);
canvas.drawCircle(0, 0, this.mainRadius, this.debugPaint);
canvas.drawCircle(this.handleX, this.handleY, this.handleRadius, this.handlePaint);
}
private void translateHandle(MotionEvent event) {
int x = (int) (event.getX() - this.mainRadius);
int y = (int) (event.getY() - this.mainRadius);
double distance = distanceFromCenter(x, y);
if (distance <= this.maxDistance) {
this.handleX = x;
this.handleY = y;
} else {
float angle = (float) Math.toDegrees(Math.atan2(y, x));
if (angle < 0)
angle += 360;
this.handleX = (int) ((this.mainRadius - this.handleRadius) * Math.cos(angle));
this.handleY = (int) ((this.mainRadius - this.handleRadius) * Math.sin(angle));
}
//onTranslateHandle(distance);
}
And here is the funky stuff in a gif image:
I cannot verify this change into your code snippet but do hope it gives some idea how to proceed further anyway;
private void translateHandle(MotionEvent event) {
float x = event.getX() - this.mainRadius;
float y = event.getY() - this.mainRadius;
double distance = distanceFromCenter(x, y);
if (distance > this.maxDistance) {
// If distance is i.e 2.0 and maxDistance is 1.0 ==> adjust is 0.5
// which repositions x and y making distance 1.0 maintaining direction
double adjust = this.maxDistance / distance;
x = (float)(x * adjust);
y = (float)(y * adjust);
}
this.handleX = (int)x;
this.handleY = (int)y;
}
I can update the answer where needed if this does not give any useful results.
So I have an ImageView using a Matrix to scale the Bitmap I'm displaying. I can double-tap to zoom to full-size, and my ScaleAnimation handles animating the zoom-in, it all works fine.
Now I want to double-tap again to zoom out, but when I animate this with ScaleAnimation, the ImageView does not draw the newly exposed areas of the image (as the current viewport shrinks), instead you see the portion of visible image shrinking in. I have tried using ViewGroup.setClipChildren(false), but this only leaves the last-drawn artifacts from the previous frame - leading to an trippy telescoping effect, but not quite what I was after.
I know there are many zoom-related questions, but none cover my situation - specifically animating the zoom-out operation. I do have the mechanics working - ie aside from the zoom-out animation, double-tapping to zoom in and out works fine.
Any suggestions?
In the end I decided to stop using the Animation classes offered by Android, because the ScaleAnimation applies a scale to the ImageView as a whole which then combines with the scale of the ImageView's image Matrix, making it complicated to work with (aside from the clipping issues I was having).
Since all I really need is to animate the changes made to the ImageView's Matrix, I implemented the OnDoubleTapListener (at the end of this post - I leave it as an "exercise to the reader" to add the missing fields and methods - I use a few PointF and Matrix fields to avoid excess garbage creation). Basically the animation itself is implemented by using View.post to keep posting a Runnable that incrementally changes the ImageView's image Matrix:
public boolean onDoubleTap(MotionEvent e) {
final float x = e.getX();
final float y = e.getY();
matrix.reset();
matrix.set(imageView.getImageMatrix());
matrix.getValues(matrixValues);
matrix.invert(inverseMatrix);
doubleTapImagePoint[0] = x;
doubleTapImagePoint[1] = y;
inverseMatrix.mapPoints(doubleTapImagePoint);
final float scale = matrixValues[Matrix.MSCALE_X];
final float targetScale = scale < 1.0f ? 1.0f : calculateFitToScreenScale();
final float finalX;
final float finalY;
// assumption: if targetScale is less than 1, we're zooming out to fit the screen
if (targetScale < 1.0f) {
// scaling the image to fit the screen, we want the resulting image to be centred. We need to take
// into account the shift that is applied to zoom on the tapped point, easiest way is to reuse
// the transformation matrix.
RectF imageBounds = new RectF(imageView.getDrawable().getBounds());
// set up matrix for target
matrix.reset();
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
matrix.postScale(targetScale, targetScale);
matrix.mapRect(imageBounds);
finalX = ((imageView.getWidth() - imageBounds.width()) / 2.0f) - imageBounds.left;
finalY = ((imageView.getHeight() - imageBounds.height()) / 2.0f) - imageBounds.top;
}
// else zoom around the double-tap point
else {
finalX = x;
finalY = y;
}
final Interpolator interpolator = new AccelerateDecelerateInterpolator();
final long startTime = System.currentTimeMillis();
final long duration = 800;
imageView.post(new Runnable() {
#Override
public void run() {
float t = (float) (System.currentTimeMillis() - startTime) / duration;
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = interpolator.getInterpolation(t);
float tempScale = scale + interpolatedRatio * (targetScale - scale);
float tempX = x + interpolatedRatio * (finalX - x);
float tempY = y + interpolatedRatio * (finalY - y);
matrix.reset();
// translate initialPoint to 0,0 before applying zoom
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
// zoom
matrix.postScale(tempScale, tempScale);
// translate back to equivalent point
matrix.postTranslate(tempX, tempY);
imageView.setImageMatrix(matrix);
if (t < 1f) {
imageView.post(this);
}
}
});
return false;
}
The following is supposed to draw an axis in the middle of the screen. However, nothing appears. I am positive that is has to do with my Paths.
#Override
protected void onDraw(Canvas canvas) {
//Variables declared here temporarily for testing purposes
int canterX = getWidth() /2;
int centerY = getHeight() /2;
int radius = 150;
Path verticalAxis = new Path();
Path horizontalAxis = new Path();
drawAxis();
}
private void drawAxis(Canvas canvas) {
int axisLineThickness = 1;
int verticalEndX;
int verticalEndY;
int horizontalEndX;
int horizontalEndY;
Paint axisPaint = new Paint();
axisPaint.setColor(Color.WHITE);
axisPaint.setStrokeWidth(axisLineThickness);
double theta;
for(int i = 90; i < 360; i += 180) {
theta = toRadians(i);
verticalEndX = centerX + (int) ((cos(theta)) * radius);
verticalEndY = centerY + (int) ((sin(theta)) * radius);
verticalAxis.moveTo(centerX, centerY);
verticalAxis.lineTo(verticalEndX, verticalEndY);
}
canvas.drawPath(verticalAxis, axisColor);
for(int i = 90; i < 360; i += 180) {
theta = toRadians(i);
horizontalEndX = centerX + (int) ((cos(theta)) * radius);
horizontalEndY = centerY + (int) ((sin(theta)) * radius);
horizontalAxis.moveTo(centerX, centerY);
horizontalAxis.lineTo(verticalEndX, verticalEndY);
}
canvas.drawPath(horizontalAxis, axisColor);
}
I know I can make the axis draw if I add the following to the vertical and horizontal for loops respectively:
Vertical For Loop:
canvas.drawLine(centerX, centerY, verticalEndX, verticalEndY, paint);
Horizontal For Loop:
canvas.drawLine(centerX, centerY, horizontalEndX, horizontalEndY, paint);
But I don't want to solve the issue this way, I want to correct what is wrong with my paths. Can anyone tell me why the points aren't adding to my path correctly? The loop should only go through twice which creates a line for each side of the axis. Ie. One loop creates the top of the vertical axis and the second loop creates the bottom part.
How do I get my paths create that full line and then draw it outside of the loop?
Paint's default style appears to be FILL, so maybe just having a line in your path is confusing things. Try setting it to STROKE:
axisPaint.setStyle(Paint.Style.STROKE);
See Paint.Style