sexta-feira, 12 de abril de 2013

Drag and Drop no Android

Olá povo,

Desde a versão 3 (API Level 11) o Android conta com uma API de Drag and Drop que facilita para o desenvolvedor a implementação da famosa ação de "arrastar e soltar". Nesse post, que foi baseado no texto orginal de Lars Vogel, vou mostrar um exemplo simples de como usar esse recurso na sua aplicação.

Para começar, crie um novo projeto Android e certifique-se de estar usando a API Level 11 ou superior. O projeto constará basicamente de uma tela com 4 LinearLayouts com uma ImageView em cada um deles. A idéia é que a ImageView de cada LinearLayout possa ser arrastada para outro LinearLayout.

Vamos começar criando a pasta res/drawable, onde vamos adicionar dois XMLs: o primeiro servirá de background normal dos LinearLayouts citados anteriormente, enquanto que o segundo será usado de background para quando estivermos arrastando a ImageView por sobre o LinearLayout.

O primeiro arquivo será o bg.xml
<shape 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle" >

  <stroke
    android:width="2dp"
    android:color="#FFFFFFFF" />

  <gradient
    android:angle="225"
    android:endColor="#DD2ECCFA"
    android:startColor="#DD000000" />

  <corners
    android:bottomLeftRadius="7dp"
    android:bottomRightRadius="7dp"
    android:topLeftRadius="7dp"
    android:topRightRadius="7dp" />
</shape> 
O arquivo bg_over.xml é idêntico ao primeiro, a única mudança é a propriedade stroke color. Ela faz com que a cor da borda fique vermelha.
<shape 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle" >

  <stroke
    android:width="2dp"
    android:color="##FFFF0000" />

  <gradient
    android:angle="225"
    android:endColor="#DD2ECCFA"
    android:startColor="#DD000000" />

  <corners
    android:bottomLeftRadius="7dp"
    android:bottomRightRadius="7dp"
    android:topLeftRadius="7dp"
    android:topRightRadius="7dp" />
</shape> 
O arquivo de layout da aplicação ficará como abaixo:
<TableLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layoutRoot"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >
  <TableRow>
    <LinearLayout
      android:id="@+id/topleft"
      android:layout_width="0dp"
      android:layout_height="200dp"
      android:layout_weight="1"
      android:background="@drawable/bg" >
      <ImageView
        android:id="@+id/myimage1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />
    </LinearLayout>
    <LinearLayout
      android:id="@+id/topright"
      android:layout_width="0dp"
      android:layout_height="200dp"
      android:layout_weight="1"
      android:background="@drawable/bg" >
      <ImageView
        android:id="@+id/myimage2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />
    </LinearLayout>
  </TableRow>
  <TableRow>
    <LinearLayout
      android:id="@+id/bottomleft"
      android:layout_width="0dp"
      android:layout_height="200dp"
      android:layout_weight="1"
      android:background="@drawable/bg" >
      <ImageView
        android:id="@+id/myimage3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />
    </LinearLayout>
    <LinearLayout
      android:id="@+id/bottomright"
      android:layout_width="0dp"
      android:layout_height="200dp"
      android:layout_weight="1"
      android:background="@drawable/bg" >

      <ImageView
        android:id="@+id/myimage4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />
    </LinearLayout>
  </TableRow>
</TableLayout>
O Layout não tem nada de especial. Os LinearLayouts topleft, topright, bottomleft e bottomright servirão de containers onde poderemos arrastar e soltar as ImageViews myimage1, myimage2, myimage3 e myimage4. Vamos agora para o código da Activity.
public class MainActivity extends Activity 
  implements OnTouchListener, OnDragListener {
 
  Drawable enterShape;
  Drawable normalShape;
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  
    enterShape = getResources().
      getDrawable(R.drawable.bg_over);
    normalShape = getResources().
      getDrawable(R.drawable.bg);
  
    findViewById(R.id.myimage1).
      setOnTouchListener(this);
    findViewById(R.id.myimage2).
      setOnTouchListener(this);
    findViewById(R.id.myimage3).
      setOnTouchListener(this);
    findViewById(R.id.myimage4).
      setOnTouchListener(this);
  
    findViewById(R.id.topleft).
      setOnDragListener(this);
    findViewById(R.id.topright).
      setOnDragListener(this);
    findViewById(R.id.bottomleft).
      setOnDragListener(this);
    findViewById(R.id.bottomright).
      setOnDragListener(this);
  }

  public boolean onTouch(View view, MotionEvent me) {
    int action = me.getAction(); 
    if (action == MotionEvent.ACTION_DOWN) {
      ClipData data = ClipData.newPlainText("", "");
      DragShadowBuilder shadowBuilder = 
        new View.DragShadowBuilder(view);

      view.startDrag(data, shadowBuilder, view, 0);
      view.setVisibility(View.INVISIBLE);
      return true;
    }
    return false;
  }

  @Override
  public boolean onDrag(View v, DragEvent event) {
    switch (event.getAction()) {
    case DragEvent.ACTION_DRAG_ENTERED:
      // Ao entrar na área que pode fazer o drop
      v.setBackgroundDrawable(enterShape);
      break;
    case DragEvent.ACTION_DRAG_EXITED:
      // Ao sair da área que pode fazer o drop
      v.setBackgroundDrawable(normalShape);
      break;
    case DragEvent.ACTION_DROP:
      // Ao fazer o drop
      View view = (View) event.getLocalState();
      ViewGroup owner = (ViewGroup) view.getParent();
      owner.removeView(view);
      LinearLayout container = (LinearLayout) v;
      container.addView(view);
      view.setVisibility(View.VISIBLE);
      break;
    case DragEvent.ACTION_DRAG_ENDED:
      // Ao terminar de arrastar
      v.setBackgroundDrawable(normalShape);
      View view2 = (View) event.getLocalState();
      view2.setVisibility(View.VISIBLE);
    default:
      break;
    }
    return true;
  }
}
O código é relativamente simples e vou dar atenção aos principais pontos do drag and drop. Nossa Activity implementa duas interfaces: OnTouchListener e OnDragListener. A primeira tem a implementação no método onTouch e a segunda no onDrag. No onCreate, chamamos setOnTouchListener para as quatro ImageViews declaradas no arquivo de Layout, e logo em seguida chamamos o setOnDragListener para os LinearLayouts que servirão de container para soltarmos a ImageView dentro.

No método onTouch é onde começará o processo de drag (arrastar), nesse momento verificamos se a ação de touch é ACTION_DOWN, o que quer dizer que o dedo está pressionado sobre a ImageView.  Em caso positivo, criamos um objeto ClipData que permite transportar alguma informação junto com o objeto que está sendo arrastado. Já o objeto DragShadowBuilder serve para criar uma cópia da ImageView que será arrastada com o efeito de transparência. Para definitivamente iniciarmos o processo de arrastar, chamamos o método startDrag passando os objetos citados anteriormente juntamente com a View que disparou o evento, que no nosso caso, é uma ImageView. Por fim, deixamos a View invisível.

Já a ação de drop (soltar) é feita no método onDrag que é chamado algumas vezes com ações diferentes. Esse método é chamado enquanto a ImageView está sendo arrastada (e ao ser solta) por sobre o LinearLayout. Ao entrar na área que podemos soltar o ImageView, esse método é chamado com a ação será ACTION_DRAG_ENTERED, nesse caso, apenas mudamos o background do LinearLayout. Fazemos o inverso ao sair da área onde não podemos mais soltar a ImageView, onde a ação será ACTION_DRAG_EXITED. Já o ato de soltar propriamente dito é feito quando a ação é igual a ACTION_DROP, nesse momento realizamos as seguintes tarefas: obtemos a ImageView real que estamos arrastando através do método getLocalState(); pegamos o LinearLayout onde ela está contida com o método getParent(); removemos a ImageView do LinearLayout anterior; adicionamos no novo LinearLayout (o parâmetro v, indica o LinearLayout onde estamos soltando a ImageView); e deixamos a ImageView visível. A ação ACTION_DRAG_ENDED acontece quando todo o processo de Drag&Drop termina, nesse momento redefinimos o background e tornamos a ImageView visível novamente.
Abaixo podemos ver a aplicação em execução.
Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

quarta-feira, 3 de abril de 2013

Jornada Acadêmica Unibratec 2013.1

Olá povo,

Nos últimos dias 2 e 3 de Abril aconteceu mais uma Jornada Acadêmica da Unibratec. Como já é de costume, ministrei uma palestra, dessa vez com o título "Android e iOS: o que eles tem de diferente... ou não" em dois dias de sala lotada. Aproveito para agradecer a presença de todos.
Na oportunidade, falei sobre as diferenças e semelhanças das plataformas do Google e da Apple. Ao final da palestra, desenvolvemos uma aplicação simples em ambas as plataformas.

Os slides da apresentação estão disponíveis abaixo.


4br4ç05,
nglauber