I'd like to be able to create an arbitrary input for time into a standard Android animation. Instead of an animation running for 1 second, I want for instance the input to be a coordinate from user touch input. This way I could for instance create a circle motion of object A when the position in the circular motion is defined by a linear input on slide A.
Crude illustration:
Now I'm thinking this could be achieved with defining the translation animation in XML just as with regular animations under /res/anim, but overriding the time input to come from a user input control instead. It minght also be done with a custom interpolator, I'm not sure. I don't what a set start and end time of the animation, in any case.
Anyone have any suggestions on how to achieve this?
edit: To further answer a couple of the comments: Think if it as the user slides/drags the blue dot. No interpolation between the input occurs. As soon as the user lifts the finger, the "animation" stops.
If I understand correctly you need some sort of 'rigging' - Defining a movement of one element as a function of another. In your case this function needs to transform the the linear position into a circular position.
There is no animation involved - When the user moves the blue circle, the red one is moved accordingly.
You should register for callbacks for the blue circle movement (i.e. onTouchEvent, or a seekBar's on change, depending on how you implement your 'bar'). Then you calculate the new position of the red circle and then you put it there.
Here's a simple working example of a custom view that draws two circles according to a given percentValue. I tested using s simple SeekBar and it works:
public class CanvasView extends View {
private int centerX = 0;
private int centerY = 0;
private int radius = 0;
private final int handleRadius = 25;
private final Paint circlePaint = new Paint();
private final Paint handlePaint = new Paint();
private float percentValue = 0f;
public CanvasView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CanvasView(Context context) {
super(context);
init();
}
private void init() {
circlePaint.setColor(Color.BLACK);
handlePaint.setColor(Color.RED);
}
// Call this whenever the value of that linear bar is changed - so when the user moves his finger etc.
public void setValue(float percentage) {
this.percentValue = percentage;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// this is the main circle
canvas.drawCircle(centerX, centerY, radius, circlePaint);
// calculate the angle based on the percentage of the linear movement (substracting (pi/2) so the zero value is on top)
double angle = (percentValue / 100) * (2 * Math.PI) - Math.PI / 2;
// sin and cos to calculate the position of the smaller circle - the 'handle'
float handleX = centerX + (float) (radius * Math.cos(angle));
float handleY = centerY + (float) (radius * Math.sin(angle));
// drawing the circle
canvas.drawCircle(handleX, handleY, handleRadius, handlePaint);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// choose whatever values you want here, based on the view's size:
centerX = w / 2;
centerY = h / 2;
radius = w / 3;
}
}
Related
Requirement is to keep a ball moving on the Grid path generated in Canvas. I have generated a Grid in canvas but not able to understand how to move the ball randomly means starting point show be different on the path. I am sharing what I have done. I have also plotted the ball in the screen but not getting the point how to put the ball exactly on the grid line randomly and start moving it
public class PixelGridView extends View {
//number of row and column
int horizontalGridCount = 11;
private Drawable horiz;
private Drawable vert;
private final float width;
long mInterpolateTime;
PointF mImageSource = new PointF();
public PixelGridView(#NonNull Context context) {
this(context, null);
}
public PixelGridView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
horiz = new ColorDrawable(Color.WHITE);
horiz.setAlpha(160);
vert = new ColorDrawable(Color.WHITE);
vert.setAlpha(160);
width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, .9f, context.getResources().getDisplayMetrics());
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
horiz.setBounds(left, 0, right, (int) width);
vert.setBounds(0, top, (int) width, bottom);
}
private float getLinePosition(int lineNumber) {
int lineCount = horizontalGridCount;
return (1f / (lineCount + 1)) * (lineNumber + 1f);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//drawTask.start();
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
canvas.drawCircle(120, 110, 10, paint);
int count = horizontalGridCount;
for (int n = 0; n < count; n++) {
float pos = getLinePosition(n);
// Draw horizontal line
canvas.translate(0, pos * getHeight());
Log.e("Position1", "" + pos * getHeight());
horiz.draw(canvas);
canvas.translate(0, -pos * getHeight());
// Draw vertical line
canvas.translate(pos * getHeight(), 0);
Log.e("Position2", "" + pos * getHeight());
vert.draw(canvas);
canvas.translate(-pos * getHeight(), 0);
}
}
}[![Canvas Image][1]][1]
//MainActivity
public class PathAnimationActivity extends AppCompatActivity {
LinearLayout rlLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_path);
rlLayout=findViewById(R.id.rlLayout);
PixelGridView pixelGrid = new PixelGridView(this);
rlLayout.addView(pixelGrid);
}
}
First thing i have noticed it that you haven't used invalidate (); at the end because thats critical in order to animate the canvas ( redraw the frames ) so please include that .
there may be several ways to achieve what you want follows is my idea
this canvas need to be divided into multiple x , y divided planes as follows and save them in array of points which you can randomize and give those points to ball to move ,
Step 1, get the canvas size
step 2, divide is in x and y coordinate depending on size of each device varies so you need to control that factor via Screen size
step 3, save the coordinates in matrix or array
step 4, set position of balls from those arrays values ( randomly can you define the random limits as per the max and min values of x and y from the coordinates division .
example , function move will take ball object and x, y are the positions move (ball, x, y ); and you can randomize the x and y based on max and min limits of your coordinates division example total y lines and total x lines values
in order to get an idea about how to move the ball on canvas you can see this code here : https://github.com/pintspin/ball_animation
I am trying to display a ExoPlayerView inside a circle, overlaying another ExoPlayer (picture in picture):
I have tried putting the 2nd player inside a frame with rounded corners (both this answer and this one) but the player will always escape the parent frame and draw the video's full rectangle.
I found this solution which uses a GLSurfaceView, however this solution uses the classic MediaPlayer and not ExoPlayer.
For the one that's supposed to have rounded corners, you can set in the layout XML file this about it:
app:surface_type="texture_view"
Found this solution here.
The drawback of using this is mainly performance and battery usage (written here) :
Should I use SurfaceView or TextureView? SurfaceView has a number of
benefits over TextureView for video playback:
Significantly lower power consumption on many devices. More accurate
frame timing, resulting in smoother video playback. Support for secure
output when playing DRM protected content. SurfaceView should
therefore be preferred over TextureView where possible. TextureView
should be used only if SurfaceView does not meet your needs. One
example is where smooth animations or scrolling of the video surface
is required prior to Android N (see How do I get smooth
animation/scrolling of video?). For this case, it’s preferable to use
TextureView only when SDK_INT is less than 24 (Android N) and
SurfaceView otherwise.
You need to create a custom container for it. try this and put you player view in it.
public class RoundFrameLayout extends FrameLayout {
private final Path clip = new Path();
private int posX;
private int posY;
private int radius;
public RoundFrameLayout(Context context) {
this(context,null);
}
public RoundFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public RoundFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// We can use outlines on 21 and up for anti-aliased clipping.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setClipToOutline(true);
}
}
#Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
posX = Math.round((float) width / 2);
posY = Math.round((float) height / 2);
// noinspection NumericCastThatLosesPrecision
radius = (int) Math.floor((float) Math.min(width, height) / 2);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setOutlineProvider(new OutlineProvider(posX, posY, radius));
} else {
clip.reset();
clip.addCircle(posX, posY, radius, Direction.CW);
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
// Not needed on 21 and up since we're clipping to the outline instead.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
canvas.clipPath(clip);
}
super.dispatchDraw(canvas);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// Don't pass touch events that occur outside of our clip to the children.
float distanceX = Math.abs(event.getX() - posX);
float distanceY = Math.abs(event.getY() - posY);
double distance = Math.hypot(distanceX, distanceY);
return distance > radius;
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
static class OutlineProvider extends ViewOutlineProvider {
final int left;
final int top;
final int right;
final int bottom;
OutlineProvider(int posX, int posY, int radius) {
left = posX - radius;
top = posY - radius;
right = posX + radius;
bottom = posY + radius;
}
#Override
public void getOutline(View view, Outline outline) {
outline.setOval(left, top, right, bottom);
}
}
}
I'm trying to develop simple board game. The board is of size 9x9 fields. The balls are appearing on the fields and when the user clicks on the field with the ball, the ball starts to jumping. I implemented the animation in two ways. The first one is working, but it's not easy to add another one following animation (like little stretch or something). And the second one, which seems to be better (there is used the AnimatorSet) is not working. When user clicks on the field with the ball, the ball disappears. I have no idea why :-(.
The first class implements the board and it is the child of View:
public class BoardView extends View {
...
/**
* Initializes fields of the board.
*/
private void initializeFields() {
this.fields = new ArrayList<Field>();
for (int row = 0; row < BoardView.FIELDS_NUMBER; row++) {
for (int column = 0; column < BoardView.FIELDS_NUMBER; column++) {
this.fields.add(new Field(this, row, column));
}
}
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(BoardView.COLOR_ACTIVITY);
if (this.fields == null) {
this.initializeFields();
}
for (int i = 0; i < this.fields.size(); i++) {
this.fields.get(i).draw(canvas);
}
}
...
}
The second one implements the field:
public class Field {
...
/**
* Draws itself on the screen.
*
* #param Canvas canvas
*/
public void draw(Canvas canvas) {
Rect field = this.getRect();
int round = (int)Math.floor(this.board.getFieldSize() / 4);
this.board.getPainter().setStyle(Paint.Style.FILL);
this.board.getPainter().setColor(Field.COLOR_DEFAULT);
// draw field
canvas.drawRoundRect(new RectF(field), round, round, this.board.getPainter());
// draw selected field
if (this.selected) {
this.board.getPainter().setColor(Field.COLOR_SELECTED);
canvas.drawRoundRect(new RectF(field), round, round, this.board.getPainter());
}
// draw ball
if (this.ball != null) {
Point fieldOrigin = new Point(field.left, field.top);
if (this.selected) {
this.ball.animate(canvas, fieldOrigin);
} else {
this.ball.draw(canvas, fieldOrigin);
}
}
}
...
}
And the last one implements the ball:
Here is the first method, which completely works, but it's not flexible enough:
public class Ball {
...
/**
* Draws itself on the screen.
*
* #param Canvas canvas
* #param Point fieldOrigin
*/
public void draw(Canvas canvas, Point fieldOrigin) {
// set painter
Paint painter = this.field.getBoard().getPainter();
painter.setStyle(Paint.Style.FILL);
painter.setColor(Ball.COLORS[this.color]);
// calculate parameters
float halfSize = this.field.getBoard().getFieldSize() / 2;
float cX = fieldOrigin.x + halfSize;
float cY = fieldOrigin.y + halfSize + this.dy;
float radius = 0.6f * halfSize;
// draw circle
canvas.drawCircle(cX, cY, radius, painter);
// the code continues, because of the shadow and light simulation (radial gradients)
}
/**
* Draws jumping animation.
*
* #param Canvas canvas
* #param Point fieldOrigin
*/
public void animate(Canvas canvas, Point fieldOrigin) {
float currentDy = (this.dy - 0.1f);
this.setDy((float)Math.abs(Math.sin(currentDy)) * (-0.15f * this.field.getBoard().getFieldSize()));
this.draw(canvas, fieldOrigin);
this.setDy(currentDy);
try {
Thread.sleep(Ball.ANIMATION_DELAY);
} catch (InterruptedException e) {}
this.field.invalidate();
}
...
}
As you can see, the animation is implemented by sleeping the current Thread and changing parameter dy.
The second method is showing the ball on the field, but the animation is not working as I said in the beginning of the post (after click, the ball disappears):
public class BallShape {
private Field field;
private LayerDrawable ball;
private int color;
private float diameter,
x, y; // top left corner - THE GETTERS AND SETTERS ARE IMPLEMENTED (because of Animator)
...
/**
* Initializes the ball.
*
* #param Field field
* #param int color
*/
public BallShape(Field field, int color) {
this.field = field;
this.color = ((color == Ball.COLOR_RANDOM) ? Ball.randomColor() : color);
// create ball
float halfSize = this.field.getBoard().getFieldSize() / 2;
this.diameter = 0.6f * field.getBoard().getFieldSize();
float radius = this.diameter / 2;
Rect fieldArea = field.getRect();
this.x = fieldArea.left + halfSize - radius;
this.y = fieldArea.top + halfSize - radius;
// color circle
OvalShape circleShape = new OvalShape();
circleShape.resize(this.diameter, this.diameter);
ShapeDrawable circle = new ShapeDrawable(circleShape);
this.initPainter(circle.getPaint());
// the code continues, because of the shadow and light simulation (radial gradients)
// compound shape - ball
ShapeDrawable[] compound = { circle };//, shadow, light };
this.ball = new LayerDrawable(compound);
}
/**
* Draws itself on the screen.
*
* #param Canvas canvas
* #param Point fieldOrigin
*/
public void draw(Canvas canvas, Point fieldOrigin) {
canvas.save();
canvas.translate(this.x, this.y);
this.ball.draw(canvas);
canvas.restore();
}
/**
* Draws jumping animation.
*
* #param Canvas canvas
* #param Point fieldOrigin
*/
public void animate(Canvas canvas, Point fieldOrigin) {
// common data
float halfSize = this.field.getBoard().getFieldSize() / 2;
float radius = this.diameter / 2;
float startY = fieldOrigin.y + halfSize - radius;
float endY = startY - halfSize + 2;
// bounce animation
ValueAnimator bounceAnimation = ObjectAnimator.ofFloat(this, "y", startY, endY);
bounceAnimation.setDuration(BallShape.ANIMATION_LENGTH);
bounceAnimation.setInterpolator(new AccelerateInterpolator());
bounceAnimation.setRepeatCount(ValueAnimator.INFINITE);
bounceAnimation.setRepeatMode(ValueAnimator.REVERSE);
//bounceAnimation.start();
// animation
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnimation);
// start the animation
bouncer.start();
}
...
}
Any idea why it's not working? What I've done wrong?
Thank you very, very much.
Two things I would fix.
First of all you start animation in draw() method. You should either start it in onClick() or at least set this.selected to false, to not start it on every draw(). Secondly, after your value animator changes a property, you need to redraw the BallShape. Otherwise nothing will change. For instance you can define setY(float Y) method, change Y there and call invalidate().
I would like to have a bubble with a precentage value in my app, I can't use 9 patches as i want it to be customizable and its background color should be changeble.
It should look something like this
How can I do it? This bubble will have views inflated inside of it, like this percentage or some larger layouts.
Also depending on the layout(phone or tablet) it might have one side larger than the other (arrow not at the center) so that's another reason i prefer doing it programmatically
Create a custom Drawable and use it for the background of whatever container you are putting your text or other views into.
You will need to modify the padding of the background to take the bubble's pointer into account.
The code below allows you to set the alignment of the pointer as LEFT, CENTER or RIGHT.
This is just a basic version to give you an idea. You could easily add a setter for the bubble color, or add stroke properties to 'mPaint' for additional flexibility.
public class BubbleDrawable extends Drawable {
// Public Class Constants
////////////////////////////////////////////////////////////
public static final int LEFT = 0;
public static final int CENTER = 1;
public static final int RIGHT = 2;
// Private Instance Variables
////////////////////////////////////////////////////////////
private Paint mPaint;
private int mColor;
private RectF mBoxRect;
private int mBoxWidth;
private int mBoxHeight;
private float mCornerRad;
private Rect mBoxPadding = new Rect();
private Path mPointer;
private int mPointerWidth;
private int mPointerHeight;
private int mPointerAlignment;
// Constructors
////////////////////////////////////////////////////////////
public BubbleDrawable(int pointerAlignment) {
setPointerAlignment(pointerAlignment);
initBubble();
}
// Setters
////////////////////////////////////////////////////////////
public void setPadding(int left, int top, int right, int bottom) {
mBoxPadding.left = left;
mBoxPadding.top = top;
mBoxPadding.right = right;
mBoxPadding.bottom = bottom;
}
public void setCornerRadius(float cornerRad) {
mCornerRad = cornerRad;
}
public void setPointerAlignment(int pointerAlignment) {
if (pointerAlignment < 0 || pointerAlignment > 3) {
Log.e("BubbleDrawable", "Invalid pointerAlignment argument");
} else {
mPointerAlignment = pointerAlignment;
}
}
public void setPointerWidth(int pointerWidth) {
mPointerWidth = pointerWidth;
}
public void setPointerHeight(int pointerHeight) {
mPointerHeight = pointerHeight;
}
// Private Methods
////////////////////////////////////////////////////////////
private void initBubble() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mColor = Color.RED;
mPaint.setColor(mColor);
mCornerRad = 0;
setPointerWidth(40);
setPointerHeight(40);
}
private void updatePointerPath() {
mPointer = new Path();
mPointer.setFillType(Path.FillType.EVEN_ODD);
// Set the starting point
mPointer.moveTo(pointerHorizontalStart(), mBoxHeight);
// Define the lines
mPointer.rLineTo(mPointerWidth, 0);
mPointer.rLineTo(-(mPointerWidth / 2), mPointerHeight);
mPointer.rLineTo(-(mPointerWidth / 2), -mPointerHeight);
mPointer.close();
}
private float pointerHorizontalStart() {
float x = 0;
switch (mPointerAlignment) {
case LEFT:
x = mCornerRad;
break;
case CENTER:
x = (mBoxWidth / 2) - (mPointerWidth / 2);
break;
case RIGHT:
x = mBoxWidth - mCornerRad - mPointerWidth;
}
return x;
}
// Superclass Override Methods
////////////////////////////////////////////////////////////
#Override
public void draw(Canvas canvas) {
mBoxRect = new RectF(0.0f, 0.0f, mBoxWidth, mBoxHeight);
canvas.drawRoundRect(mBoxRect, mCornerRad, mCornerRad, mPaint);
updatePointerPath();
canvas.drawPath(mPointer, mPaint);
}
#Override
public int getOpacity() {
return 255;
}
#Override
public void setAlpha(int alpha) {
// TODO Auto-generated method stub
}
#Override
public void setColorFilter(ColorFilter cf) {
// TODO Auto-generated method stub
}
#Override
public boolean getPadding(Rect padding) {
padding.set(mBoxPadding);
// Adjust the padding to include the height of the pointer
padding.bottom += mPointerHeight;
return true;
}
#Override
protected void onBoundsChange(Rect bounds) {
mBoxWidth = bounds.width();
mBoxHeight = getBounds().height() - mPointerHeight;
super.onBoundsChange(bounds);
}
}
Usage
The example below shows how you might use BubbleDrawable.
MainActivity.java
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.myLayout);
BubbleDrawable myBubble = new BubbleDrawable(BubbleDrawable.CENTER);
myBubble.setCornerRadius(20);
myBubble.setPointerAlignment(BubbleDrawable.RIGHT);
myBubble.setPadding(25, 25, 25, 25);
linearLayout.setBackgroundDrawable(myBubble);
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<LinearLayout
android:id="#+id/myLayout"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some Other Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
</RelativeLayout>
Obviously it's never a good idea to have code in your app that you don't understand, so I won't just write out a bunch of equations in java code for you. If however you follow and understand the maths below, then it will be a relatively simple matter for you to use the described equations in your code and draw the arc.
To achieve a rounded tip on the pointer you will need to modify updatePointerPath().
At the moment it just uses rLineTo() to draw three lines forming a triangle.
There is another method in the android Path class called arcTo() which takes the form:
arcTo(RectF oval, float startAngle, float sweepAngle)
You can use this method to draw your arc at the tip of the pointer, but you need to work out a few things first.
You can already calculate the coordinates of the three corners of the pointer triangle. This is what updatePointerPath() does already. Now take a look at the diagram below. To use arcTo(), you will need to calculate the following:
The coordinates of point T which is where your arc will start.
The coordinates of the top left and bottom right corners of the bounding RectF
Your starting angle ()
Your sweep angle (2 * )
The start angle can easily be found with basic trig as shown in the diagram below.
Note: It will be best if you stick to using Radians instead of Degrees for all the angles since this is what all the trig functions in the android 'Math' class require.
With this in mind:
There are 2 Radians in a circle
The three angles in a triangle add up to Radians
A right angle is /2 Radians
So adding the three angles in the triangle formed by points C, T and P you get:
+ + /2 =
Therefore
= /2 -
So we have now calculated and .
Next, d is the distance between the point P and the bottom of the bounding box.
You can get it by calculating the distance from point C to point P, and then subtracting the radius r.
Now:
sin() = r / (distance from C to P)
Therefore:
distance from C to P = r / sin()
And so given that the distance d is the distance from point C to point P minus the radius r, we get:
d = (r / sin()) - r
That gives you all the info you need to calculate the coordinates of the top left and bottom right corners of the bounding RectF.
Now all that's left is to work out the coordinates of point T.
First work out the distance from P to T.
Given that:
tan() = r / (distance from P to T)
We get:
distance from P to T = r / tan()
Finally, adding one more point to the diagram....
We can see that:
sin() = (distance from P to A) / (distance from P to T)
So:
distance from P to A = (distance from P to T) * sin()
Similarly:
cos() = (distance from T to A) / (distance from P to T)
So:
distance from T to A = (distance from P to T) * cos()
With this info you can now calculate the coordinates of point T !!
If you understood all that, then the coding from here is easy. If you're unsure of anything, then just ask.
The following gives an idea how the updated updatePointerPath() might look.
private void updatePointerPath() {
float xDistance;
float yDistance;
mPointer = new Path();
mPointer.setFillType(Path.FillType.EVEN_ODD);
// Set the starting point (top left corner of the pointer)
mPointer.moveTo(pointerHorizontalStart(), mBoxHeight);
// Define the lines
// First draw a line to the top right corner
xDistance= mPointerWidth;
yDistance= 0;
mPointer.rLineTo(xDistance, yDistance);
// Next draw a line down to point T
xDistance= (mPointerWidth / 2) - distancePtoA;
yDistance= mPointerHeight - distanceTtoA;
mPointer.rLineTo(-xDistance, yDistance); //Note: Negative sign because we are moving back to the left
// Next draw the arc
// Note: The RectF used in arcTo() is defined in absolute screen coordinates
float boundingBoxLeft = pointerHorizontalStart() + (mPointerWidth / 2) - (2 * radius);
float boundingBoxTop = mBoxHeight - distanceD - (2 * radius);
float boundingBoxRight = boundingBoxLeft + (2 * radius);
float boundingBoxBottom = boundingBoxTop + (2 * radius);
RectF boundingBox = new RectF(boundingBoxLeft, boundingBoxTop, boundingBoxRight, boundingBoxBottom);
// Note: While the methods in the android Math class like sin() and asin() all use Radians,
// for some reason it was decided that arcTo() in the Path class would use Degrees,
// so we have to convert the angles
float startAngleInDegrees = angleAlpha * (180 / Math.PI);
float sweepAngleInDegrees = 2 * anglePhi * (180 / Math.PI);
mPointer.arcTo(boundingBox, startAngleInDegrees, sweepAngleInDegrees);
// Finally draw the line from the end of the arc back up to the top left corner
// Note: The distances are the same as from the top right corner to point T,
// just the direction is different.
mPointer.rLineTo(-xDistance, -yDistance); // Note: Negative in front of yDistance because we are moving up
// Close off the path
mPointer.close();
}
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I have a triangle image whose one edge is always in the same direction as the circle.
This image has to be moved around the circle based on user swipe/drag. So, it has to both rotate (so that it's edge is in same direction as the circle) and at the same time revolve around the circle.
How to implement this feature?
UPDATE: My custom View is as follows:
public class ThermoView extends FrameLayout{
private ImageView mThermoBgrd;
private ImageView mCurTempArrow;
public static final int THEMROSTAT_BACKGROUND = 0;
public static final int THEMROSTAT_CURR_TEMP = 1;
public ThermostatView(Context context, AttributeSet attrs) {
super(context, attrs);
mThermoBgrd = new ImageView(context);
mThermoBgrd.setImageResource(R.drawable.circle_icon);
addView(mThermoBgrd, ThermostatView.THEMROSTAT_BACKGROUND);
mCurTempArrow = new ImageView(context);
mCurTempArrow.setImageResource(R.drawable.ruler_triangle_icon);
mCurTempArrow.setScaleType(ImageView.ScaleType.MATRIX);
addView(mCurTempArrow, ThermostatView.THEMROSTAT_CURR_TEMP, new LayoutParams(50, 50));
}
#Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
int currTempHeight = mCurTempArrow.getMeasuredHeight();
int currTempWidth = mCurTempArrow.getMeasuredWidth();
int parentWidth = right - left;
int parentHeight = bottom - top;
int padding = currTempHeight;
//We need square container for the circle.
int containerLeft = padding;
int containerTop = parentHeight - parentWidth + padding;
int containerRight = parentWidth - padding;
int containerBottom = parentHeight - padding;
int containerWidth = containerRight - containerLeft;
int containerHeight = containerBottom - containerTop;
//place the arrow indicating current temperature
int curTempLeft = containerRight - ((containerWidth/2) + currTempWidth/2);
int curTempTop = containerTop - (currTempHeight/2);
int curTempRight = curTempLeft + currTempWidth;
int curTempBottom = curTempTop + currTempHeight;
mCurTempArrow.layout(curTempLeft, curTempTop, curTempRight, curTempBottom);
}
try this (it uses Paths instead of Bitmaps but the idea is the same):
public class MyView extends View {
private Paint mPaint;
private Path mTriangle;
private Path mCircle;
private Matrix mMatrix;
private float mAngle;
public MyView(Context context) {
super(context);
mPaint = new Paint();
mPaint.setStrokeWidth(10);
mPaint.setStyle(Style.STROKE);
mTriangle = new Path();
mTriangle.moveTo(0, -21);
mTriangle.lineTo(0, 21);
mTriangle.lineTo(36, 0);
mTriangle.close();
mCircle = new Path();
mCircle.addCircle(0, 0, 50, Direction.CW);
mMatrix = new Matrix();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float w2 = getWidth() / 2f;
float h2 = getHeight() / 2f;
mAngle = (float) (180 * Math.atan2(event.getY() - h2, event.getX() - w2) / Math.PI);
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
float w2 = getWidth() / 2f;
float h2 = getHeight() / 2f;
mMatrix.reset();
mMatrix.postTranslate(w2, h2);
canvas.concat(mMatrix);
mPaint.setColor(0xaaff0000);
canvas.drawPath(mCircle, mPaint);
mMatrix.reset();
mMatrix.postTranslate(60, 0);
mMatrix.postRotate(mAngle);
canvas.concat(mMatrix);
mPaint.setColor(0xaa00ff00);
canvas.drawPath(mTriangle, mPaint);
}
}
Since I don't know if you are using open GL or a standard canevas, I can't really give you some working code. But the general idea is (assuming you have the current position of the triangle (x, y) stored, and the center of your circle (cx, cy) stored.
Do the following:
v = (cx-x, cy-y) // v is the normal vector of your triangle: it faces the center of the circle
triangle.translate(v) // translate to the center of the circle
triangle.rotate(angle) // rotate the triangle on itself
v.rotate(angle) // apply the same rotation on the normal vector
triangle.translate(-v) // translate back on the circle, but since v is rotated, the position will be updated
I hope it is clear enough, good luck
EDIT:
First, you should really try to be more accurate: in your first post, you didn't say that the triangle was an image (that's important). Then you don't say what is your current rendering, what works, what doesn't. I would be easier to help you to know what your program currently display.
From your code, I assume you place the triangle image properly , but it is not rotated. So first, try to add
//place the arrow indicating current temperature
int curTempLeft = containerRight - ((containerWidth/2) + currTempWidth/2);
int curTempTop = containerTop - (currTempHeight/2);
int curTempRight = curTempLeft + currTempWidth;
int curTempBottom = curTempTop + currTempHeight;
mCurTempArrow.setRotate(angle); // rotate the image. angle is in degrees
mCurTempArrow.layout(curTempLeft, curTempTop, curTempRight, curTempBottom);
If you don't know the angle, you might have to compute it from the previous position of the triangle