JavascriptProva

giovedì 28 aprile 2016

Parametri delle views: diversi modi

Ecco, ho smontato il codice e lascio solo un pezzo relativo al posizionamento e alle impostazioni di un'ImageView.
  ImageView imageView=new ImageView(this);
  imageView.setScaleType(ScaleType.MATRIX);
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.verticalimagetest);
  LayoutParams params=new LayoutParams(300,300);
  imageView.setLayoutParams(params);
  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(Color.GREEN);
  mainLayout.addView(imageView);
Alla luce degli altri metodi che ho visto per definire i parametri di un'ImageView, ne sperimento altri...

Invece di:
  LayoutParams params=new LayoutParams(300,300);
  imageView.setLayoutParams(params);
provo a usare:
  imageView.getLayoutParams().width=300;
  imageView.getLayoutParams().height=300;
E vediamo che succede con questo codice (ho separato le varie righe per avere una suddivisione più schematica)
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.verticalimagetest);
  
  ImageView imageView=new ImageView(this);
 
  imageView.getLayoutParams().width=300;
  imageView.getLayoutParams().height=300;
  
  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(Color.GREEN);
  mainLayout.addView(imageView);
Il programma si blocca in modo anomalo.
Ragiono un po' e penso che forse questo sarà perché la ImageView non è stata ancora "distesa" sul mainLayout.
Quindi provo a posporre il codice che ne stabilisce le dimensioni a dopo la riga mainLayout.addView(imageView)
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.verticalimagetest);
  
  ImageView imageView=new ImageView(this);
 

  
  
  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(Color.GREEN);
  mainLayout.addView(imageView);
  
  imageView.getLayoutParams().width=300;
  imageView.getLayoutParams().height=300;
e funziona.
Provo anche anteponendo il mainLayout.addView(imageView)
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.verticalimagetest);
  
  ImageView imageView=new ImageView(this);
 
  mainLayout.addView(imageView);
  imageView.getLayoutParams().width=300;
  imageView.getLayoutParams().height=300;
  
  
  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(Color.GREEN);
E funziona lo stesso: sì, il problema era proprio quello!
Adesso gioco un attimo con le dimensioni dell'immagine:
  mainLayout.addView(imageView);
  imageView.getLayoutParams().width=50;
  imageView.getLayoutParams().height=50;
e funziona ugualmente.
Molto meglio, quindi, per brevità, attribuire le dimensioni con questo metodo anziché creando un oggetto LayoutParams come facevo prima.

Provo anche a impostare la posizione:
  mainLayout.addView(imageView);
  imageView.getLayoutParams().width=100;
  imageView.getLayoutParams().height=100;
  imageView.setX(200);
  imageView.setY(100);
e funziona, nel senso che mi sposta la ImageView come previsto...
Provo a porre i metodi che stabiliscono la posizione prima della famosa riga mainLayout.addView(imageView)...
  imageView.setX(200);
  imageView.setY(100);
  mainLayout.addView(imageView);
  imageView.getLayoutParams().width=100;
  imageView.getLayoutParams().height=100;
...e scopro che questi funzionano ugualmente, a differenza dei metodi usati per stabilire le dimensioni.
Bene.
Ora mi occupo dello spostamento dell'immagine, per il quale voglio studiare come si stabiliscono dei limiti
Scrivo il codice, che però stavolta costruisco con scrollBy.
  View.OnTouchListener onTouchListener=new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
     X=event.getX();
     Y=event.getY();
     break;
    case MotionEvent.ACTION_MOVE:
     currentX=event.getX();
     currentY=event.getY();
     v.scrollBy((int)(X-currentX),(int)(Y-currentY));
     X=currentX;
     Y=currentY;
     break;
    }
    return true;
   }
  };
  imageView.setOnTouchListener(onTouchListener);
...e funziona alla grande!

Riaggiusto un po' la posizione dell'ImageView per meglio rendermi agevole l'ìnterazione con essa... e vado a studiare la parte nuova!

Traslazione di un'immagine e copia di essa su un'altra ImageView, basata sulle matrici (promemoria codice)

Torniamo allo scrolling delle immagini.
Questo è il codice che ho preso da uno dei miei progetti-laboratorio:
public class MainActivity extends Activity {

 float[] pts;
 RelativeLayout mainLayout;
 ImageView imageView;
 ImageView imgControllo;
 Bitmap bitmap;
 float X, Y;
 float currentX, currentY;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  
  
  final ImageView imageView=new ImageView(this);
  imageView.setScaleType(ScaleType.MATRIX);
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.verticalimagetest);
  LayoutParams params=new LayoutParams(300,300);
  imageView.setLayoutParams(params);
  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(Color.GREEN);
  mainLayout.addView(imageView);
 
  final ImageView imgControllo=new ImageView(this);
  imgControllo.setScaleType(ScaleType.FIT_CENTER);
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.verticalimagetest);
  LayoutParams params2=new LayoutParams(100,200);
  params2.leftMargin=500;
  params2.topMargin=100;
  imgControllo.setLayoutParams(params2);
  imgControllo.setBackgroundColor(Color.BLACK);
  mainLayout.addView(imgControllo);
  
  Button button=new Button(this);
  LayoutParams bttParams=new LayoutParams(150,50);
  bttParams.leftMargin=300;
  bttParams.topMargin=100;
  button.setLayoutParams(bttParams);
  button.setText("Crea");
  mainLayout.addView(button);
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    
    imageView.destroyDrawingCache();
    imageView.buildDrawingCache();
    Bitmap bmp=imageView.getDrawingCache();
    imgControllo.setImageBitmap(bmp);
    
   }
  });
  
  OnTouchListener onTouchListener=new View.OnTouchListener() {
   Matrix matrice=new Matrix();
   Matrix inversa=new Matrix();
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    switch (event.getAction()){
    case MotionEvent.ACTION_DOWN:
     X=event.getX();
     Y=event.getY();
     break;
    case MotionEvent.ACTION_MOVE:
     currentX=event.getX();
     currentY=event.getY();
     pts=new float[]{0,0};
     imageView.getImageMatrix().invert(inversa);
     inversa.mapPoints(pts);
     matrice.setTranslate(-pts[0]+currentX-X, -pts[1]+currentY-Y);
     imageView.setImageMatrix(matrice);
     X=currentX;
     Y=currentY;
     imageView.destroyDrawingCache();
     imageView.buildDrawingCache();
     Bitmap bmp=imageView.getDrawingCache();
     imgControllo.setImageBitmap(bmp);
     
     break;
    }
    return true;
   }
   
  };
  imageView.setOnTouchListener(onTouchListener);
  
 }

}
Questo è basato sulle matrici, ossia la traslazione di un'immagine avviene per mezzo di una Matrix.
Ma si può fare anche in modo diverso...

domenica 24 aprile 2016

Schema App per la selezione casuale di immagini (Esercitazione)

Creare un database con i seguenti campi:
  • numero _id
  • nome
  • immagine
Estrazione a sorte di tre records del database.

Posizionamento in tre immagini.

Selezione a sorte di una delle tre immagini.

Lettura del nome associato a una delle immagini.

Al click su una delle immagini, se è quella giusta, evento premiante.


Iniziamo: creazione del database...

Non ricordo il codice per convertire una bitmap in array di bytes in modo da poter essere memorizzata nel database sotto forma di un dato tipo blob...

Per fortuna ho trovato i miei stessi appunti
Devo istanziare ByteArrayOutputStream.
Ma prima devo selezionare un'immagine dalla memoria.
Non ricordo la sintassi della funzione che converte un Uri in un Path.

Devo ragionarmela...

Partiamo dall'entità più o meno astratta di "risolutore di contenuti", che ha un metodo query che accetta come parametri Uri seguito da 4 null.
Identificando un Cursor con questa query, si estrae da questo cursor il numero di indice di MediaStore.Images.ImageColumns.DATA, mediante il metodo di Cursor getColumnIndex
All'indice di questo campo si estrae la stringa.

Proviamo...

Bene, tutto bene, mi sono riesercitato a scrivere.

Voglio crearmi uno schema per un ImagePicker.
Cliccando sull'immagine anteprima si seleziona l'immagine, che va sull'immagine anteprima.
Quindi si apre una casella per l'immissione di testo (EditText), compilata la quale appare il pulsante per l'immissione nel database.
Cerchiamo di standardizzare questo schema.

Inizialmente il bttInvia è invisibile.
Ecco il codice per la modifica di un EditText (che avevo completamente dimenticato):
  editText.addTextChangedListener(new TextWatcher(){

   @Override
   public void beforeTextChanged(CharSequence s, int start, int count,
     int after) {
    // TODO Auto-generated method stub
    
   }

   @Override
   public void onTextChanged(CharSequence s, int start, int before,
     int count) {
    if(s.equals("")==false){
     radioGroup.setVisibility(View.VISIBLE);
     bttInvia.setVisibility(View.VISIBLE);
    }
    
   }

   @Override
   public void afterTextChanged(Editable s) {
    
   }
   
  });
Dunque lo schema è:
EditText INVISIBILE
bttInvia INVISIBILE

Al caricamento dell'immagine Anteprima:
EditText VISIBILE
bttInvia INVISIBILE

Al cambiamento della EditText:
EditText VISIBILE
bttInvia VISIBILE
Eccoli riprodotti:

Iniziale:
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_image_picker);
  
  testo=(EditText)findViewById(R.id.editText1);
  testo.setVisibility(View.INVISIBLE);
  
  bttInvia=(Button)findViewById(R.id.button1);
  bttInvia.setVisibility(View.INVISIBLE);


Dopo il caricamento dell'immagine Anteprima:
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data){
  if(resultCode==RESULT_OK){
   String path =UriToPath(this,data.getData());
   Bitmap bitmap=BitmapFactory.decodeFile(path);
   imgAnteprima.setImageBitmap(bitmap);
   testo.setVisibility(View.VISIBLE);
  }
 }


Dopo il cambiamento del testo della EditText:
  testo.addTextChangedListener(new TextWatcher(){

   @Override
   public void beforeTextChanged(CharSequence s, int start, int count,
     int after) {
    // TODO Auto-generated method stub
    
   }

   @Override
   public void onTextChanged(CharSequence s, int start, int before,
     int count) {
    bttInvia.setVisibility(View.VISIBLE);
    
   }

   @Override
   public void afterTextChanged(Editable s) {
    // TODO Auto-generated method stub
    
   }
   
  });
A tutto questo aggiungiamo il codice per il salvataggio nel database dell'immagine e del suo nome. Per salvare nel database devo scrivere però il codice per convertire la bitmap in un array di bytes.
  bttInvia.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    ByteArrayOutputStream bos=new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos);
    byteImage=bos.toByteArray();
    Helper helper=new Helper(getApplicationContext());
    helper.save(testo.getText().toString(), byteImage);
    finish();
    
   }
  });

sabato 23 aprile 2016

Pinch Zoom

Bisogna istanziare un oggetto di tipo GestureDetector.
Non è difficile memorizzarlo: Gesture Detector, ossia detettore di gesti.
Questo richiede come parametri, a parte il Context, un Listener che sarebbe, credo, un metodo del Gesture Detector stesso.
Provo a scrivere il codice di istanziazione del Gesture Detector per lo scaling, che si chiama ScaleGestureDetector (anche questo facile da ricordare, scomponendo in Scale, Gesture e Detector)

  ScaleGestureDetector scaleGestureDetector=
    new ScaleGestureDetector(this,new ScaleListener());
Ecco: ScaleListener non è conosciuto dal programma. Si tratta di una classe che estende un metodo di ScaleGestureDetector stesso, che sarebbe SimpleOnScaleGestureListener.b Difficile da ricordare? Simple On, poi ScaleGesture, poi Listener (perché si tratta di un Listener).

Lo scrivo a memoria (si spera):
 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
  
 }
Bene, nessun segno di errore!
Ora, questa classe overridda il metodo onScale il quale ha per parametro un oggetto di tipo ScaleGestureDetector, che sarebbe lo stesso oggetto che ha per parametro questo listener...

Il metodo onScale è booleano.
Viene immediata l'aggiunta di un return con valore booleano, suggerita direttamente da Eclipse.
Manca la linea fondamentale del metodo, che restituisce un float scale, che dovrebbe essere il risultato del metodo getScaleFactor del detector che il metodo onScale ha per parametro.
Provo a scriverlo.
 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
  @Override
  public boolean onScale(ScaleGestureDetector detector){
   float scale=detector.getScaleFactor();
   return true;
   
  }
 }


Mandala! Cancello e riscrivo tutto.
  ScaleGestureDetector detector=new ScaleGestureDetector(this,new ScaleListener());
  
  
 }
 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
  @Override
  public boolean onScale(ScaleGestureDetector detector){
   float scale=detector.getScaleFactor();
   return true;
   
  }
 }
Non male.

Ci riprovo ancora:
  ScaleGestureDetector detector=new ScaleGestureDetector(this,new ScaleListener());
  
 }
 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
  @Override
  public boolean onScale(ScaleGestureDetector detector){
   float scale=detector.getScaleFactor();
   return true;
   
  }
 }
Okay!
Ora vediamo cosa dobbiamo fare con questo "manoscritto"!

Ecco: mi sembra che la chiave di tutto sia l'evento TouchEvent, nel quale al tocco si subordina il tocco dello ScaleGestureDetector.
Scrivo l'evento TouchEvent così organizzato:
 public boolean onTouchEvent(MotionEvent ev) {
       SGD.onTouchEvent(ev);
       return true;
 }
In pratica, se con l'evento onTouchEvent si intercetta l'evento onTouchEvent a carico dello ScaleGestureDetector, questo invia il messaggio all'evento onScale del Listener (ScaleListener che estende ScaleGestureDetector.SimpleOnScaleGestureListener.
Aggiungiamo quindi le istruzioni da eseguire nell'evento onScale di ScaleListener:
 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
  @Override
  public boolean onScale(ScaleGestureDetector detector){
   float scale=detector.getScaleFactor();
   Log.v("Scale",""+scale);
   return true;
   
  }
 }
Le istruzioni sono quelle poste in rosso.
Non potendo eseguire un pinch zoom con l'emulatore, eseguo il codice sul cellulare, e ottengo in Logcat i valori della scala nel corso del movimento delle due dita:
04-23 19:28:57.080: V/Scale(28619): 1.0
04-23 19:28:57.093: V/Scale(28619): 1.0199716
04-23 19:28:57.117: V/Scale(28619): 1.0195951
04-23 19:28:57.134: V/Scale(28619): 1.011993
04-23 19:28:57.151: V/Scale(28619): 1.007305
04-23 19:28:57.168: V/Scale(28619): 1.0048231
04-23 19:28:57.184: V/Scale(28619): 1.0063298
04-23 19:28:57.202: V/Scale(28619): 1.0045882
04-23 19:28:57.218: V/Scale(28619): 1.001444
04-23 19:28:57.235: V/Scale(28619): 1.0050373
04-23 19:28:57.252: V/Scale(28619): 1.0011859
04-23 19:28:57.269: V/Scale(28619): 1.0034686
04-23 19:28:57.286: V/Scale(28619): 1.0094994
04-23 19:28:57.302: V/Scale(28619): 1.0016569

Esperimento riuscito!

Codice che replica in tempo reale su una imageView il contenuto di un'altra ImageView compreso lo sfondo

Codice che crea su una ImageView imgControllo una bitmap presa dal contenuto di una ImageView imageView spostabile, in tempo reale con gli spostamenti:
  OnTouchListener onTouchListener=new View.OnTouchListener() {
   Matrix matrice=new Matrix();
   Matrix inversa=new Matrix();
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    switch (event.getAction()){
    case MotionEvent.ACTION_DOWN:
     X=event.getX();
     Y=event.getY();
     break;
    case MotionEvent.ACTION_MOVE:
     currentX=event.getX();
     currentY=event.getY();
     pts=new float[]{0,0};
     imageView.getImageMatrix().invert(inversa);
     inversa.mapPoints(pts);
     matrice.setTranslate(-pts[0]+currentX-X, -pts[1]+currentY-Y);
     imageView.setImageMatrix(matrice);
     X=currentX;
     Y=currentY;
     imageView.destroyDrawingCache();
     imageView.buildDrawingCache();
     Bitmap bmp=imageView.getDrawingCache();
     imgControllo.setImageBitmap(bmp);
     
     break;
    }
    return true;
   }
   
  };
  imageView.setOnTouchListener(onTouchListener);

Codice per creare una bitmap dallo sfondo di una ImageView.

Ed ecco un sistema pratico e poco dispendioso per creare un'immagine da una ImageView.
Non è più necessaria nemmeno la Matrix, ma l'esperienza fatta nel maneggiarla mi tornerà incredibilmente utile in futuro.

  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    
    imageView.destroyDrawingCache();
    imageView.buildDrawingCache();
    Bitmap bmp=imageView.getDrawingCache();
    imgControllo.setImageBitmap(bmp);
    
   }
  });

venerdì 22 aprile 2016

Codice per il trascinamento di un'immagine da una ImageView mediante trasformazioni di Matrix.

Ho trovato il modo di ottenere un trascinamento dell'immagine mediante le trasformazioni della Matrix in modo da tener conto del punto dell'immagine nel quale mi trovo, allo scopo di poter ritagliare un frammento dell'immagine stessa.
Ecco il codice (da salvare come promemoria standard cui ricorrere in caso di necessità di ripasso)
  OnTouchListener onTouchListener=new View.OnTouchListener() {
   Matrix matrice=new Matrix();
   Matrix inversa=new Matrix();
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    switch (event.getAction()){
    case MotionEvent.ACTION_DOWN:
     X=event.getX();
     Y=event.getY();
     break;
    case MotionEvent.ACTION_MOVE:
     currentX=event.getX();
     currentY=event.getY();
     pts=new float[]{0,0};
     imageView.getImageMatrix().invert(inversa);
     inversa.mapPoints(pts);
     matrice.setTranslate(-pts[0]+currentX-X, -pts[1]+currentY-Y);
     imageView.setImageMatrix(matrice);
     X=currentX;
     Y=currentY;
     
     break;
    }
    return true;
   }
   
  };
  imageView.setOnTouchListener(onTouchListener);

mercoledì 20 aprile 2016

Studio per ritagliare immagini (codice grezzo)

Ho realizzato in questo modo una "finestra" sulla bitmap, ossia una ImageView che si "affaccia" su una Bitmap più grande di essa e tramite la quale si può "uncinare" la bitmap spostandola a piacimento.
  imageView =new ImageView(this);
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.facciadaculo);
  int larghezza=bitmap.getWidth();
  int altezza=bitmap.getHeight();  

  
  LayoutParams params=new LayoutParams(200,100);
  imageView.setLayoutParams(params);
  imageView.setScaleType(ScaleType.CENTER);
  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(Color.BLACK);
  
  mainLayout.addView(imageView);
  
  OnTouchListener onTouchListener=new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    switch(event.getAction()){
     case MotionEvent.ACTION_DOWN:
      X=event.getX();
      Y=event.getY();
      break;
     case MotionEvent.ACTION_MOVE:
      currentX=event.getX();
      currentY=event.getY();
      
      int scrollByX=(int)(X-currentX);
      int scrollByY=(int)(Y-currentY);
      v.scrollBy(scrollByX, scrollByY);
      X=currentX;
      Y=currentY;
      break;
    }
    return true;
   }
  };
  imageView.setOnTouchListener(onTouchListener);
 }
Bene.
Annotata questa, procedo a sperimentare un po' oltre...

Provo a usare il metodo scrollTo(int,int).
  OnTouchListener onTouchListener=new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    switch(event.getAction()){
     case MotionEvent.ACTION_DOWN:
      imageView.scrollTo(0, 0);
      break;
     
      
    }
    return true;
   }
  };
  imageView.setOnTouchListener(onTouchListener);
Giocando con ScaleType e con ScrollTo posso modificare sia la posizione iniziale della bitmap nei confronti della ImageView sia l'entità dello scorrimento della bitmap.
Adesso quello che mi interessa è prendere le coordinate della bitmap.
Se uso ScaleType.MATRIX, ottengo il posizionamento iniziale nell'angolo superiore sinistro della Bitmap, quindi zero.
Ora sposto di 50 e 50:
    switch(event.getAction()){
     case MotionEvent.ACTION_DOWN:
      imageView.scrollTo(50,50);
      break;
     
      
    }


Sposto mediante il Touch di 50,50:



E adesso quindi so di avere una finestra che esplora la bitmap secondo queste coordinate:
Angolo superiore sinistro: 50,50;
Angolo inferiore destro: 150,150 (dato che le dimensioni della finestra sono rispettivamente 100 e 100).

Provo quindi a ritagliare un'immagine di queste coordinate e dimensioni.
Per vederla, però, devo creare una nuova ImageView.

Ecco il codice:
  imageView =new ImageView(this);
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.facciadaculo);
  int larghezza=bitmap.getWidth();
  int altezza=bitmap.getHeight();
  
  
  LayoutParams params=new LayoutParams(100,100);
  imageView.setLayoutParams(params);
  imageView.setScaleType(ScaleType.MATRIX);
  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(Color.BLACK);
  
  mainLayout.addView(imageView);
  
  imgControllo=new ImageView(this);
  LayoutParams p=new LayoutParams(100,100);
  p.leftMargin=10;
  p.topMargin=200;
  imgControllo.setLayoutParams(p);
  mainLayout.addView(imgControllo);
  
  OnTouchListener onTouchListener=new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    switch(event.getAction()){
     case MotionEvent.ACTION_DOWN:
      imageView.scrollTo(50,50);
      Bitmap newBmp=Bitmap.createBitmap(bitmap,50,50,100,100);
      imgControllo.setImageBitmap(newBmp);
      break;
     
      
    }
    return true;
   }
  };
  imageView.setOnTouchListener(onTouchListener);
Questo codice prende la bitmap e, dopo lo spostamento calcolato a partire dall'angolo superiore sinistro della bitmap, calcola il ritaglio da praticare nella bitmap in relazione allo spostamento, ne crea una nuova bitmap e la pone nell'ImageView di controllo.
Ecco: prima dello spostamento:



E dopo lo spostamento con ritaglio e copia nella seconda ImageView.



Bene.


Ecco, ho creato un codice che permette di individuare le coordinate della bitmap sottostante.
Me lo copio qui, grezzo grezzo e con residui di un codice precedente che resta inutilizzato, in modo da poter attingere ad esso in caso di eventuali vuoti di memoria nella riscrittura di un codice fatto meglio
public class MainActivity extends Activity {
 float X;
 float Y;
 float currentX, currentY;

 ImageView imageView;
 ImageView imageView2;
 RelativeLayout mainLayout;
 RelativeLayout layout;
 Bitmap bitmap;
 Matrix matrix;
 ImageView imgControllo;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  
  
  mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  
  int LayLeft, LayTop, LayWidth, LayHeight;
  int ImgLeft, ImgTop, ImgWidth, ImgHeight;
  LayLeft=0;
  LayTop=0;
  LayWidth=100;
  LayHeight=100;
  
  ImgLeft=0;
  ImgTop=0;
  

  
  //CREAZIONE DEL LAYOUT
  layout=new RelativeLayout(this);
  BitmapDrawable sfondo=(BitmapDrawable)this.getResources().getDrawable(R.drawable.cartellanuova);
  layout.setBackground(sfondo);
  
  //settaggio dei parametri
  LayoutParams lParams=new LayoutParams(LayWidth,LayHeight);
  lParams.leftMargin=LayLeft;
  lParams.topMargin=LayTop;
  layout.setLayoutParams(lParams);
  
  
  //CREAZIONE DELL'IMMAGINE
  imageView =new ImageView(this);
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.facciadaculo);
  int larghezza=bitmap.getWidth();
  int altezza=bitmap.getHeight();
  
 
  
  
  
  LayoutParams params=new LayoutParams(100,100);
  imageView.setLayoutParams(params);
  imageView.setScaleType(ScaleType.CENTER);
  
  
  
  bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), 
    bitmap.getHeight(),matrix, false);
  imageView.setImageBitmap(bitmap);
  
  imageView.setBackgroundColor(Color.BLACK);
  
  mainLayout.addView(imageView);
  
  imgControllo=new ImageView(this);
  LayoutParams p=new LayoutParams(100,100);
  p.leftMargin=10;
  p.topMargin=200;
  imgControllo.setLayoutParams(p);
  imgControllo.setBackgroundColor(Color.BLACK);
  mainLayout.addView(imgControllo);
  
  OnTouchListener onTouchListener=new View.OnTouchListener() {
   Matrix matrix=imageView.getImageMatrix();
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    ((ImageView)v).getImageMatrix().invert(matrix);
    switch(event.getAction()){
     case MotionEvent.ACTION_DOWN:
      X=event.getX();
      Y=event.getY();
      float[] pts={0f,0f};
        
      
      matrix.mapPoints(pts);
      Log.v(""+pts[0],""+pts[1]);
      break;
    }
      
     
    
    return true;
   }
  };
  imageView.setOnTouchListener(onTouchListener);
  
 }
 

 
 @Override
 public void onWindowFocusChanged(boolean hasFocus){
  super.onWindowFocusChanged(hasFocus);
  Log.v("LARGHEZZA DI IMAGEVIEW",""+imageView.getWidth());
  Log.v("ALTEZZA DI IMAGEVIEW",""+imageView.getHeight());
 }
 
}

lunedì 18 aprile 2016

Studi di ridimensionamento di immagini con Matrix

Adesso ridimensiono la bitmap e poi vedo se la ImageView che la contiene si riduce di conseguenza.

  imageView =new ImageView(this);

  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  bitmap=Bitmap.createScaledBitmap(bitmap, 100, 100, false);
  Log.v("LARGHEZZA DELLA BITMAP",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP",""+bitmap.getHeight());
  
  imageView.setImageBitmap(bitmap);
  mainLayout.addView(imageView);
 }
 
 @Override
 public void onWindowFocusChanged(boolean hasFocus){
  super.onWindowFocusChanged(hasFocus);
  Log.v("LARGHEZZA DI IMAGEVIEW",""+imageView.getWidth());
  Log.v("ALTEZZA DI IMAGEVIEW",""+imageView.getHeight());
 }
04-18 15:59:10.215: V/LARGHEZZA DELLA BITMAP(6108): 100
04-18 15:59:10.216: V/ALTEZZA DELLA BITMAP(6108): 100

.....

04-18 15:59:10.434: V/LARGHEZZA DI IMAGEVIEW(6108): 100
04-18 15:59:10.434: V/ALTEZZA DI IMAGEVIEW(6108): 100

Ho ridotto l'immagine senza rispetto per le proporzioni.
Ma ricordo adesso che si può usare Matrix per scalare le immagini.
Ciò può essere molto utile, anziché calcolare il rapporto larghezza/altezza e agire di conseguenza...

Vediamo...

  imageView =new ImageView(this);

  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v("LARGHEZZA DELLA BITMAP",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP",""+bitmap.getHeight());
  Matrix matrix=new Matrix();
  matrix.postScale(.5f,.5f);
  bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
  Log.v("LARGHEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getHeight());
  
  imageView.setImageBitmap(bitmap);
  mainLayout.addView(imageView);
 }
 
 @Override
 public void onWindowFocusChanged(boolean hasFocus){
  super.onWindowFocusChanged(hasFocus);
  Log.v("LARGHEZZA DI IMAGEVIEW",""+imageView.getWidth());
  Log.v("ALTEZZA DI IMAGEVIEW",""+imageView.getHeight());
 }
04-18 19:01:38.411: V/LARGHEZZA DELLA BITMAP(6466): 408
04-18 19:01:38.411: V/ALTEZZA DELLA BITMAP(6466): 313
04-18 19:01:38.412: V/ALTEZZA DELLA BITMAP DOPO MATRIX(6466): 157

.....

04-18 19:01:39.289: V/LARGHEZZA DI IMAGEVIEW(6466): 204
04-18 19:01:39.290: V/ALTEZZA DI IMAGEVIEW(6466): 157

Perfetto! La bitmap si è ridimensionata e la ImageView con lei.


Ora voglio portare la bitmap a una larghezza di 100.
Calcolo il rapporto fra 100 e la larghezza della bitmap...


  imageView =new ImageView(this);

  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v("LARGHEZZA DELLA BITMAP",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP",""+bitmap.getHeight());
  float Ratio=100f/(float)bitmap.getWidth();
  
  Matrix matrix=new Matrix();
  matrix.postScale(Ratio,Ratio);
  bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
  Log.v("LARGHEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getHeight());
  
  imageView.setImageBitmap(bitmap);
  mainLayout.addView(imageView);
 }
 
 @Override
 public void onWindowFocusChanged(boolean hasFocus){
  super.onWindowFocusChanged(hasFocus);
  Log.v("LARGHEZZA DI IMAGEVIEW",""+imageView.getWidth());
  Log.v("ALTEZZA DI IMAGEVIEW",""+imageView.getHeight());
 }
E vediamo le misure:
04-18 19:08:39.152: V/LARGHEZZA DELLA BITMAP(6512): 408
04-18 19:08:39.152: V/ALTEZZA DELLA BITMAP(6512): 313
04-18 19:08:39.153: V/LARGHEZZA DELLA BITMAP DOPO MATRIX(6512): 100
04-18 19:08:39.153: V/ALTEZZA DELLA BITMAP DOPO MATRIX(6512): 77

.....

04-18 19:08:39.562: V/LARGHEZZA DI IMAGEVIEW(6512): 100
04-18 19:08:39.562: V/ALTEZZA DI IMAGEVIEW(6512): 77

Perfetto! La larghezza è stata portata a 100!

Ora voglio che l'altezza sia pari a 100:
  imageView =new ImageView(this);

  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v("LARGHEZZA DELLA BITMAP",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP",""+bitmap.getHeight());
  float Ratio=100f/(float)bitmap.getHeight();
  
  Matrix matrix=new Matrix();
  matrix.postScale(Ratio,Ratio);
  bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
  Log.v("LARGHEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getHeight());
  
  imageView.setImageBitmap(bitmap);
  mainLayout.addView(imageView);
 }
 
 @Override
 public void onWindowFocusChanged(boolean hasFocus){
  super.onWindowFocusChanged(hasFocus);
  Log.v("LARGHEZZA DI IMAGEVIEW",""+imageView.getWidth());
  Log.v("ALTEZZA DI IMAGEVIEW",""+imageView.getHeight());
 }
04-18 19:10:26.931: V/LARGHEZZA DELLA BITMAP(6601): 408
04-18 19:10:26.931: V/ALTEZZA DELLA BITMAP(6601): 313
04-18 19:10:26.937: V/LARGHEZZA DELLA BITMAP DOPO MATRIX(6601): 130
04-18 19:10:26.938: V/ALTEZZA DELLA BITMAP DOPO MATRIX(6601): 100

.....

04-18 19:10:27.114: V/LARGHEZZA DI IMAGEVIEW(6601): 130
04-18 19:10:27.114: V/ALTEZZA DI IMAGEVIEW(6601): 100

Perfetto!
Dunque, se un'immagine è orizzontale, la misura da riportare a 100 sarà la larghezza, mentre se è verticale la misura sarà l'altezza.
Potremo così costringere un'immagine a stare nei limiti che vogliamo.
Ma se ho un'immagine orizzontale, che riduco a 100 di larghezza ma voglio che non occupi più di una certa dimensione in altezza, ossia ho due limiti, come mi metto?
Se porto direttamente l'altezza a valori inferiori al limite verticale, potrei avere ancora una larghezza superiore a 100.
Se porto la larghezza a valori inferiori a 100 potrei avere ancora un'altezza superiore al secondo limite.
Così, per le immagini orizzontali bisogna prima rapportarle a 100 e poi al secondo limite.


Iniziamo a ridimensionare le immagini secondo la larghezza o l'altezza a seconda che siano rispettivamente orizzontali o verticali.
Facciamo conto che il limite orizzontale sia 100 e quello verticale di 50.
Innanzitutto distinguiamo se l'immagine è orizzontale o verticale. Se è perfettamente quadrata può rientrare nel campo di quelle verticali.

  imageView =new ImageView(this);

  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v("LARGHEZZA DELLA BITMAP",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP",""+bitmap.getHeight());
  
  int larghezza=bitmap.getWidth();
  int altezza=bitmap.getHeight();
  
  
  float Ratio;
  if(altezza>=larghezza) Ratio=50f/(float)bitmap.getHeight();
  else Ratio=100f/(float)bitmap.getWidth();
 
  
  Matrix matrix=new Matrix();
  matrix.postScale(Ratio,Ratio);
  bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
  Log.v("LARGHEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getHeight());
  
  imageView.setImageBitmap(bitmap);
  mainLayout.addView(imageView);
 }
 
 @Override
 public void onWindowFocusChanged(boolean hasFocus){
  super.onWindowFocusChanged(hasFocus);
  Log.v("LARGHEZZA DI IMAGEVIEW",""+imageView.getWidth());
  Log.v("ALTEZZA DI IMAGEVIEW",""+imageView.getHeight());
 }
Ecco: questo codice stabilisce un limite di larghezza 100 per le immagini orizzontali, mentre stabilisce un limite di altezza 50 per le immagini verticali e quadrate.
Lo provo dapprima con l'immagine orizzontale "anguria", quindi con l'immagine verticale "torre": Con "anguria", immagine orizzontale:
04-18 19:26:51.062: V/LARGHEZZA DELLA BITMAP(6785): 408
04-18 19:26:51.062: V/ALTEZZA DELLA BITMAP(6785): 313

04-18 19:26:51.062: V/LARGHEZZA DELLA BITMAP DOPO MATRIX(6785): 100
04-18 19:26:51.062: V/ALTEZZA DELLA BITMAP DOPO MATRIX(6785): 77

04-18 19:26:51.255: V/LARGHEZZA DI IMAGEVIEW(6785): 100
04-18 19:26:51.255: V/ALTEZZA DI IMAGEVIEW(6785): 77
Mi ha ridotto l'immagine orizzontale a una larghezza di 100 (con un'altezza di 77).

Con "torre", immagine verticale:
04-18 19:29:13.569: V/LARGHEZZA DELLA BITMAP(6832): 591
04-18 19:29:13.570: V/ALTEZZA DELLA BITMAP(6832): 888

04-18 19:29:13.570: V/LARGHEZZA DELLA BITMAP DOPO MATRIX(6832): 33
04-18 19:29:13.570: V/ALTEZZA DELLA BITMAP DOPO MATRIX(6832): 50

04-18 19:29:13.769: V/LARGHEZZA DI IMAGEVIEW(6832): 33
04-18 19:29:13.770: V/ALTEZZA DI IMAGEVIEW(6832): 50
Mi ha ridotto l'immagine verticale a un'altezza di 50 (con larghezza pari a 33).
Ora, però, io voglio che per l'immagine orizzontale anche l'altezza abbia dei limiti, ossia che sia al massimo di 50.
Come fare?
Proviamo...
  layout=new RelativeLayout(this);
  BitmapDrawable sfondo=(BitmapDrawable)this.getResources().getDrawable(R.drawable.cartellanuova);
  layout.setBackground(sfondo);
  
  //settaggio dei parametri
  LayoutParams lParams=new LayoutParams(LayWidth,LayHeight);
  lParams.leftMargin=LayLeft;
  lParams.topMargin=LayTop;
  layout.setLayoutParams(lParams);
  
  
  //CREAZIONE DELL'IMMAGINE
  imageView =new ImageView(this);

  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v("LARGHEZZA DELLA BITMAP",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP",""+bitmap.getHeight());
  
  int larghezza=bitmap.getWidth();
  int altezza=bitmap.getHeight();
  
  
  float Ratio;
  if(altezza>=larghezza) Ratio=50f/(float)bitmap.getHeight();
  else Ratio=100f/(float)bitmap.getWidth();
 
  
  Matrix matrix=new Matrix();
  matrix.postScale(Ratio,Ratio);
  bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
  Log.v("LARGHEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP DOPO MATRIX",""+bitmap.getHeight());
  
  if(bitmap.getHeight()>50){
   Ratio=50f/(float)bitmap.getHeight();
   Log.v("RATIO",""+Ratio);
   matrix=new Matrix();
   matrix.postScale(Ratio, Ratio);
   bitmap=Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(), bitmap.getHeight(),matrix,false);
   Log.v("LARGHEZZA DELLA BITMAP DOPO SECONDA MATRIX",""+bitmap.getWidth());
   Log.v("ALTEZZA DELLA BITMAP DOPO SECONDA MATRIX",""+bitmap.getHeight());
  }
  imageView.setImageBitmap(bitmap);
  mainLayout.addView(imageView);
 }
 
 @Override
 public void onWindowFocusChanged(boolean hasFocus){
  super.onWindowFocusChanged(hasFocus);
  Log.v("LARGHEZZA DI IMAGEVIEW",""+imageView.getWidth());
  Log.v("ALTEZZA DI IMAGEVIEW",""+imageView.getHeight());
 }
Ecco.
La provo con l'immagine anguria che è orizzontale con poca differenza fra altezza e larghezza:
04-18 20:05:06.598: V/LARGHEZZA DELLA BITMAP(7254): 408
04-18 20:05:06.598: V/ALTEZZA DELLA BITMAP(7254): 313

04-18 20:05:06.598: V/LARGHEZZA DELLA BITMAP DOPO MATRIX(7254): 100
04-18 20:05:06.598: V/ALTEZZA DELLA BITMAP DOPO MATRIX(7254): 77

04-18 20:05:06.600: V/RATIO(7254): 0.64935064

04-18 20:05:06.603: V/LARGHEZZA DELLA BITMAP DOPO SECONDA MATRIX(7254): 65
04-18 20:05:06.604: V/ALTEZZA DELLA BITMAP DOPO SECONDA MATRIX(7254): 50
Dopo aver ridotto l'immagine orizzontale a 100, questa ha un'altezza di 77, che è eccessiva rispetto al limite verticale di 50. Calcola nuovamente il rapporto e la riduce ulteriormente fino a un'altezza di 50.

La provo adesso con l'immagine torre che è verticale:
04-18 20:07:41.595: V/LARGHEZZA DELLA BITMAP(7302): 591
04-18 20:07:41.595: V/ALTEZZA DELLA BITMAP(7302): 888

04-18 20:07:41.596: V/LARGHEZZA DELLA BITMAP DOPO MATRIX(7302): 33
04-18 20:07:41.596: V/ALTEZZA DELLA BITMAP DOPO MATRIX(7302): 50

04-18 20:07:41.726: V/LARGHEZZA DI IMAGEVIEW(7302): 33
04-18 20:07:41.726: V/ALTEZZA DI IMAGEVIEW(7302): 50
Non va oltre la prima riduzione all'altezza di 50.

La provo adesso con l'immagine chiave che è orizzontale più stretta della precedente orizzontale:
04-18 20:09:23.188: V/LARGHEZZA DELLA BITMAP(7348): 406
04-18 20:09:23.188: V/ALTEZZA DELLA BITMAP(7348): 175

04-18 20:09:23.188: V/LARGHEZZA DELLA BITMAP DOPO MATRIX(7348): 100
04-18 20:09:23.193: V/ALTEZZA DELLA BITMAP DOPO MATRIX(7348): 43

04-18 20:09:23.401: V/LARGHEZZA DI IMAGEVIEW(7348): 100
04-18 20:09:23.402: V/ALTEZZA DI IMAGEVIEW(7348): 43
In questo caso, con la riduzione a 100 l'altezza è già sotto il limite verticale di 50 e quindi l'immagine non necessita di ulteriori riduzioni.
Tutto ciò è quanto era nelle mie intenzioni!

Adesso bisogna studiare il posizionamento delle immagini in una View, in relazione al rapporto con una TextView.

Dimensioni di una bitmap e della ImageView che la contiene

Prendo l'immagine e ne rilevo le dimensioni:
  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v(""+bitmap.getWidth(),""+bitmap.getHeight());
Ottengo
04-18 14:40:04.976: V/408(4740): 313
408 e 313.

Metto la bitmap in una ImageView.
  ImageView imageView =new ImageView(this);

  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v(""+bitmap.getWidth(),""+bitmap.getHeight());
  
  imageView.setImageBitmap(bitmap);
  mainLayout.addView(imageView);
La ImageView mi viene abbastanza grande:



Siamo sicuri che ImageView abbia le stesse dimensioni della Bitmap?
Vediamole...
  ImageView imageView =new ImageView(this);

  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v(""+bitmap.getWidth(),""+bitmap.getHeight());
  
  imageView.setImageBitmap(bitmap);
  Log.v(""+imageView.getWidth(),""+imageView.getHeight());
  mainLayout.addView(imageView);
04-18 15:47:26.523: V/408(5881): 313
04-18 15:47:26.524: V/0(5881): 0

Così facendo, ottengo come risultato 0 e 0.
Il motivo è che fino a onResume non è stata ancora dispiegata "materialmente" la ImageView, e quindi bisogna overridare l'evento onWindowFocusChanged, a quanto leggo...

Proviamo:
  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v(""+bitmap.getWidth(),""+bitmap.getHeight());
  
  imageView.setImageBitmap(bitmap);
  mainLayout.addView(imageView);
 }
 
 @Override
 public void onWindowFocusChanged(boolean hasFocus){
  super.onWindowFocusChanged(hasFocus);
  Log.v(""+imageView.getWidth(),""+imageView.getHeight());
 }
04-18 15:50:18.327: V/408(5974): 313

.....

04-18 15:50:18.537: V/408(5974): 313

Per avere la prova, faccio di meglio:
  imageView =new ImageView(this);

  Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  Log.v("LARGHEZZA DELLA BITMAP",""+bitmap.getWidth());
  Log.v("ALTEZZA DELLA BITMAP",""+bitmap.getHeight());
  
  imageView.setImageBitmap(bitmap);
  mainLayout.addView(imageView);
 }
 
 @Override
 public void onWindowFocusChanged(boolean hasFocus){
  super.onWindowFocusChanged(hasFocus);
  Log.v("LARGHEZZA DI IMAGEVIEW",""+imageView.getWidth());
  Log.v("ALTEZZA DI IMAGEVIEW",""+imageView.getHeight());
 }
04-18 15:55:33.902: V/LARGHEZZA DELLA BITMAP(6021): 408
04-18 15:55:33.910: V/ALTEZZA DELLA BITMAP(6021): 313

.....

04-18 15:55:34.016: V/LARGHEZZA DI IMAGEVIEW(6021): 408
04-18 15:55:34.016: V/ALTEZZA DI IMAGEVIEW(6021): 313

Ed è così confermato che le dimensioni della ImageView sono le stesse della bitmap in essa contenuta.

Studio per l'adattamento e la centratura delle immagini in un contenitore.

Se un'immagine è contenuta in un contenitore, ne assume la larghezza.
Provo:
  //CREAZIONE DEL LAYOUT
  layout=new RelativeLayout(this);
  BitmapDrawable sfondo=(BitmapDrawable)this.getResources().getDrawable(R.drawable.cartella);
  layout.setBackground(sfondo);
  
  //settaggio dei parametri
  LayoutParams lParams=new LayoutParams(LayWidth,LayHeight);
  lParams.leftMargin=LayLeft;
  lParams.topMargin=LayTop;
  layout.setLayoutParams(lParams);
  
  
  //CREAZIONE DELL'IMMAGINE
  ImageView imageView =new ImageView(this);
  BitmapDrawable immagine=(BitmapDrawable)this.getResources().getDrawable(R.drawable.chiave);
  imageView.setImageDrawable(immagine);
  
  //definizione delle dimensioni
  ImgWidth=immagine.getBitmap().getWidth();
  ImgHeight=immagine.getBitmap().getHeight();
  
  //scaletype
  imageView.setScaleType(ImageView.ScaleType.FIT_START);
  Log.v(""+ImgWidth,""+ImgHeight);
  
  //settaggio dei parametri
  LayoutParams imgParams=new LayoutParams(ImgWidth,ImgHeight);
  imgParams.leftMargin=ImgLeft;
  imgParams.topMargin=ImgTop;
  imageView.setLayoutParams(imgParams);
Ecco: qui ottengo questi valori di larghezza e altezza dell'immagine:
04-17 21:52:23.285: V/406(2224): 175

ossia 406 e 175.

Ma le dimensioni del Layout contenitore sono 100 e 100:
  int LayLeft, LayTop, LayWidth, LayHeight;
  int ImgLeft, ImgTop, ImgWidth, ImgHeight;
  LayLeft=0;
  LayTop=0;
  LayWidth=100;
  LayHeight=100;
  
  ImgLeft=0;
  ImgTop=0;
Bisogna rapportare quindi le dimensioni dell'immagine a quelle del contenitore.



Ecco infine la funzione che ho creato, in cui bisogna fornire una scalatura, una ImageView, la bitmap e le dimensioni del contenitore:
 public void centerImage(float Scalatura, ImageView imgView, 
   BitmapDrawable bitmapDrawable,int ContWidth, int ContHeight){
  int ImgWidth=bitmapDrawable.getBitmap().getWidth();
  int ImgHeight=bitmapDrawable.getBitmap().getHeight();
  float Scala=Scalatura;
  if(ImgWidth>ImgHeight) {
   float Ratio=(float)ImgHeight/(float)ImgWidth;
   ImgWidth=(int) (ContWidth*Scala);
   ImgHeight=(int)(ImgWidth*Ratio);
  }else{
   float Ratio=(float)ImgWidth/(float)ImgHeight;
   ImgHeight=(int)(ContHeight*Scala);
   ImgWidth=(int)(ImgHeight*Ratio);
  }
  
  int ImgTop=ContHeight/2-ImgHeight/2;
  int ImgLeft=ContWidth/2-ImgWidth/2;
  
  imgView.setScaleType(ImageView.ScaleType.FIT_START);
  LayoutParams imgParams=new LayoutParams(ImgWidth,ImgHeight);
  imgParams.leftMargin=ImgLeft;
  imgParams.topMargin=ImgTop;
  imgView.setLayoutParams(imgParams);
 }


venerdì 15 aprile 2016

Riscrittura del Listener che regola il settingMode nella mia app.

Il meccanismo del DragDrop nella mia applicazione si è rivelato fallimentare, perché l'immagine da "draggare" è troppo piccola... meglio semplificare!

Innanzitutto, ripeschiamo le due variabili:
        boolean padLockLeft=false;
 boolean padLockRight=false;
mi sono scocciato con questa esterofilia! Semplifichiamone il nome.
 boolean lucchettoSin=false;
 boolean lucchettoDx=false;
Quindi lasciando sempre le immagini dei lucchetti creerò un onLongClickListener per ognuno...
Ci provo...

  luccListener = new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
    if(v==leftBottom){
     if(lucchettoSin==false){
      leftBottom.setImageResource(R.drawable.lucaperto);
      lucchettoSin=true;
     }else{
      leftBottom.setImageResource(R.drawable.lucchiuso);
      lucchettoSin=false;
     }
    }
    
    if(v==rightBottom){
     if(lucchettoDx==false){
      rightBottom.setImageResource(R.drawable.lucaperto);
      lucchettoDx=true;
     }else{
      rightBottom.setImageResource(R.drawable.lucchiuso);
      lucchettoDx=false;
     }
    }
    if(lucchettoSin==true && lucchettoDx==true){
     settingMode=true;
     mainLayout.setBackgroundColor(Color.GRAY);
    }
    if(lucchettoSin==false && lucchettoDx==false){
     settingMode=false;
     mainLayout.setBackgroundColor(Color.BLACK);
    }
    return true;
   }
  };
  
  leftBottom.setOnLongClickListener(luccListener);
  rightBottom.setOnLongClickListener(luccListener);
Ecco, finalmente funziona.

Ci vorrei aggiungere una vibrazione...

Da quello che ho capito, basta istanziare il vibratore (l'ho già fatto una volta ma con poca attenzione, quasi copiando)

Si deve andare a getSystemService.
Ecco il codice completo di vibrazione (da sperimentare):
  luccListener = new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
    if(v==leftBottom){
     if(lucchettoSin==false){
      leftBottom.setImageResource(R.drawable.lucaperto);
      lucchettoSin=true;
     }else{
      leftBottom.setImageResource(R.drawable.lucchiuso);
      lucchettoSin=false;
     }
    }
    
    if(v==rightBottom){
     if(lucchettoDx==false){
      rightBottom.setImageResource(R.drawable.lucaperto);
      lucchettoDx=true;
     }else{
      rightBottom.setImageResource(R.drawable.lucchiuso);
      lucchettoDx=false;
     }
    }
    
    Vibrator vibrator =(Vibrator)getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
    vibrator.vibrate(500);
    
    if(lucchettoSin==true && lucchettoDx==true){
     settingMode=true;
     mainLayout.setBackgroundColor(Color.GRAY);
    }
    if(lucchettoSin==false && lucchettoDx==false){
     settingMode=false;
     mainLayout.setBackgroundColor(Color.BLACK);
    }
    return true;
   }
  };
  
  leftBottom.setOnLongClickListener(luccListener);
  rightBottom.setOnLongClickListener(luccListener);
Da provare su cellulare...

Funziona, anche con la vibrazione.

Matrix

Che cosa è una Matrix?

Proviamo a crearla e usarla come da alcuni esempi.

Ma poi come si usa? Con Bitmap.createBitmap... e com'è questo metodo?

Ecco, ho ricostruito il tutto:
public class MainActivity extends Activity {
 
 ImageView imageView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  imageView=(ImageView)findViewById(R.id.imageView1);
  
  Matrix matrix=new Matrix();
  matrix.postScale(.1f,1f);
  
  Bitmap bitmap =BitmapFactory.decodeResource(getResources(), R.drawable.lucchiuso);
  
  Bitmap bmp=Bitmap.createBitmap(bitmap,0,0,500,500,matrix,false);
  imageView.setImageBitmap(bmp);
 
 } 
}
In questo caso, impostando come parametri del metodo postScale della Matrix .1 e 1, si ottiene un'immagine fortemente allungata.

Multitouch: ricominciando da zero.

Ancora sul "two fingers": voglio penetrare un po' più a fondo in questa singolare caratteristica della programmazione per cellulari, che ovviamente con il mouse non è ripetibile, almeno penso...

Aggrediamo la cosa!
Intanto mi predispongo un'Activity con un'immagine presa dalle risorse direttamente nel codice xml.
<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.idea.MainActivity" >

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:src="@drawable/lucchiuso" />

</RelativeLayout> 


Quindi la attribuisco a una variabile nel codice Java, e le attribuisco un listener...

Ecco l'immagine istanziata nel codice Java:
public class MainActivity extends Activity {
 
 ImageView imageView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  imageView=(ImageView)findViewById(R.id.imageView1);
  
 }

}
Creo ora il listener.
public class MainActivity extends Activity {
 
 ImageView imageView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  imageView=(ImageView)findViewById(R.id.imageView1);
  
  OnTouchListener onTouchListener=new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction()==MotionEvent.ACTION_DOWN){
     Log.d("PUNTATORE", "PRINCIPALE");
    }
    return false;
   }
  };
  
  imageView.setOnTouchListener(onTouchListener);
 }

}
che, nelle mie intenzioni dovrebbe dare, in LogCat, "PUNTATORE" "PRINCIPALE", che in effetti, verificato, risulta dare ogni volta che clicco sull'immagine:
04-15 01:04:36.059: D/PUNTATORE(3990): PRINCIPALE
04-15 01:04:37.227: D/PUNTATORE(3990): PRINCIPALE
04-15 01:04:38.193: D/PUNTATORE(3990): PRINCIPALE

Ma ora, in vista dei puntatori aggiuntivi, è bene che provi la cosa sul cellulare.
04-15 11:52:45.011: D/PUNTATORE(21255): PRINCIPALE

Anche qui funziona come previsto.
Ora, tenendo conto che esiste anche un MotionEvent per puntatori aggiuntivi, provo a usarlo...
  OnTouchListener onTouchListener=new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction()==MotionEvent.ACTION_DOWN){
     Log.v("PUNTATORE", "PRINCIPALE");
    }
    if(event.getAction()==MotionEvent.ACTION_POINTER_DOWN){
     Log.v("PUNTATORE","AGGIUNTIVO");
    }
    return true;
   }
  };
e non funziona, ossia mi dà solo il riscontro del puntatore principale. (Ho cambiato in Log.v per giocare un po' sulle visualizzazioni di LogCat, senza un motivo rilevante).

Ottengo, provando più volte, anche la visualizzazione del puntatore aggiuntivo, ma in un modo molto caotico in cui non riesco a individuare la regola...

Dopo un po' di "impazzimenti" vari, ho deciso di copiare pedissequamente un codice da un tutorial, basato su onTouchEvent e non su onTouch, e funziona come previsto.
Lo riscrivo di sana pianta, un po' trasformato a modo mio, per possederlo.

Riparto dall'inizio:
public class MainActivity extends Activity {
 
 ImageView imageView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  imageView=(ImageView)findViewById(R.id.imageView1);
 
 }
 
}
E vado:
public class MainActivity extends Activity {
 
 ImageView imageView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  imageView=(ImageView)findViewById(R.id.imageView1);
 
 }
 
 @Override
 public boolean onTouchEvent(MotionEvent event){
  
  switch (event.getAction()){
  case MotionEvent.ACTION_DOWN:
   Log.v("TOCCO","Primo dito giù");
   break;
  
  case MotionEvent.ACTION_POINTER_DOWN:
   Log.v("TOCCO","Secondo dito giù");
   break;
  }
  
  return true;
  
 }
 
}
E ora vediamo se funziona...

No! Non funziona!
Individua soltanto il tocco del primo dito!

Ma indagando sul codice originale che ho copiato prima, noto che (non me ne ero accorto facendo il pedissequo copia-incolla) esso usa MotionEvent.ACTION_MASK, che non ho assolutamente idea di cosa significhi: deve essere qui il problema...
Indaghiamo su questo misterioso ACTION_MASK...

Ho idea che c'entri il calcolo binario, a giudicare dall'operatore &.

...che infatti è l'operatore logico AND!
Vediamo ora i codici numerici delle varie MotionEvent...

I valori numerici delle costanti MotionEvent sono:
  • ACTION_DOWN 0x00000000
  • ACTION_POINTER_DOWN 0x00000005.
  • ACTION_MASK 0x000000ff
Non capisco però a che scopo mascherare in questo modo una costante che ha tutti zeri tranne l'ultimo byte.

Non voglio addentrarmi troppo, per ora: scrivo meglio il codice:
public class MainActivity extends Activity {
 
 ImageView imageView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  imageView=(ImageView)findViewById(R.id.imageView1);
 
 }
 
 @Override
 public boolean onTouchEvent(MotionEvent event){
  int azione=event.getAction() & MotionEvent.ACTION_MASK;
  switch (azione){
  case MotionEvent.ACTION_DOWN:
   Log.v("TOCCO","Primo dito giù");
   break;
  
  case MotionEvent.ACTION_POINTER_DOWN:
   Log.v("TOCCO","Secondo dito giù");
   break;
  }
  
  return true;
  
 }
 
}
Ora funziona!

giovedì 14 aprile 2016

Conseguenze del nuovo tipo di "apertura" del settingMode nella mia applicazione mediante "Drag Drop di lucchettii"

Tengo memoria del complicato processo a quattro viti che avevo ingegnato per passare alla modalità settingMode nella mia App.
 //EVENT LISTENER DELLE VITI PER PASSARE A SETTINGMODE 
  screwOpen = new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
     Vibrator vib = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
     // Vibrate for 500 milliseconds
     vib.vibrate(500); 
    
     if(settingMode==false){
      if(v==leftTop) {
       if(openFlag==0){
        openFlag=1;
        leftTop.setImageResource(R.drawable.unscrewed);
       
       }
       else if(openFlag==1){
        openFlag=0;
        leftTop.setImageResource(R.drawable.screwed);
       }
            
      }
      if(v==rightTop) {
       if(openFlag==1){
        openFlag=2;
        rightTop.setImageResource(R.drawable.unscrewed);
       
       }
       else if(openFlag==2){
        openFlag=1;
        rightTop.setImageResource(R.drawable.screwed);
       }
      }
      if(v==leftBottom){
       if(openFlag==2){
        openFlag=3;
        leftBottom.setImageResource(R.drawable.unscrewed);
       
       }
       else if(openFlag==3){
        openFlag=2;
        leftBottom.setImageResource(R.drawable.screwed);
       }
      }
      if(v==rightBottom && openFlag==3) { 
      rightBottom.setImageResource(R.drawable.unscrewed);
      settingMode=true; 
      bttIniziale.setVisibility(View.INVISIBLE);
      openFlag=0;
     } 
     
     } // fine di if(settingMode==false)
    
     else{
      leftTop.setImageResource(R.drawable.screwed);
      rightTop.setImageResource(R.drawable.screwed);
      leftBottom.setImageResource(R.drawable.screwed);
      rightBottom.setImageResource(R.drawable.screwed);
      settingMode=false;
      for(int i=0;i<mainLayout.getChildCount();i++){
       ((ViewGroup)mainLayout.getChildAt(i)).getChildAt(numEtichetta).setBackgroundColor(Color.WHITE);
      }
      button.setVisibility(View.INVISIBLE);
      imgButton.setVisibility(View.INVISIBLE);
      if(currentCat.equals("iniziale")) bttIniziale.setVisibility(View.INVISIBLE);
      else bttIniziale.setVisibility(View.VISIBLE);
     
     }
     
     return true;
   }
   
  };
Quindi lo sfascio.
Ecco il nuovo Listener:
 //EVENT LISTENER DELLE VITI PER PASSARE A SETTINGMODE 
  View.OnTouchListener padTouchListener=new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    DragShadowBuilder shadowBuilder=new DragShadowBuilder(v);
    v.startDrag(null, shadowBuilder, v, 0);
    return true;
   }
  };
  
  View.OnDragListener padDragListener=new View.OnDragListener() {
   
   @Override
   public boolean onDrag(View v, DragEvent event) {
    if(event.getAction()==DragEvent.ACTION_DROP){
     
     if((v==leftBottom && event.getLocalState()==rightTop) 
       || (v==rightBottom && event.getLocalState()==leftTop)) return false;
     
     //se settingMode è falso, le immagini di chiusura vanno modificate
     //in apertura, se settingMode è vero deve accadere il
     //contrario.
     //Il valore deve diventare sempre true.
     //quindi alla fine se i valori sono ambedue true allora se
     //settingMode è falso diventa vero e viceversa.
     
     if(v==leftBottom && event.getLocalState()==leftTop){
      if(padLockLeft==false){
       ((ImageView)v).setImageResource(R.drawable.lucaperto);
       padLockLeft=true;
      }else{
       ((ImageView)v).setImageResource(R.drawable.lucchiuso);
       padLockLeft=false;
      }
     }
     
     if(v==rightBottom && event.getLocalState()==rightTop){
      if(padLockRight==false){
       ((ImageView)v).setImageResource(R.drawable.lucaperto);
       padLockRight=true;
      }else{
       ((ImageView)v).setImageResource(R.drawable.lucchiuso);
       padLockRight=false;
      }
     }
     
     if(padLockLeft==true && padLockRight==true) {
      settingMode=true;
      button.setVisibility(View.INVISIBLE);
      imgButton.setVisibility(View.INVISIBLE);
      bttIniziale.setVisibility(View.INVISIBLE);
      
     }
     if(padLockLeft==false && padLockRight==false) {
      settingMode=false;
      
 
     }
      
    }
    return true;
   }
  };
  
  leftTop.setOnTouchListener(padTouchListener);
  rightTop.setOnTouchListener(padTouchListener);
  leftBottom.setOnDragListener(padDragListener);
  rightBottom.setOnDragListener(padDragListener);
Restano dei dubbi sulla gestione dei pulsanti e del colore giallo dell'etichetta della View in questione.
Nel precedente listener come mi ero regolato?
Ecco: in caso ci troviamo nella condizione settingMode = true e passiamo a settingMode = false, c'era questo codice:
 for(int i=0;i<mainLayout.getChildCount();i++){
       ((ViewGroup)mainLayout.getChildAt(i)).getChildAt(numEtichetta).setBackgroundColor(Color.WHITE);
      }
      button.setVisibility(View.INVISIBLE);
      imgButton.setVisibility(View.INVISIBLE);
      if(currentCat.equals("iniziale")) bttIniziale.setVisibility(View.INVISIBLE);
      else bttIniziale.setVisibility(View.VISIBLE);
e cioè:
Per ogni View contenuta nel mainLayout, viene colorata in bianco l'etichetta:
questo codice:
 for(int i=0;i<mainLayout.getChildCount();i++){
       ((ViewGroup)mainLayout.getChildAt(i)).getChildAt(numEtichetta).setBackgroundColor(Color.WHITE);
      }
Quindi il bottone Cancella viene reso invisibile:
      button.setVisibility(View.INVISIBLE);
Quindi viene reso invisibile il bottone Cambio Immagine:
      imgButton.setVisibility(View.INVISIBLE);
e invece viene reso visibile il bottone che riporta la pseudoschermata iniziale se non siamo nella pseudoschermata iniziale:
      if(currentCat.equals("iniziale")) bttIniziale.setVisibility(View.INVISIBLE);
      else bttIniziale.setVisibility(View.VISIBLE);


Proviamo a sintetizzare tutto in una sola funzione.
Eccola:
 private void buttonsNoSettingMode(){
   for(int i=0;i<mainLayout.getChildCount();i++){
    ((ViewGroup)mainLayout.getChildAt(i)).getChildAt(numEtichetta).setBackgroundColor(Color.WHITE);
   }
   button.setVisibility(View.INVISIBLE);
   imgButton.setVisibility(View.INVISIBLE);
   if(currentCat.equals("iniziale")) bttIniziale.setVisibility(View.INVISIBLE);
   else bttIniziale.setVisibility(View.VISIBLE);
 }
che viene chiamata qui, quando il listener dei lucchetti cambia il settingMode a false:
     if(padLockLeft==false && padLockRight==false) {
      settingMode=false;
      
      buttonsNoSettingMode();
      //toglie il giallo dall'etichetta, nasconde
      //i pulsanti cancella e cambia immagine,
      //mostra il pulsante che torna alla schermata iniziale
     }

Ho poi ottimizzato la comparsa del button Modifica Immagine, che appare al click sulla view e sparisce quando viene cliccata un'altra view o il mainLayout

La situazione in cui viene cliccata un'altra View era già stata sistemata nell'ambito dell'OnTouchListener delle View, getAction ACTION_DOWN:
      ActiveOne=(LinearLayout)v;
      for(int i=0;i<mainLayout.getChildCount();i++){
       ((ViewGroup)mainLayout.getChildAt(i)).getChildAt(numEtichetta).setBackgroundColor(Color.WHITE);
       ActiveOne.getChildAt(numEtichetta).setBackgroundColor(Color.YELLOW);
      }
      button.setVisibility(View.VISIBLE);
      imgButton.setVisibility(View.VISIBLE);
vengono reimbiancate tutte le views, quindi quella attiva, attribuita alla variabile ActiveOne, viene colorata di giallo nella sua etichetta; si rendono visibili il button e l'imgButton che agiscono su ActiveOne.

Invece quando si clicca su mainLayout si devono imbiancare tutte le views senza colorarne di giallo alcuna e devono sparire i buttons (codice nell'OnTouchListener del mainLayout):
    ActiveOne=null;
    for(int i=0;i<mainLayout.getChildCount();i++){
     ((ViewGroup)mainLayout.getChildAt(i)).getChildAt(numEtichetta).setBackgroundColor(Color.WHITE);
    }
    button.setVisibility(View.INVISIBLE);
    imgButton.setVisibility(View.INVISIBLE);
Sperimentiamo: da modalità settingMode false a settingMode true: ho creato un'altra view, e cliccando l'una o l'altra si colora di giallo la sua etichetta, mentre cliccando sul mainLayout si decolorano tutte le etichette delle views, e spariscono i bottoni.

Ora passo dalla selezione di una di queste views alla modalità settingMode false, per mezzo dei lucchetti.
Anche questo funziona.

Ora, per vedere se nel passaggio da settingMode true a settingMode false in pseudoschermate diverse da quella iniziale appare il button che riporta allo stato iniziale.
Creo un'immagine categoria.
E ho trovato il primo bugghetto: il pulsante non sparisce passando a settingMode true.
La funzione buttonsNoSettingMode è stata chiamata quando per mezzo dei lucchetti si passa da settingMode true a settingMode false, e in quel caso funziona. Però non funziona la sua sparizione quando si va a settingMode true.
Ho ovviato in questo modo, nel listener dei lucchetti:
     if(padLockLeft==true && padLockRight==true) {
      settingMode=true;
      bttIniziale.setVisibility(View.INVISIBLE);
      
      
     }
     if(padLockLeft==false && padLockRight==false) {
      settingMode=false;
      
      buttonsNoSettingMode();
Se già è invisibile in quanto siamo nella pseudoschermata iniziale, è solo un innocuo pleonasmo.

Drag Drop con una chiave che apre e chiude un lucchetto.

Qual è il modo migliore di rappresentare un'apertura con due "serrature" che usano il meccanismo del Drag Drop?

Da una parte metterei un lucchetto e dall'altra parte una chiave.
Trascino la chiave sul lucchetto, che risulta aperto.
Quindi trascino di nuovo la chiave sul lucchetto aperto e ridiventa chiuso.
Si potrebbe provare...

Costruiamola adesso...

Per prima cosa, devo trovare le chiavi e i lucchetti, e metterli come risorse.
Ne ho trovati due, aperto e chiuso, del medesimo modello e non protetti da copyright.
Adesso li metto in risorse, dopo averli rinominati "lucchiuso" e "lucaperto" perché i nomi dei files erano complessi e poco memorizzabili.

Ora devo trovare una chiaFave.
Seguo lo stesso procedimento di prima (importante l'assenza di copyright perché li voglio inserire in un'App pubblicabile).

Trovata! Essendo un file jpg la converto in png perché mi pare di ricordare che in Android vengono meglio trattate le png, anche se non ne sono sicuro...

La inserisco nelle risorse.


Ora scrivo il codice che ha questo scopo: trascinando la chiave sopra il lucchetto chiuso, questo si trasforma in un lucchetto aperto, e viceversa.
Iniziamo...

Fatto! Mi sembra una soluzione decente:
public class MainActivity extends Activity {
 
 ImageView imgChiave, imgLucchetto;
 boolean settingMode=false;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  //INIZIALIZZAZIONI
  imgChiave=(ImageView)findViewById(R.id.imageView1);
  imgChiave.setImageResource(R.drawable.chiave);
  imgLucchetto=(ImageView)findViewById(R.id.imageView2);
  imgLucchetto.setImageResource(R.drawable.lucchiuso);
  
  //LISTENERS
  
  View.OnTouchListener onTouchListener=new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    DragShadowBuilder shadowBuilder=new DragShadowBuilder(v);
    v.startDrag(null, shadowBuilder, v, 0);
    return true;
   }
  };
  
  View.OnDragListener onDragListener=new View.OnDragListener() {
   
   @Override
   public boolean onDrag(View v, DragEvent event) {
    if(event.getAction()==DragEvent.ACTION_DROP){
     if(settingMode==false){
      ((ImageView)v).setImageResource(R.drawable.lucaperto);
      settingMode=true;
     }else{
      ((ImageView)v).setImageResource(R.drawable.lucchiuso);
      settingMode=false;
     }
    }
    return true;
   }
  };
  
  imgChiave.setOnTouchListener(onTouchListener);
  imgLucchetto.setOnDragListener(onDragListener);
}

mercoledì 13 aprile 2016

Drag-Drop in android.

Ho iniziato ad aggredire il Drag-Drop.
Riscrivo il codice per mio esercizio...

public class MainActivity extends Activity {
 
 OnTouchListener onTouchListener;
 OnDragListener onDragListener;
 TextView testo;
 Button bottone;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  testo=(TextView)findViewById(R.id.textView1);
  bottone=(Button)findViewById(R.id.button1);
  
  onTouchListener = new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    DragShadowBuilder shadowBuilder=new DragShadowBuilder(v);
    v.startDrag(null, shadowBuilder, v, 0);
    return false;
   }
  };
  
  testo.setOnTouchListener(onTouchListener);
  
 }

}
Ho derogato dal tutorial in quanto non ho usato nessuna variabile di tipo ClipData, e ho impostato null nel primo parametro di StartDrag.
Me l'ha accettato lo stesso, senza segnalarmi errori di sintassi.
Per il momento la variabile di tipo OnDragListener resta inutilizzata così come la variabile oggetto bottone: voglio solo testare se funziona il trascinamento.




Sì, funziona!
E' stata creata l' "ombra" della TextView che reca la scritta "Hello World".
Ora scrivo il codice che riceve il trascinamento sul Button, utilizzando le variabili dichiarate prima:
public class MainActivity extends Activity {
 
 OnTouchListener onTouchListener;
 OnDragListener onDragListener;
 TextView testo;
 Button bottone;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  testo=(TextView)findViewById(R.id.textView1);
  bottone=(Button)findViewById(R.id.button1);
  
  onTouchListener = new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    DragShadowBuilder shadowBuilder=new DragShadowBuilder(v);
    v.startDrag(null, shadowBuilder, v, 0);
    return false;
   }
  };
  
  onDragListener = new View.OnDragListener() {
   
   @Override
   public boolean onDrag(View v, DragEvent event) {
    if(event.getAction()==DragEvent.ACTION_DRAG_ENTERED){
     Log.d("DRAGAGGIO","RICEVUTO");
    }
    return false;
   }
  };
  
  testo.setOnTouchListener(onTouchListener);
  bottone.setOnDragListener(onDragListener);
  
  
 }
 


}
E vediamo se al trascinamento della TextView sul primo Button viene scritto il messaggio in LogCat:

No: non funziona.
Proviamo a impostare a true onDrag():
  onDragListener = new View.OnDragListener() {
   
   @Override
   public boolean onDrag(View v, DragEvent event) {
    if(event.getAction()==DragEvent.ACTION_DRAG_ENTERED){
     Log.d("DRAGAGGIO","RICEVUTO");
    }
    return true;
   }
  };
e funziona:
04-12 23:46:55.145: D/DRAGAGGIO(3448): RICEVUTO

Devo ancora capire bene come "funzioni" il valore booleano di certe funzioni.

martedì 12 aprile 2016

Aggiunta di "cambia immagine" all'applicazione.

Pongo un nuovo bottone che dovrebbe permettermi di modificare l'immagine di una View.
Intanto lo metto sull'Activity, fisicamente.

<Button
    android:id="@+id/imgButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignTop="@+id/button1"
    android:layout_marginRight="40dp"
    android:layout_toLeftOf="@+id/button1"
    android:background="#afa"
    android:textSize="10sp"
    android:text="MOD. IMMAGINE" />


Ora lo attribuisco a una variabile in MainActivity:
Button imgButton;

.....

imgButton=(Button)findViewById(R.id.imgButton);
e gli creo un Listener.
Ma per creare un Listener devo prima avere le idee chiare su cosa dovrà fare questo Listener...
Riassumiamo qui i suoi compiti:

Selezionare un'immagine presente in external storage, quindi andare nel database e, laddove categoria ed etichetta della View attiva corrispondano ai campi, updatare il campo immagine.

//LISTENER PER IL BOTTONE IMGBUTTON CHE MODIFICA LE IMMAGINI:
  imgButton.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Intent intent=new Intent();
    intent.setAction(Intent.ACTION_PICK);
    intent.setData(Uri.parse("content://media/external/images/media"));
    startActivityForResult(intent, 0);
   }
  });
E finora funziona.


Adesso devo far apparire il pulsante solo in modalità settingMode...

Fatto.
Bene, ho scritto la base di onActivityResult.
Ora devo convertire l'immagine specificata dal path in un array di bytes per memorizzarla nel database updatando il record.

Ecco cosa ho scritto finora:
//ONACTIVITYRESULT PER IMGBUTTON CHE MODIFICA LE IMMAGINI
 
 protected void onActivityResult(int requestCode  , int resultMode, Intent data){
  Uri uri= data.getData();
  String path=Funzioni.UriToPath(this, uri);
  byte[] b=Funzioni.PathToByteArray(path, 100, 50);
  
 }
Ottengo il path dell'immagine convertendo l'Uri mediante la mia funzione UriToPath;
quindi prendo l'immagine e la converto in array di bytes mediante la mia funzione PathToByteArray.

Ora devo updatare il database.
Per fare questo, devo prendere categoria ed etichetta della View attiva, ossia di ActiveOne, e creare un metodo del gestore di database che permetta di updatare il campo di tipo blob che ospita i bytes dell'immagine.
Proviamo...

Ecco, ho scritto una funzione del Database Helper che updata l'immagine date etichetta e categoria.
 public void imgUpdate(String etichetta, String categoria, byte[] byteArray){
  SQLiteDatabase db=this.getWritableDatabase();
  ContentValues values = new ContentValues();
  values.put("immagine", byteArray);
  db.update("tabella", values, "etichetta='"+etichetta+"' and categoria='"+categoria+"'", null);
 }
Ora proverò a usarla.
Ecco, ho scritto tutto e pare che funzioni:
 protected void onActivityResult(int requestCode  , int resultMode, Intent data){
  Uri uri= data.getData();
  String path=Funzioni.UriToPath(this, uri);
  byte[] b=Funzioni.PathToByteArray(path, 100, 50);
  
  //estraiamo il testo di etichetta e categoria
  TextView et=(TextView)((ViewGroup)ActiveOne).getChildAt(numEtichetta);
  TextView cat=(TextView)((ViewGroup)ActiveOne).getChildAt(numCategoria);
  helper.imgUpdate(et.getText().toString(), cat.getText().toString(), b);
  refreshScreen();
 }

venerdì 8 aprile 2016

Ciclo di vita dei BroadcastReceiver registrati su Manifest

Ed ecco come i BroadcastReceiver si rendono poi indipendenti dall'esecuzione o meno dell'App che li ha lanciati.
Ho creato una nuova applicazione che chiami i due BroadcastReceiver con la stessa Action:
 Intent intent=new Intent();
 intent.setAction("android.intent.action.Seconda");
 sendBroadcast(intent);
e lancio questa nuova applicazione curandomi bene che la prima non fosse più in esecuzione sull'emulatore.
I due BroadcastReceiver, il cui codice è rispettivamente:
public class receiver extends BroadcastReceiver{

 @Override
 public void onReceive(Context context, Intent intent) {
  Log.d("BROADCAST","RICEVUTO");
  
 }

}
...e:
public class receiver2 extends BroadcastReceiver{

 @Override
 public void onReceive(Context context, Intent intent) {
  Log.d("SECONDO BROADCAST", "RICEVUTO");
  
 }

}
vengono eseguiti ugualmente, essendo rimasti attivi sul dispositivo anche dopo la chiusura dell'applicazione nella quale sono stati creati:
04-08 10:35:12.325: D/BROADCAST(2317): RICEVUTO
04-08 10:35:12.353: D/SECONDO BROADCAST(2317): RICEVUTO


Sono andato poi a cambiare il loro codice per fare in modo che lanciassero dei Toast anziché scrivere in Logcat:
public class receiver extends BroadcastReceiver{

 @Override
 public void onReceive(Context context, Intent intent) {
  Toast.makeText(context, "PRIMO BROADCASTRECEIVER", Toast.LENGTH_LONG).show();
  
 }

}
e
public class receiver2 extends BroadcastReceiver{

 @Override
 public void onReceive(Context context, Intent intent) {
  Toast.makeText(context, "SECONDO BROADCASTRECEIVER", Toast.LENGTH_LONG).show();
  
 }

}
ma, rieseguendo il programma, non ottengo nessun Toast, bensì ancora la scrittura in LogCat:
04-08 10:39:56.533: D/BROADCAST(2317): RICEVUTO
04-08 10:39:56.537: D/SECONDO BROADCAST(2317): RICEVUTO

Questo testimonia che una volta registrato sul Manifest un BroadcastReceiver continua a funzionare indipendentemente dall'applicazione che l'ha generato.
E se io provo a eliminare l'apk dell'App che ha generato i BroadcastReceiver?

Fatto! Ho eliminato gli apk di ambedue le applicazioni, quella che ha creato i BroadcastReceiver e quella nuova che li richiamava.
Adesso faccio ripartire la nuova app che li richiamava, e vediamo se li richiama ancora...

Sembrerebbe di no...

Faccio ripartire la prima applicazione... ma stavolta con il codice dei BroadcastReceiver modificato per i Toast anziché per la scrittura in LogCat

Ed ecco cosa succede:





Questo significa che disinstallando dal dispositivo l'App che ha registrato il BroadcastReceiver si de-registra anche esso.
Voglio fare un'altra prova: creo un Intent con un'action immaginaria inesistente fra i BroadcastReceiver, per vedere se ottengo errore o no: dal momento che quando il BroadcastReceiver, nella mia ipotesi, era de-registrato, non ho ottenuto messaggi di errore, non vorrei aver condotto male la prova essendo il BroadcastReceiver comunque esistente dopo la disinstallazione dell'app creante...
  Intent intent=new Intent();
  intent.setAction("android.intent.action.Fasullo");
  sendBroadcast(intent);
No: non ottengo messaggi di errore.
Torno ad eseguire la nuova App con la precedente action:
  Intent intent=new Intent();
  intent.setAction("android.intent.action.Seconda");
  sendBroadcast(intent);
...e ottengo di nuovo i Toast! Credo di poter avere la conferma piena della mia ipotesi.

Due BroadcastReceiver con la stessa Action richiamati da un Intent.

Cosa succede con due BroadcastReceiver aventi la stessa action?

Ho creato un nuovo BroadcastReceiver:
public class receiver2 extends BroadcastReceiver{

 @Override
 public void onReceive(Context context, Intent intent) {
  Log.d("SECONDO BROADCAST", "RICEVUTO");
  
 }

}
Ora lo registro nel Manifest, dove è già registrato quello vecchio:
        <receiver android:name="receiver">
            <intent-filter>
                <action android:name="android.intent.action.Seconda" />
            </intent-filter>
        </receiver>
        
        <receiver android:name="receiver2">
            <intent-filter>
             <action android:name="android.intent.action.Seconda" />
            </intent-filter>
        </receiver> 
Ora uso il sendBroadcast con un Intent che abbia la action comune di ambedue:
  Intent intent=new Intent();
  intent.setAction("android.intent.action.Seconda");
  sendBroadcast(intent);
Vediamo...

Ecco il LogCat: come prevedevo, viene eseguita l'azione di ambedue.
04-08 09:31:29.678: D/BROADCAST(2317): RICEVUTO
04-08 09:31:29.727: W/EGL_emulation(2317): eglSurfaceAttrib not implemented
04-08 09:31:29.728: W/OpenGLRenderer(2317): Failed to set EGL_SWAP_BEHAVIOR on surface 0xa6c05880, error=EGL_SUCCESS
04-08 09:31:29.787: D/SECONDO BROADCAST(2317): RICEVUTO

giovedì 7 aprile 2016

Approfondimento sull'action negli Intents, per chiamare un'Activity o un BroadcastReceiver.

Adesso faccio un esperimento diverso.
Se è vero che un Intent implicito può chiamare sia un'Activity che un BroadcastReceiver, voglio vedere cosa succede se creo un'Activity e un BroadcastReceiver con la stessa action.

 <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".Seconda"
            android:label="@string/title_activity_seconda" >
            <intent-filter>
                <action android:name="android.intent.action.Seconda" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity
            android:name=".Altra"
            android:label="@string/title_activity_altra" >
            <intent-filter>
                <action android:name="android.intent.action.Seconda" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <receiver android:name="receiver">
            <intent-filter>
                <action android:name="android.intent.action.Seconda" />
            </intent-filter>
        </receiver> 
Proviamo ora con sendBroadcast:
Questo è il codice della classe che estende BroadcastReceiver:
public class receiver extends BroadcastReceiver{

 @Override
 public void onReceive(Context context, Intent intent) {
  Log.d("BROADCAST","RICEVUTO");
  
 }

}
che dovrebbe stamparmi "BROADCAST" "RICEVUTO" in LogCat.

E adesso uso l'Intent:
  Intent intent=new Intent();
  intent.setAction("android.intent.action.Seconda");
  sendBroadcast(intent);
Vediamo...

04-06 23:09:59.693: D/BROADCAST(3727): RICEVUTO

Perfetto!
Invece, con Activity:
  Intent intent=new Intent();
  intent.setAction("android.intent.action.Seconda");
  startActivity(intent);
Proviamo...

Ecco:

Chiamare Activities con Intent impliciti.

Ecco, adesso che ho imparato a fare gli Intent espliciti, voglio farli impliciti, ossia chiamare Activities per "azione" e non per nome.

Ecco il file Manifest di un'applicazione:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.idea"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="18"
        android:targetSdkVersion="23" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".Seconda"
            android:label="@string/title_activity_seconda" >
            <intent-filter>
                <action android:name="android.intent.action.Seconda" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest> 
...in cui ogni Activity, oltre ad avere un name, ha anche un intent-filter nel quale è specificata una action.
Proviamo a chiamare l'Activity Seconda con l'Intent implicito, specificando l'action "android.intent.action.Seconda".
  Intent intent=new Intent();
  intent.setAction("android.intent.action.Seconda");
  startActivity(intent);
E proviamo...

Funziona egregiamente!

Adesso una sfida più difficile: creiamo un'altra Activity e impostiamoci la stessa action.
Ho creato un'altra activity chiamata "Altra", ed eccola nel Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.idea"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="18"
        android:targetSdkVersion="23" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".Seconda"
            android:label="@string/title_activity_seconda" >
            <intent-filter>
                <action android:name="android.intent.action.Seconda" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity
            android:name=".Altra"
            android:label="@string/title_activity_altra" >
            <intent-filter>
                <action android:name="android.intent.action.Seconda" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>

</manifest> 
...ho dovuto aggiungere manualmente l'intent-filter.
Questa ha la stessa action dell'Activity "Seconda".
Vediamo cosa succede usando l'Intent implicito con la stessa action condivisa da due Activities:

Ecco! E' quello che mi aspettavo!



Come quando ad esempio clicco su un'immagine e mi appare la casella di dialogo che mi invita a scegliere con quale applicazione aprire l'immagine.
Perfetto!

mercoledì 6 aprile 2016

Un po' di "analisi grammaticale" dell'Intent...

Nel Manifest...
Creo un'Activity mediante il wizard di Eclipse, e questa mi viene aggiunta nel Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.idea"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="18"
        android:targetSdkVersion="23" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".Seconda"
            android:label="@string/title_activity_seconda" >
        </activity>
    </application>

</manifest> 
Aggiungo a questa nuova Activity un intent-filter.
        <activity
            android:name=".Seconda"
            android:label="@string/title_activity_seconda" >
            <intent-filter>
                <action android:name="android.intent.action.Seconda" />
            </intent-filter>
        </activity> 
E ora provo a chiamarla con un Intent esplicito...
  Intent intent=new Intent(this, Seconda.class);
  startActivity(intent);
E funziona, passando a mostrarmi l'activity Seconda.
Adesso non metto il Context e la classe di destinazione come parametri del costruttore dell'Intent, ma li setto usando il metodo setClass della classe Intent:
  Intent intent=new Intent();
  intent.setClass(this, Seconda.class);
  startActivity(intent);
e vediamo...

Funziona ancora.

Ora provo a usare setComponent:
  Intent intent=new Intent();
  ComponentName component=new ComponentName(this, Seconda.class);
  intent.setComponent(component);
  startActivity(intent);
Praticamente il parametro di setComponent è esattamente lo stesso di quello di setClass, che poi sarebbe anche lo stesso del costruttore di Intent.

Invece, usando setClassName...
  Intent intent=new Intent();
  intent.setClassName("com.example.idea","com.example.idea.Seconda");
  startActivity(intent);
...che funziona lo stesso!

E' il caso di mettere ordine in questi concetti.
Ipotizzo in una specie di pseudocodice la classe Intent:
class Intent{
      public Intent(Context context, Class class){
      }

      public setClass(Context context, Class class){
      }

      public setComponent(ComponentName componentName){
      }

      public setClassName(String packageName, String className)}
      }
Questo costruttore e questi metodi del costruttore chiamano la classe in modo esplicito.

E almeno questo lo abbiamo capito... finora.
In questo uso dell'Intent non si parla di action e di category, ma si fa solo riferimento al package e alla classe.
Ma... si può chiamare un'activity usando l'action invece del nome di questa?

martedì 5 aprile 2016

Un Service con un thread.

Adesso che ho un service, provo a fargli fare qualcosa, dopodiché si distrugge spontaneamente, secondo l'esempio che ho trovato in rete.

Eccolo:
public class Servizio extends Service{

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
 @Override
 public int onStartCommand(Intent intent,int flags, int startId){
  Log.v("SERVICE","PARTITO");
  new Thread (new Runnable(){

   @Override
   public void run() {
    for(int i=0;i<5;i++){
     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }
    stopSelf();
    
   }
   
  }).start();
  
  return Service.START_STICKY;
  
 }
 
 @Override
 public void onDestroy(){
  Log.v("SERVICE","DISTRUTTO");
 }

}


Meglio ancora questo con il contatore:
 @Override
 public int onStartCommand(Intent intent,int flags, int startId){
  Log.v("SERVICE","PARTITO");
  new Thread (new Runnable(){

   @Override
   public void run() {
    for(int i=0;i<5;i++){
     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
     Log.v("CONTATORE",""+i);
    }
    stopSelf();
    
   }
   
  }).start();
  
  return Service.START_STICKY;
  
 }
che dà un numero ogni secondo:
04-05 15:06:12.177: V/SERVICE(2323): PARTITO
04-05 15:06:13.185: V/CONTATORE(2323): 0
04-05 15:06:14.196: V/CONTATORE(2323): 1
04-05 15:06:15.206: V/CONTATORE(2323): 2
04-05 15:06:16.215: V/CONTATORE(2323): 3
04-05 15:06:17.226: V/CONTATORE(2323): 4
04-05 15:06:17.236: V/SERVICE(2323): DISTRUTTO

Services in Android

Che sono i services?
Mi esercito a creare un service.

Creo una nuova classe, e la chiamo "Servizio".
public class Servizio {

}
Estendo la classe Service:
public class Servizio extends Service{

}
Eclipse mi dà una segnalazione di errore dicendomi di aggiungere i metodi non implementati, che aggiungo:
public class Servizio extends Service{

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }

}
(parallelamente a queste operazioni mi vengono importate le classi necessarie)

Se si tratta di un service "unbound", lascio che onBind continui a restituire null, e implemento altri metodi.

Ecco, ho implementato onStartCommand, con i parametri e tutto, come da tutorial.
Non ho idea, ancora, di cosa significhino i parametri flags e startId, e perché il valore restituito sia la costante START_STICKY. Vedremo in seguito...

public class Servizio extends Service{

 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
 @Override
 public int onStartCommand(Intent intent,int flags, int startId){
  return Service.START_STICKY;
  
 }
}


Ci aggiungo un segnale alla partenza del Service, su LogCat:
 @Override
 public int onStartCommand(Intent intent,int flags, int startId){
  Log.v("SERVICE","PARTITO");
  return Service.START_STICKY;
  
 }
Adesso implemento il metodo onDestroy, quello che viene chiamato alla distruzione del Service:
 @Override
 public void onDestroy(){
  Log.v("SERVICE","DISTRUTTO");
 }
...con il relativo segnale in LogCat.
Ora con due Buttons accendo e spengo il Service, senza dimenticare però di segnalarlo anche in Manifest:
        </activity>
        <service android:name=".Servizio" />
    </application>
<uses-permission android:name="android.permission.BLUETOOTH" />
</manifest> 
Questo è il codice dell'Activity dalla quale manipolo il Service:
public class MainActivity extends Activity {
 
 Button button1;
 Button button2;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  button1=(Button)findViewById(R.id.button1);
  button2=(Button)findViewById(R.id.button2);
  
  View.OnClickListener onClickListener =new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    if(v==button1) IniziaService();
    if(v==button2) StopService();
    
   }
  };
  
  button1.setOnClickListener(onClickListener);
  button2.setOnClickListener(onClickListener);
  
 }
 
 public void IniziaService(){
  startService(new Intent(this,Servizio.class));
 }


 public void StopService(){
  stopService(new Intent(this,Servizio.class));
 }
 
}
E andiamo a vedere...

04-05 12:25:12.570: V/SERVICE(2226): PARTITO
04-05 12:25:17.742: V/SERVICE(2226): DISTRUTTO


Perfetto!!!