Mostrando postagens com marcador Personalizada. Mostrar todas as postagens
Mostrando postagens com marcador Personalizada. Mostrar todas as postagens

terça-feira, 8 de novembro de 2011

AutoCompleteTextView personalizado

Olá povo,

Estou com um monte de posts legais pra publicar, mas infelizmente (pra variar) estou sem tempo. São coisas que precisei fazer nos projetos que trabalhei, e que gosto de compartilhar com quem lê o blog. Além do mais, me serve como fonte de consulta para uma uso posterior. Afinal de contas, é pra isso que serve o blog (pelo menos pra mim).

Hoje vou mostrar como criar uma consulta personalizada para o componente AutoCompleteTextView. Esse componente é utilizado quando você tem uma lista de valores, e ao invés de selecioná-lo em uma lista, você digita parte do texto e os resultados vão sendo exibidos em uma lista (estilo drop-down) abaixo do componente. Este componente já conta com um mecanismo de busca padrão, entretanto a necessidade de personalizá-lo surgiu quando precisei realizar a busca por nomes de cidades. O cliente solicitou que a acentuação fosse ignorada, ou seja, quando eu digitasse "sao" era para aparecer "São Paulo" nos resultados.

Vamos à implementação! Primeiro vou mostrar o método que substitui os caracteres acentuados pelos mesmos não acentuados.

public static String[] REPLACES = 
{ "a", "e", "i", "o", "u", "c" };

public static Pattern[] PATTERNS = null;

public static void compilePatterns() {
PATTERNS = new Pattern[REPLACES.length];
PATTERNS[0] = Pattern.compile(
"[âãáàä]", Pattern.CASE_INSENSITIVE);
PATTERNS[1] = Pattern.compile(
"[éèêë]", Pattern.CASE_INSENSITIVE);
PATTERNS[2] = Pattern.compile(
"[íìîï]", Pattern.CASE_INSENSITIVE);
PATTERNS[3] = Pattern.compile(
"[óòôõö]", Pattern.CASE_INSENSITIVE);
PATTERNS[4] = Pattern.compile(
"[úùûü]", Pattern.CASE_INSENSITIVE);
PATTERNS[5] = Pattern.compile(
"[ç]", Pattern.CASE_INSENSITIVE);
}

public static String removeAcentos(String text) {
if (PATTERNS == null) {
compilePatterns();
}

String result = text;
for (int i = 0; i < PATTERNS.length; i++) {
Matcher matcher = PATTERNS[i].matcher(result);
result = matcher.replaceAll(REPLACES[i]);
}
return result.toUpperCase();
}

O código acima, utiliza as classes Pattern e Matcher para checar a presença de caracteres acentuados e substitui-los pelos caracteres correspondentes sem acentuação. Observe que só são sendo tratadas as vogais e o cedilha, para textos em outros idiomas você deve fazer os ajustes necessários (como o 'ñ' do espanhol).

Para que um AutoCompleteTextView faça a busca, é necessário definir um Adapter com os dados que ele irá filtrar para exibir. Para fazer nossa busca personalizada (ignorando acentuação) devemos criar nosso Adapter e sobrescrever o método getFilter(). Esse método deve retornar um objeto da classe android.widget.Filter que representa o resultado da busca.

public class MeuAutoCompleteAdapter
extends ArrayAdapter<String>
implements Filterable {

private List<String> listaCompleta;
private List<String> resultados;
private Filter meuFiltro;

public MeuAutoCompleteAdapter(
Context ctx, int layout,
List<String> textos) {

super(ctx, layout, textos);
this.listaCompleta = textos;
this.resultados = listaCompleta;
this.meuFiltro = new MeuFiltro();
}

@Override
public int getCount() {
return resultados.size();
}

@Override
public String getItem(int position) {
if (resultados != null
&& resultados.size() > 0
&& position < resultados.size()){
return resultados.get(position);
} else {
return null;
}
}

@Override
public Filter getFilter() {
return meuFiltro;
}

private class MeuFiltro extends Filter {
@Override
protected FilterResults performFiltering(
CharSequence constraint) {

FilterResults filterResults =
new FilterResults();

ArrayList<String> temp =
new ArrayList<String>();

if (constraint != null) {
String term = removeAcentos(
constraint.toString().trim().toLowerCase());

String placeStr;
for (String p : listaCompleta) {
placeStr = removeAcentos(p.toLowerCase());

if ( placeStr.indexOf(term) > -1){
temp.add(p);
}
}
}
filterResults.values = temp;
filterResults.count = temp.size();
return filterResults;
}

@SuppressWarnings("unchecked")
@Override
protected void publishResults(
CharSequence contraint,
FilterResults filterResults) {

resultados = (ArrayList<String>)
filterResults.values;

notifyDataSetChanged();
}
}
}

A classe acima tem duas listas de strings: a original e a que serve como resultado da busca. Note que os métodos getCount e getItem trabalha em cima da lista dos resultados. O outro atributo da classe é da classe MeuFiltro, e esse atributo é retornado no método getFilter.
A classe interna MeuFiltro faz a nossa busca personalizada. O filtro é realizado no método performFiltering, que retorna um objeto da classe android.widget.Filter.FilterResults que contém o resultado da busca e o total de registros encontrados. Esse resultado é passado para o método publishResults que seta o resultado da busca no atributo resultados, e logo após atualiza o adapter através do método notifyDatasetChanged.

Agora vamos ver como usar esse nosso adapter.


List<String> cidade = new ArrayList<String>();
cidade.add("Recife");
cidade.add("São Paulo");
cidade.add("Santos");
cidade.add("Santa Cruz");

MeuAutoCompleteAdapter adapter =
new MeuAutoCompleteAdapter(
contexto,
android.R.layout.simple_dropdown_item_1line,
cidade);

AutoCompleteTextView actv = (AutoCompleteTextView)
findViewById(R.id.autoCompleteTextView1);
actv.setAdapter(adapter);


O resultado é exibido na figura abaixo:


Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

quarta-feira, 15 de junho de 2011

Atributos personalizados em Views Android

Olá povo,

Estou escrevendo mais uma matéria para Java Magazine em parceria com dois colegas meus. No exemplo apresentado no artigo, está sendo usado um componente criado por nós. Para criar um componente personalizado, basta criar uma classe que estenda um componente existente (EditText, Button, TextView, etc.). Porém, se quisermos criar um componente totalmente novo, devemos estender de View, que é classe mãe de todos os componentes do Android, e implementar o método draw que vai desenhar o componente na tela.
Neste ponto, surgiu a seguinte dúvida: como adicionar uma View personalizada em um arquivo de layout e passar atributos personalizados para a View? Bem simples, como quase tudo no Android. Abra o arquivos res/values/strings.xml e deixe-o conforme abaixo:
<resources>
<string name="hello">
debug is on the table
</string>
<string name="app_name">
nglauber
</string>

<declare-styleable name="MeuComponente">
<attr name="texto" format="string"/>
</declare-styleable>
</resources>

Notem que no código acima, além de dois textos com os ids hello e app_name, declaramos um estilo chamado MeuComponente, e dentro dele um atributo chamado "texto" do tipo string. Pronto! Agora é só usar esse atributo tanto na classe que herda de View, quanto no XML. Primeiro vamos ver como fica na classe que herda de view.
public class MeuComponente extends View {

private String text;
private Paint paint;

public MeuComponente(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
paint = new Paint();
paint.setColor(Color.RED);

TypedArray a = ctx.obtainStyledAttributes(
attrs, R.styleable.MeuComponente);

String s = a.getString(
R.styleable.MeuComponente_texto);

if (s != null) {
text = s.toString();
}
a.recycle();
}

@Override
public void draw(Canvas canvas) {
super.draw(canvas);
canvas.drawColor(Color.WHITE);

if (text != null){
canvas.drawText(text, 0, 20, paint);
}
}
}


A classe não é nada complicada, mas temos que observar alguns detalhes. Ao criar uma classe que herda de View, você deve implementar um dos 3 construtores possíveis: View(Context), View(Context, AttributeSet) e View(Context, AttributeSet, int). O primeiro recebe apenas o contexto de onde o componente está sendo criado. O segundo recebe o contexto e um conjunto de atributos que são passados para o componente quando ele está inserido em um arquivo de layout. O último método recebe, além dos dois parâmetros citados anteriormente, recebe um estilo que o componente pode receber.
No exemplo acima, usamos a segunda opção, que recebe o contexto e a lista de atributos definidos no XML de layout. Com o método obtainStyledAttributes obtemos um array com os atributos de um determinados estilo, no nosso caso, passamos o estilos que criamos no arquivos strings.xml.
Na linha seguinte, tentamos obter o valor do atributo "texto", caso tenha sido atribuído algum valor a essa propriedade, o valor é passado para o atributo texto. A chamada do método recycle é obrigatória após terminar o uso do array de propriedades. Por fim, no método draw o texto definido no atributo é desenhado na tela.
Agora veja como adicionar um componente personalizado em um arquivo de layout e como utilizar a nossa própria propriedade.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ngvl="http://schemas.android.com/apk/res/ngvl.android.teste"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ngvl.android.teste.MeuComponente
ngvl:text="@string/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

Vamos analisar os detalhes importantes desse arquivo. No LinearLayout, temos uma propriedade chamada xmlns:ngvl, ela indica onde estão declaras as propriedades que começarem com "ngvl". Outro ponto é que, na declaração do componente, deve constar o nome pacote + nome classe. No nosso caso, a classe MeuComponente está no pacote ngvl.android.teste. Por fim, adicionamos a propriedade ngvl:text ao nosso componente.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Referência:
http://jeffreysambells.com/posts/2010/10/28/custom-views-and-layouts-in-android-os-sdk/