Ciao a tutti. Ecco la mia implementazione del tutorial. Ovviamente non è perfetta, quindi se qualcuno ha dei miglioramenti dica pure
Una nota: in questo tutorial si usano dei metodi che sono stati introdotti solo dalla versione Honeycomb (API level 11), per cui è necessario creare un progetto nel cui manifest sia impostato un minSdk con valore almeno pari a 11.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sfere"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="15" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
PREMESSA: Dal momento che in android non esiste un metodo simile a "scheduledTimerWithTimeInterval" (di Objective-C), e dato che solo il main-thread può intervenire sull'interfaccia grafica (per questo è spesso detto UI-Thread), questo tutorial risulterà più complesso rispetto a quello per l'objective-C. L'idea di base, comunque, è la stessa, ovvero richiamare un metodo ogni tot di tempo.
Come si può vedere dal manifest postato precedentemente, tutti i miei file stanno all'interno del package com.example.sfere.
Creiamo quindi un file xml di layout che, per semplicità, contiene solo il layout e l'immagine da muovere. Chiamiamolo main.
Il contenuto di questo file sarà:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
an[code]
droid:layout_height="match_parent"
android:id="@+id/main_layout" >
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/image"
android:src="@drawable/ic_launcher"/>
</FrameLayout>[/code]
A questo punto possiamo occuparci dell'activity principale. Come già detto, quello che vogliamo fare è spostare l'immagine ogni tot secondi (in questo caso ogni decimo di secondo).
Possiamo quindi creare un oggetto Timer e il relativo TimerTask, in cui il primo ci permette di pianificare delle operazioni, mentre il secondo specifica l'operazione da eseguire. La specializzazione della classe TimerTask (così come tutte le classi personalizzate che vedremo successivamente) è stata creata come classe privata all'interno della MainActivity per semplicità.
package com.example.sfere;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.FrameLayout;
import android.widget.ImageView;
public class MainActivity extends Activity {
private ImageView img;
private Timer timer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
img = (ImageView) findViewById(R.id.image);
timer = new Timer();
}
@Override
protected void onResume() {
super.onResume();
// Inizio a muovere l'immagine
timer.scheduleAtFixedRate(new MyTask(), 0L, 100L);
}
@Override
protected void onPause() {
super.onPause();
// Fermo il timer
timer.cancel();
}
private class MyTask extends TimerTask {
@Override
public void run() {
//Qua vanno le operazioni da eseguire
}
}
}
E' importante notare che le operazioni eseguite all'interno del TimerTask si trovano in un thread differente dall'UI-Thread, quindi non è possibile muovere l'immagine direttamente all'interno di esso. Quello che bisogna fare è "informare" l'UI-Thread di spostare questa immagine. Un possibile modo di effettuare questa comunicazione è tramite gli handler e i message, un meccanismo di comunicazione fondamentale in Android.
Praticamente ad ogni thread è associata una mail box, dalla quale preleva ciclicamente gli eventuali messaggi presenti, i quali vengono gestiti dagli handler. Andando a creare una specializzazione della classe Handler, quindi, è possibile attribuire ad una determinata tipologia di messaggi un comportamento desiderato (attraverso il metodo handleMessage). Per tornare al nostro esempio, vogliamo associare a determinati messaggi la corrispondente azione di spostare l'immagine.
Per fare ciò, quindi, creiamo una sottoclasse di Handler. Come già detto, anche la classe MyHandler è interna a MainActivity.
private class MyHandler extends Handler {
private float movement = 5F; // 5 pixel
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MOVE: //MSG_MOVE è una costante definita in MainActivity
moveImage();
break;
default:
super.handleMessage(msg);
break;
}
}
private void moveImage() {
float curX = img.getX(); // Valido per API Level > 11
// Controllo se l'immagine esce dallo schermo
FrameLayout layout = (FrameLayout) findViewById(R.id.main_layout);
int width = layout.getWidth();
if ((curX + img.getWidth()) > width || curX < 0) {
movement = -movement;
}
img.setX(curX + movement);
}
}
Andiamo quindi a modificare la classe TimerTask per darle il comportamento desiderato (ovvero inviare un messaggio all'handler):
private class MyTask extends TimerTask {
private Handler handler = new MyHandler();
@Override
public void run() {
Message msg = handler.obtainMessage(MSG_MOVE);
handler.sendMessage(msg);
}
}
Dove MSG_MOVE è una costante definita nella classe MainActivity:
public class MainActivity extends Activity {
private ImageView img;
private Timer timer;
public static final int MSG_MOVE = 1;
....
}
A questo punto non ci resta che avviare l'applicazione e vedere il risultato:
Per completezza, il codice completo è questo:
package com.example.sfere;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.FrameLayout;
import android.widget.ImageView;
public class MainActivity extends Activity {
private ImageView img;
private Timer timer;
public static final int MSG_MOVE = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
img = (ImageView) findViewById(R.id.image);
timer = new Timer();
}
@Override
protected void onResume() {
super.onResume();
// Inizio a muovere l'immagine
timer.scheduleAtFixedRate(new MyTask(), 0L, 100L);
}
@Override
protected void onPause() {
super.onPause();
// Fermo il timer
timer.cancel();
}
private class MyTask extends TimerTask {
private Handler handler = new MyHandler();
@Override
public void run() {
Message msg = handler.obtainMessage(MSG_MOVE);
handler.sendMessage(msg);
}
}
private class MyHandler extends Handler {
private float movement = 5F; // 10 pixel
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MOVE:
moveImage();
break;
default:
super.dispatchMessage(msg);
break;
}
}
private void moveImage() {
float curX = img.getX(); // Valido per API Level > 11
// Controllo se l'immagine esce dallo schermo
FrameLayout layout = (FrameLayout) findViewById(R.id.main_layout);
int width = layout.getWidth();
if ((curX + img.getWidth()) > width || curX < 0) {
movement = -movement;
}
img.setX(curX + movement);
}
}
}