JavascriptProva

martedì 5 gennaio 2016

Grafica con Canvas in Android.

A questo punto è il momento di ripassare canvas e compagnia, che ho iniziato a studiare proprio ieri.
Se ricordo bene, la Bitmap è la tela, il Canvas è l'immagine, il Paint è il pennello.
Provo a buttare giù un codice...

Ecco, per creare la Bitmap, cosa che non ho mai fatto, si usa un codice Bitmap.createBitmap.
In precedenza, ho creato una bitmap da un file, ma potrei anche dover creare una bitmap vuota per disegnarci sopra col canvas. Ecco, appunto:
Bitmap bmp= Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);
Il canvas va costruito sulla tela!
  Bitmap bmp= Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);
  Canvas canvas=new Canvas(bmp);
Invece il pennello si crea di per sé: il pennello è qualcosa di staccato dall'immagine e dalla tela.
  Bitmap bmp= Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);
  Canvas canvas=new Canvas(bmp);
  Paint paint=new Paint();
Ora intingiamo il pennello sulla tavolozza:
  Bitmap bmp= Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);
  Canvas canvas=new Canvas(bmp);
  Paint paint=new Paint();
  paint.setColor(Color.GREEN);
E ora possiamo disegnare...

  Bitmap bmp= Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);
  Canvas canvas=new Canvas(bmp);
  Paint paint=new Paint();
  paint.setColor(Color.GREEN);
  canvas.drawRect(20, 20,40,20,paint);
...e non succede niente!
Ma abbiamo dimenticato di appendere il quadro!
Proviamo...

Per far questo dobbiamo prima identificare la parete, quindi appendere.
Provo a fare così:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/parete"
    tools:context="com.example.grafica.MainActivity" >
...identifichiamo quindi lo sfondo:
  Bitmap bmp= Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);
  Canvas canvas=new Canvas(bmp);
  Paint paint=new Paint();
  paint.setColor(Color.GREEN);
  canvas.drawRect(20, 20,40,20,paint);
  
  RelativeLayout parete=(RelativeLayout)findViewById(R.id.parete);
...e ora appendiamo il quadro:
  Bitmap bmp= Bitmap.createBitmap(400,400,Bitmap.Config.ARGB_8888);
  Canvas canvas=new Canvas(bmp);
  Paint paint=new Paint();
  paint.setColor(Color.GREEN);
  canvas.drawRect(20, 20,40,20,paint);
  
  RelativeLayout parete=(RelativeLayout)findViewById(R.id.parete);
  parete.setBackgroundDrawable(new BitmapDrawable(bmp));
...e non succede assolutamente nulla!

Forse devo fare la bitmap un po' più grande, come nell'esempio che sto seguendo e interpretando liberamente...

  Bitmap bmp= Bitmap.createBitmap(400,800,Bitmap.Config.ARGB_8888);
  Canvas canvas=new Canvas(bmp);
  Paint paint=new Paint();
  paint.setColor(Color.GREEN);
  canvas.drawRect(50,50,200,200,paint);
  
  RelativeLayout parete=(RelativeLayout)findViewById(R.id.parete);
  parete.setBackgroundDrawable(new BitmapDrawable(bmp));
Ecco, sì, ce l'ho fatta!



Se tolgo tutta quella monnezza da studi precedenti, magari si visualizza anche la bitmap minuscola che avevo creato? Proviamo...

  Bitmap bmp= Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);
  Canvas canvas=new Canvas(bmp);
  Paint paint=new Paint();
  paint.setColor(Color.GREEN);
  canvas.drawRect(20, 20,40,20,paint);
  
  RelativeLayout parete=(RelativeLayout)findViewById(R.id.parete);
  parete.setBackgroundDrawable(new BitmapDrawable(bmp));
No. E' inutile che incollo un'immagine in cui non c'è nulla.
Dubbio: le dimensioni che ho impostato per il rettangolo sono 200 e 200, ossia doveva venire un quadrato! Perché invece è un rettangolo?


Ho risolto!
Evidentemente è dovuto al fatto che le dimensioni della bitmap vengono modellate sulle dimensioni del RelativeLayout, e quindi vengono "stirate". Forse, ho pensato, la soluzione sta nel creare una bitmap delle stesse dimensioni del Layout, ma come fare a individuarle?
Inizialmente ho trovato la soluzione parete.getWidth() e parete.getHeight(), però non ottenevo risultati: richiedendo il valore di queste grandezze sul LogCat ottenevo valori zero.
Poi ho trovato questa pagina sul mitico StackOverflow e ho risolto: evidentemente le misure del layout vengono prese solo all'evento onWindowFocusChanged.
 public void updateSizeInfo(){
  RelativeLayout parete=(RelativeLayout)findViewById(R.id.parete);
  h=parete.getHeight();
  w=parete.getWidth();
  Bitmap bmp=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
  Canvas canvas=new Canvas(bmp);
  Paint paint=new Paint();
  paint.setColor(Color.RED);
  canvas.drawRect(50,50,200,200,paint);
  parete.setBackground(new BitmapDrawable(bmp));
  
 }
 
 public void onWindowFocusChanged(boolean hasfocus){
  updateSizeInfo();
 }
Sono metodi separati dal metodo OnCreate.
Ottengo un quadrato rosso che resta tale anche orientando l'emulatore in modalità Portrait.
Dunque l'evento onWindowFocusChanged recepisce il cambiamento di visualizzazione?
Vediamo di sperimentarlo...

 public void onWindowFocusChanged(boolean hasfocus){
  Log.d("EVENTO","ONWINDOWFOCUSCHANGED");
 }
Ecco il LogCat:
01-04 22:24:14.807: D/EVENTO(4894): ONWINDOWFOCUSCHANGED
01-04 22:25:08.878: D/PAUSE(4894): ONPAUSE
01-04 22:25:08.878: D/STOP(4894): ONSTOP
01-04 22:25:08.928: D/CREATE(4894): ONCREATE
01-04 22:25:08.978: D/START(4894): ONSTART
01-04 22:25:08.978: D/RESUME(4894): ONRESUME
01-04 22:25:09.008: I/Choreographer(4894): Skipped 30 frames!  The application may be doing too much work on its main thread.
01-04 22:25:09.038: D/EVENTO(4894): ONWINDOWFOCUSCHANGED
01-04 22:25:25.238: D/PAUSE(4894): ONPAUSE
01-04 22:25:25.238: D/STOP(4894): ONSTOP
01-04 22:25:25.288: D/CREATE(4894): ONCREATE
01-04 22:25:25.358: D/START(4894): ONSTART
01-04 22:25:25.358: D/RESUME(4894): ONRESUME
01-04 22:25:25.378: I/Choreographer(4894): Skipped 34 frames!  The application may be doing too much work on its main thread.
01-04 22:25:25.448: D/EVENTO(4894): ONWINDOWFOCUSCHANGED
01-04 22:25:33.199: D/PAUSE(4894): ONPAUSE
01-04 22:25:33.199: D/STOP(4894): ONSTOP
01-04 22:25:33.259: D/CREATE(4894): ONCREATE
01-04 22:25:33.269: D/START(4894): ONSTART
01-04 22:25:33.269: D/RESUME(4894): ONRESUME
01-04 22:25:33.339: D/EVENTO(4894): ONWINDOWFOCUSCHANGED
Sì: A ogni movimento dell'emulatore corrisponde un Pause, uno Stop, poi un OnCreate, un OnStart e un OnResume.
Manca OnDestroy, ma mi sono accorto adesso che non avevo messo il codice che ricevesse e mostrasse l'evento.
Lo metto adesso:
 @Override
 protected void onDestroy(){
  super.onDestroy();
  Log.d("DESTROY","ONDESTROY");
 }
E riproviamo!

01-04 22:30:11.403: D/EVENTO(4941): ONWINDOWFOCUSCHANGED
01-04 22:30:11.583: I/Choreographer(4941): Skipped 43 frames!  The application may be doing too much work on its main thread.
01-04 22:30:11.623: D/gralloc_goldfish(4941): Emulator without GPU emulation detected.
01-04 22:30:11.663: D/PAUSE(4941): ONPAUSE
01-04 22:30:11.663: D/STOP(4941): ONSTOP
01-04 22:30:11.663: D/DESTROY(4941): ONDESTROY
01-04 22:30:11.673: D/CREATE(4941): ONCREATE
01-04 22:30:11.723: D/START(4941): ONSTART
01-04 22:30:11.723: D/RESUME(4941): ONRESUME
01-04 22:30:11.833: D/EVENTO(4941): ONWINDOWFOCUSCHANGED
01-04 22:31:05.364: D/PAUSE(4941): ONPAUSE
01-04 22:31:05.364: D/STOP(4941): ONSTOP
01-04 22:31:05.364: D/DESTROY(4941): ONDESTROY
01-04 22:31:05.364: D/CREATE(4941): ONCREATE
01-04 22:31:05.394: D/START(4941): ONSTART
01-04 22:31:05.394: D/RESUME(4941): ONRESUME
01-04 22:31:05.484: D/EVENTO(4941): ONWINDOWFOCUSCHANGED
01-04 22:31:11.634: D/PAUSE(4941): ONPAUSE
01-04 22:31:11.634: D/STOP(4941): ONSTOP
01-04 22:31:11.634: D/DESTROY(4941): ONDESTROY
01-04 22:31:11.694: D/CREATE(4941): ONCREATE
01-04 22:31:11.764: D/START(4941): ONSTART
01-04 22:31:11.764: D/RESUME(4941): ONRESUME
01-04 22:31:11.784: I/Choreographer(4941): Skipped 38 frames!  The application may be doing too much work on its main thread.
01-04 22:31:11.854: D/EVENTO(4941): ONWINDOWFOCUSCHANGED
E infatti...

Okay! Abbiamo risolto!
Adesso, per focalizzare meglio la cosa, rifaccio tutto con una nomenclatura metaforica che aiuti a memorizzare:
 public void Disegna(){
  Bitmap tela=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
  Canvas dipinto=new Canvas(tela);
  Paint pennello=new Paint();
  
  pennello.setColor(Color.CYAN);
  dipinto.drawCircle(100, 100, 200, pennello);
  
  RelativeLayout muro=(RelativeLayout)findViewById(R.id.muro);
  muro.setBackground(new BitmapDrawable(tela));
 }
 
 public void onWindowFocusChanged(boolean hasfocus){
  Disegna();
 }
Un piffero!!! Non ho impostato le variabili w e h ai valori di larghezza e altezza del muro!

Riproviamo...
 public void Disegna(){
  RelativeLayout muro=(RelativeLayout)findViewById(R.id.muro);
  w=muro.getWidth();
  h=muro.getHeight();
  Bitmap tela=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
  Canvas dipinto=new Canvas(tela);
  Paint pennello=new Paint();
  
  pennello.setColor(Color.CYAN);
  dipinto.drawCircle(100, 100, 200, pennello);
  
  
  muro.setBackground(new BitmapDrawable(tela));
 }
 
 public void onWindowFocusChanged(boolean hasfocus){
  Disegna();
 }
Un po' fuori, ma mi sembra un cerchio perfetto!

Nessun commento:

Posta un commento