I would like to know if it's possible to select coordinates from a path to draw a bitmap over time, for example, I have an image of a sun, and I would like to move it, over time, along an arc path.
Is there some way to define a path like this and then move along it, so that I don't have to calculate it mathematically?
Thanks.
Yes, it's possible to move image along path. I will provide simple solution to show the principle. The following code will move and rotate your image. If you don't need rotation remove the TANGENT_MATRIX_FLAG flag.
import android.graphics.*;
//somewhere global
int iCurStep = 0;// current step
//don't forget to initialize
Path pathMoveAlong = new Path();
private static Bitmap bmImage = null;
#Override
protected void onDraw(Canvas canvas) {
Matrix mxTransform = new Matrix();
PathMeasure pm = new PathMeasure(pathMoveAlong, false);
float fSegmentLen = pm.getLength() / 20;//20 animation steps
if (iCurStep <= 20) {
pm.getMatrix(fSegmentLen * iCurStep, mxTransform,
PathMeasure.POSITION_MATRIX_FLAG + PathMeasure.TANGENT_MATRIX_FLAG);
canvas.drawBitmap(bmImage, mxTransform, null);
iCurStep++;
invalidate();
} else {
iCurStep = 0;
};
};
Here are the animators I use:
Purpose: Move View "view" along Path "path"
v21+:
ValueAnimator pathAnimator = ObjectAnimator.ofFloat(view, "x", "y", path)
v11+:
ValueAnimator pathAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
pathAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
float[] point = new float[2];
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float val = animation.getAnimatedFraction();
PathMeasure pathMeasure = new PathMeasure(path, true);
pathMeasure.getPosTan(pathMeasure.getLength() * val, point, null);
view.setX(point[0]);
view.setY(point[1]);
}
});
Similar requirement to: https://stackoverflow.com/a/30254715/4344057
I'm thinking of one solution:
_
/ \
If your arc has this kind of shape (meaning less than half a circle and positioned horizontaly) then you could iterate the x value and for that x get the y that's on the path. Then move your bitmap to that position.
Related
I'm having a hard time believing this isn't possible. I have a map in an ImageViewTouch view, and I'm trying to add a "You Are Here" image on the image that I move (within the map image) based on the user's location.
When the user pans around on the map, I'd like the You Are Here to pan too by being inside the ImageViewTouch. (Ideally I'd like it to scale with zooming too, but I'll take what I can get!)
I've considered using Canvas to create a new Bitmap every time the user's location changes, but considering the map is large, it wouldn't be very performant.
Is there any way to do this?
I figured it out! Instead of using Canvas and Bitmap which are slower and cause issues, I was able to use LayerDrawable. In case this helps anyone in the future, here's my code.
public void updateYouAreHere(#Nullable MapLocation location, boolean initial){
Resources resources = getResources();
Drawable drawable;
if (location == null){
drawable = MiscUtil.getDrawable(resources, R.drawable.map_image);
}else{
Drawable[] layers = new Drawable[]{
MiscUtil.getDrawable(resources, R.drawable.map_image),
MiscUtil.getDrawable(resources, R.drawable.you_are_here)
};
mapDrawable = new LayerDrawable(layers);
final int iconWidth = 50;
final int iconHeight = 86;
mapDrawable.setLayerInset(1, location.x, location.y,
(int)(Globals.Map.MapWidth) - location.x - iconWidth, (int)(Globals.Map.MapHeight) - location.y - iconHeight);
drawable = mapDrawable;
}
if (!initial) {
Matrix currentPosition = mapScrollView.getDisplayMatrix();
float minScale = mapScrollView.getMinScale();
float maxScale = mapScrollView.getMaxScale();
mapScrollView.setImageDrawable(drawable, currentPosition, minScale, maxScale);
}else{
mapScrollView.setImageDrawable(drawable);
}
}
private class MapLocation {
public int x;
public int y;
public MapLocation(int x, int y){
this.x = x;
this.y = y;
}
}
Also, MiscUtil.getDrawable is just a wrapper that calls the appropriate getResources().getDrawable() function based on SDK version.
I have an image view "stone" and am moving it from its current position to a X,Y position. I want it to move along a curve. Please let me know how I can do that(i have set the min api as 11)
ObjectAnimator moveX = ObjectAnimator.ofFloat(stone, "x", catPos[0] );
ObjectAnimator moveY = ObjectAnimator.ofFloat(stone, "y", catPos[1] );
AnimatorSet as = new AnimatorSet();
as.playTogether(moveX, moveY);
as.start();
The answer by Budius seems perfectly useful to me.
Here are the animator objects I use:
Purpose: Move View "view" along Path "path"
Android v21+:
// Animates view changing x, y along path co-ordinates
ValueAnimator pathAnimator = ObjectAnimator.ofFloat(view, "x", "y", path)
Android v11+:
// Animates a float value from 0 to 1
ValueAnimator pathAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
// This listener onAnimationUpdate will be called during every step in the animation
// Gets called every millisecond in my observation
pathAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
float[] point = new float[2];
#Override
public void onAnimationUpdate(ValueAnimator animation) {
// Gets the animated float fraction
float val = animation.getAnimatedFraction();
// Gets the point at the fractional path length
PathMeasure pathMeasure = new PathMeasure(path, true);
pathMeasure.getPosTan(pathMeasure.getLength() * val, point, null);
// Sets view location to the above point
view.setX(point[0]);
view.setY(point[1]);
}
});
Similar to: Android, move bitmap along a path?
you have two options:
both needs a Path object that defines your curve:
Path path = new Path();
path. // define your curve here
if using Lollipop only (API 21) use ObjectAnimator.ofFloat(...path) like this:
ObjectAnimator.ofFloat(stone, View.X, View.Y, path).start();
if that's not an options, you use an AnimatorListener to receive updates about the each animator frame and use the PathMeasure to get the values on that point, like this:
PathMeasure pm;
float point[] = {0f, 0f};
private final ValueAnimator.AnimatorUpdateListener listener =
new ValueAnimator.AnimatorUpdateListener(){
#Override
public void onAnimationUpdate (ValueAnimator animation) {
float val = animation.getAnimatedFraction();
pm.getPosTan(pm.getLength() * val, point, null);
stone.setTranslationX(point[0]);
stone.setTranslationY(point[1]);
}
}
// and then to animate
pm = new PathMeasure(path, false);
ValueAnimator a = ValueAnimator.ofFloat(0.0f 1.0f);
a.setDuration(/* your duration */);
a.setInterpolator(/* your interpolator */);
a.addUpdateListener(listener);
a.start();
Play around with the interpolators. For example set 2 different Interpolators for x and y :
moveX.setInterpolator(new AccelerateDecelerateInterpolator());
moveY.setInterpolator(new DecelerateInterpolator());
Theres more (LinearInterpolator, AccelerateInterpolator...) but I think this should be the combination you want.
Thanks for the answers, but I found my own answer.Just added some extra x,y positions in the Objectanimator stmt. It went to those positions before coming to the final one and traced a path!
ObjectAnimator moveX = ObjectAnimator.ofFloat(stone, "x", catPos[0]-20,catPos[0]-10,catPos[0] );
ObjectAnimator moveY = ObjectAnimator.ofFloat(stone, "y",catPos[1]-20,catPos[1]-10, catPos[1] );
The Path class has methods for creating non-straight lines of several types; arcs, circles, ovals, rectangles, cubic & quadratic bezier curves, which you can then animate your object along.
As Budius points out, you do need to code for API 21 (Lollipop) or later to use paths with object translation. It is now over two years old.
On the other hand, there does seem a dearth of anything-more-than-very-simple examples of using paths "in the wild", so perhaps there's a reason they've not caught on yet.
Check this link for 3 ways of animating several properties of a View in parallel:
https://developer.android.com/guide/topics/graphics/prop-animation#view-prop-animator
The 1st way of doing it is using AnimatorSet.
Below is the 2nd way:
public static ObjectAnimator ofPropertyValuesHolder (Object target,
PropertyValuesHolder... values)
The API doc describes this constructor for ObjectAnimator as:
"This variant should be used when animating several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows you to associate a set of animation values with a property name."
Example:
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder transX = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2);
PropertyValuesHolder transY = PropertyValuesHolder.ofKeyframe("translationY", kf0, kf1, kf2);
ObjectAnimator transAnim = ObjectAnimator.ofPropertyValuesHolder(view2animate, transX, transY);
transAnim.setDuration(5000);
transAnim.start();
The example above moves view2animate in both x and y axis at the same time.
The 3rd way is to use ViewPropertyAnimator.
I have an image of a bullet in an ImageView that does Translate animation.
I need to show real time coordinates to show how far it is from target in real time.
ImageView myimage = (ImageView)findViewById(R.id.myimage);
Animation animation = new TranslateAnimation(100, 200, 300, 400);
animation.setDuration(1000);
myimage.startAnimation(animation);
animation.setRepeatCount(Animation.INFINITE);
Is it possible to get real time x and y coordinates of the image while it is doing TranslateAnimation ?
And if its not possible using TranslateAnimation, is there any other way that gives real time coordinates of image while in motion ?
I tried -
int x = myimage.getLeft();
int y = myimage.getTop();
and
int[] firstPosition = new int[2];
myimage.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
myimage.getLocationOnScreen(firstPosition);
int x = firstPosition[0];
int y = firstPosition[1];
but in both the ways, its giving the initial static coordinate of the ImageView.
Here's a complete example based on what user3249477 and Vikram said:
final TextView positionTextView = (TextView)findViewById(R.id.positionTextView);
ImageView myimage = (ImageView)findViewById(R.id.imageView);
ObjectAnimator translateXAnimation= ObjectAnimator.ofFloat(myimage, "translationX", 0f, 100f);
ObjectAnimator translateYAnimation= ObjectAnimator.ofFloat(myimage, "translationY", 0f, 100f);
translateXAnimation.setRepeatCount(ValueAnimator.INFINITE);
translateYAnimation.setRepeatCount(ValueAnimator.INFINITE);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(translateXAnimation, translateYAnimation);
set.start();
translateXAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
imageXPosition = (Float)animation.getAnimatedValue();
}
});
translateYAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
imageYPosition = (Float)animation.getAnimatedValue();
String position = String.format("X:%d Y:%d", (int)imageXPosition, (int)imageYPosition);
positionTextView.setText(position);
}
});
You can use an ObjectAnimator (API 11+):
ImageView iv = (ImageView)findViewById(R.id.markerRed);
// Create animators for x and y axes
ObjectAnimator oax = ObjectAnimator.ofFloat(iv, "translationX", 0f, 100f);
ObjectAnimator oay = ObjectAnimator.ofFloat(iv, "translationY", 0f, 100f);
oax.setRepeatCount(Animation.INFINITE);
oay.setRepeatCount(Animation.INFINITE);
// Combine Animators and start them together
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(oax, oay);
set.start();
Then fetch the animation values like this:
Log.e("TAG", "X: " + oax.getAnimatedValue() + " Y:" + oay.getAnimatedValue());
If you add these values to the initial ImageView's coordinates, you'll get the current location.
I had this idea: you can extend the standard TranslateAnimation and intercept every step of the animation by overriding the applyTransformation method.
Take a look at this incomplete/untested snippet:
private class ObservableTranslateAnimation extends TranslateAnimation{
private float matrixValues[] = new float[9];
private float actualDx;
private float actualDy;
public ObservableTranslateAnimation(Context context, AttributeSet attrs) {
super(context, attrs);
}
// ... more constructors
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
//After that super is called, the matrix gets updated!
//get the current matrix and extract what you need
t.getMatrix().getValues(matrixValues);
actualDx = matrixValues[Matrix.MTRANS_X];
actualDy = matrixValues[Matrix.MTRANS_Y];
/*
Notify someone here (a listener?), or just read the values of actualDx, actualDy from outside
You can also play around with the other Matrix values.
*/
}
}
I have a small problem with ploting my graph. On a picture below is what I have already done.
The graph should represent the actual signal strength of available Wi-Fi network(s). It's a simple XYPlot here data are represented with SimpleXYSeries (values are dynamically created).
Here is a little snippet of code (only for example):
plot = (XYPlot) findViewById(R.id.simplexyPlot);
series1 = new SimpleXYSeries(Arrays.asList(series1Numbers),
SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Link 1");
f1 = new LineAndPointFormatter(color.getColor(), null,
Color.argb(60, color.getRed(), color.getGreen(), color.getBlue()), null);
plot.addSeries(series1, f1);
The example in the picture is a dynamic simulation of dB changes. Everything works, I guess, correctly, but what I want to achieve is to have line with "rounded" corners (see the picture to see what I mean).
I already tried to customize LineFormatter:
f1.getFillPaint().setStrokeJoin(Join.ROUND);
f1.getFillPaint().setStrokeWidth(8);
But this didn't work as expected.
Note: The Wifi Analyzer application has a similar graph and its graph has the rounded corners I want. It looks like this:
You can use Path.cubicTo() method. It draws a line using cubic spline algorithm which results in the smoothing effect you want.
Checkout the answer to a similar question here, where a guy is talking about cubic splines. There is a short algorithm showing how to calculate input parameters for Path.cubicTo() method. You can play with divider values to achieve required smoothness. For example, in the picture below I divided by 5 instead of 3. Hope this helps.
I have spent some time and implemented a SplineLineAndPointFormatter class, which does the stuff you need in androidplot library. It uses same technics. Here is how androidplot example applications looks like. You just need to use it instead of LineAndPointFormatter.
Here is code example and the class I wrote.
f1 = new SplineLineAndPointFormatter(color.getColor(), null,
Color.argb(60, color.getRed(), color.getGreen(), color.getBlue()), null);
plot.addSeries(series1, f1);
Here is the class doing the magic. It is based on version 0.6.1 of androidplot library.
package com.androidplot.xy;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import com.androidplot.ui.SeriesRenderer;
import com.androidplot.util.ValPixConverter;
public class SplineLineAndPointFormatter extends LineAndPointFormatter {
public SplineLineAndPointFormatter() { }
public SplineLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor) {
super(lineColor, vertexColor, fillColor, null);
}
public SplineLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor, FillDirection fillDir) {
super(lineColor, vertexColor, fillColor, null, fillDir);
}
#Override
public Class<? extends SeriesRenderer> getRendererClass() {
return SplineLineAndPointRenderer.class;
}
#Override
public SeriesRenderer getRendererInstance(XYPlot plot) {
return new SplineLineAndPointRenderer(plot);
}
public static class SplineLineAndPointRenderer extends LineAndPointRenderer<BezierLineAndPointFormatter> {
static class Point {
public float x, y, dx, dy;
public Point(PointF pf) { x = pf.x; y = pf.y; }
}
private Point prev, point, next;
private int pointsCounter;
public SplineLineAndPointRenderer(XYPlot plot) {
super(plot);
}
#Override
protected void appendToPath(Path path, final PointF thisPoint, PointF lastPoint) {
pointsCounter--;
if (point == null) {
point = new Point(thisPoint);
point.dx = ((point.x - prev.x) / 5);
point.dy = ((point.y - prev.y) / 5);
return;
} else if (next == null) {
next = new Point(thisPoint);
} else {
prev = point;
point = next;
next = new Point(thisPoint);
}
point.dx = ((next.x - prev.x) / 5);
point.dy = ((next.y - prev.y) / 5);
path.cubicTo(prev.x + prev.dx, prev.y + prev.dy, point.x - point.dx, point.y - point.dy, point.x, point.y);
if (pointsCounter == 1) { // last point
next.dx = ((next.x - point.x) / 5);
next.dy = ((next.y - point.y) / 5);
path.cubicTo(point.x + point.dx, point.y + point.dy, next.x - next.dx, next.y - next.dy, next.x, next.y);
}
}
#Override
protected void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, LineAndPointFormatter formatter) {
Number y = series.getY(0);
Number x = series.getX(0);
if (x == null || y == null) throw new IllegalArgumentException("no null values in xyseries permitted");
XYPlot p = getPlot();
PointF thisPoint = ValPixConverter.valToPix(x, y, plotArea,
p.getCalculatedMinX(), p.getCalculatedMaxX(), p.getCalculatedMinY(), p.getCalculatedMaxY());
prev = new Point(thisPoint);
point = next = null;
pointsCounter = series.size();
super.drawSeries(canvas, plotArea, series, formatter);
}
}
}
1- I guess that you only use a few points to draw graphs of signals. All graph/chart applications try to connect points with direct lines and then your chart will be shown. So if you only use three points, your graph will looks like a triangle! If you want your graph to be curved, you have to add more points. Then it comes out like a curve.
2- Or you can find any library that can draw sin graph, for example GraphView Library. Then try to draw this function:
So it looks like to this:
Then translate it to (a,0), so result seems like what you want.
3- And another way, you can use built in Math.sin in Java:
Chose for example 1000 point in range a to b and compute value of above function for each point and finally create a path and show them in a canvas.
You can use quadTo (float x1, float y1, float x2, float y2) that simplify drawing quad curves for you. The documentation says:
Add a quadratic bezier from the last point, approaching control point
(x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
this contour, the first point is automatically set to (0,0).
Parameters
x1 The x-coordinate of the control point on a quadratic curve
y1 The y-coordinate of the control point on a quadratic curve
x2 The x-coordinate of the end point on a quadratic curve
y2 The y-coordinate of the end point on a quadratic curve
Finally, I add a simple class that extends View and can draw a curve that looks like what you want:
public class SinWave extends View {
private float first_X = 50;
private float first_Y = 230;
private float end_X = 100;
private float end_Y = 230;
private float Max = 50;
public SinWave(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint() {
{
setStyle(Paint.Style.STROKE);
setStrokeCap(Paint.Cap.ROUND);
setStrokeWidth(0.7f);
setAntiAlias(true);
setColor(0xFFFF00FF);
}
};
final Path path = new Path();
path.moveTo(first_X, first_Y);
path.quadTo((first_X + end_X)/2, Max, end_X, end_Y);
canvas.drawPath(path, paint);
}
}
The result must look like this:
You can add more methods to the class and change it to increase performance!
There's always been a smooth line renderer in Androidplot: BezierLineAndPointRenderer, which like the implementations above uses Android's built in Bezier drawing routines cubicTo(...) & quadTo(...). The problem is that using Beziers to draw smooth lines in this way creates a false line that overshoots the actual control points by varying amounts, which you can see happening if you look closely at the image above.
The solution is to use the Catmull-Rom spline interpolation, which is now finally supported by Androidplot. Details here: http://androidplot.com/smooth-curves-and-androidplot/
Just use ChartFactory.getCubeLineChartView instead of ChartFactory.getLineChartView using achart engine
In some simple cases, this could help:
mPaint.pathEffect = CornerPathEffect(radius)
even in combination with
path.lineTo(x,y)
try this:
symbol = new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(2);
paint.setColor(-7829368);
paint.setStrokeJoin(Paint.Join.ROUND); // set the join to round you want
paint.setStrokeCap(Paint.Cap.ROUND); // set the paint cap to round too
paint.setPathEffect(new CornerPathEffect(10) );
paint.setStyle(Paint.Style.STROKE);
symbol.moveTo(50.0F, 230.0F);
symbol.lineTo(75.0F, 100.0F);
symbol.lineTo(100.0F, 230.0F);
most of the info found here
I have created a path and circle and displayed both of them on screen as follows:
public void onDraw(Canvas canvas){
Path sPath = new Path();
sPath.moveTo(100, 100);
sPath.lineTo(300, 100);
sPath.lineTo(300, 300);
sPath.lineTo(100,300);
sPath.lineTo(100,100);
sPath.close();
Paint ballPaint = new Paint();
ballPaint.setColor(Color.GREEN);
Paint pathPaint = new Paint();
pathPaint.setColor(Color.BLUE);
canvas.drawPath(sPath, ballPaint);
canvas.drawCircle(100,100,20,pathPaint);
}
i would like to have the circle move along the path, how can i do this?
Here are the animators I use:
Purpose: Move View "view" along Path "path"
v21+:
ValueAnimator pathAnimator = ObjectAnimator.ofFloat(view, "x", "y", path)
v11+:
ValueAnimator pathAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
pathAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
float[] point = new float[2];
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float val = animation.getAnimatedFraction();
PathMeasure pathMeasure = new PathMeasure(path, true);
pathMeasure.getPosTan(pathMeasure.getLength() * val, point, null);
view.setX(point[0]);
view.setY(point[1]);
}
});
Yes, it's possible to move image along path. I will provide simple solution to show the principle. The following code will animate the circle along the path.
int iCurStep = 0;// current animation step
#Override
protected void onDraw(Canvas canvas) {
PathMeasure pm = new PathMeasure(sPath, false);
float fSegmentLen = pm.getLength() / 20;//we'll get 20 points from path to animate the circle
float afP[] = {0f, 0f};
if (iCurStep <= 20) {
pm.getPosTan(fSegmentLen * iCurStep, afP, null);
canvas.drawCircle(afP[0],afP[1],20,pathPaint);
iCurStep++;
invalidate();
} else {
iCurStep = 0;
};
};
v21+: this creates a quadratic bezier curve on a path and animates myView along it.
final Path path = new Path();
path.quadTo(controlX, controlY, finalX, finalY);
ObjectAnimator.ofFloat(myView, View.X, View.Y, path).start();
You would need to move your circle a little bit each frame towards the next waypoint and detect once it gets there, then start moving toward the next. There is no built in system that I know of.
The right way to do this is with ContraintLayout and then add a keyframe between the points on the path you want to animate, as described here: https://medium.com/google-developers/defining-motion-paths-in-motionlayout-6095b874d37.