JavascriptProva

Visualizzazione post con etichetta multitouch. Mostra tutti i post
Visualizzazione post con etichetta multitouch. Mostra tutti i post

martedì 17 maggio 2016

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.

venerdì 15 aprile 2016

Multitouch: ricominciando da zero.

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

Aggrediamo la cosa!
Intanto mi predispongo un'Activity con un'immagine presa dalle risorse direttamente nel codice xml.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.idea.MainActivity" >

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

</RelativeLayout> 


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

martedì 29 marzo 2016

Promemoria: codice per il "pinch zoom" di un layout.

Ho sistemato il problema ponendo un minimo e un massimo:
public class MainActivity extends Activity {
 
  RelativeLayout mainLayout;
  LinearLayout ll;
  ScaleGestureDetector detector;
  float scala=1.f;
  int bmpWidth, bmpHeight;
  TextView textView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mainLayout=(RelativeLayout) findViewById(R.id.mainLayout);
        ll=new LinearLayout(this);
        RelativeLayout.LayoutParams params=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
        params.width=300;
        params.height=200;
        ll.setLayoutParams(params);
        ll.setBackgroundColor(Color.GREEN);
        mainLayout.addView(ll);
        
        detector=new ScaleGestureDetector(this,new listener());
        bmpWidth=300;
        bmpHeight=200;
        textView=(TextView)findViewById(R.id.textView1);
        
        drawMatrix();
        
    }
    
    public void drawMatrix(){
     bmpWidth=(int) (Math.round(bmpWidth*scala));
     bmpHeight=(int) (Math.round(bmpHeight*scala));
     LayoutParams p=(LayoutParams)ll.getLayoutParams();
     p.width=bmpWidth;
     if(bmpWidth>=400){
      bmpWidth=400;
      bmpHeight=(int)(400/1.5);
     }
     if(bmpWidth<=220){
      bmpWidth=220;
      bmpHeight=(int)(220/1.5);
     }
     p.height=bmpHeight;
     ll.setLayoutParams(p);
    
     
     textView.setText(bmpWidth+" "+bmpHeight);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event){
     detector.onTouchEvent(event);
  return true;
     
    }
    
    class listener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
     
     @Override
     public boolean onScale(ScaleGestureDetector detector){
      scala=detector.getScaleFactor();
      drawMatrix();
   return true;
      
     }
    }
    
}

Annotazione di codice per il pinch zoom

Ecco un codice che sembra funzionare, per il pinch zoom di una bitmap:
public class MainActivity extends Activity {
 
  RelativeLayout mainLayout;
  ImageView immagine;
  Bitmap bitmap;
  ScaleGestureDetector detector;
  float scala=1.f;
  int bmpWidth, bmpHeight;
  TextView textView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mainLayout=(RelativeLayout) findViewById(R.id.mainLayout);
        
        
        detector=new ScaleGestureDetector(this,new listener());
        immagine=(ImageView)findViewById(R.id.imageView1);
        textView=(TextView)findViewById(R.id.textView1);
        
        
        
        bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.calcestruzzo);
        bmpWidth=bitmap.getWidth();
        bmpHeight=bitmap.getHeight();
        drawMatrix();
        
    }
    
    public void drawMatrix(){
     bmpWidth=(int) (bmpWidth*scala);
     bmpHeight=(int) (bmpWidth*scala);
     Bitmap resizedBitmap=Bitmap.createScaledBitmap(bitmap, bmpWidth, bmpHeight, false);
     immagine.setImageBitmap(resizedBitmap);
     textView.setText(bmpWidth+" - "+bmpHeight);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event){
     detector.onTouchEvent(event);
  return true;
     
    }
    
    class listener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
     
     @Override
     public boolean onScale(ScaleGestureDetector detector){
      scala=detector.getScaleFactor();
      
      drawMatrix();
   return true;
      
     }
    }
    
}

Me lo annoto prima di giocherellarci sopra...