I have this code what works just fine: On this custom View, 3 Ball object is bouncing continuously:
public class AnimatedView extends android.support.v7.widget.AppCompatImageView {
private Context mContext;
private boolean running = true;
private Handler h;
private final int FRAME_RATE = 10;
ArrayList<Ball> balls = new ArrayList<>();
public AnimatedView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
h = new Handler();
balls.add(new Ball(mContext, AnimatedView.this.getWidth(), AnimatedView.this.getHeight(), false));
balls.add(new Ball(mContext, AnimatedView.this.getWidth(), AnimatedView.this.getHeight(), false));
balls.add(new Ball(mContext, AnimatedView.this.getWidth(), AnimatedView.this.getHeight(), false));
}
private Runnable r = new Runnable() {
#Override
public void run() {
if (running) {
invalidate();
}
}
};
protected void onDraw(Canvas c) {
if (running) {
for (Ball ball : balls) {
if (ball.getX() < 0 && ball.getY() < 0) {
ball.setX(this.getWidth() / 2);
ball.setY(this.getHeight() / 2);
} else {
ball.setX(ball.getX() + ball.getxVelocity());
ball.setY(ball.getY() + ball.getyVelocity());
if ((ball.getX() > this.getWidth() - ball.getBall().getWidth()) || (ball.getX() < 0)) {
ball.setxVelocity(ball.getxVelocity() * -1);
}
if ((ball.getY() > this.getHeight() - ball.getBall().getHeight()) || (ball.getY() < 0)) {
ball.setyVelocity(ball.getyVelocity() * -1);
}
}
c.drawBitmap(ball.getBall(), ball.getX(), ball.getY(), null);
}
h.postDelayed(r, FRAME_RATE);
}
}
}
My only problem is, that it needs a lot of memory and hardware resources.
On my brand new phone, it's running perfectly smoothly (using 130-140MB memory with 3 bouncing balls), but it's terrible on my older phones, where the app can only use 20-30MB.
What, and how should I refactor my code in order to be more efficient (I'm pretty sure, that this is the problem).
It seems to me, that these calculations are necessary for every ball in every new frame.
Even if I set FRAME_RATE to 33 (which means cca. 30FPS), there is no noticable difference in my older phones.
Thanks! :)
Related
I want to connect several classes which are functioning separately but are related.
Lets say I am writing an app in which you can swipe to draw a chart. There are lots of classes in the app which are related and should be connected.
For example three of the classes are:
Swiper - responsible for interpreting the gesture of the user
Points - responsible for handling the points on the chart
ChartDrawer - responsible for drawing the chart on the screen
I want to know is there any design pattern such as a connector which can handle the relation and communication of these classes? Any way i can redesign in a better way or make mind more object oriented?
This is my ChartDraw class which extends a view:
public class ChartDraw extends View implements GestureReceiver {
int chartYPosition;
private int circleColor;
private int circleRadius;
int height;
private float lastPointOnChart;
private int lineColor;
private int lineWidth;
private Paint paint;
private float tempPoint;
int width;
public ChartDraw(Context context) {
super(context);
init();
}
public ChartDraw(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ChartDraw(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
this.lineWidth = 15;
this.circleRadius = 20;
this.lineColor = Color.parseColor("#1976D2");
this.circleColor = Color.parseColor("#536DFE");
this.lastPointOnChart = 0.0f;
this.tempPoint = 0.0f;
this.paint = new Paint();
this.height = getHeight();
this.width = getWidth();
this.chartYPosition = this.height / 2;
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.chartYPosition = canvas.getHeight() / 2;
this.paint.setStrokeWidth((float) this.lineWidth);
this.paint.setColor(this.lineColor);
canvas.drawLine(0.0f, (float) this.chartYPosition, this.tempPoint, (float) this.chartYPosition, this.paint);
if (this.tempPoint > 20.0f) {
this.paint.setColor(this.circleColor);
canvas.drawCircle(20.0f, (float) this.chartYPosition, 20.0f, this.paint);
drawTriangle(canvas, this.paint, this.tempPoint, this.chartYPosition);
}
}
private void drawTriangle(Canvas canvas, Paint paint, float startX, int startY) {
Path path = new Path();
path.moveTo(startX, (float) (startY - 20));
path.lineTo(startX, (float) (startY + 20));
path.lineTo(30.0f + startX, (float) startY);
path.lineTo(startX, (float) (startY - 20));
path.close();
canvas.drawPath(path, paint);
}
public void onMoveHorizontal(float dx) {
this.tempPoint = this.lastPointOnChart + dx;
invalidate();
}
public void onMoveVertical(float dy) {
}
public void onMovementStop() {
this.lastPointOnChart = this.tempPoint;
}
}
And this is My SwipeManager which is handling user gesture:
public class SwipeManager implements View.OnTouchListener {
GestureReceiver receiver;
private int activePointer;
private float initX,
initY;
private long startTime,
stopTime;
private boolean resolving = false;
private boolean resolved = false;
private Direction direction;
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (receiver == null) throw new AssertionError("You must register a receiver");
switch (motionEvent.getActionMasked()) {
case ACTION_DOWN:
activePointer = motionEvent.getPointerId(0);
initX = motionEvent.getX(activePointer);
initY = motionEvent.getY(activePointer);
startTime = new Date().getTime();
break;
case ACTION_MOVE:
if (!resolving && !resolved) {
resolving = true;
float x = motionEvent.getX(activePointer);
float y = motionEvent.getY(activePointer);
direction = resolveDirection(x, y);
if (direction != Direction.STILL) {
resolved = true;
resolving = false;
} else {
resolving = false;
resolved = false;
}
break;
}
if (resolved) {
if (direction == Direction.HORIZONTAL)
receiver.onMoveHorizontal(motionEvent.getX(activePointer) - initX);
else receiver.onMoveVertical(motionEvent.getX(activePointer) - initY);
}
break;
case ACTION_UP:
resolved = false;
receiver.onMovementStop();
break;
}
return true;
}
private Direction resolveDirection(float x, float y) {
float dx = x - initX;
float dy = y - initY;
float absDx = Math.abs(dx);
float absDy = Math.abs(dy);
if (absDx > absDy + 10) {
return Direction.HORIZONTAL;
} else if (absDy > absDx + 10) {
return Direction.VERTICAL;
}
return Direction.STILL;
}
public void setReceiver(GestureReceiver receiver) {
this.receiver = receiver;
}
private enum Direction {HORIZONTAL, VERTICAL, STILL;}
}
And i didn't start the Points class because i was not sure about the architecture.
I want this Connector to register all the listeners for the classes and wait for a change and inform the corresponding class of the change, like new point added or swipe started and finished or any other event in the app.
Chain of Responsibility might be what you are looking for.
It is a pattern to tie a series of 'processing objects' in a 'chain' that can handle 'command objects'.
I could see you making command objects that encapsulate the touch events and then get passed through several processors and finally get 'processed' by the 'processing objects' which handle input detection/output generation for that particular 'command object'.
I don't know if this is -ideal-, but it is potentially valid.
Other related patterns to look into might be:
https://en.wikipedia.org/wiki/Command_pattern
https://en.wikipedia.org/wiki/Observer_pattern
https://en.wikipedia.org/wiki/Bridge_pattern
Really what you're looking for here is an MVC-style architecture. Your application should (broadly speaking) be separated into 3 different areas:
the Model, which is completely divorced from your presentation or communication concerns. It provides an API for interaction and can be tested entirely independently with a simple framework such as JUnit.
the View, which is responsible for displaying the Model. It may be that a model can be displayed in different ways - in which case you get a single model and a few different views.
the Controller, which is responsible for making changes to the Model in response to user (or other) input.
The important thing is that the three sets of components are loosely-coupled and that responsibilities are clearly separated. All three should communicated via well defined interfaces (perhaps using the Observer, Command and ChainOfResponsibility patterns). In particular, the Model classes should have no direct knowledge of any of the View or Controller classes.
So, you might have some Model/View classes like this...
public interface ChartListener {
void notifyUpdate();
}
public interface Chart {
void newPoint(Point p);
Collection<Point> thePoints();
void addListener(ChartListener listener);
}
public class ChartModel implements Chart {
private final Collection<Point> points;
private final Collection<ChartListener> listeners;
public Collection<Point> thePoints() {
return Collections.unmodifiableCollection(points);
}
public void newPoint(Point p) {
thePoints.add(p);
listeners.stream().forEach(ChartListener::notifyUpdate);
}
public void addListener(ChartListener cl) {
listeners.append(cl);
}
}
public PieChartViewer implements ChartListener {
// All you colour management or appearance-related concerns is in this class.
private final Chart chart;
public PieChartView(Chart chart) {
this.chart = chart;
// set up all the visuals...
}
public void notifyUpdate() {
for (final Point p:chart.thePoints()) {
// draw a point somehow, lines, dots, etc,
}
}
}
Then you might have multiple different implementations of your View classes, utilising the ChartListener interface.
Your Swipe class seems like a Controller class, which would take a ChartModel implementation and then modify it in response to some input from the user.
I have this code. It isn't complex at all, I'm learning and I was practising and messing around with the surface view. I only want 2 rectangles to be there and an image going down. When we touch in the second rectangle, the image starts going up. We touch the one in the left and the image restarts going down. When it arrives the line 89, it stops and gives the null pointer exception. I guess the error happens when I create the canvas.
public class LearningThreads extends Activity {
ActivitySurface activitySurface;
boolean crossGoesUp = false;//Sets if the cross goes up or down
int leftRectangle1, topRectangle1, rightRectangle1, bottomRectangle1;
int leftRectangle2, topRectangle2, rightRectangle2, bottomRectangle2;
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
activitySurface = new ActivitySurface(this);
activitySurface.setOnTouchListener(new canvasClicked());
setContentView(activitySurface);//Sets the content to be the class we've created
}
protected void onPause() {//When the app is paused, it calls the method which pauses the thread that is constantly running
super.onPause();
activitySurface.pause();
}
protected void onResume() {//When the app starts or restarts, it calls the method which starts the thread
super.onResume();
activitySurface.resume();
}
public class canvasClicked implements OnTouchListener {
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {//Only if the user starts touching something because I'm not interested in when he releases
if (e.getX() <= leftRectangle1 && e.getX() >= rightRectangle1 && e.getY() <= topRectangle1 && e.getY() >= bottomRectangle1) {//Tests if the user touched one of the rectangles
crossGoesUp = false;
}
if (e.getX() <= leftRectangle2 && e.getX() >= rightRectangle2 && e.getY() <= topRectangle2 && e.getY() >= bottomRectangle2) {//Tests if the user touched the other rectangle
crossGoesUp = true;
}
}
return false;//It doesn't repeat
}
}
public class ActivitySurface extends SurfaceView {
Thread mainThread;
boolean isRunning = false;//Sets when the app is running or not
SurfaceHolder holder;//Gives us useful methods to use in the canvas
int crossY = 0;//Sets the y coordinate of the cross
public ActivitySurface(Context context) {
super(context);
holder = getHolder();
}
public void resume() {
isRunning = true;
mainThread = new Thread(new mainThread());
mainThread.start();
}
public void pause() {
isRunning = false;
try {
mainThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public class mainThread implements Runnable {//Takes care of the thread
public void run() {
while(isRunning) {
if (holder.getSurface().isValid())//Tests if the surface is valid, if it is not it won't do anything until it is
continue;
Canvas canvas = holder.lockCanvas();//Creating the canvas: it has a mistake, everytime I use the canvas it gives a NullPointerException
canvas.drawRGB(50, 50, 50);//Setting the color of the canvas
leftRectangle1 = canvas.getWidth()/4 - 40;//Setting the variables so they can be used outside this Thread
topRectangle1 = canvas.getHeight()/2 - 25;
rightRectangle1 = canvas.getWidth()/4 + 40;
bottomRectangle1 = canvas.getHeight()/2 + 25;
leftRectangle2 = canvas.getWidth()/4 + (canvas.getWidth()/4) * 2 - 40;
topRectangle2 = canvas.getHeight()/2 - 25;
rightRectangle2 = canvas.getWidth()/4 + (canvas.getWidth()/4) * 2 + 40;
bottomRectangle2 = canvas.getHeight()/2 + 25;
Paint paint = new Paint();//Setting the paint which will define the colors of the rectangles
paint.setARGB(0, 100, 100, 100);
Rect rectangle1 = new Rect();//Setting the position of the rectangle 1
rectangle1.set(leftRectangle1, topRectangle1, rightRectangle1, bottomRectangle1);
Rect rectangle2 = new Rect();//Setting the position of the rectangle 2
rectangle2.set(leftRectangle2, topRectangle2, rightRectangle2, bottomRectangle2);
canvas.drawRect(rectangle1, paint);//Drawing the rectangles
canvas.drawRect(rectangle2, paint);
Bitmap cross = BitmapFactory.decodeResource(getResources(), R.drawable.animation);//Creating the image which is going to go up and down
canvas.drawBitmap(cross, canvas.getWidth()/2 - cross.getWidth()/2, crossY, paint);
if (crossGoesUp) {//If the crossGoesUp is true, that means the user last touch was in the rectangle 2, so the image goes up
if (crossY < -cross.getHeight())//Tests if the image isn't out of bounds
crossY = canvas.getHeight() + cross.getHeight();
crossY -= 5;
} else {
if (crossY > canvas.getHeight() + cross.getHeight())//Same as above
crossY = -cross.getHeight();
crossY += 5;
}
holder.unlockCanvasAndPost(canvas);
}
}
}
}
}
This is my logcat:
05-02 07:13:41.897: E/AndroidRuntime(1634): FATAL EXCEPTION: Thread-103
05-02 07:13:41.897: E/AndroidRuntime(1634): Process: garden.apps.my_apps, PID: 1634
05-02 07:13:41.897: E/AndroidRuntime(1634): java.lang.NullPointerException
05-02 07:13:41.897: E/AndroidRuntime(1634): at com.apps.my_apps.LearningThreads$ActivitySurface$mainThread.run(LearningThreads.java:90)
05-02 07:13:41.897: E/AndroidRuntime(1634): at java.lang.Thread.run(Thread.java:841)
you should use SurfaceHolder.Callback, your paint is invisible
paint.setARGB(alpha,Red,Green,Blue) - alpha 0..255 0-invisible 255-visible
public class LearningThreads extends Activity {
ActivitySurface activitySurface;
boolean crossGoesUp = false;//Sets if the cross goes up or down
int leftRectangle1, topRectangle1, rightRectangle1, bottomRectangle1;
int leftRectangle2, topRectangle2, rightRectangle2, bottomRectangle2;
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
activitySurface = new ActivitySurface(this);
activitySurface.setOnTouchListener(new canvasClicked());
setContentView(activitySurface);//Sets the content to be the class we've created
}
protected void onPause() {//When the app is paused, it calls the method which pauses the thread that is constantly running
super.onPause();
}
protected void onResume() {//When the app starts or restarts, it calls the method which starts the thread
super.onResume();
}
public class canvasClicked implements View.OnTouchListener {
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {//Only if the user starts touching something because I'm not interested in when he releases
if (e.getX() <= leftRectangle1 && e.getX() >= rightRectangle1 && e.getY() <= topRectangle1 && e.getY() >= bottomRectangle1) {//Tests if the user touched one of the rectangles
crossGoesUp = false;
}
if (e.getX() <= leftRectangle2 && e.getX() >= rightRectangle2 && e.getY() <= topRectangle2 && e.getY() >= bottomRectangle2) {//Tests if the user touched the other rectangle
crossGoesUp = true;
}
}
return false;//It doesn't repeat
}
}
public class ActivitySurface extends SurfaceView implements SurfaceHolder.Callback {
Thread mainThread;
boolean isRunning = false;//Sets when the app is running or not
SurfaceHolder holder;//Gives us useful methods to use in the canvas
int crossY = 0;//Sets the y coordinate of the cross
public ActivitySurface(Context context) {
super(context);
holder = getHolder();
holder.addCallback(this);
}
public void resume() {
isRunning = true;
mainThread = new Thread(new mainThread());
mainThread.start();
}
public void pause() {
isRunning = false;
try {
mainThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public class mainThread implements Runnable {//Takes care of the thread
public void run() {
while (isRunning) {
if (!holder.getSurface().isValid())//Tests if the surface is valid, if it is not it won't do anything until it is
continue;
Canvas canvas = holder.lockCanvas();//Creating the canvas: it has a mistake, everytime I use the canvas it gives a NullPointerException
canvas.drawRGB(50, 50, 50);//Setting the color of the canvas
leftRectangle1 = canvas.getWidth() / 4 - 40;//Setting the variables so they can be used outside this Thread
topRectangle1 = canvas.getHeight() / 2 - 25;
rightRectangle1 = canvas.getWidth() / 4 + 40;
bottomRectangle1 = canvas.getHeight() / 2 + 25;
leftRectangle2 = canvas.getWidth() / 4 + (canvas.getWidth() / 4) * 2 - 40;
topRectangle2 = canvas.getHeight() / 2 - 25;
rightRectangle2 = canvas.getWidth() / 4 + (canvas.getWidth() / 4) * 2 + 40;
bottomRectangle2 = canvas.getHeight() / 2 + 25;
Paint paint = new Paint();//Setting the paint which will define the colors of the rectangles
paint.setARGB(255, 100, 100, 100);
Rect rectangle1 = new Rect();//Setting the position of the rectangle 1
rectangle1.set(leftRectangle1, topRectangle1, rightRectangle1, bottomRectangle1);
Rect rectangle2 = new Rect();//Setting the position of the rectangle 2
rectangle2.set(leftRectangle2, topRectangle2, rightRectangle2, bottomRectangle2);
canvas.drawRect(rectangle1, paint);//Drawing the rectangles
canvas.drawRect(rectangle2, paint);
Bitmap cross = BitmapFactory.decodeResource(getResources(), R.drawable.animation);//Creating the image which is going to go up and down
canvas.drawBitmap(cross, canvas.getWidth() / 2 - cross.getWidth() / 2, crossY, paint);
if (crossGoesUp) {//If the crossGoesUp is true, that means the user last touch was in the rectangle 2, so the image goes up
if (crossY < -cross.getHeight())//Tests if the image isn't out of bounds
crossY = canvas.getHeight() + cross.getHeight();
crossY -= 5;
} else {
if (crossY > canvas.getHeight() + cross.getHeight())//Same as above
crossY = -cross.getHeight();
crossY += 5;
}
holder.unlockCanvasAndPost(canvas);
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
resume();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
pause();
}
}
someActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
//Your code to run in GUI thread here
}//public void run() {
});
I hope this will help you.
I managed to figger it out by myself, the problem was in this block of code:
if (holder.getSurface().isValid())//Tests if the surface is valid, if it is not it won't do anything until it
continue;
I forgot to put the exclamation mark, so the app only did what was below when the surface was not valid and it gave me a NullPointerException.
Thank you anyway.
good day... im a new programmer in android... and actually its my first time doing so...
but i have basic knowledge about java...
so here goes.. my game will have an icon that can be controlled by a joystick... and another icon that goes up and down the screen(non controllable) i want the speed to increase a little..... i have started the code but dont know where and how to start the collision.. another one is that the icon that is controllabe always passes the screen and pop out the opposite direction...
public class GameSurface1 extends SurfaceView implements SurfaceHolder.Callback {
private Context _context;
private GameThread1 _thread;
private GameControl _controls;
private GameJoystick1 _joystick;
private int y = 0;
private int xSpeed = 1;
private Bitmap _pointer, bmp;
public GameSurface1(Context context) {
super(context);
// TODO Auto-generated constructor stub
_context = context;
init();
}
private void init(){
//initialize our screen holder
SurfaceHolder holder = getHolder();
holder.addCallback( this);
//initialize our game engine
//initialize our Thread class. A call will be made to start it later
_thread = new GameThread1(holder, _context, new Handler(),this);
setFocusable(true);
_joystick = new GameJoystick1(getContext().getResources());
_pointer = (Bitmap)BitmapFactory.decodeResource(getResources(), R.drawable.icon1);
bmp = (Bitmap)BitmapFactory.decodeResource(getResources(), R.drawable.bad1);
//contols
_controls = new GameControl();
setOnTouchListener(_controls);
}
public void doDraw(Canvas canvas){
if (y == getHeight() - bmp.getHeight()) {
xSpeed = -1;
}
if (y == 0) {
xSpeed = 1;
}
y = y + xSpeed;
//update the pointer
_controls.update(null);
//draw the pointer
canvas.drawBitmap(_pointer, _controls._pointerPosition.x, _controls._pointerPosition.y, null);
//draw the joystick background
canvas.drawBitmap(_joystick.get_joystickBg(), 15,215, null);
//draw the dragable joystick
canvas.drawBitmap(_joystick.get_joystick(),_controls._touchingPoint.x - 26, _controls._touchingPoint.y - 26, null);
canvas.drawBitmap(bmp, 280, y, null);
}
//these methods are overridden from the SurfaceView super class. They are automatically called
//when a SurfaceView is created, resumed or suspended.
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {}
private boolean retry;
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
retry = true;
//code to end gameloop
_thread.state = GameThread1.STOPED;
while (retry) {
try {
//code to kill Thread
_thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
#Override
public void surfaceCreated(SurfaceHolder arg0) {
if(_thread.state==GameThread1.PAUSED){
//When game is opened again in the Android OS
_thread = new GameThread1(getHolder(), _context, new Handler(),this);
_thread.start();
}else{
//creating the game Thread for the first time
_thread.start();
}
}
ill appreciate all the help you can give...
thank you
I've made a custom pie chart view that I want to animate starting when the pie chart is visible. Currently what I have is the pie chart animating but by the time you can actually see it on the screen the animation is half over. This is what I have:
public class SinglePieChart extends SurfaceView implements SurfaceHolder.Callback {
// Chart setting variables
private int emptyCircleCol, strokeColor, number, total;
// Paint for drawing custom view
private Paint circlePaint;
private RectF rect;
private Context context;
private AnimThread animThread;
private SurfaceHolder holder;
// animation variables
private float speed;
private float current = 0.0f;
private boolean percentsCalculated = false;
private float degree;
private int viewWidth, viewHeight;
public SinglePieChart(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
context = ctx;
// Paint object for drawing in doDraw
circlePaint = new Paint();
circlePaint.setStyle(Style.STROKE);
circlePaint.setStrokeWidth(3);
circlePaint.setAntiAlias(true);
circlePaint.setDither(true);
rect = new RectF();
//get the attributes specified in attrs.xml using the name we included
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.DashboardChartSmall, 0, 0);
try {
//get the colors specified using the names in attrs.xml
emptyCircleCol = a.getColor(R.styleable.DashboardChartSmall_smCircleColor, 0xFF65676E); // light gray is default
strokeColor = a.getColor(R.styleable.DashboardChartSmall_smColor, 0xFF39B54A); // green is default
// Default number values
total = a.getInteger(R.styleable.DashboardChartSmall_smTotal, 1);
number = a.getInteger(R.styleable.DashboardChartSmall_smNumber, 0);
} finally {
a.recycle();
}
this.setZOrderOnTop(true);
holder = getHolder();
holder.setFormat(PixelFormat.TRANSPARENT);
holder.addCallback(this);
}
protected void calculateValues() {
degree = 360 * number / total;
percentsCalculated = true;
speed = 10 * number / total;
viewWidth = this.getMeasuredWidth();
viewHeight = this.getMeasuredHeight();
float top, left, bottom, right;
if (viewWidth < viewHeight) {
left = 4;
right = viewWidth - 4;
top = ((viewHeight - viewWidth) / 2) + 4;
bottom = viewHeight - top;
} else {
top = 4;
bottom = viewHeight - 4;
left = ((viewWidth - viewHeight) / 2) + 4;
right = viewWidth - left;
}
rect.set(left, top, right, bottom);
}
protected void doDraw(Canvas canvas) {
if (total == 0) {
// Number values are not ready
animThread.setRunning(false);
return;
}
if (!percentsCalculated) {
calculateValues();
}
// set the paint color using the circle color specified
float last = current;
float start = -90;
circlePaint.setColor(strokeColor);
canvas.drawArc(rect, start, (last > degree) ? degree : last, false, circlePaint);
start += (last > number) ? number : last;
last = (last < number) ? 0 : last - number;
circlePaint.setColor(emptyCircleCol);
if (current > 360) {
current = 360;
}
canvas.drawArc(rect, start, 360 - current, false, circlePaint);
current += speed;
if (last > 0 || number == 0) {
// we're done
animThread.setRunning(false);
}
}
public void setNumbers(int num, int tot) {
number = num;
total = tot;
invalidate();
requestLayout();
}
public void setColor(int col) {
strokeColor = col;
}
public void redraw() {
calculateValues();
animThread.setRunning(true);
invalidate();
requestLayout();
}
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
#Override
public void surfaceCreated(SurfaceHolder arg0) {
animThread = new AnimThread(holder, context, this);
animThread.setRunning(true);
animThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
animThread.setRunning(false);
boolean retry = true;
while(retry) {
try {
animThread.join();
retry = false;
} catch(Exception e) {
Log.v("Exception Occured", e.getMessage());
}
}
}
public class AnimThread extends Thread {
boolean mRun;
Canvas mcanvas;
SurfaceHolder surfaceHolder;
Context context;
SinglePieChart msurfacePanel;
public AnimThread(SurfaceHolder sholder, Context ctx, SinglePieChart spanel) {
surfaceHolder = sholder;
context = ctx;
mRun = false;
msurfacePanel = spanel;
}
void setRunning(boolean bRun) {
mRun = bRun;
}
#Override
public void run() {
super.run();
while (mRun) {
mcanvas = surfaceHolder.lockCanvas();
if (mcanvas != null) {
msurfacePanel.doDraw(mcanvas);
surfaceHolder.unlockCanvasAndPost(mcanvas);
}
}
}
}
}
Also if you see any programming errors, memory leaks, poor performing code, please let me know. I'm new to Android.
Here is the layout that uses the SinglePieChart class:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.davidscoville.vokab.views.elements.SinglePieChart
android:id="#+id/smallPieChart"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="#+id/dashSmNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textSize="25sp"
android:textColor="#FFFFFF" />
</RelativeLayout>
<TextView
android:id="#+id/dashSmLabel"
android:layout_width="match_parent"
android:layout_height="20dp"
android:textSize="14sp"
android:gravity="center"
android:textColor="#FFFFFF" />
</merge>
Alright I'm going with the my pie chart won't automatically animate and it will have a new function that the Activity will trigger to start animating once it's ready. I wish there was an easier way...
Alternatively you can use the animation framework(or nine old androids if you want to support older apis). This will allow you to animate properties on your view, in your case the start and current variables.
I'd set this to happen during onAttachedToWindow.
Note if you aren't doing a lot of other things in this pie chart a surfaceview might be overkill for your needs.
i'm trying to fix this problem for more than 2 days now and have become quite desperate.
I want to write a 'Checkers-like' board game for android. The game engine itself is kinda complete but i have problems with updating the views.
I wrote a little example class to demonstrate my problem:
public class GameEngineView extends View {
private static final String TAG = GameEngineView.class.getSimpleName();
private int px;
private int py;
private int cx;
private int cy;
private boolean players_move;
private int clickx;
private int clicky;
Random rgen;
private RefreshHandler mRedrawHandler = new RefreshHandler();
class RefreshHandler extends Handler {
#Override
public void handleMessage(Message msg) {
GameEngineView.this.update();
GameEngineView.this.invalidate();
Log.d(TAG, "invalidate()");
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
};
public GameEngineView(Context context) {
super(context);
setFocusable(true);
players_move = true;
rgen = new Random();
}
public void update() {
updateGame();
Log.d(TAG, "update -> sleep handler");
mRedrawHandler.sleep(100);
}
public void updateGame() {
if(players_move) {
px = clickx;
py = clicky;
} else {
calcAIMove();
switchMove();
}
}
public void switchMove() {
players_move = !players_move;
}
public void calcAIMove() {
for(int i = 0; i < 20000; i++) {
cx = rgen.nextInt(getWidth());
cy = rgen.nextInt(getHeight());
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "event");
int eventaction = event.getAction();
if(eventaction == MotionEvent.ACTION_DOWN) {
Log.d(TAG, "action_down");
clickx = (int) event.getX();
clicky = (int) event.getY();
switchMove();
update();
}
return super.onTouchEvent(event);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint green = new Paint();
green.setColor(Color.GREEN);
Paint red = new Paint();
red.setColor(Color.RED);
canvas.drawColor(Color.BLACK);
canvas.drawCircle(px, py, 25, green);
canvas.drawCircle(cx, cy, 25, red);
}
}
The function calcAIMove() just burns time to simulate a real evaluation of the position in a board game.
Now my Problem is: If the player clicks(makes a move) the green ball is first drawn when the ai move calculation has been complete. So both moves are drawn at the same time.
I wonder HOW to accomplish this:
-Player clicks
-green Ball is drawn
-AI calculates
-red ball is drawn
-and so on..
When searching the web i found a lot of game loop examples but they all need a Thread with constant polling.. it should be possible without this since the whole program runs sequentially .. right?
Hoping for advice.
thanks,
Dave
Here is how your game could work:
user makes a move
game view is updated
game switches to CPU's turn
sleep to simulate CPU player thinking (if move computation is trivial)
compute CPU's move
game view is updated
game switches to player's turn
There is no need for constant polling in a turn-based game like this. The only place you should have sleep is in step 4, so you can remove it from the other areas (no need for the old update() method which is just a delayed call to updateGame()).
One way to implement this is to simply delay the call to calcAIMove() and switchMove() by putting it into a Runnable and using Handler.postDelayed() or similar.
I don't see how you are disabling touch events when it is the CPU's turn, which can lead to a host of other problems if switchMove() and update() are still being called...
In your game loop you can use a instance of TimerTask to ensure certain delay between greenBall and some other drawing task. Game tutorials like Snake and LunarLander are great references on when and if you need to invalidate your View. Hope this helps a little!
ok so the answer provided by antonyt helped me solve this.. I provide the corrected code for other interested ones.
public class GameEngineView extends View {
private static final String TAG = GameEngineView.class.getSimpleName();
private int px;
private int py;
private int cx;
private int cy;
private boolean players_move;
private int clickx;
private int clicky;
Random rgen;
/**
* Create a simple handler that we can use to cause animation to happen. We
* set ourselves as a target and we can use the sleep()
* function to cause an update/invalidate to occur at a later date.
*/
private RefreshHandler mRedrawHandler = new RefreshHandler();
class RefreshHandler extends Handler {
#Override
public void handleMessage(Message msg) {
GameEngineView.this.update();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
};
public GameEngineView(Context context) {
super(context);
setFocusable(true);
players_move = true;
rgen = new Random();
}
public void update() {
if(players_move) {
Log.d(TAG, "new player x");
px = clickx;
py = clicky;
GameEngineView.this.invalidate();
switchMove();
mRedrawHandler.sleep(100);
} else {
Log.d(TAG, "new ai x");
calcAIMove();
GameEngineView.this.invalidate();
switchMove();
}
Log.d(TAG, "update -> sleep handler");
}
public void switchMove() {
players_move = !players_move;
}
public void calcAIMove() {
for(int i = 0; i < 100000; i++) {
cx = rgen.nextInt(getWidth());
cy = rgen.nextInt(getHeight());
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "event");
int eventaction = event.getAction();
if(players_move && eventaction == MotionEvent.ACTION_DOWN) {
Log.d(TAG, "action_down");
clickx = (int) event.getX();
clicky = (int) event.getY();
update();
}
return super.onTouchEvent(event);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
Paint green = new Paint();
green.setColor(Color.GREEN);
Paint red = new Paint();
red.setColor(Color.RED);
canvas.drawCircle(px, py, 25, green);
canvas.drawCircle(cx, cy, 25, red);
}
}