Tag Archives: Android

Bysutradi

Android: Usando um layout customizado para ajustar altura de uma RecyclerView

O componente RecyclerView (clique neste link para ver detalhes de sua implementação, que não é o foco deste artigo) é uma widget introduzida no Android como o novo padrão para exibição de listas em substituição ao ListView.

Até aqui, OK.

Porém, este componente deve ser adicionado em um RelativeLayout que seja a identificação principal do seu layout, ou seja, você não pode ter um ScrollView > LinearLayout > seu componente RecyclerView.

Até aqui, OK, pois você pode trabalhar com RelativeLayout e adequar sua interface visual para encaixar o componente.

Outro comportamento do componente é que, normalmente, ele preenche a altura até completar a altura do dispositivo.

Não investi tempo para procurar por justificativas, mas por soluções. E uma das soluções encontradas e que funcionou logo de cara foi a criação de uma classe de Layout customizada que faz estes ajustes.

Compartilho ela com vocês logo abaixo:

 

import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

public class RecyclerViewLinearLayout extends LinearLayoutManager {

    public RecyclerViewLinearLayout(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        this.c = context;
    }

    private Context c;
    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }

        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        int widthDesired = Math.min(widthSize, width);
        setMeasuredDimension(widthDesired, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

Você usa esta implementação no momento de definir o Layout para o RecyclerView.

acoesCompradasLista = (RecyclerView) view.findViewById(R.id.cardList);
acoesCompradasLista.setHasFixedSize(true);
RecyclerViewLinearLayout llm = new RecyclerViewLinearLayout(getActivity(), LinearLayoutManager.VERTICAL, false);

Com isso, o RecyclerView cria a lista com todos os elementos, ajusta a altura automaticamente (portrait ou landscape) e você consegue adicionar outros elementos na sua interface abaixo do RecyclerView.

Bysutradi

Criando um Spinner customizado com valores preenchidos por enum

O uso de Spinners em aplicativos Android é muito comum e isso pode ser feito de muitas maneiras, seguinte a rotina: crie o spinner, passe a lista, crie o adapter, crie o layout, etc.

Em alguns casos, tais Spinners são preenchidos com informações de domínio fechado.

Diante desta necessidade, é possível usar classes do tipo enum para preencher os valores em um Spinner customizado.

Neste tutorial, mostrarei como fazer isso criando algumas classes.

Vamos começar pela interface, para padronizar as classes enum.

public interface SpinnerEnum {

   public long getId();
   public String getNome();
   public Object[] listar();
}

Usaremos esta interface em todas as nossas futuras classes de enum.

O que precisamos agora é de nosso Adapter.

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import com.sutradi.lib.drawer.DatabaseModel;

import java.util.List;

public class SpinnerAdapter extends ArrayAdapter<DatabaseModel> {

	private final Context contexto;
	private final List<DatabaseModel> lista;
	DatabaseModel tempValues = null;
	int textViewID;
	int textViewNome;
	int spinnerLayoutID;

	/**
	 * Construtor.
	 * @param contexto
	 * @param listaObjetos
	 * @param spinnerLayout Layout que contenha 02 objetos TextView para ID e Nome
	 * @param _textViewID TextView para armazenar o ID
	 * @param _textViewNome TextView para armazenar o Nome
	 */
	public SpinnerAdapter(Context contexto, List<DatabaseModel> listaObjetos,
			int spinnerLayout, int _textViewID, int _textViewNome) {

		super(contexto, spinnerLayout, listaObjetos);
		this.contexto = contexto;
		this.lista = listaObjetos;
		this.textViewID = _textViewID;
		this.textViewNome = _textViewNome;
		this.spinnerLayoutID = spinnerLayout;
	}

	@Override
	public View getDropDownView(int position, View convertView, ViewGroup parent) {
		return getCustomView(position, convertView, parent);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		return getCustomView(position, convertView, parent);
	}

	public View getCustomView(int position, View convertView, ViewGroup parent) {
		LayoutInflater inflater = (LayoutInflater) contexto
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		View rowView = inflater.inflate(spinnerLayoutID, parent, false);
		tempValues = null;
		tempValues = (DatabaseModel) lista.get(position);

		TextView viewID = (TextView) rowView.findViewById(textViewID);
		TextView viewNome = (TextView) rowView.findViewById(textViewNome);
		viewID.setText(tempValues.getId());
		viewNome.setText(tempValues.getNome());

		return rowView;
	}
}

Em nosso Adapter, informaremos o ID para o layout e os IDs para os campos que representação o ID e Nome que serão usados no Spinner.

public SpinnerAdapter(Context contexto, List<DatabaseModel> listaObjetos,
			int spinnerLayout, int _textViewID, int _textViewNome) {

Essa implementação permitirá que você crie layouts diferentes para o Spinner. Caso queira padronizar o Spinner, basta remover estas informações do construtor e definir as informações corretas na implementação em getCustomView.

A classe DatabaseModel deve conter uma implementação POJO que contém ID e Nome. Bem simples.

Agora que temos o Adapter definido, vamos criar nosso Spinner customizado.

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Spinner;

import com.sutradi.lib.drawer.DatabaseModel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Classe de apoio para uso de Spinners que apresentam somente Texto, carregando
 * dados a partir de um Enum (que deve extender à SpinnerEnum) O layout do
 * spinner deve ter somente um EditText para ID (hide) e outro para Nome.
 *
 * @author SUTRADI
 */
public class SpinnerUI extends Spinner {
    ArrayList<DatabaseModel> lista = null;

    /**
     * Construtor.
     *
     * @param _context
     */
    public SpinnerUI(Context _context) {
        super(_context);
    }

    /**
     * Construtor.
     *
     * @param _context
     * @param _attrs
     * @param _defStyle
     * @param _mode
     */
    public SpinnerUI(Context _context, AttributeSet _attrs, int _defStyle,
                     int _mode) {
        super(_context, _attrs, _defStyle, _mode);
    }

    /**
     * Construtor.
     *
     * @param _context
     * @param _attrs
     * @param _defStyle
     */
    public SpinnerUI(Context _context, AttributeSet _attrs, int _defStyle) {
        super(_context, _attrs, _defStyle);
    }

    /**
     * Construtor.
     *
     * @param _context
     * @param _attrs
     */
    public SpinnerUI(Context _context, AttributeSet _attrs) {
        super(_context, _attrs);
    }

    /**
     * Construtor.
     *
     * @param _context
     * @param _mode
     */
    public SpinnerUI(Context _context, int _mode) {
        super(_context, _mode);
    }

    /**
     * @param context
     * @param spinnerText
     * @param listaEnum
     * @param spinnerLayout
     * @param spinnerItemId
     * @param spinnerItemNome
     */
    public void loadSpinner(Context context, String spinnerText,
                            SpinnerEnum[] listaEnum, int spinnerLayout, int spinnerItemId,
                            int spinnerItemNome, SpinnerEnum... enumIgnorar) {

        ArrayList<DatabaseModel> listaDatabaseModel = montaLista(spinnerText,
                listaEnum, enumIgnorar);

        SpinnerAdapter msa = new SpinnerAdapter(context, listaDatabaseModel,
                spinnerLayout, spinnerItemId, spinnerItemNome);

        this.setAdapter(msa);

    }

    /**
     * @param labelPrimeiro
     * @param listaEnum
     * @return
     */
    private ArrayList<DatabaseModel> montaLista(final String labelPrimeiro,
                                                SpinnerEnum[] listaEnum, SpinnerEnum... enumIgnorar) {
        List<SpinnerEnum> temp = new ArrayList<SpinnerEnum>(Arrays.asList(listaEnum));

        for(SpinnerEnum e : enumIgnorar) {
            temp.remove(e);
        }
        lista = new ArrayList<DatabaseModel>();
        lista.add(new DatabaseModel("0", labelPrimeiro));
        for (SpinnerEnum obj : temp) {
            DatabaseModel dm = new DatabaseModel(String.valueOf(obj.getId()),
                    obj.getNome());
            lista.add(dm);
        }

        return lista;
    }

}

A implementação pública loadSpinner será a responsável por receber as informações necessárias para o preenchimento do Spinner, com base nas necessidades do SpinnerAdapter. Notem que além de informar a lista de nossa interface SpinnerEnum, também é necessário informar os IDs do layout, ID e Nome. De quebra, é possível também passarmos uma lista de itens do SpinnerEnum que não queremos que seja adicionado.

public void loadSpinner(Context context, String spinnerText,
                            SpinnerEnum[] listaEnum, int spinnerLayout, int spinnerItemId,
                            int spinnerItemNome, SpinnerEnum... enumIgnorar) {

A implementação privada montaLista conterá toda a lógica para o preenchimento do Spinner. Note que o primeiro elemento terá o valor “0” sempre.

E como podemos usar este SpinnerUI ?

SpinnerUI spinner = (SpinnerUI) view.findViewById(R.id.spinnerUI_despesas);
        spinner.loadSpinner(getActivity(), getString(R.string.despesaSelecionar), DespesaEnum.getSortedVaules(), R.layout.spinner_ui, R.id.spinnerUI_id, R.id.spinnerUI_nome, DespesaEnum.ABASTECIMENTO, DespesaEnum.TROCA_OLEO, DespesaEnum.REVISAO);

E como implementamos nossas classes do tipo enum? Veja uma implementação simples abaixo.

import com.sutradi.lib.ui.spinner.SpinnerEnum;

import java.util.Arrays;
import java.util.Comparator;

public enum DespesaEnum implements SpinnerEnum {

	REVISAO(1, "Revisão"), 
	ABASTECIMENTO(2, "Abastecimento"), 
	TROCA_OLEO(3,"Troca de Óleo"), 
	MANUTENCAO(4, "Manutenção / Mecânica"), 
	OUTRA(99, "Outras");

	private long id;
	private String nome;

	DespesaEnum(long _id, String _nome) {
		this.id = _id;
		this.nome = _nome;
	}

	public static DespesaEnum getById(long id) {
		for (DespesaEnum item : DespesaEnum.values()) {
			if (item.getId() == id) {
				return item;
			}
		}
		return DespesaEnum.OUTRA;
	}

	/**
	 * @return the id
	 */
	public long getId() {
		return id;
	}

	/**
	 * @param id
	 *            the id to set
	 */
	public void setId(long id) {
		this.id = id;
	}

	/**
	 * @return the nome
	 */
	public String getNome() {
		return nome;
	}

	/**
	 * @param nome
	 *            the nome to set
	 */
	public void setNome(String nome) {
		this.nome = nome;
	}

	@Override
	public Object[] listar() {
		return DespesaEnum.values();
	}
	
	public static DespesaEnum[] getSortedVaules(DespesaEnum... ignore) {
		DespesaEnum[] statures = values();
		Arrays.sort(statures, EnumByNameComparator.INSTANCE);
		return statures;
	}

	private static class EnumByNameComparator implements Comparator<Enum<?>> {
		public static final Comparator<Enum<?>> INSTANCE = new EnumByNameComparator();
		public int compare(Enum<?> enum1, Enum<?> enum2) {
			return enum1.name().compareTo(enum2.name());
		}
	}

}

Você será obrigado a implementar os métodos getId e getNome, que farão a ligação com os elementos no layout do Spinner.

Você pode adicionar mais atributos na interface SpinnerEnum e enriquecer sua implementação.

Bysutradi

Ferramentas para desenvolvimento de soluções móveis

A mobilidade, sem dúvida, veio para ficar. A cada dia vemos notícias das principais empresas do mercado divulgando investimentos e previsões promissoras para os próximos anos, seja para o uso em smartphones, tables ou qualquer outro aparelho que permita mobilidade.

Tanto é verdade que a IBM acaba de criar uma área chamada IBM Mobile Foundation para tratar exclusivamente deste assunto, já com previsão de receita na ordem de US$ 36 bilhões até 2015. Além disso, estimam que as vendas de tablets superem laptops nos próximos 5 anos.

Diante deste fato, as empresas investem em aquisições e melhorias de suas ferramentas para desenvolvimento rápido de soluções móveis.

Na IBM, temos as ferramentas IBM Worklight e IBM Web Experience Factory. Na fundação Eclipse temos o projeto Eclipse Pulsar (foco principal em plataforma BlackBerry, Motorola, Nokia). E temos várias outras no mercado.

A tabela abaixo apresenta a relação de algumas ferramentas com sua proposta de geração de artefato (nativo e HTML5):

IDEs / Plataforma Android Blackberry iOS Windows Phone HTML5
IBM Worklight x x x x x
IBM Web Experience Factory x x x
Microsoft Visual Studio 2010 x
Xcode x

Atualmente, as principais plataformas são Android e iOS, mas a Microsoft vem aprimorando seu Windows Phone e já possui alguns lançamentos previstos.

O interessante é que nenhuma das empresas citadas anteriormente são, de acordo com o Gartner, líderes de mercado em Mobile Application Development. O certo é que elas farão de tudo para serem reconhecidas neste quesito.

Mas além do desenvolvimento, precisamos pensar em outros pontos crítico para o sucessos de projetos de mobilidade, como:

Mobilidade veio para ficar.