I'm trying to make a small pong game, using a custom view that draws the platforms on each side and the ball as a circle. However when i run the app to see if the drawing is done correctly, while the platforms are drawn correctly the circle wont appear. so my question is, why is the ball not drawn at all?
This is the custom view:
public class GameView extends SurfaceView implements Runnable {
private Context mContext; // a private instance of the context
Thread gameThread=null; //the thread on which the game will run
Ball ball; //a ball
Platform lPlat,rPlat; // a platform for the left side and the right
//tools that will draw the view and the objects
Canvas canvas;
Paint paint;
Color color;
SurfaceHolder mHolder;
//variable for frame management and objects movement
long lastFrame;
long fps;
volatile boolean playing;
public GameView(Context context){
super(context);
this.mContext=context;
mHolder=getHolder();
color=new Color();
paint=new Paint();
gameThread=new Thread(this);
lPlat=new Platform(2592,620,50,200);
rPlat=new Platform(120,620,50,200);
ball=new Ball(2400,620,20);
setWillNotDraw(false);
init();
}
public void init() {
render();
playing=true;
gameThread.start();
}
//the game Thread
#Override
public void run(){
while (playing){
long currentTime=System.currentTimeMillis();
render();
lastFrame=System.currentTimeMillis()-currentTime;
//dividing the time difference between the start of the count and the end of the render (which is a frame) by 1000 to get an approximation of the fps;
if (lastFrame>=1)
fps=1000/lastFrame;
}
}
//renders the view, calling update method, and draw method afterwards
public void render(){
update();
draw();
}
public void update(){
ball.updateBallPosition();
rPlat.updatePlatformPosition();
lPlat.updatePlatformPosition();
}
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
Log.i("#CanvasStatus","Canvas onDraw");
canvas.drawColor(Color.argb(255, 100, 120, 70));
//the drawRect methods work properly
canvas.drawRect(lPlat.shape,paint);
canvas.drawRect(rPlat.shape,paint);
canvas.drawCircle(ball.x,ball.y,ball.radius,paint); //this is where I try to draw the circle
}
public void draw(){
if (mHolder.getSurface().isValid()) {
Log.i("#CanvasStatus","valid");
canvas=mHolder.lockCanvas();
paint.setColor(Color.argb(255,10,200,157));
paint.setStyle(Paint.Style.FILL);
this.draw(canvas);
mHolder.unlockCanvasAndPost(canvas);
}
else Log.i("#CanvasStatus","invalid");
}
}
and this is the ball class:
public class Ball {
float x;
float y;
float radius;
double yspeed, xspeed;
public Ball(float x, float y, float radius) {
this.x = x;
this.y = y;
this.radius = radius;
yspeed = 200;
xspeed = 200;
}
public void updateBallPosition() {
x+=xspeed;
y+=yspeed;
//checks for wall collisions
if(x>=GameActivity.screenCoordinates.x) {
x = GameActivity.screenCoordinates.x;
xspeed=-xspeed;
}
else if (x<=0){
x=0;
xspeed=-xspeed;
}
}
}
Not everything is implemented in regards to the game, i'm just trying to get the canvas to draw the objects correctly at the moment.
Thanks in advance
You don't need to override onDraw method and call draw(canvas) in your draw method, all the work with canvas should be done there. Remove onDraw method and change draw method to look like this:
public void draw(){
if (mHolder.getSurface().isValid()) {
Log.i("#CanvasStatus","valid");
canvas=mHolder.lockCanvas();
paint.setColor(Color.argb(255,10,200,157));
paint.setStyle(Paint.Style.FILL);
canvas.drawColor(Color.argb(255, 100, 120, 70));
//the drawRect methods work properly
canvas.drawRect(lPlat.shape,paint);
canvas.drawRect(rPlat.shape,paint);
canvas.drawCircle(ball.x,ball.y,ball.radius,paint);
mHolder.unlockCanvasAndPost(canvas);
}
else Log.i("#CanvasStatus","invalid");
}
By the way, you've set up boundaries for x coordinate only, you should add it for y as well. Besides that, you will possibly need to add some delay to canvas drawing (with Thread.sleep in a simplest case) or else your view will re-draw too often.
Related
I'm doing a school project. In this project I have to do a program that have one or more ball bouncing in the screen. I did some research on google to help me in this, and I found this code :
public class BouncingBallInside extends View {
private List<Ball> balls = new ArrayList<>();
public BouncingBallInside(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BouncingBallInside(Context context) {
super(context);
init();
}
private void init(){
//Add a new ball to the view
balls.add(new Ball(50,50,100, Color.RED));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Draw the balls
for(Ball ball : balls){
//Move first
ball.move(canvas);
//Draw them
canvas.drawOval(ball.oval,ball.paint);
}
invalidate(); // See note
}
}
The ball class :
public class Ball{
public int x,y,size;
public int velX = 10;
public int velY=7;
public Paint paint;
public RectF oval;
public Ball(int x, int y, int size, int color){
this.x = x;
this.y = y;
this.size = size;
this.paint = new Paint();
this.paint.setColor(color);
}
public void move(Canvas canvas) {
this.x += velX;
this.y += velY;
this.oval = new RectF(x-size/2,y-size/2,x+size/2,y+size/2);
//Do we need to bounce next time?
Rect bounds = new Rect();
this.oval.roundOut(bounds); ///store our int bounds
//This is what you're looking for ▼
if(!canvas.getClipBounds().contains(bounds)){
if(this.x-size<0 || this.x+size > canvas.getWidth()){
velX=-velX;
}
if(this.y-size<0 || this.y+size > canvas.getHeight()){
velY=-velY;
}
}
}
}
The program works perfecly.
I studied it deeply as good as I could. But after it and after watching the documentation I couldn't understand two thing:
Where and when the method onDraw(Canvas canvas) is called the first time.
Why at the end of onDraw there is invalidate()?
I mean the documentation said :
Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future.
so... if this method is used to call onDraw,why don't call it direcly? what's the difference?
1)The onDraw method will be called by the framework, whenever the view is invalid. A view is invalid when it first comes on screen, so when you set your content view for an activity they layout and all views in it will be measured, laid out, then drawn (via onDraw).
After that the UI thread will call onDraw if needed every 16ms or so (so it draws at 60 FPS).
2)Its marking the view as needing to be redrawn, so the next time the the screen is drawn onDraw will be called. Otherwise it would be skipped, as we could assume it isn't needed.
Why you don't call onDraw directly- efficiency. In a very simple drawing system you would- but drawing is time consuming, you don't want to do it more than you have to. So instead of drawing immediately (which wouldn't work anyway, you wouldn't have the right Canvas to pass to onDraw), you call invalidate and the system will call onDraw if needed at a regular interval.
Note that this isn't particularly good code. In particular, having the onDraw trigger the move which updates the balls location instead of using a timer is icky. Having onDraw call invalidate as a result is also kind of icky. A better solution would be to separate the view, model, and timer into more of an MVC or MVP system.
I am trying to draw a ball animation over the camera, that is the reason I have to use class View instead of SurfaceView. The point is that is when I run my app, animation is drawing properly sometimes, but if I back to the earlier activity, and return to it, ball draws very slow.
To call the OnDraw method I use the acelerometer:
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mView.invalidate();
}
}
An that`s my OnDraw method:
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
parabolic.getPositions();
int positionY = parabolic.pixY;
int positionX = parabolic.pixX;
ball.setBounds(positionX, positionY,
(int) (positionX + ball.getIntrinsicWidth()),
(int) (positionY + ball.getIntrinsicHeight()));
ball.draw(canvas);
canvas.restore();
}
Where parabolic is an object that I inizialize in the init method:
ParabolicMove parabolic = new ParabolicMove();
And ball is a drawable:
ball = context.getResources().getDrawable(R.drawable.ball);
How could I optimize it do draw well the animation?
You could use a static image to draw the image and the ball in a diferent thread. This may resolve your problem. For more information you can use SurfaceView:
http://developer.android.com/reference/android/view/SurfaceView.html
I'm trying to Use a BitmapShader to draw on a Canvas. I have 2 images of the same dimensions (one for the alfa channel and one for RGB), that I want to draw on a canvas that was created with the same size. The problem is that it seem as though canvas.drawBitmap users absolute coordinates for the paint. is there a way to draw with relative coordinates.
public class MaskedImageDrawable extends Drawable {
private final Bitmap mMask;
private final Paint mPaint = new Paint();
public MaskedImageDrawable(Bitmap mask, Bitmap image) {
mMask = mask;
Shader targetShader =
new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(targetShader);
}
#Override
public void draw(Canvas canvas) {
canvas.drawBitmap(mMask, 0.0f, 0.0f, mPaint);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
#Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
}
I can't seem to be able to get the drawable's absolute coordinates on the screen. I know I can use getLocationOnScreen/InWindow from the view that holds the drawable but that doesn't take animations into account.
I tried tried applying relative coordinates by applying a translation on the shader in onBoundsChange but the coordinates here seems to be relative as well.
Matrix mTranslationMatrix = new Matrix()
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mTranslationMatrix.setTranslate(bounds.left, bounds.top);
mPaint.getShader().setLocalMatrix(mTranslationMatrix);
}
As a reference - it seems drawRect with the same paint will use relative coordinates. so replacing the draw method with the following will render the RGB image on the drawable correctly (but not the mask)
#Override
public void draw(Canvas canvas) {
// getBounds returns the relative coordinates as well
canvas.drawRect(getBounds, mPaint);
}
It seems that this is a HW acceleration issue. disabling HW acceleration on the view moves the shader back to relative coordinates.
if (android.os.Build.VERSION.SDK_INT >= 11) {
myView.disableHwAcceleration();
}
Okay, so I'm trying to draw to a canvas on Android from outside of the onDraw method.
It's just easiest to show my code:
public class TestActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Paint p = new Paint();
p.setColor(Color.GREEN);
Panel a = new Panel(this,150,150,50,p);
a.drawThing();
setContentView(a);
}
class Panel extends View{
private float radius, x, y;
private Canvas CAN;
private Paint p;
public Panel(Context context, float x, float y, float radius, Paint p){
super(context);
this.x = x;
this.y = y;
this.radius = radius;
this.p = p;
}
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
CAN = canvas;
}
public void drawThing(){
CAN.drawCircle(x, y, radius, p);
}
}
}
Do you see what I'm trying to do? But for some reason it throws a NullPointerException
Many of the graphics resources are explicitly freed/released after they've been used. I'm not exactly sure why they do this, but whatever the reason, they don't you to do what you're trying.
Instead of drawing outside of the onDraw method, use some kind of flag to change what the onDraw method is doing. When you want to draw some specific thing, you can set the right flag, and call invalidate().
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
if (doThing) {
canvas.drawCircle(x, y, radius, p);
}
}
EDIT
Something else to consider is drawing to and "off-scrren" source. This means using some kind of graphics representation like a bitmap as a buffer that you can draw to in other code. This won't update your gui, but it will give you the chance to do some heavy duty drawing without locking up the user's device. Once you are done drawing to the bitmap (or whatever) you can invalidate your view and draw it to the screen in the onDraw(Canvas) method.
I'm pretty sure that the null pointer happens because you're calling drawSomething before onDraw ever gets called. So CAN is null.
You can draw onto canvas outside of the onDraw. See this Can we have two canvases in an activity ? (OR) Having a canvas outside the onDraw() is not working for more info.
I want to develop my application with a simple animation. I have used a lot of source from: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/Arcs.html
My code:
public class Animation extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
private static class AnimView extends View {
private Paint myPaint;
private Paint myFramePaint;
private RectF bigOval;
private float myStart;
private float mySweep;
private static final float SWEEP_INC = 1;
public AnimView(Context context) {
super(context);
init();
}
public AnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
myPaint = new Paint();
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.FILL);
myPaint.setColor(Color.RED);
bigOval = new RectF(40, 10, 280, 250);
myFramePaint = new Paint();
myFramePaint.setAntiAlias(true);
myFramePaint.setColor(Color.BLACK);
}
private void drawArcs(Canvas canvas, RectF oval, boolean useCenter, Paint paint) {
canvas.drawRect(oval, myFramePaint);
canvas.drawArc(oval, myStart, mySweep, useCenter, paint);
}
#Override
protected void onDraw(Canvas canvas) {
drawArcs(canvas, bigOval, true, myPaint);
myStart = -90;
mySweep -= SWEEP_INC;
invalidate();
}
}}
I'm using my view in this way in my xml file:
<view
class="go.android.Animation$AnimView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
It works properly. I know that it isn't custom animation. But is this possible to set speed of this animation (in ms or seconds)? Or to stop this animation (for example in the OnClick or OnTouch Listener), and get to know when this animation has finished?
I also want to get first whole circle, and on the end of animation - lack of circle. Simply to change direction of this animation. Is this possible?
I don't want to use frame-by-frame animation. I want to get continuous animation. Is there any possibilities to get similar animation (with setting speed, etc...)
I also want to animate not only a color but rather a round drawable.
Thank you in advance. Sorry for my English skill.
There might be better and more precise ways to control your animation than this (you could look into using OpenGL), but you can change the speed and direction of the animation relatively easily given your existing code.
The speed is controlled by the SWEEP_INC field (which stands for Sweep Increment I would guess). Every time the onDraw() method is called, it increases the size of the wedge by 1 degree. If you want the animation to go 5 times as fast, set SWEEP_INC to 5 instead of 1. If you want the animation to go half as fast, set SWEEP_INC to 0.5.
You can also just set a flag to reverse the animation each time it finishes. I've modified your code to reverse each time it reaches the end, using the addToCircle boolean.
public class Animation extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
private static class AnimView extends View {
private Paint myPaint;
private Paint myFramePaint;
private RectF bigOval;
private float myStart;
private float mySweep;
private float SWEEP_INC = 1;
//Use this flag to control the direction of the arc's movement
private boolean addToCircle = true;
public AnimView(Context context) {
super(context);
init();
}
public AnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
myPaint = new Paint();
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.FILL);
myPaint.setColor(Color.RED);
bigOval = new RectF(40, 10, 280, 250);
myFramePaint = new Paint();
myFramePaint.setAntiAlias(true);
myFramePaint.setColor(Color.BLACK);
}
private void drawArcs(Canvas canvas, RectF oval, boolean useCenter, Paint paint) {
canvas.drawRect(oval, myFramePaint);
canvas.drawArc(oval, myStart, mySweep, useCenter, paint);
}
public void setIncrement(float newIncrement)
{
SWEEP_INC = newIncrement;
}
#Override
protected void onDraw(Canvas canvas) {
drawArcs(canvas, bigOval, true, myPaint);
myStart = -90;
//If the arc is currently getting bigger, decrease the value of mySweep
if(addToCircle)
{
mySweep -= SWEEP_INC;
}
//If the arc is currently getting smaller, increase the value of mySweep
else
{
mySweep += SWEEP_INC;
}
//If the animation has reached the end, reverse it
if(mySweep%360 == 0)
{
addToCircle = !addToCircle;
}
invalidate();
}
}
}
EDIT
If you remove the final and static modifiers from SWEEP_INC you can change the speed of the animation at runtime using the setIncrement(float newIncrement) function I've added.
You can stop the animation by calling setIncrement(0).
However, I don't think there is a constant rate at which onDraw is called. So I don't know of any way to assign a duration in seconds to this animation. For that you may want to look into more sophisticated methods of animation.