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.
Related
I want to develop a game that shoots bullets on every touch of the canvas.
It works but when I touch the canvas after shooting, he takes the bullet and restarts the shooting.
I just want the bitmap to create new bullet at every touch. Here is my code:
public class MainActivity extends Activity implements OnTouchListener {
DrawBall d;
int x ;
int y;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
d = new DrawBall(this);
d.setOnTouchListener(this);
setContentView(d);
}
public class DrawBall extends View {
Bitmap alien;
public DrawBall(Context context) {
super(context);
alien = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
startDraw(canvas);
}
public void startDraw(Canvas canvas){
Rect ourRect = new Rect();
ourRect.set(0,0, canvas.getWidth(), canvas.getHeight());
Paint blue = new Paint();
blue.setColor(Color.BLACK);
blue.setStyle(Paint.Style.FILL);
canvas.drawRect(ourRect, blue);
if(y < canvas.getHeight()){
y-=5;
}
canvas.drawBitmap(alien, x, y,new Paint());
invalidate();
}
}
public boolean onTouch(View v, MotionEvent event) {
x = (int) event.getX();
y = (int) event.getY();
return false;
}
}
I only superficially read the code. It seems that you are only keeping track of one bullet using the x y coordinates.The coordinates are resetting at every touch event, and thus you lose the previous bullet.
Use a dynamic array, or a linked list to keep track of all the bullets on the screen.
When there's a new touch, add the x,y to the array.
When drawing the bullet, iterate through your array to draw and update every bullet.
If the y-coordinate of any bullet goes out of the screen, delete the bullet from the array.
Each draw starts with a blank canvas. So to draw multiple bitmaps you need to keep track of where to draw each bullet and call drawBitmap multiple times.
Also, calling invalidate in onDraw is a bad idea- it will immediately invalidate, leading you to have performance issues. I'd suggest invalidating on a timer instead. Drawing too frequently will lead to performance issues.
I'm trying to apply a visual effect to a viewgroup. My idea is to grab a bitmap of the viewgroup, shrink it down, expand it back up, and draw it over the viewgroup to give it a blocky, low quality effect.
I've got most of the way there using this code:
public class Blocker {
private static final float RESAMPLE_QUALITY = 0.66f; // less than 1, lower = worse quality
public static void block(Canvas canvas, Bitmap bitmap_old) {
block(canvas, bitmap_old, RESAMPLE_QUALITY);
}
public static void block(Canvas canvas, Bitmap bitmap_old, float quality) {
Bitmap bitmap_new = Bitmap.createScaledBitmap(bitmap_old, Math.round(bitmap_old.getWidth() * RESAMPLE_QUALITY), Math.round(bitmap_old.getHeight() * RESAMPLE_QUALITY), true);
Rect from = new Rect(0, 0, bitmap_new.getWidth(), bitmap_new.getHeight());
RectF to = new RectF(0, 0, bitmap_old.getWidth(), bitmap_old.getHeight());
canvas.drawBitmap(bitmap_new, from, to, null);
}
}
I simply pass in the canvas to draw on and a bitmap of what needs to be scaled down+up and it works well.
public class BlockedLinearLayout extends LinearLayout {
private static final String TAG = BlockedLinearLayout.class.getSimpleName();
public BlockedLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
applyCustomAttributes(context, attrs);
setup();
}
public BlockedLinearLayout(Context context) {
super(context);
setup();
}
private void setup() {
this.setDrawingCacheEnabled(true);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// block(canvas); If I call this here, it works but no updates
}
#Override
public void onDraw(Canvas canvas) {
// block(canvas); If I call this here, draws behind children, still no updates
}
private void block(Canvas canvas) {
Blocker.block(canvas, this.getDrawingCache());
}
}
The problem I'm having is in my viewgroup. If I run the block method in the viewgroup's draw, it draws over everything but doesn't ever update when child views change. I've traced function calls with Log, and the draw method seems to be running, but nothing changes.
I've also tried implementing this in onDraw. This draws the bitmap behind all the children views, and again they aren't updating.
Can anyone explain how I would go about fixing this?
Try this:
#Override
protected void dispatchDraw(Canvas canvas) {
// call block() here if you want to draw behind children
super.dispatchDraw(canvas);
// call block() here if you want to draw over children
}
And call destroyDrawingCache() and then, buildDrawingCache() each time you change a child.
Draw() method will work well for you.
I'm now trying to make a count time view in a circle shape, when time is passing, the view will reducing his angle. It's used to cover profile photo(a circle shape photo).
Starting with Android API 23, you can use onDrawForeground(Canvas) to draw on top of child views: https://developer.android.com/reference/android/view/View#onDrawForeground(android.graphics.Canvas)
Unlike onDraw() though, be sure to call through to the super class:
#Override
public void onDrawForeground(final Canvas canvas) {
super.onDrawForeground(canvas);
// Your code here
}
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.
so i created a view called "drawable view"
class DrawableView extends View{
Context mContext;
int touches=0,k,Xoffs,clicks=0;
double x_1 = 0,x_2=0;
private float mLastTouchX, mLastTouchY;
public DrawableView(Context context) {
super(context);
mContext = context;
}
....
#Override
protected void onDraw(Canvas canvas){
Paint myPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
canvas.drawColor(Color.BLUE);
myPaint.setColor(Color.WHITE);
canvas.drawCircle(200, 100, 20, myPaint);
}
..... more code....
}
and it can only be invalidated within the ondraw command! ie: calling "invalidate();" at the end of the ondraw command causes it to loop.
I have tried many times to call g_draw.invalidate(); or g_draw.postInvalidate(); (g_draw is the name of the created Drawable View)from other classes and even the main activity class and it doesnt work. why and how can i fix it?
thanks
If you want continious onDraw invoking try doing it in another thread. Create a thread, and from its run method try doing postInvalidate.
It always worked for me.
Another thing is that when you draw a circle once, next time wont make any difference - it will look the same.
You may want to call invalidate() somewhere in your DrawableView class. For example, if you want your view to redraw itself after any touch event, you would do something like this:
public boolean onTouchEvent( MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP){
invalidate();
}
}
This is how I draw the movable pieces in my puzzle game.
What is the simplest way to draw pixels, lines and circles on a View?
I want to move a cross cursor around, so nothing particularly intensive.
I thought I could extend SurfaceView and add it to an XML and it would just work, but it just appears black, however, when I look at the layout view of localmap.xml in eclipse, the graphics appear as expected.
Any ideas? My onDraw is never called on the emulator, and even calling invalidate on the class makes no difference. I shall keep trying but can anyone see anything I've missed? or is there a better way entirely?
Frink
localmap.xml contains the following (in a RelativeLayout)
<com.example.android.game.LocalMapView
android:id="#+id/localmap_map"
android:layout_width="fill_parent"
android:layout_above="#id/localmap_planettext"
android:layout_below="#id/header"/>
LocalMapView.java contains the following (amongst other things)
public class LocalMapView extends SurfaceView {
Paint mPaint = new Paint();
//Construct a LocalMapView based on inflation from XML
public LocalMapView(Context context, AttributeSet attrs) {
super(context, attrs);
// allow the map to receive the focus
setFocusable(true);
}
private void drawPixel(Canvas canvas, int x, int y, int colour) {
mPaint.setColor(colour);
if ((x >= MAP_MIN_X) && (x < MAP_MAX_X) && (y >= MAP_MIN_Y) && (y < MAP_MAX_Y)) {
canvas.drawPoint(((float)x * mScaleMapToScreenX), ((float)y * mScaleMapToScreenY), mPaint);
}
}
private void drawCircle(Canvas canvas, int x, int y, int radius, int colour) {
mPaint.setColor(colour);
canvas.drawCircle(((float)x), ((float)y), ((float)radius), mPaint);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircle(canvas, MAP_MAX_X/2, MAP_MAX_Y/2, 1, 0xFF00FFFF);
drawPixel(canvas, MAP_MAX_X/2, MAP_MAX_Y/2, 0xFF000000);
}
With SurfaceView you don't do the drawing in onDraw(). You have to grab a canvas from the underlying surface and draw in there. It seems to me you don't really know why you are using a SurfaceView. Just use a normal View instead and onDraw() will work just fine.