quinta-feira, 30 de setembro de 2010

Multi-Touch no Android

Olá povo,

Esse blog sempre é "alimentado" com dúvidas de alunos e colegas meus que eu me atrevo a querer resolver. Hoje pela manhã, chegou um amigo e me perguntou se eu já tinha feito algo com Multi-Touch no Android. Falei que já tinha feito no iPhone, então fui à caça. Achei um ótimo exemplo no site do próprio Android. Quem quiser pode baixar via SVN aqui.
Esse exemplo demonstra a utilização do "efeito pinça" que aumenta o diminui um objeto na tela fazendo movimentos com os dedos similares aos de uma pinça.

O que é preciso é criar um objeto da classe android.view.ScaleGestureDetector passando em seu construtor o contexto (Context) e um objeto que implementa a interface ScaleGestureDetector.OnScaleGestureListener. Esse objeto será notificado quando o "evento de pinça" for disparado.


public class TouchExampleView extends View {
private static final int INVALID_POINTER_ID = -1;

private Drawable mIcon;
private float mPosX;
private float mPosY;

private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;

private ScaleGestureDetector mScaleDetector;

private float mScaleFactor = 1.f;

public TouchExampleView(Context context) {
this(context, null, 0);
}

public TouchExampleView(Context context,
AttributeSet attrs) {

this(context, attrs, 0);
}

public TouchExampleView(Context context,
AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

// A imagem será o ícone da aplicação
mIcon = context.getResources().
getDrawable(R.drawable.icon);
// Define os limites da imagem
mIcon.setBounds(0, 0,
mIcon.getIntrinsicWidth(),
mIcon.getIntrinsicHeight());

// Cria o objeto que capturará o "evento de pinça"
mScaleDetector = new ScaleGestureDetector(
context, new ScaleListener());
}

@Override
// Esse método servirá pra mover a imagem na tela
// e delegar o multi-touch pro ScaleGestureDetector
public boolean onTouchEvent(MotionEvent ev) {

// Delega o evento pro ScaleGestureDetector
// que verificará se o multitouch foi utilizado
mScaleDetector.onTouchEvent(ev);

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

// Ao tocar na tela, salva o local tocado
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}

case MotionEvent.ACTION_MOVE: {
final int pointerIndex =
ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);

// Ao mover, checa se o ScaleGestureDetector
// não está processando algum gesto
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;

// Salva o deslocamento feito ao mover
mPosX += dx;
mPosY += dy;

invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}

case MotionEvent.ACTION_UP: {
// nenhum dedo da tela, ponto inválido
mActivePointerId = INVALID_POINTER_ID;
break;
}

case MotionEvent.ACTION_CANCEL: {
// nenhum dedo da tela, ponto inválido
mActivePointerId = INVALID_POINTER_ID;
break;
}

case MotionEvent.ACTION_POINTER_UP: {
// Ao retirar um dos dedos, atualize o ponto
// com o dedo que ficou na tela
final int pointerIndex =
(ev.getAction() &
MotionEvent.ACTION_POINTER_ID_MASK)
>> MotionEvent.ACTION_POINTER_ID_SHIFT;

final int pointerId =
ev.getPointerId(pointerIndex);

if (pointerId == mActivePointerId) {

final int newPointerIndex =
pointerIndex == 0 ? 1 : 0;

mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);

mActivePointerId =
ev.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}

@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);

canvas.save();

// Reposicona a matriz do canvas
canvas.translate(mPosX, mPosY);
// Define a escala (zoom) do canvas
canvas.scale(mScaleFactor, mScaleFactor);
// Redesenha a imagem
mIcon.draw(canvas);

canvas.restore();
}

// classe que ouve o evento de multi-touch
// e calcula a escala
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener{

@Override
public boolean onScale(ScaleGestureDetector d) {
mScaleFactor *= d.getScaleFactor();

// Não permite que o objeto fique
// muito grande nem muito pequeno
mScaleFactor =
Math.max(0.5f, Math.min(mScaleFactor, 5.0f));

invalidate();
return true;
}
}
}

Algumas considerações sobre esse post:
- Para testar esse exemplo você precisará do dispositivo real, afinal você não tem dois cliques de mouse :)
- O suporte a multi touch depende do aparelho;
- API de MultiTouch está disponível apenas para aparelhos com Android 2.0 ou superior.

Abaixo temos a aplicação executando no Motorola FlipOut.



É isso pessoal! Estou com muita coisa pra fazer, estou tentando terminar o mestrado, trabalhando e ainda dando aulas. Quaisquer dúvidas, postem seus comentários abaixo ou consulte o post orginal do blog do android.



Editado pra tentar atender o primeiro comentário desse post.

public class MinhaView extends View {

private Paint bluePaint;
private ArrayList<Point> points;

public MinhaView(Context c) {
this(c, null);
}

public MinhaView(Context c, AttributeSet attrs) {
super(c, attrs);
points = new ArrayList<Point>();

bluePaint = new Paint();
bluePaint.setARGB(255, 0, 0, 255);

setFocusable(true);
}

public boolean onTouchEvent(MotionEvent event){
// Se tocou na tela ou está movendo o dedo
if (event.getAction() ==
MotionEvent.ACTION_DOWN ||
event.getAction() ==
MotionEvent.ACTION_MOVE) {

// Pegue os pontos na tela e alimente a lista
// Normalmente são 1 ou 2 pontos
for(int i=1; i <= event.getPointerCount(); i++){
Point p = new Point(
(int) event.getX(event.getPointerId(i)),
(int) event.getY(event.getPointerId(i)));

points.add(p);
}
// Solicita que a view seja redesenhada
invalidate();
}
return true;
}


public void draw(Canvas canvas) {
super.draw(canvas);

// percorre a lista de pontos
while (points.size() > 0) {
Point p = points.remove(0);
canvas.drawCircle(p.x, p.y, 50, bluePaint);
}
// Limpa a lista pra evitar lixo
points.clear();
}
}


4br4ç05,
nglauber

3 comentários:

Anônimo disse...

Olá!
Tudo bem?

Sou novo nesse tipo de programação e gostaria de saber como faço para criar e mover dois objetos independentes com o touch.

Obrigado

Luc

Rafael Souza disse...

Olá caro amigo,

Gostaria de saber se tem alguma atualização para Android que habilite o dual-touch para aplicações que necessitam do recurso?
Ou isso pode ser feito via programação modificando o Kernel do Sistema?

Nelson Glauber de Vasconcelos Leal disse...

oi Rafael,

Isso vai depender primeiramente do seu hardware suportar.

4br4ç05,
nglauber