sexta-feira, 30 de novembro de 2012

Criando Views

Olá pessoal,

Depois de algum tempo venho falar sobre a criação de views e encerrar uma sequência de 3 posts relacionados a este assunto. Nos posts anteriores mostrei como customizar uma View e também como criar uma View composta de outras Views. Neste post vou mostrar como criar uma View a partir da extensão da classe android.view.View.

A View criada neste post não terá funcionalidade, será apenas um desenho multi direcional, pois vou abordar em um post mais a frente como dar vida a este tipo de controle utilizando o acelerômetro do dispositivo. Este post servirá como base para o conhecimento da estrutura de uma View criada do zero.

No final nossa View ficará como a imagem abaixo:


Aproveito este post para fazer um convite a vocês a instalar a versão Jelly Bean 4.1.2 de testes da Samsung. Está fantástica. Veja sobre no site SamMobile.com

O primeiro passo para se construir uma View do zero é criar uma classe fazer a extensão da classe android.view.View. Fazendo isso, você está herdando todos os comportamentos básicos de uma View. O ponto seguinte é se preocupar me sobrecarregar todos os construtores da classe.

public class DirectionalView extends View



public DirectionalView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initDirectionalView();

}

public DirectionalView(Context context, AttributeSet attrs) {
super(context, attrs);
initDirectionalView();
}

public DirectionalView(Context context) {
super(context);
initDirectionalView();
}


Repare que todos o três construtores fazem chamada ao initDirectionalView. Este procedimento é utilizando para inicializar as variáveis que serão utilizadas pelo sistema. É uma boa prática fazer a inicialização das várias logo no início do aplicativo, assim você não evita de executar uma ação de inicialização várias vezes desnecessariamente, lembre-se é importante se preocupar com o processo durante a execução de um aplicativo móvel.

O procedimento initDirectionalView está definindo que o controle não irá receber foco, definindo os objetos que irão desenhar as linhas na tela, carregando os textos dos Resources para exibição em tela e definindo a cor da tela baseando se nos Resources.


private void initDirectionalView()
{
if (this.isInEditMode())
return;

// esta ação define que o validador não receberá foco...
setFocusable(false);

// referencia todos os itens...
Resources r = this.getResources();

linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(r.getColor(R.color.borderColor));
linePaint.setStyle(Paint.Style.FILL);
linePaint.setStrokeWidth(1);

textHeight = 20;
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(r.getColor(R.color.foreColor));
textPaint.setTextSize(textHeight);



textEast = r.getString(R.string.east);
textNorth = r.getString(R.string.north);
textSouth = r.getString(R.string.south);
textWest = r.getString(R.string.west);

this.setBackgroundColor(r.getColor(R.color.backcolor));

}

Até o momento descrevi apenas como a View é inicializada, agora precisamos ver como a View é desenhada. O desenho da View é feito dentro da chamada onDraw, que recebe como parâmetro o Canvas, que é o local onde fazemos o desenho da View. Esta chamada é feita sempre que for necessário redesenhar a tela.


@Override
protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

/* define os valores que podem virar parametros*/
int lineMargin = 40;
int textMargin = 20;

int height = this.getHeight();
int width = this.getWidth();

/* encontra os centros */
int centerX = width/2 ;
int centerY = height/2;

/* define os pontos*/
int topX = centerX;
int topY = lineMargin;

int bottomX = centerX;
int bottomY = height - lineMargin;

int leftX = lineMargin;
int leftY = centerY;

int rightX = width - lineMargin;
int rightY = centerY;

int topLeftX = width / 3;
int topLeftY = height /3;

int bottomRightX = (width * 2)/3;
int bottomRightY = (height * 2)/3;

int topRightX = (width * 2)/3;
int topRightY = height/3;

int bottomLeftX = width / 3 ;
int bottomLeftY =  (height * 2)/3;

/* desenha as linhas principais */
canvas.drawLine(topX, topY,bottomX ,bottomY, linePaint);
canvas.drawLine(leftX, leftY,rightX ,rightY, linePaint);
canvas.drawLine(topLeftX, topLeftY,bottomRightX ,bottomRightY, linePaint);
canvas.drawLine(topRightX, topRightY, bottomLeftX, bottomLeftY, linePaint);

/* desenha as linhas secundárias */
canvas.drawLine(topX, topY, topLeftX, topLeftY, linePaint);
canvas.drawLine(topLeftX, topLeftY, leftX, leftY, linePaint);
canvas.drawLine(leftX, leftY, bottomLeftX, bottomLeftY, linePaint);
canvas.drawLine(bottomLeftX, bottomLeftY, bottomX, bottomY, linePaint);
canvas.drawLine(bottomX, bottomY, bottomRightX, bottomRightY, linePaint);
canvas.drawLine(bottomRightX, bottomRightY, rightX, rightY, linePaint);
canvas.drawLine(rightX, rightY, topRightX, topRightY, linePaint);
canvas.drawLine(topRightX, topRightY, topX, topY, linePaint);

canvas.drawText(textNorth, topX, textMargin, textPaint);
canvas.drawText(textSouth, bottomX,bottomY + textMargin+textHeight, textPaint);
canvas.drawText(textEast, rightX + textHeight, rightY, textPaint);
canvas.drawText(textWest, textMargin, leftY, textPaint);


}



Repare no código acima que a partir do canvas todas as linhas são feitas e os textos escritos.

Outro ponto importante é o redimensionamento da View. Todas as vezes que a View for redimensionada é necessário verificar o tamanho e reaplicar o redimensionamento. Repare que propositalmente eu criei uma função que verifica o tamanho retornado pelo chamada onMeasure e atribui o tamanho. Se não tiver um tamanho especificado utiliza 400dp.


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


/*captura as medidas de altura e largura...*/ 
int measuredWidth = measure(widthMeasureSpec);
int measuredHeight = measure(heightMeasureSpec);

/*define a medida baseando-se na menor*/
int d = Math.min(measuredWidth, measuredHeight);
setMeasuredDimension(d, d);

}

private int measure(int measureSpec) {

/* verifica o modo de medida e se não tiver tamanho define o valor 200 como padrão */
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);

if (mode == MeasureSpec.UNSPECIFIED)
size = 400;

return size;
}

A utilização da View é do mesmo formato que defini nos posts anteriores. Mas basta relembrar:


<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:orientation="vertical" 
    android:background="@color/backcolor"
    
    >
       <com.example.criandoviews.DirectionalView 
        android:id="@+id/directional1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true" 
        />

</RelativeLayout>

Pessoal este foi o último post em português, este blog está passando por reestruturação e o próximo post será em inglês. Espero que tenham gostado.

O download do código fonte pode ser feito clicando aqui.

Abraço

sábado, 3 de novembro de 2012

Views Compostas

Olá pessoal,

Conforme mencionei no artigo anterior, hoje estou escrevendo o segundo artigo sobre a criação de Views, hoje vamos falar de Views compostas. No artigo anterior descrevi como podemos customizar uma View e fazer uso das propriedades já existentes. Views compostas são aquelas que utilizam mais de uma View em sua composição.

O exemplo que preparei para este artigo é uma View para mostrar resumidamente as informações de contatos. Por enquanto ele irá utilizar dados fictícios, mais adiante irei postar um exemplo com a listagem real dos contatos do telefone.

As Views compostas são construídas por extensão de uma classe ViewGroup (como as classes de Layout vistas aqui neste blog) pois elas irão conter outras Views internamente. No exemplo que desenvolvi para este artigo eu utilizei do GridLayout, devido a flexibilidade que este possui para o encaixe dos objetos.

public class ContactView extends GridLayout

Assim como fizemos quando customizamos a View, podemos utilizar da boa prática de criar um arquivo XML para definir o layout e depois utilizar o serviço de "inflater" para aplicar a visualização do controle.


<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp" >

    <ImageView
        android:id="@id/id_contact_view_image"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_column="0"
        android:layout_gravity="fill_vertical"
        android:layout_row="0"
        android:layout_rowSpan="2"
        android:contentDescription="@string/string_contact_view_image_contentDescription"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@id/id_contact_view_name"
        android:layout_column="1"
        android:layout_height="30dp"       
        android:layout_marginLeft="5dp"
        android:layout_row="0" />

    <TextView
        android:id="@id/id_contact_view_phoneNumber"
        android:layout_column="1"
        android:layout_height="30dp"
        android:layout_marginLeft="5dp"
        android:textColor="@android:color/holo_blue_light"
        android:layout_row="1" />

</GridLayout>

Repare que eu utilizei valores fixos para a largura, altura e margem de alguns itens, isso foi proposital pois não utilizamos isso antes e também para que o layout ficasse definido no formato que eu necessitava.

Agora repare abaixo que eu defini todo o comportamento da View no código e no trecho em destaque é o ponto onde eu associo o XML ao controle.


package com.example.viewscompostas;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.TextView;

public class ContactView extends GridLayout {

TextView contactName;
TextView contactPhoneNumber;
ImageView contactImage;


public ContactView(Context context) {
super(context);
init();

}

public ContactView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

public ContactView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

private void init() {

if (this.isInEditMode())
return;

String infService = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li = (LayoutInflater) getContext().getSystemService(
infService);
li.inflate(R.layout.contact_view, this, true);

contactName = (TextView) findViewById(R.id.id_contact_view_name);
contactPhoneNumber = (TextView) findViewById(R.id.id_contact_view_phoneNumber);
contactImage = (ImageView) findViewById(R.id.id_contact_view_image);

}

public static ContactView Create(Context context, Drawable image, String name,
String phoneNumber) {

ContactView contactView = new ContactView(context);
contactView.setContact(image, name, phoneNumber);
return contactView;
}

public void setContact(Drawable image, String name, String phoneNumber) {
contactName.setText(name);
contactPhoneNumber.setText(phoneNumber);
contactImage.setImageDrawable(image);
}

}


No ponto em destaque há alguns pontos importantes. Enquanto eu estava desenvolvendo, reparei que havia uma mensagem de aviso para mim sobre o modo de edição, resolvi verificar o que era e descobri que você pode fazer uma condição para que o Eclipse entenda que você está no modo de edição e assim não tente efetuar funções internas da View. Isso está definido pelas linhas amarelas.

Já as linhas em verde estão descrevendo o momento onde o serviço de layout é chamado e associa a View ao controle.

As linhas em azul mostram o momento em que a referencia aos controles são capturadas para efetuar ações.

Ai está uma view composta criada. A utilização é do mesmo modo como anterior, mas neste exemplo eu fiz diferente e estou adicionando-as através do código.


LinearLayout parent = (LinearLayout) findViewById(R.id.layoutParent);

for (int index = 0; index < 3; index++) {
ContactView cv = ContactView
.Create(parent.getContext(),
getResources().getDrawable(
ContactReference.Image[index]),
ContactReference.Name[index],
ContactReference.Phone[index]);
parent.addView(cv);
}


O exemplo deste artigo pode ser baixado aqui.

O próximo post irei falar sobre como escrever uma nova View.

Abraço a todos!