Skip to content

Instantly share code, notes, and snippets.

@MatheusAmelco
Last active November 13, 2017 13:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MatheusAmelco/6fdb826ea226c92ee8a9e6e7ef968e54 to your computer and use it in GitHub Desktop.
Save MatheusAmelco/6fdb826ea226c92ee8a9e6e7ef968e54 to your computer and use it in GitHub Desktop.
Custom number picker for Android
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumberPicker">
<attr name="numberpicker_value" format="integer" />
<attr name="numberpicker_maxValue" format="integer" />
<attr name="numberpicker_minValue" format="integer" />
<attr name="numberpicker_textColor" format="color" />
<attr name="numberpicker_textSize" format="dimension" />
</declare-styleable>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:color="#640606"
tools:ignore="NewApi">
<item>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/corVermelho" />
<size
android:width="40dp"
android:height="40dp" />
</shape>
</item>
<item
android:left="13dp"
android:right="13dp">
<shape android:shape="line">
<stroke
android:width="1.5dp"
android:color="@color/corBranco" />
</shape>
</item>
</layer-list>
</item>
</ripple>
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:color="#055908"
tools:ignore="NewApi">
<item>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/corDestaque" />
<size
android:width="40dp"
android:height="40dp" />
</shape>
</item>
<item
android:left="13dp"
android:right="13dp">
<shape android:shape="line">
<stroke
android:width="1.5dp"
android:color="@color/corBranco" />
</shape>
</item>
<item
android:left="13dp"
android:right="13dp">
<rotate android:fromDegrees="90">
<shape android:shape="line">
<stroke
android:width="1.5dp"
android:color="@color/corBranco" />
</shape>
</rotate>
</item>
</layer-list>
</item>
</ripple>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Cores Principais -->
<color name="corDestaque">#4CAF50</color>
<color name="corTextoPrincipal">#212121</color>
<!-- Outras Cores -->
<color name="corBranco">#FFFFFF</color>
<color name="corVermelho">#D32F2F</color>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/number_picker_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="horizontal"
android:padding="8dp">
<Button
android:id="@+id/number_picker_menos"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/btn_circle_negative"
android:elevation="2dp"
tools:ignore="UnusedAttribute" />
<TextView
android:id="@+id/number_picker_texto"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:focusable="false"
android:gravity="center"
android:inputType="none"
android:textAlignment="center"
android:textColor="@color/corTextoSecundario"
android:textSize="16sp" />
<Button
android:id="@+id/number_picker_mais"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/btn_circle_positive"
android:elevation="2dp"
tools:ignore="UnusedAttribute" />
</LinearLayout>
</merge>
package com.matheusamelco.android.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.matheusamelco.android.R;
/**
* Componente personalizado de Stepper. Normalmente utilizado para seleção de quantidades,
* permite a definição de valores inteiros, negativos e positivos, com limite mínimo e máximo,
* e passo personalizáveis.
*
* @author Matheus Felipe Amelco
* @since 18/10/17
*/
public class NumberPicker extends LinearLayout {
// Componentes
Button menos;
TextView texto;
Button mais;
// Propriedades
int valor;
int valorMaximo;
int valorMinimo;
OnValorChanged listener;
/**
* Construtor.
*
* @param context Context - Contexto da atividade.
*/
public NumberPicker(Context context) {
super(context);
init(context, null);
}
/**
* Construtor.
*
* @param context Context - Contexto da atividade.
* @param attrs AttributeSet - Atributos utilizados pelo LayoutInflater.
*/
public NumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
/**
* Construtor.
*
* @param context Context - Contexto da atividade.
* @param attrs AttributeSet - Atributos utilizados pelo LayoutInflater.
* @param defStyle int - Estilo do componente, utilizado pelo LayoutInflater.
*/
public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
/**
* Inicializa o componente com suas ações padrões.
*
* @param context Context - Contexto da atividade.
*/
private void init(Context context, AttributeSet attrs) {
View.inflate(context, R.layout.number_stepper, this);
setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
// Pega os componentes definidos no XML.
menos = this.findViewById(R.id.number_picker_menos);
texto = this.findViewById(R.id.number_picker_texto);
mais = this.findViewById(R.id.number_picker_mais);
// HitBox
final Rect hitRectMenos = new Rect(menos.getLeft(), menos.getTop(), menos.getRight(), menos.getBottom());
final Rect hitRectMais = new Rect(mais.getLeft(), mais.getTop(), mais.getRight(), mais.getBottom());
// Define o efeito visual de toque nos componentes.
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
view.getHitRect(hitRectMenos);
view.getHitRect(hitRectMais);
// Botão Menos
if (hitRectMenos.contains((int) motionEvent.getX(), (int) motionEvent.getY())) {
motionEvent.setLocation(0.0f, 0.0f);
menos.dispatchTouchEvent(motionEvent);
view.performClick();
}
// Botão Mais
if (hitRectMais.contains((int) motionEvent.getX(), (int) motionEvent.getY())) {
motionEvent.setLocation(0.0f, 0.0f);
mais.dispatchTouchEvent(motionEvent);
view.performClick();
}
return true;
}
});
// Define a ação dos botões no clique.
setOnClickMenosListener(new OnClickListener() {
@Override
public void onClick(View v) {
int oldVal = getValor();
int newVal = oldVal - 1;
setValor(newVal);
if (listener != null) {
listener.onValorChanged(oldVal, getValor());
}
}
});
setOnClickMaisListener(new OnClickListener() {
@Override
public void onClick(View v) {
int oldVal = getValor();
int newVal = oldVal + 1;
setValor(newVal);
if (listener != null) {
listener.onValorChanged(oldVal, getValor());
}
}
});
// Define os atributos personalizados definidos no XML!
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.NumberPicker, 0, 0);
int value = 0;
int maxValue = 100;
int minValue = 0;
try {
value = a.getInteger(R.styleable.NumberStepper_value, 0);
maxValue = a.getInteger(R.styleable.NumberStepper_maxValue, 0);
minValue = a.getInteger(R.styleable.NumberStepper_minValue, 0);
} catch (Exception e) {
Logger.error(NumberPicker.class, e);
} finally {
a.recycle();
}
setValor(value);
setValorMaximo(maxValue);
setValorMinimo(minValue);
}
}
// SET
/**
* Define o valor exibido no componente.
* Seguirá as regras de valor máximo e mínimo padrões, ou as regras que foram
* definidas manualmente.
* <p>
* <b>Regras padrões:</b>
* Valor Mínimo = 0
* Valor Máximo = 100
*
* @param valor int - Valor a ser definido ao componente.
*/
public void setValor(int valor) {
if (valor < 0 || valor < valorMinimo) {
this.valor = valorMinimo;
} else if (valor > valorMaximo) {
this.valor = valorMaximo;
} else {
this.valor = valor;
}
texto.setText(String.valueOf(this.valor));
}
public void setValorMaximo(int valorMaximo) {
this.valorMaximo = valorMaximo;
}
public void setValorMinimo(int valorMinimo) {
this.valorMinimo = valorMinimo;
}
public void setonValorChangedListener(OnValorChanged listener) {
this.listener = listener;
}
/**
* Define o listener que será executado quando botão "Menos" for pressionado.
* ATENÇÃO: A utilização deste método sobrescreve a ação padrão do componente,
* portanto será necessário realizar os tratamentos manualmente!
*
* @param listener OnClickListener - Listener que será usado no botão.
*/
public void setOnClickMenosListener(CompoundButton.OnClickListener listener) {
menos.setOnClickListener(listener);
}
/**
* Define o listener que será executado quando botão "Mais" for pressionado.
* ATENÇÃO: A utilização deste método sobrescreve a ação padrão do componente,
* portanto será necessário realizar os tratamentos manualmente!
*
* @param listener OnClickListener - Listener que será usado no botão.
*/
public void setOnClickMaisListener(CompoundButton.OnClickListener listener) {
mais.setOnClickListener(listener);
}
// GET
/**
* Retorna o valor atual do componente.
*
* @return int - Valor do componente.
*/
public int getValor() {
return this.valor;
}
/**
* Interface para callback do momento em que o valor é alterado.
*/
public interface OnValorChanged {
void onValorChanged(int valorAntigo, int valorNovo);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment