How I can retrieve information on a canvas - java

I want to do a "zoomable paint", I mean a paint that I can zoom/zoom out and pan/drag the canvas and then draw on it.
I have a problem that I can't solve: when I draw while the canvas is zoomed, I retrieve the X and Y coordinate and effectively drawing it on the canvas. But these coordinates are not correct because of the zoomed canvas.
I tried to correct these (multiply by (zoomHeigh/screenHeight)) but I can't find a way to retrieve where I must draw on the original/none-zoomed screen
This is my code :
public class PaintView extends View {
public static int BRUSH_SIZE = 20;
public static final int DEFAULT_COLOR = Color.BLACK;
public static final int DEFAULT_BG_COLOR = Color.WHITE;
private static final float TOUCH_TOLERANCE = 4;
private float mX, mY;
private SerializablePath mPath;
private Paint mPaint;
private ArrayList<FingerPath> paths = new ArrayList<>();
private ArrayList<FingerPath> tempPaths = new ArrayList<>();
private int currentColor;
private int backgroundColor = DEFAULT_BG_COLOR;
private int strokeWidth;
private boolean emboss;
private boolean blur;
private boolean eraser;
private MaskFilter mEmboss;
private MaskFilter mBlur;
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
public boolean zoomViewActivated = false;
//These two constants specify the minimum and maximum zoom
private static float MIN_ZOOM = 1f;
private static float MAX_ZOOM = 5f;
private float scaleFactor = 1.f;
private ScaleGestureDetector detector;
//These constants specify the mode that we're in
private static int NONE = 0;
private static int DRAG = 1;
private static int ZOOM = 2;
private int mode;
//These two variables keep track of the X and Y coordinate of the finger when it first
//touches the screen
private float startX = 0f;
private float startY = 0f;
//These two variables keep track of the amount we need to translate the canvas along the X
//and the Y coordinate
private float translateX = 0f;
private float translateY = 0f;
//These two variables keep track of the amount we translated the X and Y coordinates, the last time we
//panned.
private float previousTranslateX = 0f;
private float previousTranslateY = 0f;
int currentPositionX = 0;
int currentPositionY = 0;
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
return true;
}
}
public ArrayList<FingerPath> getDividedPaths(float i){
for(FingerPath p : this.paths){
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(i, i);
p.path.transform(scaleMatrix);
}
return this.paths;
}
public ArrayList<FingerPath> getPaths() {
return paths;
}
public void dividePath(float i) {
for(FingerPath p : this.paths){
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(i, i);
p.path.transform(scaleMatrix);
}
}
public void setPaths(ArrayList<FingerPath> paths){
this.paths = paths;
}
public void setStrokeWidth(int value){
strokeWidth = value;
}
public PaintView(Context context) {
this(context, null);
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public PaintView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(DEFAULT_COLOR);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setXfermode(null);
mPaint.setAlpha(0xff);
mEmboss = new EmbossMaskFilter(new float[] {1, 1, 1}, 0.4f, 6, 3.5f);
mBlur = new BlurMaskFilter(5, BlurMaskFilter.Blur.NORMAL);
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public void init(DisplayMetrics metrics) {
int height = metrics.heightPixels;
int width = metrics.widthPixels;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
currentColor = DEFAULT_COLOR;
strokeWidth = BRUSH_SIZE;
}
public void normal() {
emboss = false;
blur = false;
eraser = false;
}
public void emboss() {
emboss = true;
blur = false;
eraser = false;
}
public void blur() {
emboss = false;
blur = true;
eraser = false;
}
public void eraser() {
eraser = true;
}
public void cancel(){
if(paths.size() != 0){
tempPaths.add(paths.get(paths.size()-1));
paths.remove(paths.size()-1);
invalidate();
}
}
public void redo(){
if(tempPaths.size() != 0){
paths.add(tempPaths.get(tempPaths.size()-1));
tempPaths.remove(tempPaths.size()-1);
invalidate();
}
}
public void clear() {
backgroundColor = DEFAULT_BG_COLOR;
paths.clear();
normal();
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
//We're going to scale the X and Y coordinates by the same amount
canvas.scale(scaleFactor, scaleFactor);
//If translateX times -1 is lesser than zero, let's set it to zero. This takes care of the left bound
if((translateX * -1) < 0) {
translateX = 0;
}
//This is where we take care of the right bound. We compare translateX times -1 to (scaleFactor - 1) * displayWidth.
//If translateX is greater than that value, then we know that we've gone over the bound. So we set the value of
//translateX to (1 - scaleFactor) times the display width. Notice that the terms are interchanged; it's the same
//as doing -1 * (scaleFactor - 1) * displayWidth
else if((translateX * -1) > (scaleFactor - 1) * getWidth()) {
translateX = (1 - scaleFactor) * getWidth();
}
if(translateY * -1 < 0) {
translateY = 0;
}
//We do the exact same thing for the bottom bound, except in this case we use the height of the display
else if((translateY * -1) > (scaleFactor - 1) * getHeight()) {
translateY = (1 - scaleFactor) * getHeight();
}
//We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
//because the translation amount also gets scaled according to how much we've zoomed into the canvas.
canvas.translate(translateX / scaleFactor, translateY / scaleFactor);
/* The rest of your canvas-drawing code */
mCanvas.drawColor(backgroundColor);
if(paths != null){
for (FingerPath fp : paths) {
mPaint.setColor(fp.color);
mPaint.setStrokeWidth(fp.strokeWidth);
mPaint.setMaskFilter(null);
if (fp.emboss)
mPaint.setMaskFilter(mEmboss);
else if (fp.blur)
mPaint.setMaskFilter(mBlur);
if(fp.eraser) {
mPaint.setColor(DEFAULT_BG_COLOR);
}
mCanvas.drawPath(fp.path, mPaint);
}
}
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.restore();
}
private void touchStart(float x, float y) {
mPath = new SerializablePath();
FingerPath fp = new FingerPath(currentColor, emboss, blur, eraser, strokeWidth, mPath);
paths.add(fp);
tempPaths = new ArrayList<>();
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touchMove(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touchUp() {
mPath.lineTo(mX, mY);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(zoomViewActivated){
boolean dragged = false;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = DRAG;
//We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
//amount for each coordinates This works even when we are translating the first time because the initial
//values for these two variables is zero.
startX = event.getX() - previousTranslateX;
startY = event.getY() - previousTranslateY;
break;
case MotionEvent.ACTION_MOVE:
translateX = event.getX() - startX;
translateY = event.getY() - startY;
//We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
//This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) +
Math.pow(event.getY() - (startY + previousTranslateY), 2)
);
if(distance > 0) {
dragged = true;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = ZOOM;
break;
case MotionEvent.ACTION_UP:
mode = NONE;
dragged = false;
//All fingers went up, so let's save the value of translateX and translateY into previousTranslateX and
//previousTranslate
previousTranslateX = translateX;
previousTranslateY = translateY;
currentPositionX += previousTranslateX;
currentPositionY += previousTranslateY;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = DRAG;
//This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
//and previousTranslateY when the second finger goes up
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
}
detector.onTouchEvent(event);
//We redraw the canvas only in the following cases:
//
// o The mode is ZOOM
// OR
// o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
// set to true (meaning the finger has actually moved)
if ((mode == DRAG && scaleFactor != 1f && dragged) || mode == ZOOM) {
invalidate();
}
}else{
float x = event.getX()*(getHeight()/scaleFactor)/getHeight()+currentPositionX;
float y = event.getY()*(getWidth()/scaleFactor)/getWidth()+currentPositionY;
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN :
touchStart(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE :
touchMove(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP :
touchUp();
invalidate();
break;
}
}
return true;
}
}

Related

Drawing a graph in Paint with Android graphics

I'm trying to plot a graph on a View using a canvas. So far so good it plots correctly as show in the next image:
Desired Output
once you tap on the screen the expected result should clear the screen and plot a new graph but instead draws on top of it resulting in this:
Actual Output
private List<Float> xPosList, yPosList;
private List<Path> pathList;
private Path path;
private Paint paint;
private final Paint clearPaint = new Paint();
public Plotter(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
this.xPosList = new ArrayList<>();
this.yPosList = new ArrayList<>();
this.pathList = new ArrayList<>();
this.paint = new Paint();
this.paint.setStrokeWidth(20);
this.paint.setColor(Color.GREEN);
this.clearPaint.setColor(Color.BLACK);
generateData();
}
private void generateData() {
int min = 5;
int max = 100;
double random = 0;
float xPos = 0;
float yPos = 0;
for (int i = 1; i <= 20; i++) {
random = min + Math.random() * (max - min);
xPos = 50 * i; //50 pixels
yPos = (float)random;
this.xPosList.add(xPos);
this.yPosList.add(yPos);
path = new Path(); //Create path
path.moveTo(xPos, yPos); //Add values to path
this.pathList.add(path); //Add path to pathList
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
generateData();
break;
case MotionEvent.ACTION_UP:
//invalidate();
break;
}
//invalidate(); //Refresh canvas
return true; //Activate event
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p = new Paint();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
p.setColor(Color.GREEN);
p.setStrokeWidth(10);
p.setStyle(Paint.Style.FILL);
/***
* Better use 50 by 50 pixels
*/
float startX = 0;
float startY = canvas.getHeight(); //Start at the bottom (max height)
float nextX = 0;
float nextY = 0;
for (int i = 0; i < this.xPosList.size(); i++){
nextX = this.xPosList.get(i);
nextY = canvas.getHeight() - (this.yPosList.get(i) * 10); //Add 50 offset
canvas.drawLine(startX, startY, nextX, nextY, p);
startX = nextX;
startY = nextY;
}
}
}
How do I properly clear screen?
I have tried
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
seen from other questions but it does not work.
Actually it does not have anything to do with your clear color - the screen gets cleared correctly. The problem is the xPosList and likewise the yPosList ArrayList.
As soon as the user taps the screen the generateData() function gets called which keeps adding elements to both ArrayList. But: you never clear those! So as soon as you tap the screen twice, the ArrayList's contain random data from the first and the second tap.
You need to clear those right before the for-loop:
this.xPosList.clear();
this.yPosList.clear();
for (int i = 1; i <= 20; i++) {
random = min + Math.random() * (max - min);
xPos = 50 * i; //50 pixels
yPos = (float)random;
this.xPosList.add(xPos);
this.yPosList.add(yPos);
path = new Path(); //Create path
path.moveTo(xPos, yPos); //Add values to path
this.pathList.add(path); //Add path to pathList
}

Modifying bitmap image

I'm working with an android studio project. I'm trying to select user touched pixel on an image, as shown Fig 1.
I want to add (X) on a pixel touched by the user, how can I make it by modifying bitmap ?
This is image.
If I understand your question correctly, you want to draw X in each time the user click on Bitmap, here's what you need :
ImageView img;
int clickCount = 0;
Bitmap src;
int colorTarget = Color.BLACK;
#SuppressLint("ClickableViewAccessibility")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img = findViewById(R.id.img);
img.setOnTouchListener((v, event) -> {
int x = (int) event.getX();
int y = (int) event.getY();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
drawXByPosition(img, src, x, y);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
clickCount++;
if (clickCount % 2 == 0) colorTarget = Color.BLACK;
else colorTarget = Color.WHITE;
}
return true;
});
}
private void drawXByPosition(ImageView iv, Bitmap bm, int x, int y) {
if (x < 0 || y < 0 || x > iv.getWidth() || y > iv.getHeight())
Toast.makeText(getApplicationContext(), "Outside of ImageView", Toast.LENGTH_SHORT).show();
else {
int projectedX = (int) ((double) x * ((double) bm.getWidth() / (double) iv.getWidth()));
int projectedY = (int) ((double) y * ((double) bm.getHeight() / (double) iv.getHeight()));
src = drawX(src, "X", 44, colorTarget, projectedX, projectedY);
img.setImageBitmap(src);
}
}
public Bitmap drawX(final Bitmap src,
final String content,
final float textSize,
#ColorInt final int color,
final float x,
final float y) {
Bitmap ret = src.copy(src.getConfig(), true);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Canvas canvas = new Canvas(ret);
paint.setColor(color);
paint.setTextSize(textSize);
Rect bounds = new Rect();
paint.getTextBounds(content, 0, content.length(), bounds);
canvas.drawText(content, x, y + textSize, paint);
return ret;
}

Pixel Perfect Collision detection between a custom view and an ImageView

I have a CustomView and an Image view. The CustomView is a ball that moves around the screen and bounces off the walls. The Image is a quarter circle that you can rotate in a circle on touch. I am trying to make my game so that when the filled pixels from the CustomView cross paths with the Filled pixels from the ImageView a collision is detected. The problem that I am having is I do not know how to retrieve where the filled pixels are on each view.
Here is my XML code
<com.leytontaylor.bouncyballz.AnimatedView
android:id="#+id/anim_view"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
/>
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/quartCircle"
android:layout_gravity="center_horizontal"
android:src="#drawable/quartercircle"
android:scaleType="matrix"/>
My MainActivity
public class MainActivity extends AppCompatActivity {
private static Bitmap imageOriginal, imageScaled;
private static Matrix matrix;
private ImageView dialer;
private int dialerHeight, dialerWidth;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// load the image only once
if (imageOriginal == null) {
imageOriginal = BitmapFactory.decodeResource(getResources(), R.drawable.quartercircle);
}
// initialize the matrix only once
if (matrix == null) {
matrix = new Matrix();
} else {
// not needed, you can also post the matrix immediately to restore the old state
matrix.reset();
}
dialer = (ImageView) findViewById(R.id.quartCircle);
dialer.setOnTouchListener(new MyOnTouchListener());
dialer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// method called more than once, but the values only need to be initialized one time
if (dialerHeight == 0 || dialerWidth == 0) {
dialerHeight = dialer.getHeight();
dialerWidth = dialer.getWidth();
// resize
Matrix resize = new Matrix();
resize.postScale((float) Math.min(dialerWidth, dialerHeight) / (float) imageOriginal.getWidth(), (float) Math.min(dialerWidth, dialerHeight) / (float) imageOriginal.getHeight());
imageScaled = Bitmap.createBitmap(imageOriginal, 0, 0, imageOriginal.getWidth(), imageOriginal.getHeight(), resize, false);
// translate to the image view's center
float translateX = dialerWidth / 2 - imageScaled.getWidth() / 2;
float translateY = dialerHeight / 2 - imageScaled.getHeight() / 2;
matrix.postTranslate(translateX, translateY);
dialer.setImageBitmap(imageScaled);
dialer.setImageMatrix(matrix);
}
}
});
}
MyOnTouchListener class:
private class MyOnTouchListener implements View.OnTouchListener {
private double startAngle;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startAngle = getAngle(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
double currentAngle = getAngle(event.getX(), event.getY());
rotateDialer((float) (startAngle - currentAngle));
startAngle = currentAngle;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
private double getAngle(double xTouch, double yTouch) {
double x = xTouch - (dialerWidth / 2d);
double y = dialerHeight - yTouch - (dialerHeight / 2d);
switch (getQuadrant(x, y)) {
case 1:
return Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
case 2:
return 180 - Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
case 3:
return 180 + (-1 * Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
case 4:
return 360 + Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
default:
return 0;
}
}
/**
* #return The selected quadrant.
*/
private static int getQuadrant(double x, double y) {
if (x >= 0) {
return y >= 0 ? 1 : 4;
} else {
return y >= 0 ? 2 : 3;
}
}
/**
* Rotate the dialer.
*
* #param degrees The degrees, the dialer should get rotated.
*/
private void rotateDialer(float degrees) {
matrix.postRotate(degrees, dialerWidth / 2, dialerHeight / 2);
dialer.setImageMatrix(matrix);
}
And my AnimatedView
public class AnimatedView extends ImageView {
private Context mContext;
int x = -1;
int y = -1;
private int xVelocity = 10;
private int yVelocity = 5;
private Handler h;
private final int FRAME_RATE = 60;
public AnimatedView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
h = new Handler();
}
private Runnable r = new Runnable() {
#Override
public void run() {
invalidate();
}
};
protected void onDraw(Canvas c) {
BitmapDrawable ball = (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.smallerball);
if (x<0 && y <0) {
x = this.getWidth()/2;
y = this.getHeight()/2;
} else {
x += xVelocity;
y += yVelocity;
if ((x > this.getWidth() - ball.getBitmap().getWidth()) || (x < 0)) {
xVelocity = xVelocity*-1;
}
if ((y > this.getHeight() - ball.getBitmap().getHeight()) || (y < 0)) {
yVelocity = yVelocity*-1;
}
}
c.drawBitmap(ball.getBitmap(), x, y, null);
h.postDelayed(r, FRAME_RATE);
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
My question is: How can I retrieve the filled pixels from both of these views, and pass them through a function that detects a collision.
Thanks in advance for the help!:)
You could encapsulate your images with Array of points marking your border coordinates(and update them on move/calculate them based on origin) then you'll be able to decide whether oposing object are in touch or not(if any of oposing arrays share the same point)
You really need to define "filled pixels". I assume you mean the non-transparent pixels. The easiest way to find those, is by converting your entire view into a bitmap and iterating through its pixels. You can convert a View into a Bitmap like this:
private Bitmap getBitmapFromView(View view) {
view.buildDrawingCache();
Bitmap returnedBitmap = Bitmap.createBitmap(view.measuredWidth,
view.measuredHeight,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedBitmap);
canvas.drawColor(Color.WHITE, PorterDuff.Mode.SRC_IN);
Drawable drawable = view.background;
drawable.draw(canvas);
view.draw(canvas);
return returnedBitmap;
}
You'd also need to get the absolute location of the views:
Point getViewLocationOnScreen(View v) {
int[] coor = new int[]{0, 0};
v.getLocationOnScreen(coor);
return new Point(coor[0], coor[1]);
}
Then you just have to iterate through the pixels of each Bitmap, check their colors to know whether they're "filled", and also check whether they're overlapping based on their coordination inside the bitmaps and the absolute location of the views on screen.
Iterating through a bitmap's is done like this:
for (int x = 0; x < myBitmap.getWidth(); x++) {
for (int y = 0; y < myBitmap.getHeight(); y++) {
int color = myBitmap.getPixel(x, y);
}
}
But I must say, I don't think performing this sort of heavy computations on UI thread is really a good idea. There are dozens of much better ways to detect collisions than pixel-perfect checking. This will probably come out extremely laggy.

Eclipse, drawCircle function doesn't draw to the correct location

I made a program to draw a ball bouncing around the screen. It worked fine yesterday, but I just loaded it up and now it won't draw at the correct X location, its just stuck at 0. Y is still fine though..
class GameThreadView extends SurfaceView implements Runnable {
// for thread
Thread renderThread = null;
// volatile equals keep order of execution
volatile boolean running = false;
// for canvas, also used to synchronize
SurfaceHolder holder;
GameThreadAndroid gta;
Paint bg;
Paint fg;
// game stuff
int frameCount = 0;
int count = 0;
float aDeltaTime = 0f;
float accumTime = 0f;
int touchX = -50;
int touchY = -50;
float ballX, ballY = 0.5f;
float moveX, moveY = 0.2f;
Random r = new Random();
public GameThreadView(Context context) {
super(context);
gta = (GameThreadAndroid)context;
holder = getHolder();
fg = new Paint(Paint.ANTI_ALIAS_FLAG);
fg.setTextSize(36);
fg.setTextAlign(Paint.Align.CENTER);
fg.setColor(getResources().getColor(R.color.text));
bg = new Paint();
bg.setColor(getResources().getColor(R.color.background));
Log.d("GAME","GameThreadView");
//
moveY = r.nextFloat() * 0.5f;
}
/*
* Actually always starts a new thread
*/
public void resume() {
running = true;
renderThread = new Thread(this);
renderThread.start();
}
/*
* Actually kills thread
*/
public void pause() {
running = false;
while(true) {
try {
renderThread.join();
break;
} catch (InterruptedException e) {
// retry
}
}
renderThread = null;
}
public void run() {
long startTime = System.nanoTime();
while(running) {
if(!holder.getSurface().isValid())
continue;
float deltaTime = (System.nanoTime()-startTime)/1000000000.0f;
startTime = System.nanoTime();
updateGame(deltaTime);
Canvas canvas = holder.lockCanvas();
drawSurface(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
// All game logic
private void updateGame(float deltaTime) {
accumTime += deltaTime;
count++;
if (accumTime > 1f) {
aDeltaTime = deltaTime;
frameCount = count;
count = 0;
accumTime = 0;
Log.d("GAME", String.format("frame count = %d", frameCount));
}
// move the ball
ballX += moveX * deltaTime;
ballY += moveY * deltaTime;
// hit walls
if(ballX > 1.0f){
ballX = 1.0f;
moveX = -moveX;
}
if(ballY > 1.0f){
ballY = 1.0f;
moveY = -moveY;
}
if(ballX < 0.0f){
ballX = 0.0f;
moveX = -moveX;
}
if(ballY < 0.0f){
ballY = 0.0f;
moveY = -moveY;
}
}
private void drawSurface(Canvas canvas) {
canvas.drawPaint(bg);
int w = canvas.getWidth() / 2;
canvas.drawText(Integer.toString(frameCount), w, canvas.getHeight() * 1/3, fg);
canvas.drawText(Float.toString(aDeltaTime), w, canvas.getHeight() * 2/3, fg);
canvas.drawCircle(ballX * canvas.getWidth(), ballY * canvas.getHeight(), 20, fg);
}
/*
* Called from outside this object so must be synchronized
* #see android.view.View#onTouchEvent(android.view.MotionEvent)
*/
public boolean onTouchEvent(MotionEvent event) {
synchronized (this) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
touchX = (int) event.getX();
touchY = (int) event.getY();
break;
}
return true;
}
}
I think the problem is that you never initialize moveX (its initialized with zero) in here:
float moveX, moveY = 0.2f;
and the, in the next method, you set moveY again, but moveX is still with the value of zero:
public GameThreadView(Context context) {
super(context);
gta = (GameThreadAndroid)context;
holder = getHolder();
fg = new Paint(Paint.ANTI_ALIAS_FLAG);
fg.setTextSize(36);
fg.setTextAlign(Paint.Align.CENTER);
fg.setColor(getResources().getColor(R.color.text));
bg = new Paint();
bg.setColor(getResources().getColor(R.color.background));
Log.d("GAME","GameThreadView");
// Here you set moveY
moveY = r.nextFloat() * 0.5f;
// You need to add somthing similar to moveX, like this
moveX = r.nextFloat() * 0.5f;
}
With that change it should run as expected.
Hope it helps you :)

How to draw android animated Doughnut chart

Here I have use 'HoloGraph' Library for Doughnut chart But Now I need to show with animation. Please suggest me How can I do it?
I have done without animation
Here's how i finally did it after two days of search with help of this library https://github.com/Ken-Yang/AndroidPieChart
And equations to center text done with help of my friends and alot of search
on MainActivity onCreate or oncreateView if you are using fragments:
PieChart pie = (PieChart) rootView.findViewById(R.id.pieChart);
ArrayList<Float> alPercentage = new ArrayList<Float>();
alPercentage.add(2.0f);
alPercentage.add(8.0f);
alPercentage.add(20.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.85f);
alPercentage.add(9.15f);
try {
// setting data
pie.setAdapter(alPercentage);
// setting a listener
pie.setOnSelectedListener(new OnSelectedLisenter() {
#Override
public void onSelected(int iSelectedIndex) {
Toast.makeText(getActivity(),
"Select index:" + iSelectedIndex,
Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
if (e.getMessage().equals(PieChart.ERROR_NOT_EQUAL_TO_100)) {
Log.e("kenyang", "percentage is not equal to 100");
}
}
public class PieChart extends View {
public interface OnSelectedLisenter {
public abstract void onSelected(int iSelectedIndex);
}
private OnSelectedLisenter onSelectedListener = null;
private static final String TAG = PieChart.class.getName();
public static final String ERROR_NOT_EQUAL_TO_100 = "NOT_EQUAL_TO_100";
private static final int DEGREE_360 = 360;
private static String[] PIE_COLORS = null;
private static int iColorListSize = 0;
ArrayList<Float> array;
private Paint paintPieFill;
private Paint paintPieBorder;
private Paint paintCenterCircle;
private ArrayList<Float> alPercentage = new ArrayList<Float>();
private int mCenterX = 320;
private int mCenterY = 320;
private int iDisplayWidth, iDisplayHeight;
private int iSelectedIndex = -1;
private int iCenterWidth = 0;
private int iShift = 0;
private int iMargin = 0; // margin to left and right, used for get Radius
private int iDataSize = 0;
private Canvas canvas1;
private RectF r = null;
private RectF centerCircle = null;
private float fDensity = 0.0f;
private float fStartAngle = 0.0f;
private float fEndAngle = 0.0f;
float fX;
float fY;
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
PIE_COLORS = getResources().getStringArray(R.array.colors);
iColorListSize = PIE_COLORS.length;
array = new ArrayList<Float>();
fnGetDisplayMetrics(context);
iShift = (int) fnGetRealPxFromDp(30);
iMargin = (int) fnGetRealPxFromDp(40);
centerCircle = new RectF(200, 200, 440, 440);
// used for paint circle
paintPieFill = new Paint(Paint.ANTI_ALIAS_FLAG);
paintPieFill.setStyle(Paint.Style.FILL);
// used for paint centerCircle
paintCenterCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
paintCenterCircle.setStyle(Paint.Style.FILL);
paintCenterCircle.setColor(Color.WHITE);
// used for paint border
paintPieBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
paintPieBorder.setStyle(Paint.Style.STROKE);
paintPieBorder.setStrokeWidth(fnGetRealPxFromDp(3));
paintPieBorder.setColor(Color.WHITE);
Log.i(TAG, "PieChart init");
}
// set listener
public void setOnSelectedListener(OnSelectedLisenter listener) {
this.onSelectedListener = listener;
}
float temp = 0;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i(TAG, "onDraw");
float centerX = (r.left + r.right) / 2;
float centerY = (r.top + r.bottom) / 2;
float radius1 = (r.right - r.left) / 2;
radius1 *= 0.5;
float startX = mCenterX;
float startY = mCenterY;
float radius = mCenterX;
float medianAngle = 0;
Path path = new Path();
for (int i = 0; i < iDataSize; i++) {
// check whether the data size larger than color list size
if (i >= iColorListSize) {
paintPieFill.setColor(Color.parseColor(PIE_COLORS[i
% iColorListSize]));
} else {
paintPieFill.setColor(Color.parseColor(PIE_COLORS[i]));
}
fEndAngle = alPercentage.get(i);
// convert percentage to angle
fEndAngle = fEndAngle / 100 * DEGREE_360;
// if the part of pie was selected then change the coordinate
if (iSelectedIndex == i) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
float fAngle = fStartAngle + fEndAngle / 2;
double dxRadius = Math.toRadians((fAngle + DEGREE_360)
% DEGREE_360);
fY = (float) Math.sin(dxRadius);
fX = (float) Math.cos(dxRadius);
canvas.translate(fX * iShift, fY * iShift);
}
canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieFill);
float angle = (float) ((fStartAngle + fEndAngle / 2) * Math.PI / 180);
float stopX = (float) (startX + (radius/2) * Math.cos(angle));
float stopY = (float) (startY + (radius/2) * Math.sin(angle));
// if the part of pie was selected then draw a border
if (iSelectedIndex == i) {
canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieBorder);
canvas.drawLine(startX, startY, stopX, stopY, paintPieFill);
canvas.restore();
}
fStartAngle = fStartAngle + fEndAngle;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// get screen size
iDisplayWidth = MeasureSpec.getSize(widthMeasureSpec);
iDisplayHeight = MeasureSpec.getSize(heightMeasureSpec);
if (iDisplayWidth > iDisplayHeight) {
iDisplayWidth = iDisplayHeight;
}
/*
* determine the rectangle size
*/
iCenterWidth = iDisplayWidth / 2;
int iR = iCenterWidth - iMargin;
if (r == null) {
r = new RectF(iCenterWidth - iR, // top
iCenterWidth - iR, // left
iCenterWidth + iR, // right
iCenterWidth + iR); // bottom
}
if (centerCircle == null) {
// centerCircle=new RectF(left, top, right, bottom);
}
setMeasuredDimension(iDisplayWidth, iDisplayWidth);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// get degree of the touch point
double dx = Math.atan2(event.getY() - iCenterWidth, event.getX()
- iCenterWidth);
float fDegree = (float) (dx / (2 * Math.PI) * DEGREE_360);
fDegree = (fDegree + DEGREE_360) % DEGREE_360;
// get the percent of the selected degree
float fSelectedPercent = fDegree * 100 / DEGREE_360;
// check which pie was selected
float fTotalPercent = 0;
for (int i = 0; i < iDataSize; i++) {
fTotalPercent += alPercentage.get(i);
if (fTotalPercent > fSelectedPercent) {
iSelectedIndex = i;
break;
}
}
if (onSelectedListener != null) {
onSelectedListener.onSelected(iSelectedIndex);
}
invalidate();
return super.onTouchEvent(event);
}
private void fnGetDisplayMetrics(Context cxt) {
final DisplayMetrics dm = cxt.getResources().getDisplayMetrics();
fDensity = dm.density;
}
private float fnGetRealPxFromDp(float fDp) {
return (fDensity != 1.0f) ? fDensity * fDp : fDp;
}
public void setAdapter(ArrayList<Float> alPercentage) throws Exception {
this.alPercentage = alPercentage;
iDataSize = alPercentage.size();
float fSum = 0;
for (int i = 0; i < iDataSize; i++) {
fSum += alPercentage.get(i);
}
if (fSum != 100) {
Log.e(TAG, ERROR_NOT_EQUAL_TO_100);
iDataSize = 0;
throw new Exception(ERROR_NOT_EQUAL_TO_100);
}
}
In Layout:
<com.example.piecharts.PieChart
android:id="#+id/pieChart"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</com.example.piecharts.PieChart>

Categories

Resources