JavascriptProva

domenica 29 maggio 2016

mercoledì 25 maggio 2016

Esercizi con il codice per muovere le immagini

Memorizzo qui il codice:

public class Immagine extends View{
 private float bordo=50f;
 private float X,Y;
 int activePointerIndex=0;;
 int activePointerId=0;
 private Drawable mImage;
 private float posX,posY;
 private float lastX,lastY;
 private ScaleGestureDetector SGD;
 private float scale=1;
 
 public Immagine(Context context) {
  super(context);
  setBackgroundColor(Color.BLACK);
  mImage=getResources().getDrawable(R.drawable.facciadaculo);
  mImage.setBounds(0, 0, mImage.getIntrinsicHeight(), mImage.getIntrinsicHeight());
  SGD=new ScaleGestureDetector(context,new ScaleListener());
 }
 
 @Override
 public boolean onTouchEvent(MotionEvent event){
  SGD.onTouchEvent(event);
  int action=event.getAction() & MotionEvent.ACTION_MASK;
  
  switch(action){
  case MotionEvent.ACTION_DOWN:
   X=event.getX();
   Y=event.getY();
   lastX=X;
   lastY=Y;
   activePointerId=event.getPointerId(0);
   break;
  case MotionEvent.ACTION_MOVE:
   activePointerIndex=event.findPointerIndex(activePointerId);
   X=event.getX(activePointerIndex);
   Y=event.getY(activePointerIndex);
   posX+=(X-lastX);
   posY+=(Y-lastY);
   invalidate();
   lastX=X;
   lastY=Y;
   break;
  case MotionEvent.ACTION_POINTER_UP:
   int oldPointerIndex=(event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)>>MotionEvent.ACTION_POINTER_INDEX_SHIFT;
   int oldPointerId=event.getPointerId(oldPointerIndex);
   if(oldPointerId==activePointerId){
    int newPointerIndex=oldPointerIndex==0? 1:0;
    lastX=event.getX(newPointerIndex);
    lastY=event.getY(newPointerIndex);
    activePointerId=event.getPointerId(newPointerIndex);
   }
   break;
  }

  
  
  return true;
 }
 
 

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
     
     
        canvas.save();
        canvas.translate(posX,posY);
        canvas.scale(scale, scale);
        mImage.draw(canvas);
        canvas.restore();
    }
    
    class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
     @Override
     public boolean onScale(ScaleGestureDetector SGD){
      scale*=SGD.getScaleFactor();
      invalidate();
   return true;
      
     }
    }

}
e mi esercito a riscriverlo da capo.

Ecco: primo punto di dubbio. Cosa si inserisce nel costruttore?
Ovviamente, la risorsa da caricare, più le dimensioni dell'immagine.
Ecco, si usa un Drawable.
public class Immagine extends View{

 private Drawable mImage;
 public Immagine(Context context) {
  super(context);
  mImage=getResources().getDrawable(R.drawable.facciadaculo);
  mImage.setBounds(0,0,mImage.getIntrinsicWidth(),mImage.getIntrinsicHeight());
  
 }
 
 
}
Tutto qui quello che è minimamente necessario.
Il resto sarà aggiunta di dopo.
Una volta fatto questo, si va all'evento onTouchEvent.

Ma manca una cosa fondamentale: l'evento onDraw!
Studiamocelo un po'...

Quello è il minimo necessario per mettere a video l'immagine.
 @Override
 public void onDraw(Canvas canvas){
  super.onDraw(canvas);
  canvas.save();
  mImage.draw(canvas);
  canvas.restore();
  
 }
E in effetti questo è sufficiente per mostrare l'immagine!
Mandala! Cancello e poi riscrivo.
public class Immagine extends View{

 private Drawable mImage;
 public Immagine(Context context) {
  super(context);
  mImage=getResources().getDrawable(R.drawable.facciadaculo);
  mImage.setBounds(0,0,mImage.getIntrinsicWidth(),mImage.getIntrinsicHeight());
 }
 
 @Override
 public void onDraw(Canvas canvas){
  super.onDraw(canvas);
  canvas.save();
  mImage.draw(canvas);
  canvas.restore();
 }
 
}
Ecco, il codice riscritto da me a memoria funziona!
Ora dobbiamo aggiungere onTouchEvent.

 @Override
 public boolean onTouchEvent(MotionEvent event){
  float X,Y;
  float lastX=0,lastY=0;
  int action=event.getAction() & MotionEvent.ACTION_MASK;
  switch(action){
  case MotionEvent.ACTION_DOWN:
   X=event.getX();
   Y=event.getY();
   lastX=X;
   lastY=Y;
   break;
  case MotionEvent.ACTION_MOVE:
   X=event.getX();
   Y=event.getY();
   posX+=(X-lastX);
   posY+=(Y-lastY);
   invalidate();
   lastX=X;
   lastY=Y;
   break;
  }
  
  return true;
  
 }
Questo è stato un codice fallimentare!
Non posso dichiarare le variabili come variabili locali nel contesto di un metodo.
Ottengo un'immagine che vola via a una velocità pazzesca!
Dichiarandole all'inizio, alla dichiarazione della classe Immagine, ottengo il risultato voluto:
public class Immagine extends View{

 private Drawable mImage;
 float X,Y;
 float lastX=0,lastY=0;
 float posX=0,posY=0;
 public Immagine(Context context) {
  super(context);
  mImage=getResources().getDrawable(R.drawable.facciadaculo);
  mImage.setBounds(0,0,mImage.getIntrinsicWidth(),mImage.getIntrinsicHeight());
 }

Un altro "mandala" lo facciamo?...

E dai!
public class Immagine extends View{

 float X,Y,lastX,lastY,posX,posY;
 Drawable mImage;
 public Immagine(Context context) {
  super(context);
  mImage=getResources().getDrawable(R.drawable.facciadaculo);
  mImage.setBounds(0,0,mImage.getIntrinsicWidth(),mImage.getIntrinsicHeight()); 
 }
 
 @Override
 public boolean onTouchEvent(MotionEvent event){
  
  switch(event.getAction() & MotionEvent.ACTION_MASK){
  case MotionEvent.ACTION_DOWN:
   X=event.getX();
   Y=event.getY();
   lastX=X;
   lastY=Y;
   break;
  case MotionEvent.ACTION_MOVE:
   X=event.getX();
   Y=event.getY();
   posX+=(X-lastX);
   posY+=(Y-lastY);
   invalidate();
   lastX=X;
   lastY=Y;
   break;
  }
  return true;
  
 }
 
 
 @Override
 public void onDraw(Canvas canvas){
  super.onDraw(canvas);
  
  canvas.save();
  canvas.translate(posX,posY);
  mImage.draw(canvas);
  canvas.restore();
 }
}
Perfetto... qualche piccola dimenticanza, come quella del metodo translate del canvas, ma niente di più!

mercoledì 18 maggio 2016

Analisi delle trasformazioni della Matrix.

Lettura di una Matrix.
       float[] values=new float[9];
       matrix.setTranslate(deltaX, deltaY);
       matrix.getValues(values);
       for(int i=0;i<9;i++){
        Log.e("VALORE",""+values[i]);
       }
Vediamo cosa viene fuori...

Ecco: analizziamo...

Movimento da sinistra a destra:
05-18 11:20:29.847: E/VALORE0(2504): 1.0
05-18 11:20:29.848: E/VALORE1(2504): 0.0
05-18 11:20:29.848: E/VALORE2(2504): -29.622559
05-18 11:20:29.848: E/VALORE3(2504): 0.0
05-18 11:20:29.848: E/VALORE4(2504): 1.0
05-18 11:20:29.848: E/VALORE5(2504): -15.548462
05-18 11:20:29.849: E/VALORE6(2504): 0.0
05-18 11:20:29.849: E/VALORE7(2504): 0.0
05-18 11:20:29.849: E/VALORE8(2504): 1.0
05-18 11:20:29.886: E/VALORE0(2504): 1.0
05-18 11:20:29.886: E/VALORE1(2504): 0.0
05-18 11:20:29.887: E/VALORE2(2504): -27.967407
05-18 11:20:29.887: E/VALORE3(2504): 0.0
05-18 11:20:29.887: E/VALORE4(2504): 1.0
05-18 11:20:29.887: E/VALORE5(2504): -15.548462
05-18 11:20:29.887: E/VALORE6(2504): 0.0
05-18 11:20:29.887: E/VALORE7(2504): 0.0
05-18 11:20:29.888: E/VALORE8(2504): 1.0
05-18 11:20:29.903: E/VALORE0(2504): 1.0
05-18 11:20:29.903: E/VALORE1(2504): 0.0
05-18 11:20:29.912: E/VALORE2(2504): -26.961212
05-18 11:20:29.912: E/VALORE3(2504): 0.0
05-18 11:20:29.912: E/VALORE4(2504): 1.0
05-18 11:20:29.912: E/VALORE5(2504): -15.548462
05-18 11:20:29.912: E/VALORE6(2504): 0.0
05-18 11:20:29.912: E/VALORE7(2504): 0.0
05-18 11:20:29.912: E/VALORE8(2504): 1.0
05-18 11:20:29.928: E/VALORE0(2504): 1.0
05-18 11:20:29.928: E/VALORE1(2504): 0.0
05-18 11:20:29.929: E/VALORE2(2504): -25.948822
05-18 11:20:29.936: E/VALORE3(2504): 0.0
05-18 11:20:29.936: E/VALORE4(2504): 1.0
05-18 11:20:29.939: E/VALORE5(2504): -15.548462
05-18 11:20:29.940: E/VALORE6(2504): 0.0
05-18 11:20:29.940: E/VALORE7(2504): 0.0
05-18 11:20:29.940: E/VALORE8(2504): 1.0
Me li rappresento in forma di matrice:
1   0   -29.622559
0   1   -15.548462
0   0    1


1   0   -27.967407
0   1   -15.548462
0   0    1


1   0   -26.961212
0   1   -15.548462
0   0    1


1   0   -25.948822
0   1   -15.548462
0   0    1
Eccellente!!! Dalla disposizione dei numeri delle matrici deduco che si tratti proprio di una matrice di traslazione! E' evidente!
Si tratta, infatti, del codice che ho creato io per il trascinamento di un'immagine, basato sulla manipolazione della Matrix!

Ora posso analizzare le modificazioni della Matrix in relazione a scalature e traslazioni, in modo da regolarmi bene per creare gli opportuni prodotti fra matrici, dato che il prodotto fra matrici non è dotato della proprietà commutativa!

martedì 17 maggio 2016

Studio sulle matrici.

Studiando le trasformazioni della Matrix

matrix.setTranslate(0, 200);


E fin qui ci siamo.
Ora è inutile che metta un altro set.
Provo a vedere se riesco a fare prima la traslazione e poi la scalatura:
    matrix.setScale(0.5f, 0.5f);
    matrix.preTranslate(0, 200);
Faccio il calcolo della matrice "a mano":


Mi aspetto dunque un'immagine ridotta della metà e spostata in basso di 100. Vediamo cosa succede...



Sembrerebbe... combinare!
Ora invertiamo:
    matrix.setScale(0.5f, 0.5f);
    matrix.postTranslate(0, 200);




Mi aspetto dunque un'immagine ridotta della metà e spostata di 200.
Verifichiamo...



Credo che sia perfettamente riuscita!

Ancora multitouch, analisi di un pezzo di codice pan zoom

Provo ad analizzare un codice...

    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);
Dopo la dichiarazione dell'evento subito lo scalegesturedetector "ispeziona", come descritto nel commento...

    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);

        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
Come di prassi, mascheriamo il byte alto di event.getAction.

    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);

        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                if (!mScaleDetector.isInProgress()) {
                    final float x = ev.getX();
                    final float y = ev.getY();

                    mLastTouchX = x;
                    mLastTouchY = y;
                    mActivePointerId = ev.getPointerId(0);
                }
                break;
La procedura è sempre quella: in caso di MotionEvent.ACTION_DOWN, se lo ScaleDetector non è in azione, vengono rilevati x e y dell'evento, e i loro valori vengono trasferiti nelle variabili mLastTouchX e mLastTouchY.
C'è poi mActivePointerId, variabile alla quale viene attribuito l'id del puntatore 0 (ossia il primo dito che viene appoggiato).

C'è poi l'eventualità di MotionEvent.ACTION_POINTER_1_DOWN.
            case MotionEvent.ACTION_POINTER_1_DOWN: {
                if (mScaleDetector.isInProgress()) {
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();
                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }
                break;
            }
Questa funziona praticamente solo se lo ScaleDetector sta funzionando: rileva il FocusX e il FocusY e li attribuisce alle variabili mLastGestureX e mLastGestureY.


Ecco poi l'eventualità MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_MOVE: {

                // Only move if the ScaleGestureDetector isn't processing a gesture.
                if (!mScaleDetector.isInProgress()) {
                    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                    final float x = ev.getX(pointerIndex);
                    final float y = ev.getY(pointerIndex);

                    final float dx = x - mLastTouchX;
                    final float dy = y - mLastTouchY;

                    mPosX += dx;
                    mPosY += dy;

                    invalidate();

                    mLastTouchX = x;
                    mLastTouchY = y;
                }
In assenza di azione dello ScaleDetector, procede pressappoco come quella che conosco.
L'unica differenza è che viene posto in luce il fatto che viene rilevata la coordinata X del puntatore 0.
                else{
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();

                    final float gdx = gx - mLastGestureX;
                    final float gdy = gy - mLastGestureY;

                    mPosX += gdx;
                    mPosY += gdy;

                    invalidate();

                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }

                break;
Questo è un codice che serve per poter traslare anche qualora sia attivo ScaleDetector...

Ma ora ho trovato altri input...



mercoledì 11 maggio 2016

Estrapolare l'indice del puntatore

Facciamo una sequenza e analizziamo i valori di MotionEvent...
indice giù       down: 0
                 down MASK: 0

medio giù        pointer down: 261
                 pointer down MASK: 5

medio su         pointer up: 262
                 pointer up MASK: 6

indice su        up: 1 
                 up MASK: 1


indice giù        down: 0
                  down MASK: 0

medio giù         pointer down: 261
                  pointer down MASK: 5

indice su         pointer up: 6
                  pointer up MASK: 6

medio su          up: 1
                  up MASK: 1

Quando c'è un solo dito giù viene riconosciuto come puntatore principale, quando ce ne sono due l'ultimo ad essere stato aggiunto viene riconosciuto come puntatore secondario.
Quando ci sono due dita giù, il primo a sollevarsi viene riconosciuto come puntatore secondario, quindi ne rimane uno viene riconosciuto come puntatore primario.

Però c'è una differenza:
L'ultimo bit del byte alto viene attribuito a 0 per il primo puntatore, a 1 per il puntatore secondario, ma quando il primo puntatore diventa secondario continua ad avere codice 0.
Estrapoliamo dunque il codice del puntatore...

   OnTouchListener onTouchListener=new View.OnTouchListener(){
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction() & event.ACTION_MASK){
    case MotionEvent.ACTION_DOWN:
     Log.e("event.getAction() down",""+event.getAction());
     Log.e("event.getAction() down MASK",""+(event.getAction() & event.ACTION_MASK));
     Log.e("CODICE DEL PUNTATORE",""+((event.getAction() & 65280)>>8));
     break; 
    case MotionEvent.ACTION_POINTER_DOWN:
     Log.e("event.getAction() pointer down",""+event.getAction());
     Log.e("event.getAction() pointer down MASK",""+(event.getAction() & event.ACTION_MASK));
     Log.e("CODICE DEL PUNTATORE",""+((event.getAction() & 65280)>>8));
     break;
    case MotionEvent.ACTION_POINTER_UP:
     Log.e("event.getAction() pointer up",""+event.getAction());
     Log.e("event.getAction() pointer up MASK",""+(event.getAction() & event.ACTION_MASK));
     Log.e("CODICE DEL PUNTATORE",""+((event.getAction() & 65280)>>8));
     break;    
    case MotionEvent.ACTION_UP:
     Log.e("event.getAction() up",""+event.getAction());
     Log.e("event.getAction() up MASK",""+(event.getAction() & event.ACTION_MASK));
     Log.e("CODICE DEL PUNTATORE",""+((event.getAction() & 65280)>>8));
     break;
    }
    return true;
   }
  };
Con questo codice ho creato una maschera di due bytes con 0xFF00, in modo da isolare il byte alto, e poi l'ho spinto in fondo con l'operazione >>8.
Mi mostra che il codice, quando i punatori sono due, è di 0 per quello che è stato definito prima e 1 per quello secondario.
Quando il puntatore torna uno solo, peraltro, viene riassunto il codice 0 dal puntatore che prima era secondario con codice 1.

Utilità della MASK per gli eventi del multitouch.

Cerchiamo di capire bene che significato hanno quelle maschere.
Mi concentro sul valori di event.getAction() e tutti gli annessi e connessi...

Ecco il codice:
   OnTouchListener onTouchListener=new View.OnTouchListener(){
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
     X=(int)event.getX();
     Y=(int)event.getY();
     Log.e("event.getAction() down",""+event.getAction());
     break; 
     
     
    case MotionEvent.ACTION_MOVE:
     int currentX=(int)event.getX();
     int currentY=(int)event.getY();
     currentX=Math.max(10, Math.min(currentX,v.getWidth()-10));
     currentY=Math.max(10,Math.min(currentY,v.getHeight()-10));
     v.scrollBy(X-currentX, Y-currentY);
     X=currentX;
     Y=currentY;
     Log.e("event.getAction() move",""+event.getAction());
     break;
    }
    return true;
   }
  };
   imageView.setOnTouchListener(onTouchListener);
Ed ecco i codici che ottengo da event.getAction() sull'evento DOWN e MOVE.
05-11 11:36:49.125: E/event.getAction() down(3496): 0
05-11 11:36:50.177: E/event.getAction() move(3496): 2
05-11 11:36:50.218: E/event.getAction() move(3496): 2
05-11 11:36:50.234: E/event.getAction() move(3496): 2
05-11 11:36:50.283: E/event.getAction() move(3496): 2
05-11 11:36:50.302: E/event.getAction() move(3496): 2
Al DOWN ottengo zero, mentre al MOVE ottengo 2.
Aggiungiamo UP:
   OnTouchListener onTouchListener=new View.OnTouchListener(){
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
     X=(int)event.getX();
     Y=(int)event.getY();
     Log.e("event.getAction() down",""+event.getAction());
     break; 
     
     
    case MotionEvent.ACTION_MOVE:
     int currentX=(int)event.getX();
     int currentY=(int)event.getY();
     currentX=Math.max(10, Math.min(currentX,v.getWidth()-10));
     currentY=Math.max(10,Math.min(currentY,v.getHeight()-10));
     v.scrollBy(X-currentX, Y-currentY);
     X=currentX;
     Y=currentY;
     Log.e("event.getAction() move",""+event.getAction());
     break;
    
    case MotionEvent.ACTION_UP:
     Log.e("event.getAction() up",""+event.getAction());
     break;
    }
    return true;
   }
  };
   imageView.setOnTouchListener(onTouchListener);
05-11 11:43:17.934: E/event.getAction() down(3543): 0
05-11 11:43:19.812: E/event.getAction() move(3543): 2
05-11 11:43:19.831: E/event.getAction() move(3543): 2
05-11 11:43:19.862: E/event.getAction() move(3543): 2
05-11 11:43:19.918: E/event.getAction() move(3543): 2
05-11 11:43:20.039: E/event.getAction() move(3543): 2
05-11 11:43:20.050: E/event.getAction() move(3543): 2
05-11 11:43:20.073: E/event.getAction() move(3543): 2
05-11 11:43:20.118: E/event.getAction() move(3543): 2
05-11 11:43:20.886: E/event.getAction() up(3543): 1
Bene.
I codici sono 0, 2 e 1 rispettivamente per DOWN, MOVE e UP.

Aggiungo gli altri valori:
    case MotionEvent.ACTION_DOWN:
     Log.e("event.getAction() down",""+event.getAction());
     break; 
    case MotionEvent.ACTION_POINTER_DOWN:
     Log.e("event.getAction() pointer down",""+event.getAction());
     break;
    case MotionEvent.ACTION_POINTER_UP:
     Log.e("event.getAction() pointer up",""+event.getAction());
     break;    
    case MotionEvent.ACTION_UP:
     Log.e("event.getAction() up",""+event.getAction());
     break;
Ecco: Sequenza:
indice giù
medio giù
medio su
indice su
05-11 17:04:23.343: E/event.getAction() down(5658): 0
""
""
05-11 17:04:32.408: E/event.getAction() up(5658): 1
I movimenti del medio non vengono proprio segnalati, passando completamente "muti".

Sequenza:
indice giù
medio giù
indice su
medio su
05-11 17:07:18.452: E/event.getAction() down(5658): 0
""
05-11 17:07:24.066: E/event.getAction() pointer up(5658): 6
05-11 17:07:25.797: E/event.getAction() up(5658): 1
Adesso facciamo la correzione con la maschera e vediamo il confronto...

   OnTouchListener onTouchListener=new View.OnTouchListener(){
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction() & event.ACTION_MASK){
    case MotionEvent.ACTION_DOWN:
     Log.e("event.getAction() down",""+event.getAction());
     break; 
    case MotionEvent.ACTION_POINTER_DOWN:
     Log.e("event.getAction() pointer down",""+event.getAction());
     break;
    case MotionEvent.ACTION_POINTER_UP:
     Log.e("event.getAction() pointer up",""+event.getAction());
     break;    
    case MotionEvent.ACTION_UP:
     Log.e("event.getAction() up",""+event.getAction());
     break;
    }
    return true;
   }
  };
   imageView.setOnTouchListener(onTouchListener);
Sequenza:
indice giù
medio giù
medio su
indice su
05-11 21:50:03.180: E/event.getAction() down(22986): 0
05-11 21:50:03.969: E/event.getAction() pointer down(22986): 261
05-11 21:50:05.013: E/event.getAction() pointer up(22986): 262
05-11 21:50:05.900: E/event.getAction() up(22986): 1


Sequenza:
indice giù
medio giù
indice su
medio su
05-11 21:51:43.030: E/event.getAction() down(22986): 0
05-11 21:51:44.041: E/event.getAction() pointer down(22986): 261
05-11 21:51:45.039: E/event.getAction() pointer up(22986): 6
05-11 21:51:46.461: E/event.getAction() up(22986): 1


Un po' "confusivo"...

Cerco di tirare le fila del discorso in modo "pulito".
Scrivo un codice che mi dà event.getAction() e event.getAction() & MotionEvent.ACTION_MASK:
   OnTouchListener onTouchListener=new View.OnTouchListener(){
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction() & event.ACTION_MASK){
    case MotionEvent.ACTION_DOWN:
     Log.e("event.getAction() down",""+event.getAction());
     Log.e("event.getAction() down MASK",""+(event.getAction() & event.ACTION_MASK));
     break; 
    case MotionEvent.ACTION_POINTER_DOWN:
     Log.e("event.getAction() pointer down",""+event.getAction());
     Log.e("event.getAction() pointer down MASK",""+(event.getAction() & event.ACTION_MASK));
     break;
    case MotionEvent.ACTION_POINTER_UP:
     Log.e("event.getAction() pointer up",""+event.getAction());
     Log.e("event.getAction() pointer up MASK",""+(event.getAction() & event.ACTION_MASK));
     break;    
    case MotionEvent.ACTION_UP:
     Log.e("event.getAction() up",""+event.getAction());
     Log.e("event.getAction() up MASK",""+(event.getAction() & event.ACTION_MASK));
     break;
    }
    return true;
   }
  };
e vediamo il risultato:
05-12 02:19:04.006: E/event.getAction() down(32362): 0
05-12 02:19:04.006: E/event.getAction() down MASK(32362): 0
05-12 02:19:05.007: E/event.getAction() pointer down(32362): 261
05-12 02:19:05.007: E/event.getAction() pointer down MASK(32362): 5
05-12 02:19:06.116: E/event.getAction() pointer up(32362): 262
05-12 02:19:06.116: E/event.getAction() pointer up MASK(32362): 6
05-12 02:19:07.175: E/event.getAction() up(32362): 1
05-12 02:19:07.175: E/event.getAction() up MASK(32362): 1
Ecco: con ACTION_DOWN e ACTION_UP non ottengo nessuna differenza fra il valore senza AND ACTION_MASK, mentre con ACTION_POINTER_DOWN e ACTION_POINTER_UP ottengo una differenza:
Senza MASK ottengo rispettivamente 261 e 5;
Con MASK ottengo rispettivamente 262 e 6.

Trasponendo tutto questo in codice binario, ottengo questo:
ACTION_DOWN:           00000000    00000000
ACTION_UP:             00000001    00000001
ACTION_POINTER_DOWN: 1 00000101    0 00000101
ACTION_POINTER_UP:   ‭1 00000110‬    0 00000110
Quindi ACTION_MASK risulta fondamentale per coprirmi il bit basso del byte alto, in modo da farmi ottenere il codice 5 e 6 rispettivamente per ACTION_POINTER_DOWN e ACTION_POINTER_UP.
Risolto e compresa l'utilità della MASK!

Multitouch

Codice:
  @Override
  public boolean onTouchEvent(MotionEvent event){
   int action=event.getAction();
   switch(action & MotionEvent.ACTION_MASK){
   case MotionEvent.ACTION_DOWN:
    Log.e("DOWN "+event.getActionIndex(),"giù");
    break;
   case MotionEvent.ACTION_POINTER_DOWN:
    Log.e("POINTER DOWN "+event.getActionIndex(),"giù");
    break;
   case MotionEvent.ACTION_UP:
    Log.e("UP "+event.getActionIndex(),"su");
    break;
   case MotionEvent.ACTION_POINTER_UP:
    Log.e("POINTER UP "+event.getActionIndex(),"su");
    break;
   
  
   }
   
     
  return true;
   
  }
Sequenza:
INDICE GIU'
MEDIO GIU'
MEDIO SU
INDICE SU
Risultato:
05-11 12:37:42.529: E/DOWN 0(22439): giù
05-11 12:37:43.976: E/POINTER DOWN 1(22439): giù
05-11 12:37:48.295: E/UP 0(22439): su
Dunque INDICE = 0 e MEDIO = 1.

Sequenza:
INDICE GIU'
MEDIO SU
INDICE SU
MEDIO SU
05-11 12:42:09.100: E/DOWN 0(23100): giù
05-11 12:42:10.356: E/POINTER DOWN 1(23100): giù
05-11 12:42:11.966: E/POINTER UP 0(23100): su
05-11 12:42:13.633: E/UP 0(23100): su
Dunque INDICE=0, MEDIO=1, INDICE=0, MEDIO=0.

martedì 10 maggio 2016

Impostare i limiti dello scorrimento di un'immagine senza far ricorso alla Matrix.

Cerchiamo di capire questa formula del Math.max.

 x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth));
 y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX));
AXIS_X_MIN sarebbe il numero minimo sull'asse delle X. La funzione Math.max sceglie il massimo fra questo valore minimo e un altro valore, che è Math.min(x, AXIS_X_MAX - curWidth).
Dobbiamo interpretare questo, adesso.
Sceglie il valore minimo fra la x corrente e la larghezza, credo, della larghezza di un rettangolo.
Se la x corrente è bassa, viene scelta come valore dalla funzione Math.min.
Se la x comincia a crescere, quando raggiunge il valore di AXIS_X_MAX - curWidth non viene più scelta, ma viene scelta la X.
Questo potrebbe essere un modo per limitare lo scorrimento di un'immagine in una finestra senza far ricorso alle matrici come ho fatto io...

Se infatti io imposto che x è uguale a x se però la x è inferiore a un certo valore, altrimenti è uguale a quel valore, il problema dovrebbe essere risolto in modo molto semplice e senza scomodare matrici varie...

Math.max invece serve per rendere la x uguale a x nel caso in cui questa sia superiore al valore minimo, ossia AXIS_X_MIN.
Ci potrebbe essere un problema nel caso in cui la x sia maggiore del valore massimo considerato, perché verrebbe scelto questo, ma è un falso problema, perché questo è considerato solo se la x tende a eccederlo.

Ho capito la formula!

Ora provo a costruire una limitazione dei movimenti semplicemente per mezzo di quella funzione che non coinvolgeva la Matrix ma era molto più semplice.


   OnTouchListener onTouchListener =new View.OnTouchListener() {
   
    int X,Y;
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    int action=event.getAction();
    switch(action & MotionEvent.ACTION_MASK){
    case MotionEvent.ACTION_DOWN:
     X=(int)event.getX();
     Y=(int)event.getY();
     
     break;
    case MotionEvent.ACTION_MOVE:
     int currentX=(int)event.getX();
     int currentY=(int)event.getY();
     v.scrollBy(X-currentX, Y-currentY);
     X=currentX;
     Y=currentY;
     break;
    }
    return true;
   }
  };
  imageView.setOnTouchListener(onTouchListener);
Ecco, questo è il semplicissimo modellino di un'immagine scrollabile.
Però ora dobbiamo provare a metterci dei limiti secondo quanto visto con la formula di cui sopra.

   OnTouchListener onTouchListener =new View.OnTouchListener() {
   int fetta=50;
   int X,Y;
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    int action=event.getAction();
    switch(action & MotionEvent.ACTION_MASK){
    case MotionEvent.ACTION_DOWN:
     X=(int)event.getX();
     Y=(int)event.getY();
     
     break;
    case MotionEvent.ACTION_MOVE:
     int currentX=(int)Math.max(fetta,Math.min(event.getX(),v.getWidth()-fetta));
     int currentY=(int)Math.max(fetta,Math.min(event.getY(),v.getWidth()-fetta));
     v.scrollBy(X-currentX, Y-currentY);
     X=currentX;
     Y=currentY;
     break;
    }
    return true;
   }
  };
  imageView.setOnTouchListener(onTouchListener);
Funziona egregiamente!
Studiamo lo scaling, adesso...

private class ScaleListener 
        extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        mScaleFactor *= detector.getScaleFactor();

        // Don't let the object get too small or too large.
        mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

        invalidate();
        return true;
    }
}
che cosa è mScaleFactor?
Andiamo a cercarci la definizione...
private float mScaleFactor = 1.f;
che non aggiunge niente... vediamo come viene usato.
 canvas.scale(mScaleFactor, mScaleFactor);
Ora, sul canvas sono ancora poco esperto.
Ma sarebbe il fattore scala...

Come posso usarlo qui?
Proviamo...

Con una matrix è l'unico modo che mi venga in mente...

lunedì 2 maggio 2016

Codice per scrolling e zoom di un'immagine.

Ecco il codice che realizza un'immagine scrollabile e zoomabile:
public class MainActivity extends Activity {
 float scale=1f;
 ScaleGestureDetector SGD;
 
 RelativeLayout mainLayout;
 ImageView imageView;
 
 Bitmap bitmap;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  SGD = new ScaleGestureDetector(this, new ScaleListener());
  mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.angurie2);
  final int larghezza=300;
  final int altezza=300;
  ImageView i=newImage(mainLayout,larghezza, altezza, 10, 10, bitmap, Color.BLACK, ScaleType.MATRIX);
    
  View.OnTouchListener onTouchListener=new View.OnTouchListener() {
   Matrix matrix=new Matrix();
   Matrix inversa=new Matrix();
   float X,Y,currentX, currentY;
   float deltaX, deltaY;
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
     X=event.getX();
     Y=event.getY();
     
     break;
    case MotionEvent.ACTION_MOVE:
     
     currentX=event.getX();
     currentY=event.getY();
     float[] pts={0,0};
     ((ImageView)v).getImageMatrix().invert(inversa);
     inversa.mapPoints(pts);
     
     
     if(currentX<X){
      if((pts[0]-currentX+X)<(bitmap.getWidth()-larghezza/scale)){
       deltaX=-pts[0]+currentX-X;
      }
      else{
       if((pts[0]-currentX+X)>(bitmap.getWidth()-larghezza/scale))
       deltaX=-(bitmap.getWidth()-larghezza/scale);
       
      }
     }
     if(currentX>X){
      if((pts[0]-currentX+X)>0){
       deltaX=-pts[0]+currentX-X;
      }
      else{
       if((pts[0]-currentX+X)<0) deltaX=0;
      }
     }
     
     if(currentY<Y){
      if((pts[1]-currentY+Y)<(bitmap.getHeight()-altezza/scale)){
       deltaY=-pts[1]+currentY-Y;
      }
      else{
       if((pts[1]-currentY+Y)>(bitmap.getHeight()-altezza/scale))
        deltaY=-(bitmap.getHeight()-altezza/scale);
       
      }
     }
     if(currentY>Y){
      if((pts[1]-currentY+Y)>0){
       deltaY=-pts[1]+currentY-Y;
      }
      else{
       if((pts[1]-currentY+Y)<0) deltaY=0;
      }
     }
     
     
     matrix.setTranslate(deltaX,deltaY);
     matrix.postScale(scale, scale);
     ((ImageView)v).setImageMatrix(matrix);
     X=currentX;
     Y=currentY;
     SGD.onTouchEvent(event);
     break;
    }
    return true;
   }
  };
  i.setOnTouchListener(onTouchListener);
 
  
  
  
 }

 class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
  @Override
  public boolean onScale(ScaleGestureDetector SGD){
   scale *= SGD.getScaleFactor();
   return true;
  }
 }
 public ImageView newImage(ViewGroup layout, int larghezza, int altezza, 
   float X, float Y, Bitmap bitmap, int colore, 
   ScaleType scaletype){
  ImageView imageView=new ImageView(this);
  layout.addView(imageView);
  imageView.getLayoutParams().width=larghezza;
  imageView.getLayoutParams().height=altezza;
  imageView.setX(X);
  imageView.setY(Y);

  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(colore);
  imageView.setScaleType(scaletype);
  return imageView;
 }

} 
Ora devo provvedere a metterlo nell'applicazione...
Dopo averla opportunamente SALVATA!!!

Il codice per prendere l'immagine devo andare a ripescarmelo...

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

domenica 1 maggio 2016

Codice perfezionato per il trascinamento di una bitmap attraverso una ImageVIew.

Ecco il codice perfezionato, per il trascinamento di unìimmagine attraverso una ImageView più piccola.
public class MainActivity extends Activity {

 
 RelativeLayout mainLayout;
 ImageView imageView;
 
 Bitmap bitmap;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  
 
  ImageView imageView=new ImageView(this);
 
  

  
  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(Color.BLACK);
  imageView.setScaleType(ScaleType.MATRIX);
  
  
  mainLayout.addView(imageView);
  imageView.setX(200);
  imageView.setY(10);
  imageView.getLayoutParams().width=200;
  imageView.getLayoutParams().height=200;
  
  
  View.OnTouchListener onTouchListener=new View.OnTouchListener() {
   Matrix matrix=new Matrix();
   Matrix inversa=new Matrix();
   float X,Y,currentX, currentY;
   float deltaX, deltaY;
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
     X=event.getX();
     Y=event.getY();
     break;
    case MotionEvent.ACTION_MOVE:
     currentX=event.getX();
     currentY=event.getY();
     float[] pts={0,0};
     ((ImageView)v).getImageMatrix().invert(inversa);
     inversa.mapPoints(pts);
     
     /*se la direzione è da destra a sinistra il punto d'arresto
      * è a bitmap.getWidth-200. Per valori inferiori la 
      * translazione è normale, per valori uguali la translazione
      * è zero, per valori superiori torna al valore massimo
      */
     Log.d(pts[0]+"",""+(bitmap.getWidth()-200));
     if(currentX<X){
      if(pts[0]<(bitmap.getWidth()-200)){
       deltaX=-pts[0]+currentX-X;
      }
      else{
       if(pts[0]>(bitmap.getWidth()-200))
       deltaX=-(bitmap.getWidth()-200);
       
      }
     }
     if(currentX>X){
      if(pts[0]>0){
       deltaX=-pts[0]+currentX-X;
      }
      else{
       if(pts[0]<0) deltaX=0;
      }
     }
     
     if(currentY<Y){
      if(pts[1]<(bitmap.getHeight()-200)){
       deltaY=-pts[1]+currentY-Y;
      }
      else{
       if(pts[1]>(bitmap.getHeight()-200))
        deltaY=-(bitmap.getHeight()-200);
       
      }
     }
     if(currentY>Y){
      if(pts[1]>0){
       deltaY=-pts[1]+currentY-Y;
      }
      else{
       if(pts[1]<0) deltaY=0;
      }
     }
     
     
     matrix.setTranslate(deltaX,deltaY);
     ((ImageView)v).setImageMatrix(matrix);
     X=currentX;
     Y=currentY;
    }
    return true;
   }
  };
  imageView.setOnTouchListener(onTouchListener);
  
  
 }

} 
Sembra perfetto.

Scrolling di un'immagine per mezzo di Matrix (mio codice)

Questo è il codice per mezzo del quale sono riuscito a imporre dei limiti allo scorrimento di un'immagine all'interno di una finestra.
Me lo annoto, e poi cerco, se possibile, di apporre dei perfezionamenti.
public class MainActivity extends Activity {

 
 RelativeLayout mainLayout;
 ImageView imageView;
 
 Bitmap bitmap;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mainLayout=(RelativeLayout)findViewById(R.id.mainLayout);
  
  bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.anguria);
  
 
  ImageView imageView=new ImageView(this);
 
  

  
  imageView.setImageBitmap(bitmap);
  imageView.setBackgroundColor(Color.BLACK);
  imageView.setScaleType(ScaleType.MATRIX);
  
  
  mainLayout.addView(imageView);
  imageView.setX(200);
  imageView.setY(10);
  imageView.getLayoutParams().width=200;
  imageView.getLayoutParams().height=200;
  
  
  View.OnTouchListener onTouchListener=new View.OnTouchListener() {
   Matrix matrix=new Matrix();
   Matrix inversa=new Matrix();
   float X,Y,currentX, currentY;
   float deltaX, deltaY;
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
    case MotionEvent.ACTION_DOWN:
     X=event.getX();
     Y=event.getY();
     break;
    case MotionEvent.ACTION_MOVE:
     currentX=event.getX();
     currentY=event.getY();
     float[] pts={0,0};
     ((ImageView)v).getImageMatrix().invert(inversa);
     inversa.mapPoints(pts);
     
     /*se la direzione è da destra a sinistra il punto d'arresto
      * è a bitmap.getWidth-200. Per valori inferiori la 
      * translazione è normale, per valori uguali la translazione
      * è zero, per valori superiori torna al valore massimo
      */
     Log.d(pts[0]+"",""+(bitmap.getWidth()-200));
     if(currentX<X){
      if(pts[0]<(bitmap.getWidth()-200)){
       deltaX=-pts[0]+currentX-X;
      }
      else{
       if(pts[0]>(bitmap.getWidth()-200))
       deltaX=-(bitmap.getWidth()-200);
       
      }
     }
     if(currentX>X){
      if(pts[0]>0){
       deltaX=-pts[0]+currentX-X;
      }
      else{
       if(pts[0]<0) deltaX=0;
      }
     }
     
     if(currentY<Y){
      if(pts[1]<(bitmap.getHeight()-200)){
       deltaY=-pts[1]+currentY-Y;
      }
      else{
       if(pts[1]>(bitmap.getHeight()-200))
        deltaY=-(bitmap.getHeight()-200);
       
      }
     }
     if(currentY>Y){
      if(pts[1]>0){
       deltaY=-pts[1]+currentY-Y;
      }
      else{
       if(pts[1]<0) deltaY=0;
      }
     }
     
     
     matrix.setTranslate(deltaX,deltaY);
     ((ImageView)v).setImageMatrix(matrix);
     X=currentX;
     Y=currentY;
    }
    return true;
   }
  };
  imageView.setOnTouchListener(onTouchListener);
  
  
 }

}