# Canvas Circle bewegen?



## Robokopp (29. Feb 2012)

Hallo,

ich habe in Java ein kleines Programm geschrieben, welches einen Button mit s=1/2gt² zu Boden gleiten lässt. Nun wollte ich etwas ähnliches für Android machen, nur statt einem Button eine Figur.
Ich habe viel zu Canvas gefunden und damit einen Circle gezeichnet, dem ich nun mit hilfe einer while Schleife Schwerkraft beisetzen möchte.


```
package de.android.myandroapp;

private final float x=100;
	    private final float y=100;
	    private final  int r=100;
	    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	    
	    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    	Button button1 = (Button)findViewById(R.id.button1);
		button1.setOnClickListener(this);
		FrameLayout main = (FrameLayout)findViewById(R.id.view1);
		main.setBackgroundColor(Color.WHITE);
		
		
    }
 

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		FrameLayout main = (FrameLayout)findViewById(R.id.view1);
		main.addView(new Ball(this,50,50,25));
		
		
	}
}
```



```
package de.android.myandroapp;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
import android.widget.FrameLayout;
 
public class Ball extends View {

	private double time;
	private double g = 9.81;
	private double ycoordinate;
	private int ycoordinatesconverted;
	private Ball ball;
	
	private int x=10;
    private int y=10;
    private final int r;
    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
    MyAndroApp2Activity myandroapp = new MyAndroApp2Activity();
    FrameLayout main = (FrameLayout) findViewById(R.id.view1);
    public void setY(int coordinatesconverted) {
		this.y = coordinatesconverted;
	}

	public Ball(Context context, int x, int y, int r) {
        super(context);        
        mPaint.setColor(0xFFFF0000);
        this.x = x;
        this.y = y;
        this.r = r;
    }

	


    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(x, y, r, mPaint);
        
    	
    		while(ycoordinate <= 300){
    			try {
					Thread.sleep(5);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
    			time=time+0.1;
    			ycoordinate = 0.5 * g * time*time;
    			y = (int) ycoordinate;
    					
    		}
    	
    
}	
}
```

Hinbekommen habe ich es, dass ganz viele kreise gezeichnet werden(Trifft nicht auf den aktuellen Code zu, hab da mal wieder was geändert und er Besitzt nach rückgängig machen einiger Änderungen einen Fehler, wodurch das so nicht lauffähig ist)
Ich habe als Darstellungsbereich das FrameLayout gewählt, was offenbar keine repaint() Methode besitzt.Das ist auch mein größtes Problem, denn entweder packe ich drawCircle unter die whileschleife, und er zeichnet ganz viele Kreise oder ich lasse es so wie es jetzt ist und es wird nur der erste Kreis gezeichnet.


zum repainten hab ich noch validate probiert, ohne Erfolg.

Gibt es irgendeinen Trick zum repainten?was muss überhaupt gerepaintet werden? Canvas selbst oder das LayOut? und existiert für Canvas-Objekte so etwas ähnliches wie setLocation() oder kann so ein Objekt selbstständig mit neuen Werten umgehen und es reicht die repaint methode?

MfG


----------



## Marco13 (29. Feb 2012)

Bin mit Android nicht so firm, aber während des Zeichnens zu Warten ist dort genauso schlecht wie bei Swing. Die Animation sollte in einem eigenen Thread laufen.


----------



## Robokopp (29. Feb 2012)

Du redest von dem sleep,richtig? Ja ich werde es auch noch in threads verpacken, aber es sollte dennoch funktionieren. Mit dem Button lief es ja ebenfalls 

Gesendet von meinem GT-I9100 mit Tapatalk


----------



## Marco13 (29. Feb 2012)

Dann müßtest du nochmal genau sagen, was die eigentliche Frage ist. So wird er vermultich einmal den Kreis zeichnen, dann warten (blockieren), und ihn dann vielleicht nochmal zeichnen, aber das war's dann wohl...


----------



## Robokopp (29. Feb 2012)

Marco13 hat gesagt.:


> Dann müßtest du nochmal genau sagen, was die eigentliche Frage ist. So wird er vermultich einmal den Kreis zeichnen, dann warten (blockieren), und ihn dann vielleicht nochmal zeichnen, aber das war's dann wohl...



also: er soll einen Kreis zeichnen, und wenn möglich die Position verändern.
Wenn das nicht geht, dann soll er von mir aus einen Kreis zeichnen, einen weiteren Kreis zeichnen und den alten löschen usw..

Wenn ich die draw Methode in die while Schleife packe, dann passiert folgendes: für 1-2 Sekunden geschieht nichts, danach ist ein Strich nach unten zu sehen.Gegen Ende des Striches erkennt man die einzelnen Kreise, weil ja da die Beschleunigung zu nimmt und somit die Abstände vergrößert werden

Ich will einfach eine Bewegung des Kreises realisieren


Edit: was mir gerade einfällt, ich hab das nicht nur mit einem Button hingekriegt, sondern auch mit einem Kreis, nur dass ich da statt dem canvas einfach Graphics.fillOval() verwendet habe, und das überprüft anscheinend ständig seine Koordinaten.

Hab das mal in den Anhang gepackt. Die Pulsdatei einfach ignorieren, die tut hier nichts zur Sache.
Das ist ebenfalls ohne Threads, funktioniert aber


----------



## Marco13 (29. Feb 2012)

In dem Beispiel wird repaint ja auch von der paint aus aufgerufen - das sollte man ohnehin nicht machen, dadurch zwingt man ihn ja, immer und so schnell wie möglich neu zu zeichnen. Dass die Bewegung in einem anderen Thread gemacht wird, sieht man in diesem Fall nicht: Es ist nämlich der main-Thread (während das Zeichnen im EDT abläuft). Aber... das Beispiel ist ohnehin in Swing, und die eigentliche Frage var auf Android bezogen... Ich kapier's nicht...???:L:bahnhof:


----------



## Robokopp (29. Feb 2012)

Marco13 hat gesagt.:


> In dem Beispiel wird repaint ja auch von der paint aus aufgerufen - das sollte man ohnehin nicht machen, dadurch zwingt man ihn ja, immer und so schnell wie möglich neu zu zeichnen. Dass die Bewegung in einem anderen Thread gemacht wird, sieht man in diesem Fall nicht: Es ist nämlich der main-Thread (während das Zeichnen im EDT abläuft). Aber... das Beispiel ist ohnehin in Swing, und die eigentliche Frage var auf Android bezogen... Ich kapier's nicht...???:L:bahnhof:



Ich versteh nicht wirklich was es da nicht zu kappieren gibt. Ich drück mich ja nicht völlig unverständlich aus.

Also nochmal:

Android:
1.Ich verwende Canvas zum zeichnen.
2.1 Die Bewegung wird nicht dargestellt.Stattdessen sieht man nur den ersten Kreis, weil ich nicht weiß wie ich bei Canvas repainten muss. Und da ich mit validate() nichts erreiche, außer dass mir das ganze Programm abschmiert ist mir damit nicht geholfen.(Das passiert, wenn die draw-Methode außerhalb der while-Schleife liegt)

oder 2.2 Die Bewegung wird nicht dargestellt.Stattdessen sieht man den fertig gezeichneten Ablauf OHNE BEWEGUNG(Dass passiert wenn die draw-Methode innerhalb der While-Schleife liegt)

Normales Beispiel:

ich verwende Swing. Repainten funktioniert ganz normal, das Objekt lässt sich bewegen.

Ich hoffe das ist nun verständlich, denn noch deutlicher kann ich mein Problem nicht beschreiben.


----------



## Marco13 (29. Feb 2012)

Robokopp hat gesagt.:


> 2.1 Die Bewegung wird nicht dargestellt.Stattdessen sieht man nur den ersten Kreis, weil ich nicht weiß wie ich bei Canvas repainten muss. Und da ich mit validate() nichts erreiche, außer dass mir das ganze Programm abschmiert ist mir damit nicht geholfen.


validate gibt es bei canvas nicht. Das wäre dann Swing. Auf einer Android-View gibt es bestenfalls invalidate, was das sein könnte, was du suchst. Wenn man das aber NICHT auf dem UI-Thread (sondern z.B. auf dem Main-Thread) aufruft, kachelt das ganze (so wie es auch in der Doku steht) ab. In so einem Fall: Auf die LogCat schauen... Vermutlich suchst du also postInvalidate, was auch auf dem nicht-UI-Thread aufgerufen werden kann. Was auch in der Doku steht.



> (Das passiert, wenn die draw-Methode außerhalb der while-Schleife liegt)


Oder... umgekehrt... (!? :bahnhof: ?!)



> 2.2 Die Bewegung wird nicht dargestellt.Stattdessen sieht man den fertig gezeichneten Ablauf OHNE BEWEGUNG(Dass passiert wenn die draw-Methode innerhalb der While-Schleife liegt)



Wenn man sich nicht mit der Frage beschäftigt, was auf welchem Thread passiert (und/oder glaubt, Dinge von Swing 1:1 auf Android übertragen zu können), muss man damit rechnen, dass Dinge nicht funktionieren....



> Normales Beispiel:
> 
> ich verwende Swing. Repainten funktioniert ganz normal, das Objekt lässt sich bewegen.



Wenn du in der paint (die man auf JComponents i.a. nicht überschreiben sollte) immer "repaint" aufrufst, ""funktioniert"" es natürlich. Das ändert aber nichts daran, dass das _falsch_ ist. Wenn du auf einen Zettel den Satz schreibst: "Schreib' auf einen Zettel, dass du etwas auf einen Zettel schreiben sollst", und diesen Befehl dann befolgst, wirst du aus dem Schreiben nicht mehr rauskommen. Und genau das machst du dort mit Swing.

Wie auch immer: Das hier ist ein Programm wo man mit einem Button immer neue bunte "Bälle" erzeugen kann, der dann auf den Boden fallen und ein bißchen rumhüpfen. Mit dem Finger kann man sich die Bälle schnappen und ein bißchen in der Gegend rumschnicken.  Garantiert kommentarfrei :smoke: 


```
package de.javagl.android.ball;

import java.util.Random;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.LinearLayout;

public class BallAnimationActivity extends Activity 
{
    private BallsView ballsView;
    private BallsAnimator ballsAnimator;
    private Ball currentBall = null;
    
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        
        LinearLayout mainLayout = new LinearLayout(this);
        mainLayout.setOrientation(LinearLayout.VERTICAL);
        
        LinearLayout buttonLayout = new LinearLayout(this);
        Button button = new Button(this);
        button.setText("New Ball");

        button.setOnClickListener(new View.OnClickListener()
        {
            final Random random = new Random(0);

            @Override
            public void onClick(View v)
            {
            	float x = random.nextInt(ballsView.getWidth());
            	float y = ballsView.getHeight();
            	float radius = 20 + random.nextInt(20);
            	int c = 255;
        		int cr = random.nextInt(c);
        		c-=cr;
        		int cg = random.nextInt(c);
        		c-=cg;
        		int cb = random.nextInt(c);
        		int color = Color.argb(255, cr, cg, cb);
                Ball ball = new Ball(x, y, radius, color);
                ballsAnimator.addBall(ball);
                ballsView.addBall(ball);
            }
        });
        buttonLayout.addView(button);
        mainLayout.addView(buttonLayout);

        ballsView = new BallsView(this);
        ballsView.setOnTouchListener(new TouchListener());

        ballsAnimator = new BallsAnimator(new BoundsSource() 
        {
			@Override
			public int getWidth() 
			{
				return ballsView.getWidth();
			}
			
			@Override
			public int getHeight() 
			{
				return ballsView.getHeight();
			}
		});
        startAnimation();
        
        mainLayout.addView(ballsView);
        setContentView(mainLayout);
    }
    
    private void startAnimation()
    {
        Thread thread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                while (true)
                {
                    float stepSize = 0.1f;
                    ballsAnimator.doStep(stepSize);
                    ballsView.postInvalidate();
                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (InterruptedException e)
                    {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    	
    }
    
    
    class TouchListener implements OnTouchListener
    {
    	private float previousX = 0;
    	private float previousY = 0;
    	private float lastMoveX = 0;
    	private float lastMoveY = 0;

        @Override
        public boolean onTouch(View v, MotionEvent event) 
        {
            int pointerCount = event.getPointerCount();
            if (pointerCount == 1)
            {
                int pointerIndex = 0;
                float currentX = event.getX(pointerIndex);
                float currentY = ballsView.getHeight()-event.getY(pointerIndex);

                //System.out.println("Action "+event.getAction()+" at "+currentX+" "+currentY);
                if (event.getAction() == MotionEvent.ACTION_DOWN)
                {
                	currentBall = null;
                    for (Ball ball : ballsAnimator.getBalls())
                    {
                        float x = ball.getX();
                        float y = ball.getY();
                        float dx = x - currentX;
                        float dy = y - currentY;
                        float dist = (float)Math.sqrt(dx*dx+dy*dy);
                        if (dist < ball.getRadius())
                        {
                            currentBall = ball;
                            break;
                        }
                    }
                    ballsAnimator.setFixedBall(currentBall);
                    previousX = currentX;
                    previousY = currentY;
                }
                else if (event.getAction() == MotionEvent.ACTION_MOVE)
                {
                    if (currentBall != null)
                    {
                        currentBall.setX(currentX);
                        currentBall.setY(currentY);
                        lastMoveX = previousX;
                        lastMoveY = previousY;
                        previousX = currentX;
                        previousY = currentY;
                    }
                }
                else if (event.getAction() == MotionEvent.ACTION_UP)
                {
                    ballsAnimator.setFixedBall(null);
                    if (currentBall != null)
                    {
	                    float dx = currentX - lastMoveX;
	                    float dy = currentY - lastMoveY;
	                    currentBall.setVelocityX(dx);
	                    currentBall.setVelocityY(dy);
	                	currentBall = null;
                    }
                    previousX = currentX;
                    previousY = currentY;
                }
                
            }
            
            return true;
        }
    }
    
}
```


```
package de.javagl.android.ball;
 
public class Ball 
{
    private float x = 0;
    private float y = 0;
    private float velocityX = 0;
    private float velocityY = 0;
    private float accelerationX = 0;
    private float accelerationY = 0;
    private float radius;
    private int color;
    
    public Ball(float x, float y, float radius, int color)
    {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.color = color;
    }
    
    public int getColor()
    {
    	return color;
    }

    public float getX()
    {
        return x;
    }
    public void setX(float x)
    {
        this.x = x;
    }

    public float getY()
    {
        return y;
    }
    public void setY(float y)
    {
        this.y = y;
    }

    public float getVelocityX()
    {
        return velocityX;
    }
    public void setVelocityX(float velocityX)
    {
        this.velocityX = velocityX;
    }

    public float getVelocityY()
    {
        return velocityY;
    }
    public void setVelocityY(float velocityY)
    {
        this.velocityY = velocityY;
    }
    
    public float getAccelerationX()
    {
        return accelerationX;
    }
    public void setAccelerationX(float accelerationX)
    {
        this.accelerationX = accelerationX;
    }

    public float getAccelerationY()
    {
        return accelerationY;
    }
    public void setAccelerationY(float accelerationY)
    {
        this.accelerationY = accelerationY;
    }
    
    public float getRadius()
    {
        return radius;
    }
    
    @Override
    public String toString()
    {
        return String.format("Ball[p=(%.2f,%.2f),v=(%.2f,%.2f),a=(%.2f,%.2f)]",
            x, y, velocityX, velocityY, accelerationX, accelerationY); 
    }
    
    
}
```


```
package de.javagl.android.ball;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class BallsAnimator
{
    private static final float GRAVITY_Y = -9.81f;
    
    private BoundsSource boundsSource;
    private List<Ball> balls = new CopyOnWriteArrayList<Ball>();
    private Ball fixedBall = null;
    
    public BallsAnimator(BoundsSource boundsSource)
    {
    	this.boundsSource = boundsSource;
    }
    
    public void setFixedBall(Ball fixedBall)
    {
    	this.fixedBall = fixedBall;
    }
    
    public void addBall(Ball ball)
    {
        balls.add(ball);
    }
    
    public List<Ball> getBalls()
    {
        return Collections.unmodifiableList(new ArrayList<Ball>(balls));
    }
    
    public void doStep(float t)
    {
        updateAccelerations();
        updateVelocities(t);
        updatePositions(t);
    }

    private void updateAccelerations()
    {
        for (Ball ball : balls)
        {
            ball.setAccelerationX(0);
            ball.setAccelerationY(GRAVITY_Y);
        }
    }
    
    private void updateVelocities(float t)
    {
        for (Ball ball : balls)
        {
        	if (ball == fixedBall)
        	{
                ball.setVelocityX(0);
                ball.setVelocityY(0);
        		continue;
        	}
        	float vx = ball.getVelocityX() + t * ball.getAccelerationX();
        	float vy = ball.getVelocityY() + t * ball.getAccelerationY();
        	vx *= 0.995;
        	vy *= 0.995;
            ball.setVelocityX(vx);
            ball.setVelocityY(vy);
        }
    }
    
    private void updatePositions(float t)
    {
    	int w = boundsSource.getWidth();
    	int h = boundsSource.getHeight();
        for (Ball ball : balls)
        {
        	if (ball == fixedBall)
        	{
        		continue;
        	}
        	float x = ball.getX() + t * ball.getVelocityX();
        	float y = ball.getY() + t * ball.getVelocityY();
        	float r = ball.getRadius();
        	float vy = ball.getVelocityY();
        	if (x < 0)
        	{
        		x += w;
        	}
        	if (x >= w)
        	{
        		x -= w;
        	}
        	if (y < r)
        	{
        		y = r + (r-y);
        		vy = -vy;
        	}
        	if (y >= h)
        	{
        		y = h-(y-h);
        		vy = -vy;
        	}
        	
            ball.setX(x);
            ball.setY(y);
            ball.setVelocityY(vy);
            
        }
    }
    
    
}
```


```
package de.javagl.android.ball;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;

public class BallsView extends View {
    
    private List<Ball> balls = new CopyOnWriteArrayList<Ball>();

    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
    public BallsView(Context context) 
    {
        super(context);        
    }
    
    public void addBall(Ball ball)
    {
        balls.add(ball);
        invalidate();
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (Ball ball : balls)
        {
            paint.setColor(ball.getColor());
            canvas.drawCircle(
                ball.getX(),
                getHeight()-ball.getY(), 
                ball.getRadius(), 
                paint);
        }
    }   
}
```


```
package de.javagl.android.ball;

public interface BoundsSource 
{
	int getWidth();
	int getHeight();
}
```

(BTW: Ich habe auch keine Ahung von Android, d.h. es kann sein dass da einiges grober Unfug ist... aber er zeichnet zumindest bewegte Kreise...)


----------



## Spacerat (29. Feb 2012)

Innerhalb von "onDraw()" darfst du nur einen einzigen Frame einer Animation zeichnen, weil der Grafikpuffer erst beim Verlassen der Methode zur Anzeige kommt. "onDraw()" wird auch nur einmal beim hinzufügen des Canvas auf die Oberflache aufgerufen und ist eher mit dem EventSystem in JavaScript, als dem in Java selbst vergleichbar. Bei Animationen kommt man deswegen in Android um Threads nicht herum, es gibt glaub' ich sogar einen speziellen AnimThread für solche Zwecke. Schau dir diesbezüglich besser erst mal einige Tuts des Android-SDKs an.


----------



## Robokopp (1. Mrz 2012)

Ok vielen Danke Leute 
Werd mich da noch ein wenig einarbeiten müssen


----------



## Marco13 (1. Mrz 2012)

BTW: Eine Sache, von der ich schon "weiß", dass sie ... naja, nicht "grober Unfug", aber doch... "suboptimal" ist, ist die Verwendung der 
for (Object element : list)
foreach-Schleifen. Das sollte man bei Android, speziell bei Spielen und anderen Zeitrkitischen Anwendungen, vermeiden.


----------



## Fu3L (1. Mrz 2012)

Magst du erläutern/verlinken warum? Ich weiß zwar mitlerweile, warum man in C++ lieber ++x machen sollte als x++, aber das ist mir neu^^


----------



## Marco13 (1. Mrz 2012)

Weil bei jedem
for (Object object : list)
ein Iterator-Objekt erzeugt wird. Android (bzw. die Dalvik VM) ist da an manchen Stellen etwas empfindlicher (sieht man ja auch in dem (oder der?) LogCat *miau* : "GC freed 10000000 Objects in 250ms"). Laut einem Vortrag (ich glaube sogar von einem der Dalvik-Entwicker) über die Designentscheidungen und Performance (speziell in Bezug auf Spiele) bei der Dalivik-VM sollte man am besten GAR keinen Speicher allokieren  und bestimmte Dinge, die bei Java "Best practices" sind (Kapselung mit gettern/settern, Listen statt Arrays etc etc) können bei der Dalvik offenbar stärkere negative Auswirkungen haben (nochmal: Speziell bei zeitkritischen Dingen wie Spielen, wo ein Stocken von >100ms kaum akzeptabel ist). Aber diese Aussage ist nur durch das fundiert, was der Mensch da in dem Vortrag gesagt hat, und nicht durch eigene Tests und Benchmarks. Man kann erstens davon ausgehen, dass das in den meisten Anwendungen irrelevant ist, und zweitens die Dalivik VM und ihr GC in Zukunft schneller werden - und sei es nur durch die 3GHz-QuadCores, die sie inzwischen in die Tablets stecken


----------



## Marco13 (1. Mrz 2012)

Hab nochmal geschaut, es war in Google I/O 2009 - Writing Real-Time Games for Android - YouTube - Ab ca. Minute 20, 25 redet er einleitend, und bei 29:30 sagt er: "Never allocate memory" (die Iteratoren erwähnt er AFAIR auf der Tonspur), später dann noch "Never call functions" usw. ... (Das "never" natürlich mit einem  "  ", aber der Grundtenor ist klar: Mach' es der Dalvik nicht so schwer...


----------



## Fu3L (2. Mrz 2012)

Ok, danke^^ Also im Prinzip auch das, was ein C++ Game Engine Programmierer sagen würde


----------

