JavascriptProva

lunedì 29 febbraio 2016

Ricostruendo l'applicazione persa

Bene.
Ho salvato la mia Main Activity.
Ecco il codice xml recuperato.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/superLayout"
    android:background="@drawable/w3"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
   <ImageView
            android:id="@+id/leftTop"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:layout_marginLeft="0dp"
            android:layout_marginTop="0dp"
            android:src="@drawable/screwed" />
   <ImageView
            android:id="@+id/rightTop"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_alignParentRight="true"
            android:layout_alignParentTop="true"
            android:layout_marginRight="0dp"
            android:layout_marginTop="0dp"
            android:src="@drawable/screwed" />
   <ImageView
            android:id="@+id/leftBottom"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_alignParentLeft="true"
            android:layout_alignParentBottom="true"
            android:layout_marginLeft="0dp"
            android:layout_marginBottom="0dp"
            android:src="@drawable/screwed" />
   <ImageView
            android:id="@+id/rightBottom"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_alignParentRight="true"
            android:layout_alignParentBottom="true"
            android:layout_marginRight="0dp"
            android:layout_marginBottom="0dp"
            android:src="@drawable/screwed" />
<ScrollView 
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:fillViewport="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_marginLeft="30dp"
    android:layout_marginTop="30dp"
    android:layout_marginRight="30dp"
    android:layout_marginBottom="30dp"
    android:id="@+id/scrollView" >

    <RelativeLayout 
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/celeste">

        

</RelativeLayout>
</ScrollView>
</RelativeLayout> 
Mi sarebbe dispiaciuto doverlo riscrivere di sana pianta, anche se sarebbe servito come esercizio.

Ora scrivo il richiamo di elementi dal database.

public void leggiDaDatabase(String currentCat){
  Cursor crs=helper.query(currentCat);
  
  do{
   LinearLayout layout=new LinearLayout(this);
   
   ImageView imageView=new ImageView(this);
   Bitmap bmp=BitmapFactory.decodeByteArray(crs.getBlob(numImmagine), 0, crs.getBlob(numImmagine).length);
   imageView.setImageBitmap(bmp);
   layout.addView(imageView);
   
   TextView txtEtichetta =new TextView(this);
   txtEtichetta.setText(crs.getString(numEtichetta));
   layout.addView(txtEtichetta);
   
   TextView txtCategoria=new TextView(this);
   txtCategoria.setText(crs.getString(numCategoria));
   layout.addView(txtCategoria);
   
   LinearLayout.LayoutParams params=new LinearLayout.LayoutParams(100,100);
   layout.setGravity(0);
   layout.setOrientation(LinearLayout.VERTICAL);
   
   mainLayout.addView(layout);
   
   
  }while(crs.moveToNext());
  
 }

E lo faccio chiamare così dall'evento onStart() prevedendo che dovrà essere usato al rientro da un'altra activity:
 @Override
 public void onStart(){
  super.onStart();
  if(categoriaCorrente==null) categoriaCorrente="iniziale";
  try{
  leggiDaDatabase(categoriaCorrente);
  }catch(Exception e){}
 }


Devo sperimentare se funziona.
Ma non avendo ancora immagini nel database mi preparo il "terreno" per crearle.
Creo l'eventListener per il layout principale al click lungo, per chiamare l'activity "ImagePicker" che sceglie le immagini dalla memoria".
Ecco l'xml di ImagePicker:
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    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.progettoultimo.ImagePicker" >

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="27dp"
        android:layout_marginTop="24dp"
        android:src="@drawable/ic_launcher" />

    <EditText
        android:id="@+id/editText1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginRight="24dp"
        android:layout_marginTop="10dp"
        android:ems="10" >

        <requestFocus />
    </EditText>

    <RadioGroup
        android:id="@+id/radioGroup1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginRight="80dp"
        android:layout_marginTop="70dp" >


       <RadioButton
            android:id="@+id/radio0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="RadioButton" />

        <RadioButton
            android:id="@+id/radio1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="RadioButton" />
    </RadioGroup>

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="16dp"
        android:layout_marginRight="150dp"
        android:text="INVIA" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="16dp"
        android:layout_marginRight="30dp"
        android:text="ANNULLA" />


</RelativeLayout> 
E quindi creo l'evento OnLongClickListener per il mainLayout in modo da chiamare quest'altra activity:
  mainLayout.setOnLongClickListener(new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
    Intent intent=new Intent(MainActivity.this,ImagePicker.class);
    startActivity(intent);
    return false;
   }
  });
Ho sperimentato, e funziona.

VBA Excel: un paio di funzioni utili per manipolare un documento

Ecco un paio di routines di VBA utili per manipolare il foglio Excel.

Questa copia un frammento di una riga in una colonna:
'Codice per ricopiare un range di celle disposte in riga in colonna su un altro o sullo stesso foglio.
'Accetta come parametri:
'NOME DEL FOGLIO DI ORIGINE
'RIGA DEL RANGE DA COPIARE
'COLONNA DI INIZIO DEL RANGE DA COPIARE
'COLONNA DI FINE DEL RANGE DA COPIARE
'NOME DEL FOGLIO DI DESTINAZIONE
'RIGA DI INIZIO DEL RANGE DI DESTINAZIONE
'COLONNA DESTINAZIONE DEL RANGE DA COPIARE

Private Sub copy(SourceSheetName As String, SourceRow As Integer, SourceCol1 As Integer, SourceCol2 As Integer, DestSheetName As String, DestRow As Integer, DestCol As Integer)
For n = SourceCol1 To SourceCol2
Sheets(SourceSheetName).Cells(SourceRow, n).copy Destination:=Sheets(DestSheetName).Cells(DestRow + n - 2, DestCol)
Next
End Sub

Questa cancella le righe vuote da un documento:
'Cancella le righe vuote di un foglio.
Sub DeleteEmptyRows()
Dim Righe As Collection
Set Righe = New Collection
For n = 1 To ActiveSheet.UsedRange.Rows.Count
Righe.Add ActiveSheet.Rows(n)
Next n
For Each riga In Righe
If WorksheetFunction.CountA(riga) = 0 Then riga.Delete
Next
End Sub

sabato 27 febbraio 2016

Compattamento di codice con la creazione della funzione refreshScreen

Ho compattato il codice che rinnova la schermata.
Cancella tutte le immagini dal layout principale e apre quelle indicate dalla categoria voluta.
Adesso vediamo i problemi relativi.

Il cambio di schermata è solo virtuale, vengono scaricate le immagini portatrici di una certa categoria e caricate quelle portatrici di una nuova categoria.
Questo meccanismo scatta:

1) Alla prima apertura.
Inizialmente, la categoria è "iniziale".
Alla prima apertura, che si verifica all'evento onStart, si ha questo codice:
 @Override
 public void onStart(){
  super.onStart();
  if(currentCat==null) currentCat="iniziale";
  mainLayout.removeAllViews();
  try{
   leggiDaDatabase(currentCat);
  } catch(Exception e){} 
  
 }
Se la categoria è indeterminata, come da apertura del programma, viene posta su "iniziale" per aprire la schermata iniziale.
Quindi vengono rimosse tutte le immagini presenti (all'atto della prima apertura non sarebbe necessario in quanto non ve ne sono) e quindi vengono lette dal database tutte quelle con la categoria corrente, che alla prima apertura è stata posta come "iniziale".

2)Al cambio di schermata dopo aver toccato e risollevato il mouse da un'immagine di tipo "categoria":
    case MotionEvent.ACTION_UP:
     if(settingMode){
      TextView et=(TextView)((ViewGroup)v).getChildAt(numEtichetta);
      TextView cat=(TextView)((ViewGroup)v).getChildAt(numCategoria);
      helper.Update(et.getText().toString(), cat.getText().toString(),v.getLeft(), v.getTop());
     }
     else{
      TextView t=(TextView) ((ViewGroup)v).getChildAt(numTipo);
      if(t.getText().equals("categoria")){
       String s=(String) ((TextView)((ViewGroup)v).getChildAt(numEtichetta)).getText();
       mainLayout.removeAllViews();
       try{
        leggiDaDatabase(s);
       }catch(Exception e){}
       currentCat=s;
      }
      else {
       
       TextView e=(TextView) ((ViewGroup)v).getChildAt(numEtichetta);
       tts.speak(e.getText().toString(), TextToSpeech.QUEUE_FLUSH, null);
      }
     }
     
    }
Dopo aver selezionato un'immagine, al sollevamento del mouse da essa, se non siamo in modo "setting" e se si tratta di un'immagine di tipo "categoria", cancelliamo tutte le immagini e le riapriamo con la nuova categoria, per poi impostare currentCat con il nuovo valore di categoria.

3)Dopo la cancellazione di un'immagine:
//Codice del pulsante "CANCELLA" per la cancellazione delle immagini
  
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    TextView et=(TextView)ActiveOne.getChildAt(numEtichetta);
    TextView ct=(TextView)ActiveOne.getChildAt(numCategoria);
    TextView tp=(TextView)ActiveOne.getChildAt(numTipo);
    Log.d(et.getText().toString(), ct.getText().toString());
    helper.cancella(et.getText().toString(), ct.getText().toString());
    if(tp.getText().toString().equals("categoria")) helper.cancellaCat(et.getText().toString());
    ActiveOne=null;
    mainLayout.removeAllViews();
    try{
     leggiDaDatabase(currentCat);
    } catch(Exception e){} 
    button.setVisibility(View.INVISIBLE);
    
   }
  });
bisogna eliminare tutte le immagini e quindi riaprire il database sempre con la stessa categoria, senza cambiarla, in modo che non venga più caricata l'immagine eliminata.

Vado a compattare con la nuova routine refreshScreen:
 private void refreshScreen(String cat){
  mainLayout.removeAllViews();
  try{
   leggiDaDatabase(cat);
  } catch(Exception e){} 
 }
...e quindi vado a sostituire nei tre punti di utilizzo il codice con questa routine:

1)Allo Start:
 @Override
 public void onStart(){
  super.onStart();
  if(currentCat==null) currentCat="iniziale";
  refreshScreen(currentCat);
 }


2) Al cambio di schermata (evento onTouch su un'immagine di tipo categoria in modalità non settingMode):
    case MotionEvent.ACTION_UP:
     if(settingMode){
      TextView et=(TextView)((ViewGroup)v).getChildAt(numEtichetta);
      TextView cat=(TextView)((ViewGroup)v).getChildAt(numCategoria);
      helper.Update(et.getText().toString(), cat.getText().toString(),v.getLeft(), v.getTop());
     }
     else{
      TextView t=(TextView) ((ViewGroup)v).getChildAt(numTipo);
      if(t.getText().equals("categoria")){
       String s=(String) ((TextView)((ViewGroup)v).getChildAt(numEtichetta)).getText();
       refreshScreen(s);
       currentCat=s;
      }
      else {
       
       TextView e=(TextView) ((ViewGroup)v).getChildAt(numEtichetta);
       tts.speak(e.getText().toString(), TextToSpeech.QUEUE_FLUSH, null);
      }
     }
     
    }
...questa volta però non dando la categoria corrente, ma la nuova categoria ricavata dall'etichetta dell'immagine "categoria";

3) Dopo la cancellazione di un'immagine:
//Codice del pulsante "CANCELLA" per la cancellazione delle immagini
  
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    TextView et=(TextView)ActiveOne.getChildAt(numEtichetta);
    TextView ct=(TextView)ActiveOne.getChildAt(numCategoria);
    TextView tp=(TextView)ActiveOne.getChildAt(numTipo);
    Log.d(et.getText().toString(), ct.getText().toString());
    helper.cancella(et.getText().toString(), ct.getText().toString());
    if(tp.getText().toString().equals("categoria")) helper.cancellaCat(et.getText().toString());
    ActiveOne=null;
    refreshScreen(currentCat);
    button.setVisibility(View.INVISIBLE);
    
   }
  });
sempre con la stessa categoria, stavolta.

Vediamo se funziona...

Testato sia all'avvio sia al cambio categoria sia alla cancellazione di un'immagine: FUNZIONA!

venerdì 26 febbraio 2016

Promemoria. Il codice dello "svitamento" nella mia applicazione.

Il codice di "apertura delle viti".
Lo salvo prima di modificarlo.
  screwOpen = new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
     Vibrator vib = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
     // Vibrate for 500 milliseconds
     vib.vibrate(500); 
    if(v==leftTop && openFlag==0) {
     openFlag=1;
     if(settingMode==false) leftTop.setImageResource(R.drawable.unscrewed);
     else leftTop.setImageResource(R.drawable.screwed);
     
    }
    if(v==rightTop && openFlag==1) {
     openFlag=2; 
     if(settingMode==false) rightTop.setImageResource(R.drawable.unscrewed);
     else rightTop.setImageResource(R.drawable.screwed);
    }
    if(v==leftBottom && openFlag==2){
     openFlag=3;
     if(settingMode==false) leftBottom.setImageResource(R.drawable.unscrewed);
     else leftBottom.setImageResource(R.drawable.screwed);
    }
    if(v==rightBottom && openFlag==3) {
     if(settingMode==false){
      rightBottom.setImageResource(R.drawable.unscrewed);
      settingMode=true;
     
      
     } else
     {
      rightBottom.setImageResource(R.drawable.screwed);
      settingMode=false; 
      button.setVisibility(View.INVISIBLE);
      
     }

     openFlag=0;
    }
    return false;
   }
  };


Ho elaborato una forma "a chiusura rapida":
screwOpen = new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
     Vibrator vib = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
     // Vibrate for 500 milliseconds
     vib.vibrate(500); 
     if(settingMode==false){
      if(v==leftTop && openFlag==0) {
       openFlag=1;
       Log.d("Openflag",openFlag+"");
       Log.d("settingMode",settingMode+"");
       leftTop.setImageResource(R.drawable.unscrewed);     
      }
      if(v==rightTop && openFlag==1) {
       openFlag=2; 
       Log.d("Openflag",openFlag+"");
       rightTop.setImageResource(R.drawable.unscrewed);
      }
      if(v==leftBottom && openFlag==2){
       openFlag=3;
       Log.d("Openflag",openFlag+"");
       leftBottom.setImageResource(R.drawable.unscrewed);
      }
      if(v==rightBottom && openFlag==3) { 
       Log.d("Openflag",openFlag+"");
      rightBottom.setImageResource(R.drawable.unscrewed);
      settingMode=true; 
      openFlag=0;
     } 
     
     } // fine di if(settingMode==false)
    
     else{
      leftTop.setImageResource(R.drawable.screwed);
      rightTop.setImageResource(R.drawable.screwed);
      leftBottom.setImageResource(R.drawable.screwed);
      rightBottom.setImageResource(R.drawable.screwed);
      settingMode=false;
     
     }
     
     return true;
   }
   
  };


Ma adesso devo lasciare spazio al "ripensamento"...

Eccolo (che faticaccia!)
  screwOpen = new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
     Vibrator vib = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
     // Vibrate for 500 milliseconds
     vib.vibrate(500); 
    
     if(settingMode==false){
      if(v==leftTop) {
       if(openFlag==0){
        openFlag=1;
        leftTop.setImageResource(R.drawable.unscrewed);
       
       }
       else if(openFlag==1){
        openFlag=0;
        leftTop.setImageResource(R.drawable.screwed);
       }
            
      }
      if(v==rightTop) {
       if(openFlag==1){
        openFlag=2;
        rightTop.setImageResource(R.drawable.unscrewed);
       
       }
       else if(openFlag==2){
        openFlag=1;
        rightTop.setImageResource(R.drawable.screwed);
       }
      }
      if(v==leftBottom){
       if(openFlag==2){
        openFlag=3;
        leftBottom.setImageResource(R.drawable.unscrewed);
       
       }
       else if(openFlag==3){
        openFlag=2;
        leftBottom.setImageResource(R.drawable.screwed);
       }
      }
      if(v==rightBottom && openFlag==3) { 
      rightBottom.setImageResource(R.drawable.unscrewed);
      settingMode=true; 
      openFlag=0;
     } 
     
     } // fine di if(settingMode==false)
    
     else{
      leftTop.setImageResource(R.drawable.screwed);
      rightTop.setImageResource(R.drawable.screwed);
      leftBottom.setImageResource(R.drawable.screwed);
      rightBottom.setImageResource(R.drawable.screwed);
      settingMode=false;
     
     }
     
     return true;
   }
   
  };
...depurato di tutti i Log.d che ho usato per verificare i risultati in LogCat precedentemente.

Approccio VBA a un documento

Veniamo al VBA...

Devo elaborare un file di risposte di pazienti a un'intervista.
Lo avevo già fatto, ma dal momento che rimuovo attivamente memorie connesse a questa attività che detesto, rimuovo anche i codici.
Così è meglio che metta tutto per iscritto.
Mi rianalizzo un po' il codice.
Sub main()
For n = 1 To 100
Sheets("Risposte").Cells(n, 1).Formula = n
Sheets("Risposte").Cells(n, 2).Formula = Sheets("Questionario").Cells(1, n).Formula
Next n
End Sub
In italiano, significa che nel foglio chiamato "Risposte", su 100 righe nella prima colonna mettiamo il numero di riga, mentre nella seconda mettiamo il contenuto delle celle su 100 colonne progressive della prima riga del foglio "Questionario".

Creo un nuovo foglio chiamato "Risposte" per vedere come funziona senza peraltro combinare danni.
Ecco applicata la macro:

Foglio "Questionario":


Foglio "Risposte":


Era come ho previsto io.

Ma ora sono a un punto morto: come ho fatto a ottenere i SI NO? Da quali dati?

Cerchiamo di decifrare altri files...

Ho dimenticato tutto.
Riprendo il file originale e me lo ristudio con calma...



Ecco, qui sono presentati in modo molto confuso. L'unico riferimento è il numero che sta sulla colonna Q.
Dal momento che devo fare calcoli mi conviene avere le colonne in formato numerico.

Fatto! Strumenti->Opzioni->Generale->Stile di riferimento R1C1.

Fatto questo, mi vorrei separare le varie interviste. Il punto di riferimento è il numero nella colonna Q, che ora diventa colonna 17! (E infatti contando sulle ditine ho verificato che la Q è la 17esima lettera dell'alfabeto inglese... GUH! GUH!)

Separarle potrebbe significare mettere delle righe vuote per separare ciascuna intervista dall'altra.
O anche, più elegantemente e funzionalmente, ricopiare ognuna su un foglio separato!
Studierei una macro in tal senso...

Contare le righe che presentano il numero 1 sulla colonna 17...
Sub suddividi()
Dim contatore As Integer
contatore = 0
Dim linea As Integer
linea = 174

Do Until Cells(linea, 17).Formula <> 3
linea = linea + 1
contatore = contatore + 1
Loop
Debug.Print linea & " " & contatore
End Sub
Cambiando il numero di linea iniziale e il numero da ricercare nella colonna 17 ottengo sempre lo stesso numero.
88 86
174 86
260 86

Ora, su questa base, costruisco un codice in cui copio ogni singolo troncone di 86 linee su un nuovo foglio di Excel.
Separo, praticamente, le interviste, ognuna su un nuovo foglio---

mercoledì 24 febbraio 2016

Funzioni di cancellazione di immagini dalla mia applicazione

Se viene toccata l'immagine si attiva il pulsante "Cancella".
Metto una variabile ActiveOne di tipo Layout e lo imposto al valore del Layout toccato o, se viene cliccato lo sfondo, a null.
Creo la variabile...
...

 int openFlag=0;
 boolean settingMode=false;
 String currentCat;
 int mlX;
 int mlY;
 LinearLayout ActiveOne;

...
Inserisco in ACTIVE_DOWN dell'OnTouch il codice che la imposta:
    case MotionEvent.ACTION_DOWN:
     if(settingMode){
      scrollView.requestDisallowInterceptTouchEvent(true);
      RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams)v.getLayoutParams();
      DeltaX=X-lp.leftMargin;
      DeltaY=Y-lp.topMargin;
      ActiveOne=(LinearLayout)v;
     }
     break;
...e inserisco il comando che annulla ActiveOne quando viene toccato lo sfondo:
  mainLayout.setOnTouchListener(new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    mlX=(int)event.getX();
    mlY=(int)event.getY();
    scrollView.requestDisallowInterceptTouchEvent(false);
    ActiveOne=null;
    return false;
   }
  });


Insieme al toccamento di un'immagine va fatto apparire il bottone per cancellarla:
    case MotionEvent.ACTION_DOWN:
     if(settingMode){
      scrollView.requestDisallowInterceptTouchEvent(true);
      RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams)v.getLayoutParams();
      DeltaX=X-lp.leftMargin;
      DeltaY=Y-lp.topMargin;
      ActiveOne=(LinearLayout)v;
      button.setVisibility(View.VISIBLE);
     }
     break;
...e va fatto sparire il bottone quando si seleziona lo sfondo:
  mainLayout.setOnTouchListener(new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    mlX=(int)event.getX();
    mlY=(int)event.getY();
    scrollView.requestDisallowInterceptTouchEvent(false);
    ActiveOne=null;
    button.setVisibility(View.INVISIBLE);
    return false;
   }
  });
Aggiungo un codice per il button che cancelli il Layout identificato da ActiveOne:
  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    TextView et=(TextView)ActiveOne.getChildAt(numEtichetta);
    TextView ct=(TextView)ActiveOne.getChildAt(numCategoria);
    Log.d(et.getText().toString(), ct.getText().toString());
    helper.cancella(et.getText().toString(), ct.getText().toString());
    ActiveOne=null;
    mainLayout.removeAllViews();
    try{
     leggiDaDatabase(currentCat);
    } catch(Exception e){} 
    button.setVisibility(View.INVISIBLE);
    
   }
  });
E funziona!

Ho aggiunto anche un'autocancellazione del button, che a questo punto non serve. Per riattivarlo bisognerà cliccare su un altro Layout.

Il button viene reso invisibile anche qualora si uscisse dalla modalità settingMode, vi ho aggiunto il codice necessario, perché se è stato selezionato un Layout in modalità settingMode e si tornasse alla modalità operativa immediatamente il bottone resterebbe indebitamente visibile, rischiandosi di cancellare involontariamente un Layout.
Il codice è stato aggiunto al listener screwOpen, quello che governa lo "svitamento" delle viti:
  screwOpen = new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
     Vibrator vib = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
     // Vibrate for 500 milliseconds
     vib.vibrate(500); 
    if(v==leftTop && openFlag==0) {
     openFlag=1;
     if(settingMode==false) leftTop.setImageResource(R.drawable.unscrewed);
     else leftTop.setImageResource(R.drawable.screwed);
     
    }
    if(v==rightTop && openFlag==1) {
     openFlag=2; 
     if(settingMode==false) rightTop.setImageResource(R.drawable.unscrewed);
     else rightTop.setImageResource(R.drawable.screwed);
    }
    if(v==leftBottom && openFlag==2){
     openFlag=3;
     if(settingMode==false) leftBottom.setImageResource(R.drawable.unscrewed);
     else leftBottom.setImageResource(R.drawable.screwed);
    }
    if(v==rightBottom && openFlag==3) {
     if(settingMode==false){
      rightBottom.setImageResource(R.drawable.unscrewed);
      settingMode=true;
      
     } else
     {
      rightBottom.setImageResource(R.drawable.screwed);
      settingMode=false; 
      button.setVisibility(View.INVISIBLE);
     }

     openFlag=0;
    }
    return false;
   }
  };

lunedì 22 febbraio 2016

Catch SQLiteConstraintException.

Ho scoperto finalmente come impedire l'interruzione del programma quando si verifichi il tentativo di un reinserimento nel database in un campo UNIQUE.
Avevo già incontrato in passato il problema di non riuscire a intercettare le eccezioni lanciate quando si tenti di inserire un duplicato in un campo UNIQUE, ma non ero riuscito a trovare la soluzione in rete e così ero ricorso a complicati mezzi cervellotici per ottenere questo risultato.
Adesso, invece, basta intercettare l'eccezione lanciata mediante il metodo insertOrThrow e gestirla eventualmente con un codice alternativo senza che si interrompa l'esecuzione del programma.
Ecco il codice completo:
 public void salva(String nome){
  SQLiteDatabase db=this.getWritableDatabase();
  ContentValues values=new ContentValues();
  values.put("nome", nome);
  try{
   db.insertOrThrow("tabella", null, values);
  }catch(SQLiteConstraintException e){
   Log.d("Exception", "Caught");
  }
 }
Ecco il risultato in LogCat:
02-22 19:25:39.944: E/SQLiteLog(12396): (2067) abort at 11 in [INSERT INTO tabella(nome) VALUES (?)]: UNIQUE constraint failed: tabella.nome
02-22 19:25:39.944: D/Exception(12396): Caught
02-22 19:25:39.944: V/LETTURA(12396): DATABASE
02-22 19:25:39.945: V/1(12396): topolino
02-22 19:25:39.945: V/2(12396): minni
02-22 19:25:39.945: V/3(12396): paperino
Praticamente, l'errore viene intercettato e viene lasciato il messaggio di avvenuta intercettazione, quindi il flusso del programma procede liberamente inserendo gli altri records nel database.

domenica 21 febbraio 2016

Finestra di dialogo in Android

Mi annoto qui il codice per la finestra di dialogo, che ho imparato questa sera:
public class MainActivity extends Activity {

 Button button;
 
 DialogInterface.OnClickListener listener=new DialogInterface.OnClickListener() {
  
  @Override
  public void onClick(DialogInterface dialog, int which) {
   switch(which){
   case DialogInterface.BUTTON_POSITIVE:
    Log.d("ALLORA","VAFFANCULO");
    break;
   case DialogInterface.BUTTON_NEGATIVE:
    Log.d("ALLORA","NON ANDARE A FANCULO");
    break;
   }
   
  }
 };

 
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  button=(Button)findViewById(R.id.button1);

  button.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    AlertDialog.Builder builder=new AlertDialog.Builder(MainActivity.this);
    builder.setMessage("Sei sicuro che vuoi andare a fanculo?").setPositiveButton("SI", listener).setNegativeButton("NO", listener).show();
    
   }
  });
  
 }
}

Finestra popup

Rivedendo un appunto passato, a proposito delle finestre popup.
Ecco il codice, ricostruito da me ora:
package com.example.laboratorio21022016;

import android.app.Activity;
import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MainActivity extends Activity {

 PopupWindow popupWindow;
 LinearLayout linearLayout;
 TextView textView;
 RelativeLayout mainLayout;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  popupWindow=new PopupWindow(this);
  linearLayout=new LinearLayout(this);
  textView=new TextView(this);
  textView.setText("CIAO CICCIO");
  mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  
  linearLayout.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
  linearLayout.setBackgroundColor(Color.YELLOW);
  linearLayout.addView(textView);
  popupWindow.setContentView(linearLayout);
 }

 @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) {
   popupWindow.showAtLocation(mainLayout, 1, 100, 100);
   popupWindow.update(100,100);
   return true;
  }
  if(id==R.id.secondo_menu){
   popupWindow.dismiss();
  }
  return super.onOptionsItemSelected(item);
 }
}
...in modo da avere vita più facile di quanto l'ho avuta stasera non ricordando il codice per ottenerla!

Altro errore

Il passaggio alla schermata ImagePick viene sempre fatto con il passaggio della categoria iniziale.
Vediamo bene il codice che chiama questa "schermata".
  mainLayout.setOnLongClickListener(new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
    if(settingMode){
     Intent intent=new Intent(MainActivity.this,ImagePick.class);
     intent.putExtra("Categoria", currentCat);
     intent.putExtra("CoordX", mlX);
     intent.putExtra("CoordY", mlY);
     startActivity(intent);
    }
    return true;
   }
  });
Trasmette alla schermata (activity) ImagePick il valore di currentCat, la categoria corrente, che quando si parte dalla schermata iniziale è "iniziale", e sta bene, ma che probabilmente resta "iniziale" anche quando si verifica il cambiamento apparente di schermata con lo scarico e il carico di immagini dal database.
Verifichiamo se con il cambio di schermata la categoria corrente resta sempre la stessa.
Prendiamo il codice che cambia la schermata.
.....
.....
case MotionEvent.ACTION_UP:
    if(settingMode){
     TextView et=(TextView)((ViewGroup)v).getChildAt(numEtichetta);
     TextView cat=(TextView)((ViewGroup)v).getChildAt(numCategoria);
     helper.Update(et.getText().toString(), cat.getText().toString(),v.getLeft(), v.getTop());
    }
    else{
     TextView t=(TextView) ((ViewGroup)v).getChildAt(numTipo);
     if(t.getText().equals("categoria")){
      String s=(String) ((TextView)((ViewGroup)v).getChildAt(numEtichetta)).getText();
      mainLayout.removeAllViews();
      try{
       leggiDaDatabase(s);
       currentCat=s;
      }catch(Exception e){}
     }
     
      
     
    }
Traduzione della parte successiva a else:
Prendi una TextView t e identificala con la TextView che rappresenta il Tipo dell'immagine.
Se il suo testo è "categoria", allora: prendi una stringa s e dalle il valore dell'etichetta dell'immagine.
Togli tutte le immagini dalla schermata.
Se ci riesci, leggi dal database le immagini che hanno come tipo la stringa s e imposta la categoria corrente al valore dell'etichetta.
E forse ho individuato il "topo"! Io dico "se ci riesci leggi dal database E cambia la categoria corrente". Ora, se il database è vuoto, l'operazione di lettura fallisce e l'eccezione viene gestita senza messaggio di errore, ma dal momento che nel Try...catch è compresa anche l'istruzione di cambiare la categoria corrente, questo cambio non viene fatto, mentre le immagini sono cancellate passando così virtualmente alla seconda schermata.
Quindi mi trovo su una seconda schermata, vuota perché non vi sono salvate immagini, ma con la categoria di partenza, in quanto il cambio di categoria è stato "trascinato nel fallimento della operazione di lettura dal database".
Provvedo e provo...

sabato 20 febbraio 2016

Alcuni errori e perplessità

Mettiamo a confronto.

Viene presa l'immagine, e sottoposta a questa procedura:
//DA RISORSE A BITMAP
 public static Bitmap ResourcesToBitmap(Context context,int id, int dimensioni){
   BitmapFactory.Options opzioni=new BitmapFactory.Options();
   opzioni.inJustDecodeBounds=true;
   BitmapFactory.decodeResource(context.getResources(),id,opzioni);
   int fattore=1;
   while(opzioni.outWidth/fattore>dimensioni && opzioni.outHeight/fattore>dimensioni){
    fattore*=2;
   }
   opzioni.inSampleSize=fattore;
   opzioni.inJustDecodeBounds=false;
   Bitmap bmp=BitmapFactory.decodeResource(context.getResources(),id,opzioni);
   return bmp;
 }
Da file, invece:
//DA PATH A BITMAP
 public static Bitmap PathToBitmap(String path, int dimensioni){
  BitmapFactory.Options opzioni=new BitmapFactory.Options();
  opzioni.inJustDecodeBounds=true;
  BitmapFactory.decodeFile(path,opzioni);
  int fattore=1;
  while(opzioni.outWidth/fattore>dimensioni && opzioni.outHeight/dimensioni>dimensioni){
   fattore*=2;
  }
  opzioni.inSampleSize=fattore;
  opzioni.inJustDecodeBounds=false;
  Bitmap bmp=BitmapFactory.decodeFile(path, opzioni);
  return bmp;
 }
Ho trovato il banale ma tremendo errore!


Ora le immagini caricate da path risultano appena più grandi di quelle caricate da risorse.
Perché?

Vediamo con quali parametri vengono chiamate le funzioni di cui sopra.
Il salvataggio di immagini da risorse avviene tramite la seguente funzione:
 private void salvaInDatabase(String etichetta, String categoria, int idImmagine, String tipo, int coordX, int coordY){
  JImage jImage=new JImage();
  jImage.etichetta=etichetta;
  jImage.categoria=categoria;
  jImage.immagine=Funzioni.ResourcesToByteArray(this, idImmagine, 100, 80);
  jImage.tipo=tipo;
  jImage.coordX=coordX;
  jImage.coordY=coordY;
  helper.save(jImage);
 }
La funzione che fa uso della funzione ResourcesToBitmap(Context context,int id, int dimensioni) viene chiamata dalla funzione ResourcesToByteArray(Context context, int id, int dimensioni,int quality)
 public static byte[] ResourcesToByteArray(Context context, int id, int dimensioni,int quality){
  return BitmapToByteArray(ResourcesToBitmap(context,id,dimensioni), quality);
 }
La funzione ResourcesToBitmap(Context context,int id, int dimensioni)viene chiamata con i parametri Context context, int id, int dimensioni che sono this,idImmagine,100, mentre la funzione BitmapToByteArray(Bitmap bmp,int quality) ha il parametro Int quality che è 80.

Invece la funzione PathToBitmap(String path, int dimensioni) viene chiamata con i parametri path e dimensioni, che viene usato allo stesso modo che nella funzione ResourcesToBitap.
, e prima di salvarla in un database viene trasformata in byte[] mediante la stessa funzione BitmapToByteArray(Bitmap bmp,int quality), sempre con un parametro quality di 80.

Mah... Le immagini vengono trattate allo stesso modo, e non riesco a spiegarmi perché vengano di dimensione diversa.
Fa nulla, la differenza è di poco...

martedì 16 febbraio 2016

Alleggerimento del codice con metodi statici di un'altra classe

Allestiamo il database, che dovrà avere i seguenti campi:
  1. _id
  2. etichetta
  3. categoria
  4. immagine
  5. tipo
  6. coordinata X
  7. coordinata Y
Prendo un modulo di classe a parte per la classe Helper che istanzia SQLiteOpenHelper.
public 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, tipo text, coordX integer, coordY integer)");
 }
 
 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.immagine);
  values.put("tipo", jImage.tipo);
  values.put("coordX", jImage.coordX);
  values.put("coordY", jImage.coordY);
  db.insert("tabella", null, values);
 }
 
 public Cursor query(){
  SQLiteDatabase db=this.getWritableDatabase();
  Cursor crs=db.rawQuery("select * from tabella where categoria='iniziale' ", null);
  return crs;
  
 }

 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  // TODO Auto-generated method stub
  
 }

}
Ho creato anche il tipo JImage su un altro modulo di classe:
public class JImage {
 public String etichetta;
 public String categoria;
 public byte[] immagine;
 public String tipo;
 public int coordX;
 public int coordY;
}
Ora nella classe principale mi devo creare una funzione che salvi i dati di un'immagine impacchettando tutto in una variabile di tipo JImage perché venga salvato nel database.
I dati di tipo String possono essere immessi direttamente, ma quelli di tipo byte[] o, nel database, blob, devono essere immessi a partire da un'immagine.
Per fare questo, al momento metto delle immagini in risorse.

Una volta messe in risorse, devo costruire una bitmap e poi ridurla a un array di bytes.
 private byte[] decodeImage(int id){
  BitmapFactory.Options opzioni=new BitmapFactory.Options();
  opzioni.inJustDecodeBounds=true;
  BitmapFactory.decodeResource(getResources(),id,opzioni);
  int fattore=1;
  while(opzioni.outWidth/fattore>100 && opzioni.outHeight/fattore>100){
   fattore*=2;
  }
  opzioni.inSampleSize=fattore;
  opzioni.inJustDecodeBounds=false;
  Bitmap bmp=BitmapFactory.decodeResource(getResources(),id,opzioni);

  ByteArrayOutputStream stream=new ByteArrayOutputStream();
  bmp.compress(Bitmap.CompressFormat.JPEG, 50, stream);
  byte[] b=stream.toByteArray();
  return b;
 }
E allora, costruita questa funzione, posso poi inserire le altre più facilmente.

Mi viene in mente di cercare di alleggerire il mio modulo principale mediante la "dislocazione" di alcune funzioni in altri moduli.
Proviamo: creo un modulo di classe...

public class Funzioni {
 public static Bitmap BitmapFromResources(Context context,int id){
  BitmapFactory.Options opzioni=new BitmapFactory.Options();
  opzioni.inJustDecodeBounds=true;
  BitmapFactory.decodeResource(context.getResources(),id,opzioni);
  int fattore=1;
  while(opzioni.outWidth/fattore>100 && opzioni.outHeight/fattore>100){
   fattore*=2;
  }
  opzioni.inSampleSize=fattore;
  opzioni.inJustDecodeBounds=false;
  Bitmap bmp=BitmapFactory.decodeResource(context.getResources(),id,opzioni);
  return bmp;
 }

 public static byte[] BitmapToByteArray(Bitmap bmp){
  ByteArrayOutputStream stream=new ByteArrayOutputStream();
  bmp.compress(Bitmap.CompressFormat.JPEG, 50, stream);
  byte[] b=stream.toByteArray();
  return b;
 }
}
Ecco: dopo alcuni tentativi ho creato una classe con dei metodi statici, che possano essere eseguiti senza dover istanziare la classe.
Ho sperimentato il primo metodo e funziona. Dalla classe dell'Activity principale eseguo questo codice:
ImageView imageView=new ImageView(this);
  imageView.setImageBitmap(Funzioni.BitmapFromResources(this, R.drawable.faciadecul));
  mainLayout.addView(imageView);
E ottengo così, fornendo come parametri il context (this) e l'indirizzo dell'immagine in risorse, una ImageView con l'immagine!

lunedì 15 febbraio 2016

Blocco dello scrolling al tocco di una specifica View o gruppo di Views.

Adesso l'obiettivo è quello di ottenere l'attivazione della ScrollBar soltanto quando si tocchi lo "sfondo" e non gli elementi TextView (o altri che si volessero creare nel Layout)

Creo e istanzio le variabili oggetto per gli elementi:
public class MainActivity extends Activity {

 ScrollView scrollView;
 RelativeLayout mainLayout;
 TextView textView1;
 TextView textView2;
 TextView textView3;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  scrollView=(ScrollView)findViewById(R.id.scrollView);
  textView1=(TextView)findViewById(R.id.textView1);
  textView2=(TextView)findViewById(R.id.textView2);
  textView3=(TextView)findViewById(R.id.textView3);
  
  
 }

 
 
}
]

...e modifico la coordinata verticale di uno degli elementi per poter mettere in evidenza il comportamento della ScrollView.
    <TextView
        android:background="@color/nero"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="400dp"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="200dp"
        android:id="@+id/textView2"
        android:text="@string/hello_world" />

Bene.
Ora creo un OnTouchListener per la ScrollView, che restituisca true, nel qual caso la ScrollView sarà disabilitata, o false nel qual caso la ScrollView sarà abilitata: praticamente bisogna che il listener restituisca un valore booleano contrario al fatto che la ScrollView sia abilitata o no.
Pongo il listener in un metodo privato a parte, separato dal metodo onCreate dell'Activity.
public class MainActivity extends Activity {

 ScrollView scrollView;
 RelativeLayout mainLayout;
 TextView textView1;
 TextView textView2;
 TextView textView3;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  scrollView=(ScrollView)findViewById(R.id.scrollView);
  textView1=(TextView)findViewById(R.id.textView1);
  textView2=(TextView)findViewById(R.id.textView2);
  textView3=(TextView)findViewById(R.id.textView3);
  

  
 }

 private void ScrollViewEnabled(final boolean enabled){
  scrollView.setOnTouchListener(new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    // TODO Auto-generated method stub
    return !enabled;
   }
  });
 }
 
 
}
Mettendo come parametro "true", ottengo che la funzione restituisca "false", nel qual caso la ScrollView sarà abilitata, mentre mettendo come parametro "false", la ScrollView sarà disabilitata.
Ora creo un OnTouchListener comune per le TextBox, in modo che toccando queste si disabiliti la ScrollView:
View.OnTouchListener onTouchListener=new View.OnTouchListener() {
  
  @Override
  public boolean onTouch(View v, MotionEvent event) {
   ScrollViewEnabled(false);
   return false;
  }
 };
 
 
}
e attribuisco il listener a tutte e tre le TextView.

  textView1.setOnTouchListener(onTouchListener);
  textView2.setOnTouchListener(onTouchListener);
  textView3.setOnTouchListener(onTouchListener);
E verifichiamo...

Funziona benissimo!.
La ScrollView funziona benissimo, ma quando vado a toccare una delle TextView non si ha più lo scorrimento.
Però la ScrollView continua a rimanere disabilitata anche se poi si va a toccare nuovamente la parte libera.
Provo a risolvere creando un OnTouchListener per il mainLayout, ossia per il Layout di base, e facendo sì che al touch su questo si riattivi la ScrollView ponendo una nuova chiamata della funzione ScrollViewEnabled però questa volta con parametro "true".

  mainLayout.setOnTouchListener(new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    ScrollViewEnabled(true);
    return false;
   }
  });
Vado a provare, e che accade?
Accade che non funziona più il blocco dello scrolling quando tocco le TextView.
Perché?

Dalla mia esperienza deduco che ciò sia perché il touchListener delle TextView restituisca false, e questo fa sì che l'evento bubbli diffondendosi dalle TextView al loro "parent" che è il mainLayout!
Prima questo bubbling passava inosservato perché non c'era nessun listener associato al mainLayout, ma adesso che c'è un listener che al tocco del mainLayout attiva la ScrollView, si ha che al tocco sulla TextView, dapprima la ScrollView viene bloccata per l'evento onTouch sulla TextView, quindi quando l'evento bubbla si realizza anche l'evento mainLayout.onTouch che invece sblocca la ScrollView.
Per rimediare faccio in modo che l'evento onTouch in comune per le TextView restituisca true anziché false.
  View.OnTouchListener onTouchListener=new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    ScrollViewEnabled(false);
    return true;
   }
  };
...e funziona perfettamente!

Impostazioni perché la ScrollView occupi tutta l'area del display

Importanti acquisizioni con la ScrollView, che metto qui come appunti utili per eventuali futuri ripassi.

Per prima cosa, non riuscivo a mettere un RelativeLayout contenuto in una ScrollView che mi riempisse tutto lo schermo.
Ripercorro le tappe.
Il codice iniziale era questo:
<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="match_parent" 
    android:id="@+id/scrollView" >

    <RelativeLayout 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    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.laboratorio1322016.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_marginTop="200dp"
        android:id="@+id/textView1"
        android:text="fanculo" />
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_marginTop="300dp"
        android:id="@+id/textView2"
        android:text="@string/hello_world" />
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_marginTop="700dp"
        android:id="@+id/textView3"
        android:text="Ciao, ciccio bello!" />

</RelativeLayout>
</ScrollView> 
...e il risultato è questo:



Provo a mettere nel codice di ScrollView android:fillViewport=true":
<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="match_parent" 
    android:fillViewport="true"
    android:id="@+id/scrollView" >

    <RelativeLayout 
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="@color/celeste"
    tools:context="com.example.laboratorio1322016.MainActivity" >

    <TextView
        android:background="@color/nero"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="0dp"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="0dp"
        android:id="@+id/textView1"
        android:text="fanculo" />
    
    <TextView
        android:background="@color/nero"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="0dp"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="200dp"
        android:id="@+id/textView2"
        android:text="@string/hello_world" />
    
    <TextView
        android:background="@color/nero"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="0dp"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="400dp"
        android:id="@+id/textView3"
        android:text="Ciao, ciccio bello!" />

</RelativeLayout>
</ScrollView>


...e proviamo il risultato:


Ma modificando l'altezza della ScrollView, le cose cambiano.
Cambio da match_parent a wrap_content:
<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:fillViewport="true"
    android:id="@+id/scrollView" >

    <RelativeLayout 
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="@color/celeste"
    tools:context="com.example.laboratorio1322016.MainActivity" >

    <TextView
        android:background="@color/nero"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="0dp"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="0dp"
        android:id="@+id/textView1"
        android:text="fanculo" />
    
    <TextView
        android:background="@color/nero"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="0dp"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="200dp"
        android:id="@+id/textView2"
        android:text="@string/hello_world" />
    
    <TextView
        android:background="@color/nero"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="0dp"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="400dp"
        android:id="@+id/textView3"
        android:text="Ciao, ciccio bello!" />

</RelativeLayout>
</ScrollView> 
e il risultato torna:



Quindi impostare android:fillViewport="true" non è sufficiente: l'altezza della ScrollView deve essere impostata anche a android:layout_height="match_parent".
Invece l'altezza del RelativeLayout figlio della ScrollView può essere impostata sia a match_parent sia a wrap_content, senza che il risultato cambi.
Provo anche a settare l'altezza del RelativeLayout a un valore determinato:
<RelativeLayout 
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="@color/celeste"
    tools:context="com.example.laboratorio1322016.MainActivity" > 
e il risultato è sempre quello:



...Okay!

sabato 13 febbraio 2016

Il codice funzionante prima che venga istanziata la ScrollView

Un semplice promemoria: il codice funzionante prima che venga materializzata la ScrollView:
public class MainActivity extends Activity {

 //DICHIARAZIONI
 
 RelativeLayout mainLayout;
 ImageView imageView;
 ScrollView scrollView;
 
 
 int deltaX;
 int deltaY;
 RelativeLayout.LayoutParams absParam;
 
 //dichiarazioni delle variabili posizioni delle sottoViews
 int posEtichetta=0;
 int posCategoria=1; 
 
 
 //dichiarazione del database
 Helper helper=new Helper(this);
 
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  //DEFINIZIONI E ATTRIBUTI
  mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  imageView=(ImageView)findViewById(R.id.imageView1);
 
  
  /*
  SalvaJElemento("bonazza","iniziale",R.drawable.bonazza,"base",0,0);
  SalvaJElemento("caffè","iniziale",R.drawable.cafe,"base",200,0);
  SalvaJElemento("faccia da culo","iniziale",R.drawable.faciadecul,"base",400,0);
  */
  //helper.aggiorna();

  Cursor c=helper.query("iniziale");
  
  //per ogni record...
  do{
   LinearLayout layout=new LinearLayout(this);
   
   //abbiamo preso i records dal database e li dobbiamo mostrare.
   //Ho materializzato un nuovo layout per ospitare un record, e vi attribuisco
   //dei parametri:
   //Creo i parametri, vi attribuisco le coordinate prelevate dal database,
   //e setto con essi i parametri del layout.
   RelativeLayout.LayoutParams params=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
   params.leftMargin=c.getInt(5);
   params.rightMargin=-250;
   params.topMargin=c.getInt(6);
   params.bottomMargin=-250;
   Log.d("CARICAMENTO X",""+c.getInt(5));
   Log.d("CARICAMENTO Y",""+c.getInt(6));
   layout.setLayoutParams(params);
   
   //ora setto altre caratteristiche del layout, ORIENTAMENTO e COLORE.
   layout.setOrientation(LinearLayout.VERTICAL);
   layout.setBackgroundColor(Color.WHITE);
   
   
   
   //Mi occupo ore del CONTENUTO del layout: 
   
   //creo la CATEGORYVIEW e vi imposto il testo prelevato dal database, 
   //campo CATEGORIA.
   TextView categoryView = new TextView(this);
   categoryView.setText(c.getString(2));
   //la setto di altezza zero
   categoryView.setHeight(0);
   //la setto come invisibile
   categoryView.setVisibility(View.INVISIBLE);
   //aggiungo al layout la TextView CATEGORIA
   layout.addView(categoryView);
  
   //creo la TIPOVIEW e vi imposto il testo prelevato dal database,
   //campo TESTO (4° campo)
   TextView tipoView =new TextView(this);
   tipoView.setText(c.getString(4));
   //la setto di altezza zero
   tipoView.setHeight(0);
   //la setto come invisibile
   tipoView.setVisibility(View.INVISIBLE);
   //aggiungo al layout la TextView TIPOVIEW
   layout.addView(tipoView);
   
   //creo la ImageView e ne setto l'immagine prelevando il dato BLOB dal database
   //decodificandolo da array di bytes in Bitmap: 
   ImageView img=new ImageView(this);
   Bitmap b=BitmapFactory.decodeByteArray(c.getBlob(3), 0, c.getBlob(3).length);
   img.setImageBitmap(b);
   //aggiungo al layout l'ImageView IMG:
   layout.addView(img);
   

   //creo la TextView e ne setto il testo
   //prelevato dal database, campo ETICHETTA:
   TextView textView =new TextView(this);
   textView.setText(c.getString(1));
   //aggiungo al layout la TextView ETICHETTA:
   layout.addView(textView);
   
   
   //Quindi AGGIUNGO AL MAINLAYOUT il mio layout:
   mainLayout.addView(layout);
   
   //e infine setto il TOUCH LISTENER del LAYOUT:
   layout.setOnTouchListener(onTouchListener);
   
  //passo al successivo record... 
  }while(c.moveToNext());
   
  

  
 }
 
 
 //CODICE DEL TOUCH LISTENER DA ATTRIBUIRE AI LAYOUT:
 View.OnTouchListener onTouchListener=new View.OnTouchListener(){
  public boolean onTouch(View v, MotionEvent event) {
   int X=(int)event.getRawX();
   int Y=(int)event.getRawY();

    switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
     
     absParam = (RelativeLayout.LayoutParams)v.getLayoutParams();
     deltaX=X-absParam.leftMargin;
     deltaY=Y-absParam.topMargin;
     break;
    case MotionEvent.ACTION_MOVE:
     absParam = (RelativeLayout.LayoutParams)v.getLayoutParams();
     absParam.leftMargin=X-deltaX;
     absParam.topMargin=Y-deltaY;
     v.setLayoutParams(absParam);
     
     break;
    case MotionEvent.ACTION_UP:
     
     TextView ch =(TextView)((ViewGroup)v).getChildAt(3);
     
     helper.aggiorna(ch.getText()+"", v.getLeft(), v.getTop());
     
     break;
    }
   

   
   return true;
  }




 };
 
 public void SalvaJElemento(String etichetta, String categoria, int idImage, String tipo, int x, int y){
  
  //metto tutti i campi da salvare nel database in un unico parametro di tipo Elemento.
  //istanzio un elemento
  Elemento jElemento=new Elemento();
  
  //Nell'ordine: stabilisco direttamente il campo "etichetta" preso direttamente
  //dal parametro:
  jElemento.etichetta=etichetta;
  
  //stabilisco il campo "categoria", sempre direttamente, prendendolo dal parametro:
  jElemento.categoria=categoria;
  
  //L'immagine si trova invece nelle RISORSE, e devo usare il parametro Id che
  //ne specifica la risorsa per prelevarla.
  //creandone una Bitmap, rendendola più piccola e quindi riducendola di peso:
  BitmapFactory.Options opzioni=new BitmapFactory.Options();
  opzioni.inJustDecodeBounds=true;
  BitmapFactory.decodeResource(getResources(), idImage, opzioni);
  int fattore=1;
  while(opzioni.outWidth/fattore>100 && opzioni.outHeight/fattore>100){
   fattore*=2;
  }
  opzioni.inSampleSize=fattore;
  opzioni.inJustDecodeBounds=false;
  Bitmap bmp=BitmapFactory.decodeResource(getResources(), idImage,opzioni);
  
  //ora creo uno stream di array di bytes e vi decomprimo la bitmap, per poi
  //convertirla in array di bit.
  ByteArrayOutputStream stream=new ByteArrayOutputStream();
  bmp.compress(CompressFormat.JPEG, 20, stream);
  byte[] byteArray=stream.toByteArray();
  
  //e la carico nell'Elemento.
  jElemento.immagine=byteArray;
  
  //Imposto direttamente la stringa "tipo" prelevandola dai parametri
  jElemento.tipo=tipo;
  
  //Imposto direttamente le coordinate prelevandole dalle risorse di tipo intero
  jElemento.x=x;
  jElemento.y=y;
  
  //Invoco infine, completato l'Elemento, il metodo save() del database
  //per salvarvelo.
  helper.save(jElemento);
 }
}

Disabilitazione della ScrollView in Android.

Come disabilitare la ScrollView.

Finalmente sono riuscito a scoprirlo!

Codice XML:
<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="match_parent" 
    android:id="@+id/scrollView" >


Codice java:
 private void ScrollEnable(final boolean enabled){
  scrollView.setOnTouchListener(new View.OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    // TODO Auto-generated method stub
    return !enabled;
   }
  });
 }
In questo modo basta usare la funzione ScrollEnable mettendo come parametro un valore booleano true o false per significare rispettivamente che la ScrollView debba essere abilitata o disabilitata, e funziona!