JavascriptProva

domenica 31 gennaio 2016

Esercizio con finestra popup attivabile da pulsante o da menu.

Bene.
Esercizio sui menu e sulle PopupWindows...

public class MainActivity extends Activity {
 
 PopupWindow popup;
    LinearLayout popupLayout;
    TextView popupTextView;
    LinearLayout mainLayout;
    Button button;
    ImageView popupImageView;
    boolean click=true;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        
        
        setContentView(R.layout.activity_main);
        mainLayout =(LinearLayout) findViewById(R.id.rl);
        popup=new PopupWindow(this);
        popupLayout=new LinearLayout(this);
        popupTextView=new TextView(this);
        button=new Button(this);
        button.setText("MOSTRA FINESTRA");
        button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    if(click){
     popup.showAtLocation(mainLayout, Gravity.TOP, 10, 10);
     popup.update(100,100,300,300);
     click=false;
    }else{
     popup.dismiss();
     click=true;
    }
    
   }
  });
        mainLayout.addView(button);
        
        popupTextView.setText("FACCIA DA CULO");
        popupImageView=new ImageView(this);
        popupImageView.setImageResource(R.drawable.faciadecul);
        popupLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        popupLayout.setOrientation(LinearLayout.VERTICAL);
        popupLayout.addView(popupImageView);
        popupLayout.addView(popupTextView);
        popupLayout.setBackgroundColor(Color.YELLOW);
        popup.setContentView(popupLayout);
        
        
    }
  
    @Override
    public boolean onCreateOptionsMenu(Menu menu){
     getMenuInflater().inflate(R.menu.main, menu);
     return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item){
     int id=item.getItemId();
     if(id==R.id.action_settings){
      return true;
     }
     if(id==R.id.action_settings1){
      if(click){
    popup.showAtLocation(mainLayout, Gravity.TOP, 10, 10);
    popup.update(100,100,300,300);
    click=false;
   }else{
    popup.dismiss();
    click=true;
   }
     }
  return super.onOptionsItemSelected(item);
    } 
}


Ed ecco il risultato:

sabato 30 gennaio 2016

Comparsa di un'immagine al Long Click nelle coordinate dell'evento.

Un click lungo sul display vuoto, e si materializza una nuova immagine: potrebbe essere un'idea!
Devo studiare l'evento...

Ecco: questo è il codice che determina la comparsa di una nuova immagine dove viene esercitato un LongClick:
public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  final AbsoluteLayout main=(AbsoluteLayout) findViewById(R.id.main);
  final int[] X={0};
  final int[] Y={0};
  OnTouchListener onTouch = new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    X[0]=(int)event.getX();
    Y[0]=(int)event.getY();
    return false;
   }
  };
  main.setOnTouchListener(onTouch);
  
  OnLongClickListener onLong = new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
    ImageView immagine=new ImageView(getApplicationContext());
    immagine.setImageResource(R.drawable.ic_launcher);
    immagine.setLayoutParams(new AbsoluteLayout.LayoutParams(100,100,X[0]-100,Y[0]-50));
    main.addView(immagine);
    return false;
   }
  };
  main.setOnLongClickListener(onLong);
Ecco il filmato:

Creazione di una ScrollView programmaticamente

Ed ecco finalmente lo schema per creare una Scrollview programmaticamente:
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  ScrollView scroll=new ScrollView(this);
  scroll.setLayoutParams(new AbsoluteLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT,100,0));
  RelativeLayout relative=(RelativeLayout) findViewById(R.id.relative);
  ((ViewGroup)relative.getParent()).removeView(relative);
  scroll.addView(relative);
  setContentView(scroll);
  
 }
Ho dovuto rimuovere il RelativeLayout dal suo genitore per aggiungerlo alla ScrollView.
Così finalmente funziona.
Ora però la ScrollView devo studiarmela in modo da lasciare libero uno spazio a sinistra per i comandi.

...E' DA IERI SERA CHE STO IMPAZZENDO Finalmente sono arrivato alla soluzione!!!
Un passo fondamentale è stato l'uso di Hierarchy Viewer, uno strumento scaricato con l'SDK Android, che mi ha mostrato che la radice è un FrameLayout.
E poi non avevo capito che nell'impostare i parametri di una View contenuta in un Layout bisogna usare i parametri di questo Layout, per cui se voglio riprodurre programmaticamente quello che ho fatto nell'XML devo fare in modo che la ScrollView abbia parametri di tipo FrameLayout.LayoutParams!!!

Quindi il codice di cui sopra, impostando parametri di tipo AbsoluteLayout, non può funzionare, è sbagliato, pur mostrando la ScrollView a tutto schermo. Ecco il codice (ci aggiungo, perché doverosa, l'esclamazione abruzzese 'NGUL' A MAMMETE!!!)
public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  
  setContentView(R.layout.activity_main);
    ScrollView sv=new ScrollView(this);
    FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    params.setMargins(200,0, 0, 0);
    sv.setBackgroundColor(Color.CYAN);
    sv.setLayoutParams(params);
    ((ViewGroup)findViewById(android.R.id.content)).addView(sv);
 }
Ed ecco il pregevole risultato:


Però per essere fedele al codice che ho creato in XML devo inserire nella ScrollView un RelativeLayout.
Proviamoci:
public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  
  setContentView(R.layout.activity_main);
    ScrollView sv=new ScrollView(this);
    FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    params.setMargins(200,0, 0, 0);
    sv.setLayoutParams(params);
    sv.setBackgroundColor(Color.CYAN);
    RelativeLayout relative=new RelativeLayout(this);
    ImageView immagine=new ImageView(this);
    immagine.setImageResource(R.drawable.faciadecul);
    RelativeLayout.LayoutParams relParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
    relParams.setMargins(200,250,0,0);
    immagine.setLayoutParams(relParams);
    relative.addView(immagine);
    sv.addView(relative);
    ((ViewGroup)findViewById(android.R.id.content)).addView(sv);
 }
Ed ecco:



E lo scroll funziona!!!
Dato che c'è un po' di confusione, mi contrassegno i "pezzi" con codici di colore diversi...
public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  
  setContentView(R.layout.activity_main);
    ScrollView sv=new ScrollView(this);

    FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    params.setMargins(200,0, 0, 0);
    sv.setLayoutParams(params);

    sv.setBackgroundColor(Color.CYAN);


    RelativeLayout relative=new RelativeLayout(this);

    ImageView immagine=new ImageView(this);
    immagine.setImageResource(R.drawable.faciadecul);

    RelativeLayout.LayoutParams relParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
    relParams.setMargins(200,250,0,0);
    immagine.setLayoutParams(relParams);


    relative.addView(immagine);
    sv.addView(relative);
    ((ViewGroup)findViewById(android.R.id.content)).addView(sv);
 }
Ecco: per ogni view o layout distinguo:
  • creazione
  • attribuzione dei parametri
  • altro
Spero che mi aiuti a chiarirmi le idee e a rinfrescarmele quando in futuro dovessi rivisitare questo post per ripassare.

venerdì 29 gennaio 2016

Esercizio sul RelativeLayout: alignTop, alignBottom, marginLeft, marginTop, marginBottom

In RelativeLayout la definizione della posizione di una view è espressa da "istruzioni" che definiscono la posizione sull'asse X e sull'asse Y.
        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/hello_world" />

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"


            android:layout_alignTop="@+id/textView1"

            android:layout_marginLeft="135dp"
            android:layout_toRightOf="@+id/textView1"

            android:src="@drawable/faciadecul" /> 
Codice:
In arancio le istruzioni che definiscono la posizione relativamente a qualcos'altro sull'asse Y;
in rosa le istruzioni che misurano la posizione sull'asse Y.

in verdino le istruzioni che definiscono la posizione relativamente a qualcos'altro sull'asse X;
in ciano le istruzioni che misurano la posizione sull'asse X.


Così, questa ImageView è alignTop a textView1 ossia allineate al margine superiore di textView1.
Sull'asse X, invece, è posizionata

Immagine:


Provo a fare spostamenti: azzero la misura del margine sinistro:
        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignTop="@+id/textView1"
            android:layout_marginLeft="0dp"
            android:layout_toRightOf="@+id/textView1"
            android:src="@drawable/faciadecul" /> 
Ed ecco l'immagine:



In questo caso, avendo la ScrollView una proprietà android:layout_width="wrap_content" non occupa a destra tutta la superficie dell'activity.
Adesso la imposto a android:layout_width="match_parent"

Infatti:



Comunque lo scopo di eliminare la distanza fra ImageView e TextView è stato raggiunto riducendo a zero marginLeft di ImageView.


Ora invece di alignTop pongo la ImageView sull'asse Y relativamente a TextView con un alignBottom:
        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@+id/textView1"
            android:layout_marginLeft="0dp"
            android:layout_toRightOf="@+id/textView1"
            android:src="@drawable/faciadecul" /> 




Ecco, questa non me l'aspettavo: alignBottom significa che il bottom di una view si allinea con il bottom di un'altra, mentre io mi aspettavo che fosse il top dell'ImageView ad allinearsi con il bottom della TextView.

E se provassi a usare, insieme all'alignTop/alignBottom, una proprietà che misura la distanza?
Proviamo a usare alignTop insieme a un marginTop...

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignTop="@+id/textView1"
            android:layout_marginTop="200dp"
            android:layout_marginLeft="0dp"
            android:layout_toRightOf="@+id/textView1"
            android:src="@drawable/faciadecul" /> 




Sì, funziona!

ScrollView nell'XML in Android

Avere una ScrollView è estremamente importante per il mio progetto, allo scopo di superare il limite delle dimensioni ridotte del display dello smartphone.

Sono riuscito finalmente a creare, in XML, una ScrollView senza errori:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
 android:layout_height="wrap_content" >
    <RelativeLayout 
    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.lab12.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

</RelativeLayout>
</ScrollView> 
Le righe marcate sono quelle critiche, senza le quali si ottengono messaggi di errore vari: se sono assenti le prime due righe si ottiene un errore a compiletime con assenza di caricamento automatico di java.R (che è spesso una conseguenza di un errore nel XML), mentre se mancano le due successive righe ottengo un errore a runtime.

Così facendo, sono riuscito a mettere un RelativeLayout entro uno ScrollView.
Ora, però, devo provare lo ScrollView.
La miglior cosa che posso fare è metterci varie views, in modo da ripassare anche la sintassi del RelativeLayout, che non mi è ancora molto chiara...


Ho realizzato grandi cose: uno ScrollView all'interno dell'Activity, che lascia dello spazio a sinistra, utile per posizionare comandi.
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:background="@color/ciano"
 android:layout_marginLeft="300dp" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="722dp"
        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.lab12.MainActivity" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/hello_world" />

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignTop="@+id/textView1"
            android:layout_marginLeft="135dp"
            android:layout_toRightOf="@+id/textView1"
            android:src="@drawable/faciadecul" />

        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:src="@drawable/faciadecul" />
        
    </RelativeLayout>

</ScrollView> 


mercoledì 27 gennaio 2016

Creazione di un form per il caricamento di immagini in database e creazione di un bordo in un LinearLayout programmaticamente.

Prendere un'immagine dalla memoria e metterla in un database, insieme a etichetta e categoria.
Mi predispongo una tabella di database in cui vi siano i tre campi.
 class Helper extends SQLiteOpenHelper{

  public Helper(Context context) {
   super(context, "database", null, 1);
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("CREATE TABLE TABELLA(_ID INTEGER PRIMARY KEY,ETICHETTA TEXT,CATEGORIA TEXT,IMMAGINE BLOB)");
   
   
  }
  
  

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
 }
Ora creo il metodo save e il metodo query.
Ma prima forse mi conviene definire una classe che abbia tutte le proprietà
E qui la cosa si fa interessante per la gestione dell'immagine.
Credo che mi convenga memorizzare un array di bytes.

 class JImage{
  public String Etichetta;
  public String Categoria;
  public byte[] ImageByteArray;
 }
Questo presuppone che nell'immissione di dati venga creata prima la ByteArrayOutputStream e quindi dalla bitmap venga fatta la compressione con successiva conversione della ByteArrayOutputStream in byte di array.

Adesso provo a creare il metodo save().
  public void save(JImage jImage){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("ETICHETTA", jImage.Etichetta);
   values.put("CATEGORIA", jImage.Categoria);
   values.put("IMMAGINE", jImage.ImageByteArray);
   db.insert("TABELLA", null, values);
  }
Ecco.

Adesso devo costruire il meccanismo per l'immissione dell'immagine e delle sue caratteristiche.

Fatto.
Il codice che ho inserito è questo (completo):
public class Scelta extends Activity {

 //DICHIARAZIONI
 Bitmap bmp;
 JImage jImage;
 Helper helper;
 Button button;
 Adapter adapter;

 ImageView imageView;
 EditText editText;
 AutoCompleteTextView autoComplete;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_scelta);
  
  //DEFINIZIONI
  helper=new Helper(this);
  imageView=(ImageView) findViewById(R.id.imageView1);
  editText=(EditText) findViewById(R.id.editText1);
  autoComplete=(AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);
  button=(Button) findViewById(R.id.button1);
  button.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    Intent intent=new Intent();
    intent.setData(Uri.parse("content://media/external/images/media"));
    intent.setAction(Intent.ACTION_PICK);
    startActivityForResult(intent,0);
    
   }
  });
  
  adapter=new Adapter(this,helper.catQuery(" "));
  autoComplete.setAdapter(adapter);
  autoComplete.setOnEditorActionListener(new TextView.OnEditorActionListener() {
   
   @Override
   public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    ByteArrayOutputStream stream=new ByteArrayOutputStream();
    bmp.compress(CompressFormat.JPEG, 50, stream);
    byte[] blob=stream.toByteArray();
    jImage=new JImage();
    jImage.Etichetta=editText.getText().toString();
    jImage.Categoria=autoComplete.getText().toString();
    jImage.ImageByteArray=blob;
    helper.save(jImage);
    jImage=null;
    bmp=null;
    return false;
   }
  });
  

  
 }
 public void onActivityResult(int requestCode, int resultCode, Intent data){
  if(resultCode==RESULT_OK){
   BitmapFactory.Options opzioni=new BitmapFactory.Options();
   opzioni.inJustDecodeBounds=true;
   BitmapFactory.decodeFile(getPathFromUri(data.getData()),opzioni);
   int fattore=1;
   while(opzioni.outWidth/fattore>200 && opzioni.outHeight/fattore>200){
    fattore*=2;
   }
   opzioni.inSampleSize=fattore;
   opzioni.inJustDecodeBounds=false;
   bmp=BitmapFactory.decodeFile(getPathFromUri(data.getData()),opzioni);
   imageView.setImageBitmap(bmp);
   
   
   
   
  }
 }
 
 private String getPathFromUri(Uri uri){
  Cursor crs=getContentResolver().query(uri, null, null, null, null);
  crs.moveToFirst();
  int index=crs.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
  String s=crs.getString(index);
  return s;
 }
 class Helper extends SQLiteOpenHelper{

  public Helper(Context context) {
   super(context, "database", null, 1);
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("CREATE TABLE TABELLA(_id INTEGER PRIMARY KEY,ETICHETTA TEXT,CATEGORIA TEXT,IMMAGINE BLOB)");
   db.execSQL("CREATE TABLE TBLCATEGORIE(_id INTEGER PRIMARY KEY,CATEGORIA TEXT)");
  }
  
  public void save(JImage jImage){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("ETICHETTA", jImage.Etichetta);
   values.put("CATEGORIA", jImage.Categoria);
   values.put("IMMAGINE", jImage.ImageByteArray);
   db.insert("TABELLA", null, values);
   values.clear();
   values.put("CATEGORIA", jImage.Categoria);
   db.insert("TBLCATEGORIE", null, values);
   imageView.setImageBitmap(null);
   editText.setText(null);
   autoComplete.setText(null);
  }
  
  public Cursor query(){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor c = db.rawQuery("SELECT * FROM TABELLA", null);
   c.moveToFirst();
   return c;
  }
  
  public Cursor catQuery(String str){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor c=db.rawQuery("SELECT * FROM TBLCATEGORIE WHERE CATEGORIA LIKE '"+str+"%'", null);
   c.moveToFirst();
   return c;
  }
  

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   
   
  }
  
 }
 
 class JImage{
  public String Etichetta;
  public String Categoria;
  public byte[] ImageByteArray;
 }
 
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);
   
  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView) inflater.inflate(android.R.layout.simple_dropdown_item_1line, null);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(1));
   
  }
  
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   if(constraint!=null) return helper.catQuery(constraint+"");
   return null;
  }
  
  @Override
  public String convertToString(Cursor cursor){
   return cursor.getString(1);
   
  }
  
 }
Questo codice è stato trasferito su un'altra activity, chiamata "Scelta".
Ora vorrei visualizzare le immagini su dei LinearLayout.
Per prima cosa, trasferisco il codice su un'altra activity.

E l'ho fatto, ripassando l'Intent esplicito (per passare da un'activity all'altra.
  button=(Button) findViewById(R.id.button1);
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    
    Intent intent=new Intent(MainActivity.this,Scelta.class);
    startActivity(intent);
    
   }
  });
Bene.
Ora, all'avvio di questa Activity, voglio che le immagini salvate nel database vengano riportate su dei LinearLayout.
Devo ripassare le procedure per creare programmaticamente un linearlayout.
LinearLayout ll = new LinearLayout(this);
E poi?
Ecco il codice per aggiungere un Layout all'Activity:
  LinearLayout ll = new LinearLayout(this);
  ll.setBackgroundColor(Color.BLUE);
  ll.setOrientation(LinearLayout.VERTICAL);
  AbsoluteLayout.LayoutParams params=new AbsoluteLayout.LayoutParams(200,200,100,0);
  ll.setLayoutParams(params);
  AbsoluteLayout abs=(AbsoluteLayout)findViewById(R.id.lay);
  abs.addView(ll);
Ora potrebbe essere utile disegnare un bordo al layout.
Questa è una novità.
E ho trovato subito la soluzione
  LinearLayout ll = new LinearLayout(this);
  
  ll.setOrientation(LinearLayout.VERTICAL);
  AbsoluteLayout.LayoutParams params=new AbsoluteLayout.LayoutParams(200,200,100,0);
  ll.setLayoutParams(params);
  GradientDrawable gd=new GradientDrawable();
  gd.setColor(Color.WHITE);
  gd.setStroke(5, Color.BLACK);
  ll.setBackground(gd);
  AbsoluteLayout abs=(AbsoluteLayout)findViewById(R.id.lay);
  abs.addView(ll);
...che mi dà un Layout bianco con un bordo nero di spessore 5 (il numero che appare come parametro nel metodo setStroke).

martedì 26 gennaio 2016

Salvataggio nel database di un'immagine sotto forma di Blob

Una possibile soluzione: memorizzare direttamente immagini in un database, opportunamente già scalate.

Come si fa a memorizzare un'immagine in un database? Lo avevo visto ma non lo ricordo...

Che tipo di dati è Blob?

Per memorizzare un'immagine come dati blob bisognerebbe convertirla in array di bytes.

Il metodo compress su una bitmap, cosa è?
Troviamolo...

Scrive la bitmap su un outputstream.
Questo può essere convertito in ByteArray.

link.

Realizzati tutti e due i codici, per l'immissione e per la lettura dell'immagine da database:

metodo di helper:
  public void save(int drawable){
   SQLiteDatabase db=this.getWritableDatabase();
   Bitmap bmp=BitmapFactory.decodeResource(getResources(), drawable);
   //dovrei creare un outputstream...
   ByteArrayOutputStream stream=new ByteArrayOutputStream();
   //ora usare il metodo compress sulla bitmap...
   bmp.compress(CompressFormat.JPEG, 50, stream);
   byte[] blob=stream.toByteArray();
   ContentValues values=new ContentValues();
   values.put("IMMAGINE", blob);
   db.insert("TABELLA", null, values);
   
  }


Dal metodo query() di helper ottengo il cursore, che gestisco così:
  byte[] blob=helper.query().getBlob(1);
  Bitmap bmp=BitmapFactory.decodeByteArray(blob, 0, blob.length);

lunedì 25 gennaio 2016

Aggiunta di nuovi layouts creati programmaticamente al layout principale dell'activity.

Bene.
Ho creato dei LinearLayout da inserire nell'activity, ognuno dei quali contiene un'immagine e una casella di testo, il tutto programmaticamente.
In più, ho esteso la classe LinearLayout per aggiungere una proprietà pubblica Tipo che potrà servirmi per decidere il diverso comportamento di ogni layout al click.

Ecco il codice del metodo che crea il layout:
 private void buildLayout(int X, int Y, int width, int height,String StrTesto,String path){
  JacLinearLayout linearLayout=new JacLinearLayout(this);
  linearLayout.Tipo="Folder";
  linearLayout.setBackgroundColor(Color.GREEN);
  linearLayout.setOrientation(LinearLayout.VERTICAL);
  AbsoluteLayout.LayoutParams absParam = new        AbsoluteLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT,X,Y);
  linearLayout.setLayoutParams(absParam);
  ImageView immagine = new ImageView(this);
  BitmapFactory.Options opzioni=new BitmapFactory.Options();
  opzioni.inJustDecodeBounds=true;
  BitmapFactory.decodeFile(path,opzioni);
  int fattore=1;
  while(opzioni.outWidth/fattore>width && opzioni.outHeight/fattore>height){
   fattore*=2;
  }
  opzioni.inSampleSize=fattore;
  opzioni.inJustDecodeBounds=false;
  Bitmap bmp=BitmapFactory.decodeFile(path,opzioni);
  immagine.setImageBitmap(bmp);
  
  TextView testo=new TextView(this);
  testo.setText(StrTesto);
  testo.setTextSize(18);
  LinearLayout.LayoutParams params=new LinearLayout.LayoutParams(width,height);
  
  immagine.setLayoutParams(params);
  linearLayout.addView(immagine);
  linearLayout.addView(testo);
  
  
  
  AbsoluteLayout abs=(AbsoluteLayout)findViewById(R.id.lay);
  abs.addView(linearLayout);
 }
Così facendo, posso creare un nuovo layout semplicemente richiamando questo metodo:
  buildLayout(0,0,200,200,"ASARO MUDMAN","storage/sdcard/DCIM/asaromudman.jpg");
  buildLayout(210,0,200,200,"CAFFE'","storage/sdcard/DCIM/cafe.jpg");
  buildLayout(420,0,200,200,"FACCIA DA CULO","storage/sdcard/DCIM/faciadecul.jpg");
con questo risultato:

domenica 24 gennaio 2016

Gestione dell'evento di immissione di testo (EditText, AutoCompleteTextView)

Gestire l'evento dell'immissione di dati di un campo mediante la tastiera virtuale:
nella fattispecie si tratta di una AutoCompleteTextView.
  autoComplete.setOnEditorActionListener(new OnEditorActionListener() {
   
   @Override
   public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    
    return false;
   }
  });

Alcune impostazioni importanti dei campi per l'immissione di testo in Android.

Appunti importanti sulle impostazioni da dare alle views per l'immissione di testi in Android.
In questo specifico caso voglio che:
  • Siano a linea singola;
  • si visualizzi l'activity e non la tastiera virtuale anche in modalità landscape;
  • il focus passi alla successiva view.
Ecco le impostazioni sull'xml:
<EditText
        android:id="@+id/editText1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_x="140dp"
        android:layout_y="155dp"
        android:inputType="text"
        android:imeOptions="flagNoExtractUi"
        android:ems="10" >

        <requestFocus />
    </EditText>

    <AutoCompleteTextView
        android:id="@+id/autoCompleteTextView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_x="144dp"
        android:layout_y="203dp"
        android:imeOptions="flagNoExtractUi"
        android:inputType="text"
        android:ems="10" /> 
android:inputType="text" rende a singola linea la view;
android:imeOptions="flagNoExtractUi" mostra l'activity anche in modalità landscape.
Se sono tipo "singola linea" automaticamente appare il tasto "Next" e quindi con la sua pressione il focus passa alla successiva.

sabato 23 gennaio 2016

Ripasso delle modifiche del CursorAdapter per la AutoCompleteTextView.

Ora devo ricordare quelle due funzioni che servono per accoppiare un database a una AutoCompleteTextView.
Vediamo un po'...

  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   
   return null;
   
  }
Cosa restituisce questo? Un cursor nel caso in cui la stringa digitata corrisponda alla parte iniziale di alcuni campi.
Come si riempiva? Non lo ricordo...

Ci provo.
Intanto mi creo l'AutoCompleteTextView.
E la istanzio da codice java.

Ecco alla fine ricostruite le due funzioni che ho appreso recentemente e che avevo in parte dimenticato:
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   if(constraint!="") return helper.query(constraint+"");
   return null;
   
  }
  
  @Override
  public CharSequence convertToString(Cursor c){
   return c.getString(1);
   
  }
con le quali la autoComplete funziona alla perfezione! L'adapter, aggiustato per la autoComplete, è:
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);
   
  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line, null);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(1));
   
  }
  
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   if(constraint!="") return helper.query(constraint+"");
   return null;
   
  }
  
  @Override
  public CharSequence convertToString(Cursor c){
   return c.getString(1);
   
  }
  
 }

Ripasso del LIKE nella query di selezione di un database.

Bene.
Adesso l'esercizio consiste nel caricare immagini su una GridView ordinabile per categorie.

Per prima cosa creo un database con una tabella Categorie, la quale ha due campi, Categoria e Path.
Questa tabella è collegata con una AutoComplete.
Ho problemi a ricordare con certezza la sintassi della query con il LIKE.
Apro un sottoprogramma di ripasso...

Questo è il codice della query che ho scritto:
  public Cursor query(String str){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select * from TblCategorie where categoria like '" +str+"%'", null);
   return crs;
  }
e che devo verificare per essere sicuro che sia scritto bene.
Dunque mi predispongo una ListView in cui scaricare e visualizzare tutti i record del database.
Ecco: ho inserito due records in cui i valori del campo "categoria" erano "cazzate" e "minchiate", e constato che il funzionamento è quello che mi aspettavo:
codice completo:
public class MainActivity extends Activity {
 
 private ListView listView;
 private Helper helper;
 private Adapter adapter;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  helper=new Helper(this);
  listView=(ListView)findViewById(R.id.listView1);
  helper.save("cazzate","unCertoPath");
  helper.save("minchiate","unCertoPath");
  adapter=new Adapter(this,helper.query("m"));
  listView.setAdapter(adapter);
  
 }
 
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);
   
  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
   View v=inflater.inflate(R.layout.hills, null);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   TextView testo=(TextView)view.findViewById(R.id.textView1);
   testo.setText(cursor.getString(1));
   
  }
  
 }
 
 class Helper extends SQLiteOpenHelper{

  public Helper(Context context) {
   super(context, "mioDatabase", null, 1);
   // TODO Auto-generated constructor stub
  }

  public void save(String categoria,String path){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("categoria",categoria);
   db.insert("TblCategorie", null, values);
  }
  
  public Cursor query(String str){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select * from TblCategorie where categoria like '" +str+"%'", null);
   return crs;
  }
  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("create table tblCategorie(_id integer primary key, categoria text,path text)");
   
   
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
 }
Se la parte marcata in rosso ha come parametro una lettera (in questo caso la "m") appaiono solo i records in cui "categoria" inizia per "m", se la "c" solo quelli in cui "categoria" inizia per "c", se si tratta di una stringa vuota appaiono tutti i records, se è uno spazio " " non appare nessun record.
Ottimo ripasso!

martedì 19 gennaio 2016

Approccio con i BroadcastReceiver

Ho scritto questo codice:
public class AirplaneReceiver extends BroadcastReceiver {
 @Override
 public void onReceive(Context context, Intent intent) {
 }
}
L'ho chiamato AirplaneReceiver, ma avrei potuto chiamarlo pure Pasquale, se avessi voluto.
Anzi, facciamo una cosa: lo chiamo veramente Pasquale! O meglio con un nome mio di fantasia, sempre pertinente...
public class Ricevitore extends BroadcastReceiver {
 @Override
 public void onReceive(Context context, Intent intent) {
 }
}
Scrivo il codice delle azioni da fare al ricevimento del Broadcast:
public class Ricevitore extends BroadcastReceiver {
 @Override
 public void onReceive(Context context, Intent intent) {
  Toast.makeText(context, "Ciao, bella gente!", Toast.LENGTH_SHORT).show();
 }
}
(con la scusa, ho ripassato un po' la sintassi del Toast)

E adesso, seguendo il tutorial, andiamo alla parte della registrazione.
Per prima cosa scrivo il tag %lt;receiver>
        <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>
        
        <receiver>
            
        </receiver> 
Il questo tag devo specificare android:name
        <receiver android:name=>
           
        </receiver> 
e ottengo a questo punto una lista di suggerimenti:



...dalla quale scelgo il primo:
        <receiver android:name="com.example.broadcast.Ricevitore"           
        </receiver> 
Fondamentale è notare che questo android:name è uguale al nome del package più il nome della classe che estende BroadcastReceiver!


Poi si va avanti con un <intent-filter></intent-filter> nel quale ci va un <action>.
Ecco:
        <receiver android:name="com.example.broadcast.Ricevitore"> 
            <intent-filter >
                <action android:name=""
            </intent-filter>         
        </receiver> 
E qui c'è un elemento decisivo: il nome del Broadcast che viene ascoltato!
        <receiver android:name="com.example.broadcast.Ricevitore"> 
            <intent-filter >
                <action android:name="android.provider.Telephony.SMS_RECEIVED"></action>
            </intent-filter>         
        </receiver> 
Ho scelto un Broadcast diverso da quello del tutorial, con l'intenzione di provarlo.

Dunque, il codice che do in Java serve a stabilire la risposta del BroadcastReceiver al messaggio Broadcast, ma non individua l'azione.
Abbiamo il nome e l'azione. Punto. Quindi nella registrazione si mettono insieme il nome del BroadcastReceiver e come intent-filter si stabilisce il messaggio che viene ascoltato dal BroadcastReceiver.

Non è difficile: xml dice: "C'è un messaggio di tipo SMS ricevuto, attivare il receiver Ricevitore. Attivato questo receiver, esso ha specificata l'azione da intraprendere, ossia la creazione di un messaggio Toast.

Vediamo se funziona!

domenica 17 gennaio 2016

Ottenere il popolamento della lista di suggerimenti di una AutoCompleteTextView direttamente da un database

setFilterQueryProvider.
dovrebbe eseguire la query con le limitazioni date da constraint.
Restituisce getCursor(constraint).
Il problema è che getCursor non ce l'abbiamo.
E' una funzione che deve essere creata, ed è qui che non ci capisco niente.

Ogni volta che cambia il contenuto della textview scatta un metodo che mi mostra la stringa constraint.
Cerchiamo di riprodurla.

Non mi è chiaro se la view che è stata inflatata nel metodo newView deve essere... Se è una TextView, non si fa come si è fatto nell'inflatare una view esterna, ossia cercare dentro di essa una TextView, ma se ne imposta direttamente il testo prendendo il valore da una colonna del Cursor.

 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);
   
  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line,null);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(0));
   
  }
  
 }
Ecco: così, nel momento in cui viene digitato un testo nella autoComplete, la lista viene popolata di TUTTI i valori presenti nella colonna 0 del Cursor.
Invece dobbiamo fare in modo che venga popolata soltanto di quelli che iniziano con i caratteri digitati. E' quello il problema!

E abbiamo conseguito una prima acquisizione.

Ora, c'è un evento che si verifica a ogni digitazione di testo nella autoComplete.
Cerco di ricordarlo e di catturarlo.
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   Log.d("CONSTRAINT",""+constraint);
   return null;
   
  }
01-17 15:21:07.555: D/CONSTRAINT(2233): null
01-17 15:21:08.945: D/CONSTRAINT(2233): ci
01-17 15:21:10.225: D/CONSTRAINT(2233): cia
01-17 15:21:11.756: D/CONSTRAINT(2233): ciao
Ecco: quando la lunghezza della stringa è di due o più caratteri, questa funzione viene eseguita e su LogCat mi appaiono i caratteri digitati.
Ora devo praticamente elaborare qualcosa che da un Cursor, ossia da una serie di records, riconosca l'inizio della stringa e mi scriva i valori che iniziano con quell'inizio.

E finalmente ci sono riuscito!!!

Ho modificato la query in questo senso (codice dell'helper):
  public Cursor query(String args){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select categoria as _id from TabCat where categoria like '%"+args+"%' ", null);
   crs.moveToFirst();
   return crs;
  }
e poi ho aggiunto, nel codice del CursorAdapter:
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   Log.d("CONSTRAINT",""+constraint);
   if(constraint==null){
    return helper.query(" ");
   } else{
    return helper.query(""+constraint);
   } 
  }
Ecco, adesso ottengo una lista adeguata per la mia AutoCompleteTextView!
Codice completo con classe che estende SQLiteOpenHelper per la creazione del database e classe che estende CursorAdapter:
public class MainActivity extends Activity {
 
 Helper helper;
 private AutoCompleteTextView autoComplete;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  autoComplete=(AutoCompleteTextView)findViewById(R.id.autoCompleteTextView1);
  helper=new Helper(this);
  helper.save("foto");
  helper.save("cartoni");
  helper.save("mostri");
  
  
  Adapter adapter=new Adapter(this,helper.query(""));
   
 
  
  autoComplete.setAdapter(adapter);
 }
 
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);
   
  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line,null);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(0));
   
  }
  
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   if(constraint==null){
    return helper.query(" ");
   } else{
    return helper.query(""+constraint);
   } 
  }

  
 }
 
 class Helper extends SQLiteOpenHelper{

  public Helper(Context context) {
   super(context, "mioDatabase", null, 1);
   
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("create table TabCat(categoria text unique, path text )");
   
  }
  
  public void save(String categoria){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("categoria", categoria);
   values.put("path", "path");
   db.insert("TabCat", null, values);
  }
  
  public Cursor query(String args){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("select categoria as _id from TabCat where categoria like '%"+args+"%' ", null);
   crs.moveToFirst();
   return crs;
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
  
 }
C'è un problema.
Come faccio a ottenere nella autoComplete il testo?
Il metodo runQueryOnBackgroundThread mi restituisce un cursor.
I suggerimenti mi risultano congruenti.
Ma poi, quando vado a cliccare sul suggerimento, ottengo una strana formula nella autoComplete al posto della stringa presente come suggerimento.
Perché?

Probabilmente perché, per qualche motivo che non riesco ancora a mettere a fuoco, viene scelta un'altra colonna del cursore per riempire la TextView una volta che sia stata selezionata la voce.
Con un po' di intuito, seguendo questo link, ho sistemato le cose aggiungendo al CursorAdapter questa funzione:
  @Override
  public String convertToString(Cursor cursor) { 
   return cursor.getString(0); 
  } 
e tutto sembra risolto!

Tentativo di CursorAdapter con AutoCompleteTextView

runQueryOnBackgroundThread sarebbe un metodo che scatterebbe quando si modifica il testo della AutoCompleteTextView.
Cerco ulteriori conferme a ciò.
Ecco: Io inserisco nella classe che estende CursorAdapter il metodo runQueryOnBackGroundThread e non ottengo più i suggerimenti della AutoCompleteTextView:
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);


  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent,false);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(0));
   
  }
  
  @Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   return null;
 
        }
 }
E...



Adesso la tolgo:
 class Adapter extends CursorAdapter{

  public Adapter(Context context, Cursor c) {
   super(context, c);


  }

  @Override
  public View newView(Context context, Cursor cursor, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)context.getSystemService(LAYOUT_INFLATER_SERVICE);
   TextView v=(TextView)inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent,false);
   return v;
  }

  @Override
  public void bindView(View view, Context context, Cursor cursor) {
   ((TextView)view).setText(cursor.getString(0));
   
  }
  
  /*@Override
  public Cursor runQueryOnBackgroundThread(CharSequence constraint){
   return null;
 
         }*/
 }

e...



Dunque la chiave è in questo metodo: bisogna riempirlo in modo tale che espanda i suggerimenti in modo filtrato.
E come?



Basta! Sono arrivato a capire che c'è un metodo che viene chiamato quando cambia il testo e prende come parametro la stringa presente nella AutoComplete, ma poi come faccia a selezionare i records del database in base a questo, non lo so.
Ulteriori tentativi in seguito. Meglio allentare, per il momento.

venerdì 15 gennaio 2016

Sperimentando queries su Android.

Realizzare un database con due tavole: una con tre campi e una con due campi con un campo dallo stesso nome.
Fatto. Ambedue con tre campi di cui un _id e altri due...

Adesso devo costruire una query JOIN.
Vediamo se mi riesce...

Intanto faccio delle prove in modo graduale.
Seleziono alcuni campi di una tabella invece di selezionarli tutti. Non vi figura il campo _id
Ogni volta che tento di inserire un valore in un campo UNIQUE, se il valore è già presente, ottengo un messaggio di errore con "Is not unique", ma non si tratta di un errore fatale.
Ottengo poi un messaggio di errore fatale, con crash dell'applicazione, quando invece il CursorAdapter non trova il campo _id in quanto questo è omesso dalla selezione nella query.
Tolgo la condizione UNIQUE per evitare di ottenere quei messaggi di errore che mi rendono più difficile la lettura del LogCat, e faccio ripartire il programma. Strano che gli errori "is not unique" mi figurano ugualmente.
Tralascio almeno al momento questo particolare, e trovo l'errore FATALE:
01-12 22:23:02.175: E/AndroidRuntime(26294): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.lab10/com.example.lab10.MainActivity}: java.lang.IllegalArgumentException: column '_id' does not exist
Ora applico quel trucco dell'aggiungere a uno dei campi selezionati "AS _id", e vediamo:
		public Cursor query(){
			SQLiteDatabase db=this.getWritableDatabase();
			Cursor crs=db.rawQuery("SELECT NOME AS _id,CATEGORIA FROM TABELLA", null);
			return crs;
		}
E vediamo se ottengo ancora errori fatali...

No! Non ho trovato nessun messaggio di errore! Dunque, se voglio selezionare campi in cui non vi sia compreso _id, o se la tabella semplicemente non possiede un campo chiamato _id, basta aggiungere quelle due paroline e il problema è risolto! Ora, dato che il mio codice inserisce valori anche in un'altra tabella, provo a selezionare da tabelle multiple!

Creo campi e record come li ho creati in quel mio piccolo esercizio con Access...

Una tabella Padri e una tabella Figli, la prima con i campi id e nome, la seconda con i campi extId e nome. Ne do qui una rappresentazione:
IDNome
1Mario
2Antonio
3Giuseppe
4Luigi


IDextIdNome
11Pierino
22Carletto
31Gigetto
43Paolino
12Checco
22Dadino
31Lallo
43Bobo


Creo la tabella:
		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL("CREATE TABLE TABELLA(_id INTEGER PRIMARY KEY, NOME TEXT)");
			db.execSQL("CREATE TABLE TAB2(_id INTEGER PRIMARY KEY, ExtID INTEGER, NOME TEXT)");
		}
E la riempio...

Ho creato due metodi per il riempimento della prima e della seconda tabella.
		public void save(String nome){
			SQLiteDatabase db=this.getWritableDatabase();
			ContentValues values=new ContentValues();
			values.put("NOME", nome);
			db.insert("TABELLA", null, values);
			
		}
		
		public void save2(int extId, String nome){
			SQLiteDatabase db=this.getWritableDatabase();
			ContentValues values=new ContentValues();
			values.put("ExtId",extId);
			values.put("NOME", nome);
			db.insert("TAB2", null, values);
			
		}
Ora mi esercito a creare diversi tipi di query, mediante l'uso del SQL.

Ecco una query, che ho creato dopo aver sperimentato query più semplici che non è il caso di riportare, perché fatte solo per verificare che non commettessi errori di sintassi.
		public Cursor query(){
			SQLiteDatabase db=this.getWritableDatabase();
			Cursor crs=db.rawQuery("SELECT TABELLA.NOME AS _id,TAB2.NOME FROM TABELLA,TAB2 WHERE TABELLA._ID = TAB2.EXTID", null);
			return crs;
		}
Ora provo a ottenere lo stesso risultato con il JOIN.
		public Cursor query(){
			SQLiteDatabase db=this.getWritableDatabase();
			Cursor crs=db.rawQuery("SELECT TABELLA.NOME AS _id,TAB2.NOME FROM TABELLA INNER JOIN TAB2 ON TABELLA._ID = TAB2.EXTID", null);
			return crs;
		}




Bene.
Il risultato è quello voluto ad eccezione dell'ordine.
Come si fa a ordinare in ordine alfabetico i records?

Ma sì: con la clausola ORDER BY:
		public Cursor query(){
			SQLiteDatabase db=this.getWritableDatabase();
			Cursor crs=db.rawQuery("SELECT TABELLA.NOME AS _id,TAB2.NOME FROM TABELLA INNER JOIN TAB2 ON TABELLA._ID = TAB2.EXTID ORDER BY TABELLA.NOME", null);
			return crs;
		}
Ed ecco:



...e non ci sono problemi!

mercoledì 13 gennaio 2016

Rispolverando reminiscenze di SQL con Access. Corrispondenza uno a molti fra tabelle

Tabelle di database.
Ricominciamo dalle basi elementari del discorso...

Corrispondenza uno a molti, corrispondenza molti a molti.
Facciamoci un esempio pratico di corrispondenza uno a molti: un padre che ha più figli. Un padre può avere più figli, ma un figlio può avere un solo padre.
Costruiamo una tabella o due tabelle...

id Nome
1 Mario
2 Antonio
3 Giuseppe
4 Luigi
id Nome extId
1 Pierino 1
2 Carletto2
3 Gigetto 1
4 Paolino 3
5 Checco 2
6 Dadino 2
7 Lallo 1
8 Bobo 3
L'extID della seconda tabella corrisponde all'ID della prima tabella.
E dunque creiamo la query che fornisce i risultati dell'unione delle tabelle:
SELECT Padri.Nome, Figli.Nome
FROM Padri INNER JOIN Figli ON Padri.ID=Figli.ExtID;
Ed ecco la tabella che mi viene restituita:
Padri.Nome Figli.Nome
Mario         Pierino
Mario         Gigetto
Mario         Lallo
Antonio         Carletto
Antonio         Checco
Antonio         Dadino
Giuseppe Paolino
Giuseppe Bobo
Ma nel compilare la seconda tabella mi ero dimenticato che la prima comprendesse quattro records e non tre.
Così ho dimenticato dl attribuire figli al quarto genitore.
La cosa mi torna utile per insegnare a me stesso la differenza fra INNER JOIN, che ho usato in questa query, e LEFT JOIN, ipotizzando che Luigi sia un uomo che non ha ancora avuto figli.
E adesso sostituisco nella query INNER JOIN con LEFT JOIN e mi compaiono i record non "appaiati" della prima tabella.
SELECT Padri.Nome, Figli.Nome
FROM Padri LEFT JOIN Figli ON Padri.ID=Figli.ExtID;
Vediamo...
Padri.Nome Figli.Nome
Mario         Pierino
Mario         Gigetto
Mario         Lallo
Antonio         Carletto
Antonio         Checco
Antonio         Dadino
Giuseppe Paolino
Giuseppe Bobo
Luigi ott
Ecco qua.
Da dove dipende che una tabella sia considerata Left o Right?
Ecco: se inverto l'ordine delle tabelle a cavallo dell'istruzione LEFT JOIN ottengo un'inversione del ruolo di Left e Right fra le due.
Anziché
SELECT Padri.Nome, Figli.Nome
FROM Padri LEFT JOIN Figli ON Padri.ID=Figli.ExtID;
scrivo, invertendo:
SELECT Padri.Nome, Figli.Nome
FROM Figli LEFT JOIN Padri ON Padri.ID=Figli.ExtID;
e ottengo questa tabella:
Padri.Nome Figli.Nome
Mario         Pierino
Mario         Gigetto
Mario         Lallo
Antonio         Carletto
Antonio         Checco
Antonio         Dadino
Giuseppe Paolino
Giuseppe Bobo
Ma se adesso pongo un RIGHT JOIN cosa accadrà? Prevedibile...

SELECT Padri.Nome, Figli.Nome
FROM Figli RIGHT JOIN Padri ON Padri.ID=Figli.ExtID;
No...prevedibile un corno!
Padri.Nome Figli.Nome
Mario Pierino
Mario Gigetto
Mario Lallo
Antonio Carletto
Antonio Checco
Antonio Dadino
Giuseppe Paolino
Giuseppe Bobo
Luigi 
Mi viene esattamente come prima...

...è necessario che metta un po' di ordine fra i concetti di sinistra e destra...

lunedì 11 gennaio 2016

Creazione di un JFrame in Java


Perché non proviamo a capire la struttura del codice che crea il JFrame?
Creiamo una classe, che abbia lo stesso nome del modulo in cui viene dichiarata (regola fondamentale).
Questa classe deve avere un costruttore:
package javaLab1;

public class test{
 
 public test(){
  
 }
}
Ecco: classe e costruttore.
La classe si istanzia da sola mediante un metodo static main.
Nel costruttore della classe viene istanziato il JFrame, ne vengono stabilite le dimensioni e viene reso visibile.
Molto lineare e semplice!

Riprendendo in mano il Java...


Facciamo un passo indietro... Java!
Avevo imparato, con NetBeans, a fare qualcosina, anzi più di qualcosina, ma la non pratica mi ha fatto scordare quasi tutto.
Ricominciamo a curiosare in materia...

Rapido ripasso e...
package javaLab1;

public class main {
 
 public static void main(String[] args){
  System.out.println("Ciao, mondo di merda!");
 }

}
Ciao, mondo di merda!
Consolante...

Come visualizzare l'EditText anche in modalità Landscape durante l'inserimento dei dati da tastiera.


Se la mia applicazione è fatta per essere eseguita esclusivamente in modalità Landscape, non vedo perché non potrei inserirci un AbsoluteLayout... Se il cellulare ruota, allora credo che succeda una catastrofe, in quanto i controlli andrebbero a nascondersi o chissà che altro (poi lo proverò per curiosità), ma se non ruota, avere la possibilità di posizionare i controlli in modo fisso e determinato mi sembra un vantaggio.

E adesso ecco affrontato il problema della EditText, che in modalità Portrait appare regolarmente, sul suo sfondo dell'activity, mentre in modalità Landscape sparisce insieme a tutto lo sfondo, sostituita da una schermata per l'inserimento dei dati da tastiera:




Inserisco la seguente riga marcata in giallo...
    <EditText
        android:id="@+id/editText1"
        android:layout_width="144dp"
        android:layout_height="wrap_content"
        android:layout_x="10dp"
        android:layout_y="88dp"
        android:imeOptions="flagNoExtractUi"
        android:ems="10" />
...e diventa così:

E anche questo è stato nuovamente risolto.

Esercizio di riscrittura del codice che carica un'immagine scalata dalla memoria in una ImageView.

Ricostruire un'applicazione che inserisca in una GridView immagini dalla memoria, con etichetta e categoria.

Per prima cosa, creo un tipo che contenga i tre membri Path, Etichetta e Categoria.
class JImmagine{
 public String Path;
 public String Etichetta;
 public String Categoria;
}
Adesso inserisco un pulsante che prenda l'immagine dalla memoria e mi crei una bitmap da inserire in una ImageView in un thread separato mediante AsyncTask.
Sull'Activity metto un Button e una ImageView e poi scrivo il codice...
public class MainActivity extends Activity {
 
 Button button;
 ImageView imageView;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  button=(Button)findViewById(R.id.button1);
  imageView=(ImageView)findViewById(R.id.imageView1);
  
  button.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);
    
   }
  });
  

 }
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data){
  if(resultCode==RESULT_OK){
   BitmapFactory.Options opzioni=new BitmapFactory.Options();
   opzioni.inJustDecodeBounds=true;
   BitmapFactory.decodeFile(getPathFromUri(data.getData()), opzioni);
   int fattore=1;
   while(opzioni.outHeight/fattore>100 && opzioni.outWidth/fattore>100){
    fattore*=2;
   }
   opzioni.inSampleSize=fattore;
   opzioni.inJustDecodeBounds=false;
   Bitmap bitmap=BitmapFactory.decodeFile(getPathFromUri(data.getData()));
   imageView.setImageBitmap(bitmap);
   
  }
 }
 
 private String getPathFromUri(Uri uri){
  Cursor crs=getContentResolver().query(uri, null, null, null, null);
  int indice=crs.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
  crs.moveToFirst();
  String s=crs.getString(indice);
  crs.close();
  return s;
 }
Ecco, ho scritto di getto il codice che, a parte un errore di distrazione per non aver scritto crs.moveToFirst() mi comprometteva il risultato, ma l'ho individuato e corretto subito.
Funziona.
Ora, però, devo usare la mia classe JImmagine.
Per farlo, devo però avere due controlli per l'immissione dei dati Etichetta e Categoria.

Su questi, ricordo di aver trovato alcuni suggerimenti per far sì che, anche in modalità Landscape, l'activity restasse visibile...
E quindi è il caso di ripassare il tutto, dando magari anche uno sguardo ad altri tipi di layout...

Ripasso dei Database SQL e creazione di più tabelle sul database.


Stavo pensando che forse potrei semplicemente, anziché usare onItemClickListener sulla AutoCompleteTextView, creare una tabella del database con una chiave a chiave unica... ma il rischio è che l'utente digiti una categoria in modo sbagliato e il programma la inserisca come categoria diversa quando nelle intenzioni dell'utente avrebbe dovuto essere una categoria già esistente.
Meglio mantenere la AutoComplete, ma potrei anche mettere la chiave unica...
Per il momento, cerchiamo di rimettere le mani sul database creando un'altra tabella.
Per fare questo, devo crearmi un'esercitazione sui database.

public class MainActivity extends Activity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  DBHelper helper=new DBHelper(this);
  helper.save("Mario", "Deficiente");
  Log.d("RECORD",helper.query().getString(1)+" "+helper.query().getString(2));
 }
 
 class DBHelper extends SQLiteOpenHelper{

  public DBHelper(Context context) {
   super(context, "mioDatabase", null, 1);
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("CREATE TABLE TABELLA(_ID INTEGER PRIMARY KEY, NOME TEXT, CATEGORIA TEXT)");
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
  public void save(String nome, String categoria){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("NOME", nome);
   values.put("CATEGORIA", categoria);
   db.insert("TABELLA", null, values);
   
  }
  
  public Cursor query(){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("SELECT * FROM TABELLA", null);
   crs.moveToFirst();
   return crs;
  }
  
 }
...con tanto di verifica in LogCat. Ripetuto più volte.
Adesso devo creare un'altra tabella...

Finalmente ci sono riuscito! Ho avuto grossi problemi con la gestione di ContentValues fino a quando ho trovato il metodo clear() che ha risolto la situazione:
public class MainActivity extends Activity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  DBHelper helper=new DBHelper(this);
  helper.save("Antonello","Scemo");
  Log.d("RISULTATO",helper.query().getString(1));
 }
 
 class DBHelper extends SQLiteOpenHelper{

  public DBHelper(Context context) {
   super(context, "mioDatabase", null, 1);
  
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
   db.execSQL("CREATE TABLE TABELLA(_ID INTEGER PRIMARY KEY, NOME TEXT)");
   db.execSQL("CREATE TABLE TABELLA2(_ID INTEGER PRIMARY KEY, CATEGORIA TEXT)");
   
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   // TODO Auto-generated method stub
   
  }
  
  public void save(String nome, String categoria){
   SQLiteDatabase db=this.getWritableDatabase();
   ContentValues values=new ContentValues();
   values.put("NOME", nome);
   db.insert("TABELLA", null, values);
   values.clear();
   values.put("CATEGORIA", categoria);
   db.insert("TABELLA2", null, values);
  }
  
  public Cursor query(){
   SQLiteDatabase db=this.getWritableDatabase();
   Cursor crs=db.rawQuery("SELECT * FROM TABELLA2", null);
   crs.moveToFirst();
   return crs;
  }
  
 }
Ed ecco il risultato:
01-10 22:32:49.126: D/dalvikvm(16447): GC_CONCURRENT freed 0K, 1% free 14249K/14368K, paused 4ms+1ms, total 7ms
01-10 22:32:49.686: D/RISULTATO(16447): Scemo
01-10 22:32:49.786: D/gralloc_goldfish(16447): Emulator without GPU emulation detected.
01-10 22:32:49.876: I/ActivityManager(1224): Displayed com.example.lab10/.MainActivity: +1s257ms (total +20m1s538ms)
il dato preso dalla seconda tabella.

Ora potrei mettermi a giocherellare con le istruzioni SQL per maneggiare le tabelle...

domenica 10 gennaio 2016

AutoCompleteTextView: evento del click sul suggerimento.


Discriminare se si sia introdotta una categoria ex novo o se si tratti di una categoria già preesistente.
La categoria viene selezionata attraverso una AutoCompleteTextView, per cui bisogna trovare il modo di distinguere le due evenienze di "nuova categoria" o di "categoria già esistente" in questo controllo.

Forse ho trovato qualcosa...

Bene. Costruiamo un programma con una AutoComplete che esegua un certo programma solo se viene inserito nuovo testo rispetto a uno suggerito.

Ho creato un AutoCompleteTextView:
public class MainActivity extends Activity {
 
 AutoCompleteTextView autoComplete;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  autoComplete = (AutoCompleteTextView)findViewById(R.id.autoCompleteTextView1);
  
  
 }
Ora dovrei usare un adapter per connetterla a una lista.
Prima ancora mi serve una lista: procedo a creare un'ArrayList:
  
  ArrayList<String> lista=new ArrayList<String>();
  
  String[] matrice={"lunedi","martedi","mercoledi","giovedi","venerdi","sabato","domenica"};
  
  lista.addAll(Arrays.asList(matrice)); 
E ho trovato anche il modo di riempire un'ArrayList senza stare lì a dare tremila righe di codice: dichiarare e istanziare un array e poi aggiungere il contenuto di quest'array tutto in un colpo.

Bene, ora che ho creato la lista devo connetterla mediante un adapter alla AutoCompleteTextView.
Come fare?

Estendiamo un ArrayAdapter... anzi no, non estendiamo un bel niente: mi serve un ArrayAdapter così com'è...
public class MainActivity extends Activity {
 
 AutoCompleteTextView autoComplete;
 
 
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  autoComplete = (AutoCompleteTextView)findViewById(R.id.autoCompleteTextView1);
  
  ArrayList lista=new ArrayList();
  
  String[] matrice={"lunedi","martedi","mercoledi","giovedi","venerdi","sabato","domenica"};
  
  lista.addAll(Arrays.asList(matrice));
  
  Log.d("LISTA",lista.get(0));
  ArrayAdapter myArrAdapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,lista);
  
  autoComplete.setAdapter(myArrAdapter);
  
  
 }
La sintassi dell'ArrayAdapter è molto più semplice di quella del BaseAdapter che ho esteso finora: l'ho fatto sempre per la mia mania di voler andare nel cuore delle cose, ma è più comodo prendersi un ArrayAdapter già bello e costruito.

Ecco: come view da estendere si usa espressamente android.R.layout.simple_list_item_1, e il gioco è fatto.

Nella mia applicazione, ho una AutoCompleteTextView che dà il valore al campo categoria del database.
Ora a ogni categoria va abbinata un'immagine, che avrà il suo path, il quale andrà memorizzato in un altro campo del database. Ma non sarà più economico creare un'altra tabella con un abbinamento categorie-immagini anziché creare un altro campo nella tabella contenente il path dell'immagine?
Se la categoria è sconosciuta, scegliere l'immagine e aggiungere alla tabella delle categorie un nuovo record categoria con il path; se la categoria è conosciuta evitare il codice che fa tutto questo... Poi però all'atto della selezione per popolare la ListView dell'activity principale si deve andare sulla nuova tabella categorie-path, così si può evitare il distinct. E nel momento in cui viene operata la scelta delle categorie si fa una query basata su un where... Sì, mi sa che devo rivedere da capo quell'applicazione, smontare di sana pianta i database e rifarli con un criterio più razionale!
Ora però mi devo occupare della mia AutoCompleteTextView...

Con il codice di cui sopra, la AutoComplete funziona!
Ora provo giusto l'evento click sul suggerimento...

public class MainActivity extends Activity {
 
 AutoCompleteTextView autoComplete;
 
 
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  autoComplete = (AutoCompleteTextView)findViewById(R.id.autoCompleteTextView1);
  
  ArrayList lista=new ArrayList();
  
  String[] matrice={"lunedi","martedi","mercoledi","giovedi","venerdi","sabato","domenica"};
  
  lista.addAll(Arrays.asList(matrice));
  
  Log.d("LISTA",lista.get(0));
  ArrayAdapter myArrAdapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,lista);
  
  autoComplete.setAdapter(myArrAdapter);
  
  
  autoComplete.setOnItemClickListener(new AdapterView.OnItemClickListener() {

   @Override
   public void onItemClick(AdapterView parent, View view,
     int position, long id) {
    Log.d("AUTOCOMPLETE","ITEMCLICK");
    
   }
   
  });
Funziona! Ottengo in LogCat le stringhe "AUTOCOMPLETE" "ITEMCLICK" solo quando clicco sul suggerimento!
Questo mi può permettere di aggiungere alla tabella categorie del mio ipotetico nuovo database solo nel caso in cui questo suggerimento non venga cliccato.
Ci vorrà anche un elemento di maggiore sicurezza, ossia non inserire nel database una nuova categoria dal nome uguale al precedente: bisogna creare campi con valore unico...

Ripasso del BaseAdapter.


Adapter: ho avuto a che fare con due tipi di adapter: uno che può essere utile per connettere una matrice di elementi con un controllo tipo GridView o ListView, uno che invece connette con questi controlli un database.
Inizio il ripasso con una matrice.
Ora, mentre in JavaScript abbiamo l'oggetto Array, in Java ci sono alcune classi che non mi risultano ancora molto chiare nel maneggiarle...
Ho usato ArrayList, che, dato che la sintassi che dichiara una variabile è corretta anche con List, dovrebbe essere una classe derivata da List.
Ecco il codice:
public class MainActivity extends Activity {
 
 List lista;
 GridView grid;
 Adapter adattatore;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  
  lista=new ArrayList();
  grid=(GridView)findViewById(R.id.gridView1);
  adattatore=new Adapter();
  grid.setAdapter(adattatore);
  
  lista.add("Mammolo");
  lista.add("Gongolo");
  lista.add("Pisolo");
  lista.add("Eolo");
  lista.add("Dotto");
  lista.add("Cucciolo");
  lista.add("Brontolo");
  
 }
  
 class Adapter extends BaseAdapter{

  @Override
  public int getCount() {
   // TODO Auto-generated method stub
   return lista.size();
  }

  @Override
  public String getItem(int position) {
   // TODO Auto-generated method stub
   return (String)lista.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return position; 
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   convertView=inflater.inflate(R.layout.hills, null);
   TextView testo=(TextView)convertView.findViewById(R.id.textView1);
   testo.setText(getItem(position));
    
   return convertView;
  }
  
 }
Ottengo una griglia di elementi di forma rettangolare con lo sfondo che avevo selezionato per il layout da inserire in ogni elemento della GridView, e sulla TextView di ognuno di essi compare uno dei nomi dei sette nani che ho messo nell'ArrayList.

Dov'è che il BaseAdapter acquisisce i dati della matrice da usare?
Ecco, individuo i tre metodi forniti automaticamente:
  @Override
  public int getCount() {
   // TODO Auto-generated method stub
   return lista.size();
  }

  @Override
  public String getItem(int position) {
   // TODO Auto-generated method stub
   return (String)lista.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return position; 
  }
in cui appare il nome lista che è appunto il nome che ho dato alla mia ArrayList.
Ma nel secondo la procedura automatica mi dava Object: forse devo inserire un generic?
Ci riprovo: distruggo il mandala e lo riedifico.

 class adapter extends BaseAdapter{
  
 }
No: ottengo un segnale di errore:
The type BaseAdapter is not generic; it cannot be parameterized with arguments <String>
Forse quando si tratta di ArrayAdapter le cose stanno diversamente.

Comunque con i tre metodi suddetti otteniamo:
  • Numero degli elementi
  • Elemento a una data posizione;
  • Posizione
Questi vengono poi usati nel momento in cui vengono messi nella TextBox dell'elemento corrispondente.
Innanzitutto, il processo di inflataggio e disposizione nella Grid viene eseguito un numero di volte corrispondente al numero di elementi nella matrice;
Quindi l'elemento e la posizione verranno utilizzati al momento della creazione, per individuare l'elemento da usare con il layout inflatato volta per volta.

Ora voglio focalizzarmi soltanto sull'inflataggio del layout e sul numero di volte che viene effettuato.
Elimino le informazioni relative al singolo elemento e l'inserimento di testo nelle TextView del layout, limitandomi solo all'inflataggio.
 class Adapter extends BaseAdapter{

  @Override
  public int getCount() {
   // TODO Auto-generated method stub
   return lista.size();
  }

  @Override
  public String getItem(int position) {
   // TODO Auto-generated method stub
   return null;
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0; 
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   convertView=inflater.inflate(R.layout.hills, null);
   return convertView;
  }
  
 }
Per tenere sotto controllo tute le volte che viene effettuato un "inflate" di una view, ho disposto in modo che nella finestra LogCat venisse lasciato un messaggio ogni volta che si fa un "inflating":
        class Adapter extends BaseAdapter{

  @Override
  public int getCount() {
   // TODO Auto-generated method stub
   return 50;
  }

  @Override
  public String getItem(int position) {
   // TODO Auto-generated method stub
   return null;
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0; 
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   LayoutInflater inflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   convertView=inflater.inflate(R.layout.hills, null);
   Log.d("INFLATAGGIO","INFLATAGGIO");
   return convertView;
  }
  
        }
Ed ecco il LogCat:
01-10 12:45:59.567: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.567: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.597: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.607: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.607: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.607: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.607: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.607: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.637: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.657: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.657: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.667: D/gralloc_goldfish(6024): Emulator without GPU emulation detected.
01-10 12:45:59.697: W/IInputConnectionWrapper(1530): showStatusIcon on inactive InputConnection
01-10 12:45:59.717: I/ActivityManager(1224): Displayed com.example.lab10/.MainActivity: +1s369ms
01-10 12:45:59.717: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:45:59.737: D/INFLATAGGIO(6024): INFLATAGGIO

Ne conto 27.
Sull'activity la mia GridView ne ha 21.
Ora "scrollo" la mia GridView e vediamo cosa succede in LogCat.
01-10 12:48:57.410: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:48:57.410: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:48:57.430: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:48:57.500: D/dalvikvm(6024): GC_FOR_ALLOC freed 8161K, 51% free 8048K/16300K, paused 4ms, total 8ms
01-10 12:48:57.880: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:48:57.890: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:48:57.930: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:48:59.760: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:48:59.760: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:48:59.760: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:00.270: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:00.270: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:00.270: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:00.880: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:00.880: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:00.890: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:01.070: I/Choreographer(6024): Skipped 44 frames!  The application may be doing too much work on its main thread.
01-10 12:49:02.210: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:02.210: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:02.210: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:02.380: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:02.380: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:02.380: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:02.600: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:02.600: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:02.600: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:03.340: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:03.340: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:03.350: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:04.740: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:49:04.740: D/INFLATAGGIO(6024): INFLATAGGIO
Sono 29. La somma supera 50.
Scrollo indietro...

01-10 12:52:23.433: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:23.433: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:23.443: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:23.773: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:23.773: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:23.783: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:24.053: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:24.053: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:24.113: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:24.963: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:24.963: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:24.973: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.093: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.103: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.103: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.243: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.243: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.243: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.373: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.373: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.373: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:25.413: D/dalvikvm(6024): GC_FOR_ALLOC freed 897K, 33% free 10964K/16300K, paused 29ms, total 34ms
01-10 12:52:26.203: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:26.203: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:26.203: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:26.273: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:26.273: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:26.273: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:26.363: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:26.363: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:26.363: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.463: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.463: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.463: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.533: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.533: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.533: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.643: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.653: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.653: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.663: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.663: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.663: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.733: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.743: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.743: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.883: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.883: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:27.923: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.113: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.113: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.113: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.183: D/dalvikvm(6024): GC_FOR_ALLOC freed 12K, 21% free 12967K/16300K, paused 11ms, total 12ms
01-10 12:52:28.753: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.753: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.773: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.813: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.823: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.823: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.943: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:28.943: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.713: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.713: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.713: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.793: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.803: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.803: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.853: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.913: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.913: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.913: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.913: D/INFLATAGGIO(6024): INFLATAGGIO
01-10 12:52:30.913: D/INFLATAGGIO(6024): INFLATAGGIO
Scorrendo sotto e sopra la GridView, ogni volta si ha un nuovo inflataggio.
Ogni volta che l'applicazione, o chi per lei, riceve un elemento della GridView, inflata nuovamente una view e ce la mette, anche se per quell'elemento la view è già stata inflatata.
Ecco dunque la condizione per cui la convertview ricevuta come parametro non sia nulla per inflatarne una nuova:
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   if(convertView!=null){
    LayoutInflater inflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    convertView=inflater.inflate(R.layout.hills, null);
    Log.d("INFLATAGGIO","INFLATAGGIO");
   }
   return convertView;
  }
...e vediamo:
01-10 13:19:13.935: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.015: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.015: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.015: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.015: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.015: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.015: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.015: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.015: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.055: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.055: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.055: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.095: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.095: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.095: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.095: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.095: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.095: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.115: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.115: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.115: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:19:14.115: I/Choreographer(2766): Skipped 41 frames!  The application may be doing too much work on its main thread.
01-10 13:19:14.115: D/INFLATAGGIO(2766): INFLATAGGIO
Ne conto 22.
Quelle visibili sono 21.
Vediamo cosa accade "scrollando":
01-10 13:43:50.157: D/INFLATAGGIO(2766): INFLATAGGIO
01-10 13:43:50.157: D/INFLATAGGIO(2766): INFLATAGGIO
...e non più! Questo significa che ogni volta che il metodo getView riceve una convertView "nuova" inflata ex novo la view, mentre se riceve una convertView già inflatata (quelle che si nascondono scrollando) non la inflata più, risparmiandosi un sacco di lavoro.
Questo indipendentemente da ogni specifica matrice di elementi da porre nella GridView.

Non sono concetti facili da farmi digerire...

Esercizio sull'elaborazione di un'immagine mediante AsyncTask.


Un nuovo esercizio di scrittura per un codice che ponga in un'ImageView un'immagine presa dal contenuto della memoria dello smartphone, elaborando l'immagine al di fuori dell'UI mediante AsyncTask.
public class MainActivity extends Activity {
 
 ImageView immagine;
 Button button;
 Intent intent;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  immagine=(ImageView)findViewById(R.id.imageView1);
  button=(Button)findViewById(R.id.button1);
  intent=new Intent();
  intent.setAction(Intent.ACTION_PICK);
  intent.setData(Uri.parse("content://media/external/images/media"));
  
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    startActivityForResult(intent,0);
    
   }
  });
  
  
 }
 
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data){
  if(resultCode==RESULT_OK){
   asyncTask async=new asyncTask(immagine);
   async.execute(getPathFromUri(data.getData()));
   
  }
 }
 
 private String getPathFromUri(Uri uri){
  Cursor crs=getContentResolver().query(uri, null, null, null, null);
  crs.moveToFirst();
  int indice=crs.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
  String s=crs.getString(indice);
  crs.close();
  return s;
  
 }
 
 class asyncTask extends AsyncTask<String,Void,Bitmap>{

  WeakReference<ImageView> reference;
  public asyncTask(ImageView img){
   reference=new WeakReference<ImageView>(img);
  }
  @Override
  protected Bitmap doInBackground(String... params) {
   BitmapFactory.Options opzioni=new BitmapFactory.Options();
   opzioni.inJustDecodeBounds=true;
   BitmapFactory.decodeFile(params[0],opzioni);
   int fattore=1;
   while(opzioni.outWidth/fattore>20 && opzioni.outWidth/fattore>20){
    fattore*=2;
   }
   opzioni.inJustDecodeBounds=false;
   opzioni.inSampleSize=fattore;
   Bitmap bmp=BitmapFactory.decodeFile(params[0],opzioni);
   return bmp;
  }
  
  @Override
  protected void onPostExecute(Bitmap bmp){
   if(bmp!=null && reference!=null){
    ImageView immagine=(ImageView)reference.get();
    if(immagine!=null){
     immagine.setImageBitmap(bmp);
    }
   }
  }

  

 } 
Funziona, funziona...

sabato 9 gennaio 2016

Ristudio dell'AsyncTask in Android


Ripassiamo l'asyncTask.
Riscrivere il codice che carica in una ImageView un'immagine scalata mediante InJustDecodeBounds, quindi farla elaborare nel contesto di un AsyncTask.
Riscrivo il codice che carica un'immagine scalata, scelta fra le immagini in memoria, in un ImageView:
public class MainActivity extends Activity {
 ImageView immagine;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  immagine=(ImageView)findViewById(R.id.imageView1);
  
  Intent intent=new Intent();
  intent.setAction(Intent.ACTION_PICK);
  intent.setData(Uri.parse("content://media/external/images/media"));
  startActivityForResult(intent, 0);
 }
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data){
  if(resultCode==RESULT_OK){
   BitmapFactory.Options opzioni=new BitmapFactory.Options();
   opzioni.inJustDecodeBounds=true;
   BitmapFactory.decodeFile(getPathFromUri(data.getData()),opzioni);
   int fattore=1;
   while(opzioni.outWidth/fattore>200 && opzioni.outHeight/fattore>200){
    fattore*=2;
   }
   opzioni.inSampleSize=fattore;
   opzioni.inJustDecodeBounds=false;
   Bitmap bitmap=BitmapFactory.decodeFile(getPathFromUri(data.getData()),opzioni);
   immagine.setImageBitmap(bitmap);
  }
 }
 
 private String getPathFromUri(Uri uri){
  Cursor cursor=getContentResolver().query(uri, null, null, null, null);
  cursor.moveToFirst();
  int indice=cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
  String s=cursor.getString(indice);
  return s;
  
 }


 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  // Handle action bar item clicks here. The action bar will
  // automatically handle clicks on the Home/Up button, so long
  // as you specify a parent activity in AndroidManifest.xml.
  int id = item.getItemId();
  if (id == R.id.action_settings) {
   return true;
  }
  return super.onOptionsItemSelected(item);
 }
}
Bene: tutto il codice in rosso l'ho scritto a memoria, commettendo solo un errore di distrazione (una maiuscola al posto di una minuscola).
Adesso riaffrontiamo l'AsyncTask...

Come si estende AsyncTask?
Ecco: scrivo
private class asyncTask extends AsyncTask{
ed Eclipse mi dà automaticamente l'indicazione di scrivere i metodi non implementati.
Li aggiungo automaticamente e ottengo questo:
 private class asyncTask extends AsyncTask{

  @Override
  protected Object doInBackground(Object... params) {
   // TODO Auto-generated method stub
   return null;
  }
  
 }
ottengo il solo metodo Object doInBackground, che dovrebbe riferirsi all'attività da svolgere in modo asincrono, al di fuori dell'UI.

Quell'Object come tipo del metodo doInBackground e quell'Object riferito ai parametri dello stesso metodo mi ricordano qualcosa: quando ho usato AsyncTask in precedenza, se non usavo i Generics nella dichiarazione della classe ottenevo Object automaticamente.
Vado a rivedere il codice di quando ho usato questa classe in precedenza:
 class MyAsync extends AsyncTask<String,Void,Bitmap>{
  
  
  private WeakReference<ImageView> riferimento;
  public MyAsync(ImageView immagine){
   riferimento=new WeakReference<ImageView>(immagine);
   
   
   
   
  }
  @Override
  protected Bitmap doInBackground(String... params) {
   BitmapFactory.Options opzioni=new BitmapFactory.Options();
   opzioni.inJustDecodeBounds=true;
   BitmapFactory.decodeFile(params[0],opzioni);
   int imageHeight=opzioni.outHeight;
   int imageWidth=opzioni.outWidth;
   opzioni.inJustDecodeBounds=false;
   int altezza=imageHeight/2;
   int larghezza=imageWidth/2;
   int reqHeight=riferimento.get().getLayoutParams().height;
   int reqWidth=riferimento.get().getLayoutParams().width;
   int inSampleSize=1;
   while(altezza/inSampleSize>reqHeight || larghezza/inSampleSize>reqWidth){
    inSampleSize*=2;
   }
   opzioni.inSampleSize=inSampleSize;
   Bitmap bmp=BitmapFactory.decodeFile(params[0],opzioni);
   
   return bmp;
  }
  @Override 
  protected void onPostExecute(Bitmap bmp){
   if(riferimento!=null && bmp!=null){
    ImageView immagine=(ImageView)riferimento.get();
    if(immagine!=null){
     immagine.setImageBitmap(bmp);
    }
   }
   
   
  }
 } 
Ricordo che quello di mezzo era spesso impostato a Void. Il primo corrisponde al tipo dei parametri mentre il secondo corrisponde al tipo del metodo doInBackground.

Proviamo... il metodo dovrebbe restituire una Bitmap da inserire nell'immagine, mentre dovrebbe "ingoiare" una String che sarebbe il Path, quindi i generics dovrebbero essere <String, Void, Bitmap>, ossia prima quello che si deve dare in pasto al metodo doInBackground, quindi come ultimo il risultato dello stesso metodo.
Se metto questo, il tipo e i parametri del metodo dovrebbero generarmisi automaticamente con l'addizione automatica dei metodi non implementati:
 private class asyncTask extends AsyncTask<String,Void,Bitmap>{

  @Override
  protected Bitmap doInBackground(String... params) {
   // TODO Auto-generated method stub
   return null;
  }
 } 
Ottimo!

Poi nel corpo del metodo ho inserito tutto il codice per l'elaborazione dell'immagine scalata...
Su questo non ci sono problemi. Il metodo "ingoia" il Path dell'immagine e "sputa fuori" l'immagine scalata e impacchettata da mettere nell'ImageView.
C'è poi il metodo onPostExecute, che dovrebbe fare ciò che viene dopo l'attività eseguita fuori dalla UI.
Cosa fa questo metodo?
Ecco! Ottenuto il Path e restituita l'immagine, la deve mettere nell'ImageView.
E l'ImageView viene ricevuta in pasto dal costruttore.
@Override 
  protected void onPostExecute(Bitmap bmp){
   if(riferimento!=null && bmp!=null){
    ImageView immagine=(ImageView)riferimento.get();
    if(immagine!=null){
     immagine.setImageBitmap(bmp);
    }
   }
   
   
  }
Ecco.
Il costruttore ha "impacchettato" il controllo ImageView in una variabile.
Qui lo restituisce come metodo get() della variabile, per attaccarci la bitmap, dopo essersi assicurato che la bitmap esista e che la variabile non sia nulla e che l'oggetto restituito dalla variabile non sia nullo.
Quante paranoie!

Il costruttore prende come argomento l'ImageView.
Prima dichiara la variabile di tipo WeakReference.
  private WeakReference riferimento;
  public MyAsync(ImageView immagine){
   riferimento=new WeakReference(immagine);
  }
In effetti questa variabile è un po' particolare: va istanziata con il Generic del tipo ImageView, e quindi vi va conservata l'ImageView fornita come parametro.
A questo punto mi riscrivo tutto: ricomponiamo il mandala
public class MainActivity extends Activity {
 ImageView immagine;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  setContentView(R.layout.activity_main);
  immagine=(ImageView)findViewById(R.id.imageView1);
  
  Intent intent=new Intent();
  intent.setAction(Intent.ACTION_PICK);
  intent.setData(Uri.parse("content://media/external/images/media"));
  startActivityForResult(intent,0);
  
 }
 
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data){
  if(resultCode==RESULT_OK){
   asyncTask async=new asyncTask(immagine);
   async.execute(getPathFromUri(data.getData()));
   Log.d("uri",data.getData()+" ");
  }
 }
 

 private String getPathFromUri(Uri uri){
  Cursor cursor=getContentResolver().query(uri, null, null, null, null);
  cursor.moveToFirst();
  int index=cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
  String s=cursor.getString(index);
  return s;
 }
 

 class asyncTask extends AsyncTask{
  
  WeakReference riferimento;
  public asyncTask(ImageView i){
   riferimento=new WeakReference(i);
  }
  @Override
  protected Bitmap doInBackground(String... params) {
   BitmapFactory.Options opzioni=new BitmapFactory.Options();
   opzioni.inJustDecodeBounds=true;
   BitmapFactory.decodeFile(params[0],opzioni);
   int fattore=1;
   while(opzioni.outWidth/fattore>200 && opzioni.outHeight>200){
    fattore*=2;
   }
   opzioni.inJustDecodeBounds=false;
   opzioni.inSampleSize=fattore;
   Bitmap bitmap=BitmapFactory.decodeFile(params[0],opzioni);
   return bitmap;
  }
  @Override
  protected void onPostExecute(Bitmap bitmap){
   if(bitmap!=null && riferimento!=null){
    if(bitmap!=null){
     ImageView i=(ImageView)riferimento.get();
     i.setImageBitmap(bitmap);
    }
   }
  }
  
 }
Scritto tutto il codice marcato. Piccolo errore di distrazione: un true al posto di un false, corretto il quale funziona perfettamente!!!