sábado, 19 de março de 2016

Customizando o TextInputLayout com ImageSpan

Olá povo,

 Post curto do dia. Estava querendo customizar o erro do TextInputLayout (TIL) e só consegui graças a ajuda da Lisa Wray, que deu a ótima sugestão de utilizar as classes SpannableStringBuilder + ImageSpan, que eu não conhecia.

 Muitos devs Android, quando vêm algum método que recebe um objeto CharSequence como parâmetro, pensam imediatamente é o mesmo que passar uma String. Mas na verdade o CharSequence é bem mais poderoso, pois permite ir além de textos planos. A classe SpannableStringBuilder é uma subclasse de CharSequence que permite adicionar Spannable's como o ImageSpan.
Vejamos o código a seguir.
TextInputLayout textInputLayout = (TextInputLayout) editText.getParent();
if (campoInvalido()) { // sua verificação do campo vem aqui.
    // Cria a imagem que será incluída no texto do erro.
    // O alinhamento poderia ser ALIGN_BASELINE
    ImageSpan imageSpan = new ImageSpan(
        this, R.drawable.ic_erro, DynamicDrawableSpan.ALIGN_BOTTOM);
    // O SpannableStringBuilder substituirá o primeiro caracter pela imagem
    // por isso o " " no começo.
    SpannableStringBuilder ssbErrorMessage = 
        new SpannableStringBuilder(" "+ getString(R.string.msg_erro));
    // Adicionando a imagem como texto do erro
    ssbErrorMessage.setSpan(imageSpan, 0, 1, 0);

    // Habilita o erro do TIL
    textInputLayout.setErrorEnabled(true);
    // define a mensagem de erro
    textInputLayout.setError(ssbErrorMessage);
    // Atualiza o componente
    textInputLayout.refreshDrawableState();
}
O resultado ficará como a imagem abaixo.
Dois detalhes importantes nesse exemplo:
1) essa abordagem pode ser utilizada por qualquer componentes que receba um CharSequence \o/
2) Podemos utilizar qualquer Bitmap como imagem ;)

 4br4ç05,
nglauber

terça-feira, 15 de março de 2016

ViewPager sem FragmentPagerAdapter + Indicator

Olá povo,

Resolvi escrever esse post porque toda vez que eu preciso disso tenho que procurar na internet. Não que seja difícil de achar, mas me vi na obrigação moral de fazer :)
Quando utilizamos o ViewPager, normalmente estamos fazendo alguma tela de abas ou algo similar. Nesse caso é recomendado fazer com que cada aba/página seja um Fragment. Mas se não houver nenhuma lógica nessas páginas, criar um fragment pode ser desnecessário.
Posso dar como exemplo aquelas telas de boas-vindas que mostram um breve tutorial de como utilizar a aplicação, onde cada passo é uma página com as instruções. Nesse caso, utilizar um layout (definido em um arquivo de layout) para cada página seria o suficiente, e não precisaríamos de Fragment para tal.
Vamos ver como fazer isso,  começando pelo arquivo de layout a seguir.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <com.viewpagerindicator.CirclePageIndicator
        android:id="@+id/indicator"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:paddingBottom="10dp"
        android:paddingTop="10dp"
        android:background="@color/colorPrimary"/>
</LinearLayout>

Esse arquivo possui um ViewPager e um CirclePageIndicator (criado por Jake Wharton) que servirá para exibir um indicador da página atual. Para utiliza-lo, basta adicionar no build.gradle a dependência do componente.
dependencies {
    ...
    compile 'com.android.support:appcompat-v7:23.2.0' 
    compile 'com.github.JakeWharton:ViewPagerIndicator:2.4.1@aar'
}
Vejamos agora o código que cria as páginas do ViewPager.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewPager viewPager = (ViewPager)findViewById(R.id.viewpager);
        viewPager.setAdapter(new ViewSimplesAdapter());

        CirclePageIndicator indicator = 
                (CirclePageIndicator)findViewById(R.id.indicator);
        indicator.setViewPager(viewPager);
    }

    private class ViewSimplesAdapter extends PagerAdapter {
        @Override
        public int getCount() {
            return 3;
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            int layout;
            if (position == 0){
                layout = R.layout.layout_pagina1;
            } else if (position == 1){
                layout = R.layout.layout_pagina2;
            } else {
                layout = R.layout.layout_pagina3;
            }
            View view = LayoutInflater.from(container.getContext())
                    .inflate(layout, container, false);
            container.addView(view);
            return view;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, 
                                Object object) {
            container.removeView((View)object);
        }
    }
}
A classe ViewSimplesAdapter herda de PagerAdapter e nela, temos o método getCount() que retorna a quantidade páginas que iremos exibir e mais três métodos:
  • isViewFromObject() - basicamente determina se uma View está relacionada com o objeto retornado pelo método instantiateItem().
  • instantiateItem() carrega o arquivo de layout da página específica. Perceba que a View carregada é adicionada ao ViewGroup recebido como parâmetro e é retornada pelo método.
  • destroyItem() vai ser responsável por destruir os itens criados. É importante ressaltar que o ViewPager mantém no máximo três páginas ativas: a que está sendo exibida, a anterior e a posterior.
Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

quinta-feira, 10 de março de 2016

Tchau CESAR :(

Olá povo,

No dia 16 de janeiro de 2006 eu começava a trabalhar no Centro de Estudos e Sistemas Avançados do Recife. Ainda estava terminando a faculdade e lembro que meu sonho era "usar o crachá laranja".
Hoje, 10 de março de 2016 é o meu último dia de trabalho no CESAR. Foram mais de 10 anos de muito desafios, aprendizados, crescimento pessoal e profissional, e muita história boa para contar.
Tive a satisfação de trabalhar com muita gente boa (e outras nem tão legais assim) e fiz muitas amizades em todos os projetos em que passei eu acho.

O nome do CESAR estará cravado na minha história para sempre, com muito orgulho, pois foi nessa empresa fantástica que construí a maior e melhor parte da minha carreira. Isso sem contar a parte acadêmica, já que eu cursei o mestrado no CESAR.edu.

Mas como é evidente a todos que me conhecem, estou fortemente envolvido com a área de desenvolvimento para dispositivos móveis, e nos últimos meses não estava tendo a oportunidade de trabalhar com mobile e nem com desenvolvimento de software (codificação em si). Isso acabou me deixando um pouco desmotivado, então resolvi conversar com meus superiores e verificar a possibilidade de me alocar em um projeto mobile dentro da instituição. Mas infelizmente, fui informado de que não havia previsão de novos projetos com mobile no curto/médio prazo. Sendo assim, chegamos a conclusão de que meu plano de carreira não estava alinhado com as expectativas do CESAR, então resolvi procurar novos desafios em um novo lugar.

Espero de todo coração estar deixando as portas abertas, pois o CESAR é um lugar incrível para trabalhar, que eu considero minha segunda casa, onde (quem sabe?) um dia terei a satisfação de voltar.
Obrigado a todos vocês que fizeram parte da minha vida durante esses 10 anos, vocês me fizeram muito feliz. :)

Chegou a hora de começar uma nova etapa(!) e escrever um novo capítulo da minha história.

4br4ç05,
nglauber