Tillbaka till lektionslistan

Mobila applikationer med Android: Lektion 5

Tillägg 13 september 2015:

I den här lektionen används en äldre version av Android, och den gamla utvecklingsmiljön, Eclipse. Det kommer så småningom en uppdaterad lektion, men läs tills vidare den här.

Den nya utvecklingsmiljön Android Studio ser ganska annorlunda ut, men mycket är ytliga skillnader, medan principerna är desamma.

För mer information, och aktuella instruktioner, bör man läsa på Googles Android-webbplats: developer.android.com.

Idag: Grafik och programmet "Simple Bouncing Ball"
  • Grafik
  • Canvas
  • Rörlig grafik

Klicka på startknappen i den lilla mediaspelaren ovan för att lyssna på lektionen. (Man kan behöva vänta en stund på att ljudfilen laddas ner.) Om mediaspelaren inte syns, eller om det inte fungerar av något annat skäl, kan man klicka här för att ladda ner mp3-filen (ca 16 minuter, ca 7 megabyte). Beroende på hur webbläsaren är konfigurerad kan det kräva ett separat mp3-spelarprogram av något slag.

Bild 1: Skapa projektet

Skapa projektet

Bild 2: Den automatgenererade aktiviteten

default-aktiviteten

"Bild" 3: Koden för den automatgenererade aktiviteten

Klassen SimpleBouncingBallActivity

package se.nekotronic.simpleball;

import android.app.Activity;
import android.os.Bundle;

public class SimpleBouncingBallActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Bild 4: Skapa en ny vy-klass

Skapa en ny vy-klass

"Bild" 5: Koden för den nya vy-klassen

BounceView.java

    1   package se.nekotronic.simpleball;

Vi hoppar över lite...

   14   public class BounceView extends View {
   15       // Ball position, measured in SCREENS. Starts in the middle.
   16       private float ball_x = 0.5f;
   17       private float ball_y = 0.5f;
   18       // Ball movement, measured in SCREENS PER SECOND
   19       private float ball_dx = 0.1f;
   20       private float ball_dy = 0.05f;
   21       // Measured in pixels
   22       private int ball_radius = 10;

Och så vidare...

Bild 6: Default-layouten grafiskt

Den automatgenererade layouten

"Bild" 7: Default-layouten som XML

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>

"Bild" 8: Ändra layouten

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<se.nekotronic.simpleball.BounceView
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:id="@+id/bounceview"
    />
</LinearLayout>

Bild 9: Den nya vyn visas

Men den står still!

Studsbollen, men den står still

"Bild" 10: Den nya aktiviteten

SimpleBouncingBallActivity.java

    1   package se.nekotronic.simpleball;
    2   
    3   import android.app.Activity;
    4   import android.os.Bundle;
    5   
    6   public class SimpleBouncingBallActivity extends Activity {
    7       private BounceView the_view;
    8   
    9       /** Called when the activity is first created. */
   10       @Override
   11       public void onCreate(Bundle savedInstanceState) {
   12           super.onCreate(savedInstanceState);
   13           setContentView(R.layout.main);
   14           the_view = (BounceView)findViewById(R.id.bounceview);
   15       }
   16       
   17       @Override
   18       protected void onResume() {
   19           super.onResume();
   20           // App is now visible (perhaps: again). Tell the view to start moving.
   21           the_view.resume();
   22       }
   23       
   24       @Override
   25       protected void onPause() {
   26           super.onPause();
   27           // App has been hidden. Tell the view to stop moving.
   28           the_view.pause();
   29       }
   30   }

Bild 11: Nu funkar det

Studsbollen, och den rör sig

"Bild" 12: Koden för BounceView-klassen

BounceView.java

Ett tillägg våren 2012 (inte med på ljudspåret):
I nyare versioner av Android-miljön kan det hända att run-metoden inte ska deklareras med @Override. Om det ger kompileringsfel, så prova att ta bort @Override.

    1   package se.nekotronic.simpleball;
    2   
    3   import java.util.Timer;
    4   import java.util.TimerTask;
    5   
    6   import android.content.Context;
    7   import android.graphics.Canvas;
    8   import android.graphics.Color;
    9   import android.graphics.Paint;
   10   import android.util.AttributeSet;
   11   import android.util.Log;
   12   import android.view.View;
   13   
   14   public class BounceView extends View {
   15       // Ball position, measured in SCREENS. Starts in the middle.
   16       private float ball_x = 0.5f;
   17       private float ball_y = 0.5f;
   18       // Ball movement, measured in SCREENS PER SECOND
   19       private float ball_dx = 0.1f;
   20       private float ball_dy = 0.05f;
   21       // Measured in pixels
   22       private int ball_radius = 10;
   23           
   24       private int width_pixels;
   25       private int height_pixels;
   26           
   27       private Timer the_ticker;
   28           
   29       public BounceView(Context context) {
   30           super(context);
   31       }
   32           
   33       public BounceView(Context context, AttributeSet attrs) {
   34           super(context, attrs);
   35       }
   36   
   37       @Override
   38           protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   39           super.onSizeChanged(w, h, oldw, oldh);
   40           width_pixels = w;
   41           height_pixels = h;
   42       }
   43   
   44       public static final double roundDouble(double d, int places) {
   45           return Math.round(d * Math.pow(10, (double)places)) / Math.pow(10, (double)places);
   46       }
   47           
   48       private int frames = 0;
   49           
   50       @Override
   51           protected void onDraw(Canvas canvas) {
   52           super.onDraw(canvas);
   53           Paint p = new Paint();
   54           int w = width_pixels > 0 ? width_pixels : canvas.getHeight();
   55           int h = height_pixels > 0 ? height_pixels : canvas.getWidth();
   56           
   57           // The ball is green when in the crosshairs
   58           if (Math.sqrt(Math.pow(ball_x * w - w/2.0f, 2) + Math.pow(ball_y * h - h/2.0f, 2)) < ball_radius)
   59               p.setColor(Color.GREEN);
   60           else
   61               p.setColor(Color.BLUE);
   62           canvas.drawCircle(ball_x * w, ball_y * h, ball_radius, p);
   63   
   64           p.setColor(Color.RED);
   65           canvas.drawText("x = " + String.format("%.2f", ball_x) +
   66                           ", y = " + String.format("%.2f", ball_y) +
   67                           ", dx = " + String.format("%.2f", ball_dx) +
   68                           ", dy = " + String.format("%.2f", ball_dy),
   69                           20.0f, 20.0f, p);
   70           canvas.drawText("w = " + w + " (" + width_pixels + ")" +
   71                           ", h = " + h + " (" + height_pixels + ")",
   72                           20.0f, 40.0f, p);
   73           ++frames;
   74           canvas.drawText("frames = " + frames,
   75                           20.0f, 60.0f, p);
   76           
   77           // A crosshair at the middle of the screen
   78           float crosswidth = Math.min(w, h) * 0.1f;
   79           canvas.drawLine(w * 0.5f - crosswidth, h * 0.5f, w * 0.5f + crosswidth, h * 0.5f, p);
   80           canvas.drawLine(w * 0.5f, h * 0.5f - crosswidth, w * 0.5f, h * 0.5f + crosswidth, p);
   81           
   82           canvas.drawText("basen.oru.se/android",
   83                           20.0f, h - 5.0f, p);
   84       } // onDraw
   85   
   86       // Called when the app is visible(possibly: again). We should start moving.
   87       public void resume() {
   88           the_ticker = new Timer();
   89           TimerTask task = new TimerTask() {
   90                   @Override
   91                       public void run() {
   92                       update_simulation();
   93                       // We get CalledFromWrongThreadException if we just just call invalidate().
   94                       // It must be done in the right thread!
   95                       Runnable r = new Runnable() {
   96                               @Override
   97                                   public void run() {
   98                                   invalidate();
   99                               }
  100                           };
  101                       getHandler().post(r);
  102                   }
  103               };
  104           // Give it a full second to set things up, before we start ticking
  105           // (It crashed with java.lang.NullPointerException when starting after 30 ms, but worked with 40.)
  106           the_ticker.schedule(task, 1000, 10);
  107       } // resume
  108   
  109       // Called when the app has been hidden. We should stop moving.
  110       public void pause() {
  111           nanos_when_paused = java.lang.System.nanoTime(); 
  112           the_ticker.cancel();
  113           the_ticker = null;
  114       }
  115           
  116       // -1 is not guaranteed to never happen, but we ignore that 
  117       private long previous_nanos = -1;
  118       private long nanos_when_paused = -1;
  119   
  120       // Calculate the ball's new position and speed
  121       private void update_simulation() {
  122           long now_nanos = java.lang.System.nanoTime();
  123           if (previous_nanos == -1 || now_nanos < previous_nanos) {
  124               // First time, or overflow, so don't update the game
  125               previous_nanos = now_nanos; 
  126               return;
  127           }
  128   
  129           long nanos = now_nanos - previous_nanos; 
  130           if (nanos_when_paused != -1) {
  131               // We have been paused!
  132               nanos = nanos_when_paused - previous_nanos;
  133               nanos_when_paused = -1;
  134           }
  135   
  136           previous_nanos = now_nanos;
  137           double seconds = nanos / 1e9;
  138           
  139           ball_x += ball_dx * seconds;
  140           ball_y += ball_dy * seconds;
  141   
  142           // Yes, this ignores that the ball has a radius.
  143           
  144           if (ball_x < 0) {
  145               Log.d("Bounce", "Bounce on left wall");
  146               ball_x = -ball_x;
  147               ball_dx = -ball_dx;
  148           }
  149           if (ball_x > 1) {
  150               Log.d("Bounce", "Bounce on right wall");
  151               ball_x = 2 - ball_x;
  152               ball_dx = -ball_dx;
  153           }
  154           if (ball_y < 0) {
  155               Log.d("Bounce", "Bounce on ceiling");
  156               ball_y = -ball_y;
  157               ball_dy = -ball_dy;
  158           }
  159           if (ball_y > 1) {
  160               Log.d("Bounce", "Bounce on floor");
  161               ball_y = 2 - ball_y;
  162               ball_dy = -ball_dy;
  163           }        
  164       } // update_simulation      
  165   } // class BounceView

Ett tillägg våren 2012 (inte med på ljudspåret):
I nyare versioner av Android-miljön kan det hända att run-metoden inte ska deklareras med @Override. Om det ger kompileringsfel, så prova att ta bort @Override.

Övningar

Tillbaka till lektionslistan


Thomas Padron-McCarthy (thomas.padron-mccarthy@oru.se), 1 oktober 2015