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().
Related
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;
}
}
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();
}
I need to draw something like this which will be painted and have little transparency
Also it needs to be clickable (onTouch event etc)
I know that in API v1 you have to use Overlay and extend it using canvas and some mathematics.
What is easiest way to do it in Google Map API v2?
PS: Radius is variable.
(For further reference)
EDIT 1:
I implemented CanvasTileProvider subclass and override its onDraw() method:
#Override
void onDraw(Canvas canvas, TileProjection projection) {
// TODO Auto-generated method stub
LatLng tempLocation = moveByDistance(mSegmentLocation, mSegmentRadius, mSegmentAngle);
DoublePoint segmentLocationPoint = new DoublePoint(0, 0);
DoublePoint tempLocationPoint = new DoublePoint(0, 0);
projection.latLngToPoint(mSegmentLocation, segmentLocationPoint);
projection.latLngToPoint(tempLocationPoint, tempLocationPoint);
float radiusInPoints = FloatMath.sqrt((float) (Math.pow(
(segmentLocationPoint.x - tempLocationPoint.x), 2) + Math.pow(
(segmentLocationPoint.y - tempLocationPoint.y), 2)));
RectF segmentArea = new RectF();
segmentArea.set((float)segmentLocationPoint.x - radiusInPoints, (float)segmentLocationPoint.y - radiusInPoints,
(float)segmentLocationPoint.x + radiusInPoints, (float)segmentLocationPoint.y + radiusInPoints);
canvas.drawArc(segmentArea, getAdjustedAngle(mSegmentAngle),
getAdjustedAngle(mSegmentAngle + 60), true, getOuterCirclePaint());
}
Also, I added this from MapActivity:
private void loadSegmentTiles() {
TileProvider tileProvider;
TileOverlay tileOverlay = mMap.addTileOverlay(
new TileOverlayOptions().tileProvider(new SegmentTileProvider(new LatLng(45.00000,15.000000), 250, 30)));
}
Now I'm wondering why my arc isn't on map?
For drawing the circle segments, I would register a TileProvider, if the segments are mainly static. (Tiles are typically loaded only once and then cached.) For checking for click events, you can register an onMapClickListener and loop over your segments to check whether the clicked LatLng is inside one of your segments. (see below for more details.)
Here is a TileProvider example, which you could subclass and just implement the onDraw method.
One important note: The subclass must be thread safe! The onDraw method will be called by multiple threads simultaneously. So avoid any globals which are changed inside onDraw!
/* imports should be obvious */
public abstract class CanvasTileProvider implements TileProvider {
private static int TILE_SIZE = 256;
private BitMapThreadLocal tlBitmap;
#SuppressWarnings("unused")
private static final String TAG = CanvasTileProvider.class.getSimpleName();
public CanvasTileProvider() {
super();
tlBitmap = new BitMapThreadLocal();
}
#Override
// Warning: Must be threadsafe. To still avoid creation of lot of bitmaps,
// I use a subclass of ThreadLocal !!!
public Tile getTile(int x, int y, int zoom) {
TileProjection projection = new TileProjection(TILE_SIZE,
x, y, zoom);
byte[] data;
Bitmap image = getNewBitmap();
Canvas canvas = new Canvas(image);
onDraw(canvas, projection);
data = bitmapToByteArray(image);
Tile tile = new Tile(TILE_SIZE, TILE_SIZE, data);
return tile;
}
/** Must be implemented by a concrete TileProvider */
abstract void onDraw(Canvas canvas, TileProjection projection);
/**
* Get an empty bitmap, which may however be reused from a previous call in
* the same thread.
*
* #return
*/
private Bitmap getNewBitmap() {
Bitmap bitmap = tlBitmap.get();
// Clear the previous bitmap
bitmap.eraseColor(Color.TRANSPARENT);
return bitmap;
}
private static byte[] bitmapToByteArray(Bitmap bm) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, bos);
byte[] data = bos.toByteArray();
return data;
}
class BitMapThreadLocal extends ThreadLocal<Bitmap> {
#Override
protected Bitmap initialValue() {
Bitmap image = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE,
Config.ARGB_8888);
return image;
}
}
}
Use the projection, which is passed into the onDraw method, to get at first the bounds of the tile. If no segment is inside the bounds, just return. Otherwise draw your seqment into the canvas. The method projection.latLngToPoint helps you to convert from LatLng to the pixels of the canvas.
/** Converts between LatLng coordinates and the pixels inside a tile. */
public class TileProjection {
private int x;
private int y;
private int zoom;
private int TILE_SIZE;
private DoublePoint pixelOrigin_;
private double pixelsPerLonDegree_;
private double pixelsPerLonRadian_;
TileProjection(int tileSize, int x, int y, int zoom) {
this.TILE_SIZE = tileSize;
this.x = x;
this.y = y;
this.zoom = zoom;
pixelOrigin_ = new DoublePoint(TILE_SIZE / 2, TILE_SIZE / 2);
pixelsPerLonDegree_ = TILE_SIZE / 360d;
pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);
}
/** Get the dimensions of the Tile in LatLng coordinates */
public LatLngBounds getTileBounds() {
DoublePoint tileSW = new DoublePoint(x * TILE_SIZE, (y + 1) * TILE_SIZE);
DoublePoint worldSW = pixelToWorldCoordinates(tileSW);
LatLng SW = worldCoordToLatLng(worldSW);
DoublePoint tileNE = new DoublePoint((x + 1) * TILE_SIZE, y * TILE_SIZE);
DoublePoint worldNE = pixelToWorldCoordinates(tileNE);
LatLng NE = worldCoordToLatLng(worldNE);
return new LatLngBounds(SW, NE);
}
/**
* Calculate the pixel coordinates inside a tile, relative to the left upper
* corner (origin) of the tile.
*/
public void latLngToPoint(LatLng latLng, DoublePoint result) {
latLngToWorldCoordinates(latLng, result);
worldToPixelCoordinates(result, result);
result.x -= x * TILE_SIZE;
result.y -= y * TILE_SIZE;
}
private DoublePoint pixelToWorldCoordinates(DoublePoint pixelCoord) {
int numTiles = 1 << zoom;
DoublePoint worldCoordinate = new DoublePoint(pixelCoord.x / numTiles,
pixelCoord.y / numTiles);
return worldCoordinate;
}
/**
* Transform the world coordinates into pixel-coordinates relative to the
* whole tile-area. (i.e. the coordinate system that spans all tiles.)
*
*
* Takes the resulting point as parameter, to avoid creation of new objects.
*/
private void worldToPixelCoordinates(DoublePoint worldCoord, DoublePoint result) {
int numTiles = 1 << zoom;
result.x = worldCoord.x * numTiles;
result.y = worldCoord.y * numTiles;
}
private LatLng worldCoordToLatLng(DoublePoint worldCoordinate) {
DoublePoint origin = pixelOrigin_;
double lng = (worldCoordinate.x - origin.x) / pixelsPerLonDegree_;
double latRadians = (worldCoordinate.y - origin.y)
/ -pixelsPerLonRadian_;
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians))
- Math.PI / 2);
return new LatLng(lat, lng);
}
/**
* Get the coordinates in a system describing the whole globe in a
* coordinate range from 0 to TILE_SIZE (type double).
*
* Takes the resulting point as parameter, to avoid creation of new objects.
*/
private void latLngToWorldCoordinates(LatLng latLng, DoublePoint result) {
DoublePoint origin = pixelOrigin_;
result.x = origin.x + latLng.longitude * pixelsPerLonDegree_;
// Truncating to 0.9999 effectively limits latitude to 89.189. This is
// about a third of a tile past the edge of the world tile.
double siny = bound(Math.sin(Math.toRadians(latLng.latitude)), -0.9999,
0.9999);
result.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny))
* -pixelsPerLonRadian_;
};
/** Return value reduced to min and max if outside one of these bounds. */
private double bound(double value, double min, double max) {
value = Math.max(value, min);
value = Math.min(value, max);
return value;
}
/** A Point in an x/y coordinate system with coordinates of type double */
public static class DoublePoint {
double x;
double y;
public DoublePoint(double x, double y) {
this.x = x;
this.y = y;
}
}
}
Finally you need something to check, whether a click on a LatLng-Coordinate is inside of your segment.
I would therefore approximate the segment by a list of LatLng-Coordinates, where in your case a simple triangle may be sufficient. For each list of LatLng coordinates, i.e. for each segment, you may then call something like the following:
private static boolean isPointInsidePolygon(List<LatLng> vertices, LatLng point) {
/**
* Test is based on a horizontal ray, starting from point to the right.
* If the ray is crossed by an even number of polygon-sides, the point
* is inside the polygon, otherwise it is outside.
*/
int i, j;
boolean inside = false;
int size = vertices.size();
for (i = 0, j = size - 1; i < size; j = i++) {
LatLng vi = vertices.get(i);
LatLng vj = vertices.get(j);
if ((vi.latitude > point.latitude) != (vj.latitude > point.latitude)) {
/* The polygonside crosses the horizontal level of the ray. */
if (point.longitude <= vi.longitude
&& point.longitude <= vj.longitude) {
/*
* Start and end of the side is right to the point. Side
* crosses the ray.
*/
inside = !inside;
} else if (point.longitude >= vi.longitude
&& point.longitude >= vj.longitude) {
/*
* Start and end of the side is left of the point. No
* crossing of the ray.
*/
} else {
double crossingLongitude = (vj.longitude - vi.longitude)
* (point.latitude - vi.latitude)
/ (vj.latitude - vi.latitude) + vi.longitude;
if (point.longitude < crossingLongitude) {
inside = !inside;
}
}
}
}
return inside;
}
As you may see, I had a very similar task to solve :-)
Create a View, override its onDraw method to use drawArc on its canvas, and add it to your MapFragment. You can specify the radius in drawArc. Set the onClickListener on the View (or onTouch, any listener you can use for normal views, really).
i have a button defined in xml i am adding animation to it (Card Flip 3D) its works fine in google nexus one but not working properly on Kindle's.
You can see a demo here Demo Card Flip Animation this is what i am trying to achive in ANDROID.
this is my code:
public class CardFlipAnimation extends Animation {
private Camera camera;
private View fromView;
private View toView;
private float centerX;
private float centerY;
private boolean forward = true;
private boolean visibilitySwapped;
/**
* Creates a 3D flip animation between two views. If forward is true, its
* assumed that view1 is "visible" and view2 is "gone" before the animation
* starts. At the end of the animation, view1 will be "gone" and view2 will
* be "visible". If forward is false, the reverse is assumed.
*
* #param fromView First view in the transition.
* #param toView Second view in the transition.
* #param centerX The center of the views in the x-axis.
* #param centerY The center of the views in the y-axis.
* #param forward The direction of the animation.
*/
public CardFlipAnimation(View fromView, View toView, int centerX, int centerY) {
this.fromView = fromView;
this.toView = toView;
this.centerX = centerX;
this.centerY = centerY;
setDuration(500);
setFillAfter(true);
setInterpolator(new AccelerateDecelerateInterpolator());
}
public void reverse() {
forward = false;
View temp = toView;
toView = fromView;
fromView = temp;
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
camera = new Camera();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// Angle around the y-axis of the rotation at the given time. It is
// calculated both in radians and in the equivalent degrees.
final double radians = Math.PI * interpolatedTime;
float degrees = (float) (180.0 * radians / Math.PI);
// Once we reach the midpoint in the animation, we need to hide the
// source view and show the destination view. We also need to change
// the angle by 180 degrees so that the destination does not come in
// flipped around. This is the main problem with SDK sample, it does not
// do this.
if (interpolatedTime >= 0.5f) {
degrees -= 180.f;
if (!visibilitySwapped) {
fromView.setVisibility(View.GONE);
toView.setVisibility(View.VISIBLE);
visibilitySwapped = true;
}
}
if (forward)
degrees = -degrees;
final Matrix matrix = t.getMatrix();
camera.save();
camera.translate(0.0f, 0.0f, (float) (150.0 * Math.sin(radians)));
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
in my activity i am doing like this:
CardFlipAnimation animator = new CardFlipAnimation(button, button,
button.getWidth() / 2, Button.getHeight() / 2);
if (button.getVisibility() == View.GONE) {
animator.reverse();
}
layout.startAnimation(animator); // Relative layout with the button
What is wrong with code it works fine in some devices and not work in some devices.Is nit giving me the 3d effect.
EDIT
This link solved my problem http://www.inter-fuser.com/2009/08/android-animations-3d-flip.html
Thanks to RobinHood
I have worked on pie chart in android. I found an excellent solution from http://tutorials-android.blogspot.in/2011/05/how-create-pie-chart-in-android.html and worked on that. I am able to display the pie chart with colors but in my application in addition to colors I need to display the text also dynamically on that pie chart. How can I display text dynamically on those pie chart slices?
Please help me regarding this...Will be thankful...
To draw piechart you had use very long process.....Hope this help you..
public class Demo extends Activity {
/** Called when the activity is first created. */
float values[]={500,400,300,200,100};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout linear=(LinearLayout) findViewById(R.id.linear);
values=calculateData(values);
linear.addView(new MyGraphview(this,values));
}
private float[] calculateData(float[] data) {
// TODO Auto-generated method stub
float total=0;
for(int i=0;i<data.length;i++)
{
total+=data[i];
}
for(int i=0;i<data.length;i++)
{
data[i]=360*(data[i]/total);
}
return data;
}
public class MyGraphview extends View
{
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
private float[] value_degree;
private int[] COLORS={Color.BLUE,Color.GREEN,Color.GRAY,Color.CYAN,Color.RED};
RectF rectf = new RectF (10, 10, 200, 200);
int temp=0;
public MyGraphview(Context context, float[] values) {
super(context);
value_degree=new float[values.length];
for(int i=0;i<values.length;i++)
{
value_degree[i]=values[i];
}
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
for (int i = 0; i < value_degree.length; i++) {//values2.length; i++) {
if (i == 0) {
paint.setColor(COLORS[i]);
canvas.drawArc(rectf, 0, value_degree[i], true, paint);
}
else
{
temp += (int) value_degree[i - 1];
paint.setColor(COLORS[i]);
canvas.drawArc(rectf, temp, value_degree[i], true, paint);
}
}
}
}
}
Which set the color according to values in decending order...
And for text,you can set dynamic text separately and give color square in front of text:)
To draw text at the centre of each pie chart segment you need to calculate the centre of each segment. The centre of each text item to paint on that segment should align with that centre point - which is achieved by subtracting half the text bounds width from the central x coord (or using paint.setTextAlign(Align.CENTER); ) and half the text bound height from the central y coord.
As for finding the centre of a segment, it requires just a little bit more consideration than using simple geometry.
The central coords of a segment can be found by:
x = (/* radius of pie chart */ /2)*cos(/*angle in RADIANS */) [angle in radians = Math.toRadians(/*half the sweep angle in degrees*/)
y = (/* radius of pie chart */ /2)*sin(/*angle in RADIANS */)
Almost there... dont forget to add the x and y coords of the centre of your pie chart to the above x and y values, otherwise you're trying to paint on a circle centring on (0,0) in your custom view!
Say your pie chart is centred at the actual centre of your view, you want to be adding:
x += getWidth()/2;
y += getHeight()/2;
Last but not least, accounting for the length of the text to be painted - get the bounds of your text using, for example:
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextAlign(Align.CENTER); // This centres the text horizontally
String labelText = "TEST";
Rect textBounds = new Rect();
paint.getTextBounds(labelText, 0, labelText.length(), textBounds);
y -= textBounds.height()/2;
Then your text should appear correctly.
You can use the library called MPAndroidChart, which is very simple and easy to use. Simply import this
compile 'com.github.PhilJay:MPAndroidChart:v3.0.1'
and add this line in your gradle file
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
Hope this will help you.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mState != IS_READY_TO_DRAW) {
return;
}
canvas.drawColor(mBgcolor);
mBagpaints.setAntiAlias(true);
mBagpaints.setStyle(Paint.Style.FILL);
mBagpaints.setColor(0x88FF0000);
mBagpaints.setStrokeWidth(0.0f);
mLinePaints.setAntiAlias(true);
mLinePaints.setColor(0xff000000);
mLinePaints.setStrokeWidth(3.0f);
mLinePaints.setStyle(Paint.Style.STROKE);
RectF mOvals = new RectF(mGapleft, mGapTop, mWidth - mGapright, mHeight
- mGapBottm);
mStart = START_INC;
PieDetailsItem item;
for (int i = 0; i < mdataArray.size(); i++) {
item = (PieDetailsItem) mdataArray.get(i);
mBagpaints.setColor(item.color);
mSweep = (float) 360* ((float) item.count / (float) mMaxConnection);
canvas.drawArc(mOvals, mStart, mSweep, true, mBagpaints);
canvas.drawArc(mOvals, mStart, mSweep, true, mLinePaints);
mStart = mStart + mSweep;
// set your text here
canvas.drawText("here is some text", mStart, someYvalue, mLinePaints);
}
mState = IS_DRAW;
}
Just some drawText() calls ought to work fine.
You'll have to do a little math to decide where your y coordinate should be