Ao desenvolver a interface de registro de um aplicativo de controle financeiro, exibir campos de texto estáticos para observações e horários é insuficiente. Para proporcionar uma experiência adequada, é necessário permitir que os usuários insiram notas personalizadas e selecionem a data e a hora exatas da transação. Isso pode ser alcançado implementando eventos de clique que acionam diálogos personalizados.
Diálogo para Inserção de Observações
Primeiramente, criaremos o layout para o diálogo de notas. Utilizaremos um arquivo XML dedicado para definir a interface, contendo um campo de texto e botões de ação.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@android:color/white">
<TextView
android:id="@+id/tv_note_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Adicionar Observação"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@android:color/black"/>
<EditText
android:id="@+id/et_note_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv_note_title"
android:layout_marginTop="12dp"
android:layout_marginBottom="24dp"
android:hint="Digite sua nota aqui..."
android:background="@android:color/transparent">
<requestFocus/>
</EditText>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_below="@id/et_note_input"
android:gravity="end">
<Button
android:id="@+id/btn_note_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancelar"
android:background="?android:attr/selectableItemBackground"
android:textColor="@android:color/darker_gray"/>
<Button
android:id="@+id/btn_note_confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Confirmar"
android:background="?android:attr/selectableItemBackground"
android:textColor="@android:color/holo_blue_dark"/>
</LinearLayout>
</RelativeLayout>
Em seguida, implemantamos a classe Java que geerncia este diálogo. Para garantir que o teclado virtual apareça automaticamente ao abrir o diálogo, aplicamos um pequeno atraso usando postDelayed, o que evita falhas de renderização do sistema.
public class NoteInputDialog extends Dialog implements View.OnClickListener {
private EditText etNote;
private Button btnCancel;
private Button btnConfirm;
private NoteConfirmCallback callback;
public interface NoteConfirmCallback {
void onConfirm(String noteText);
}
public void setCallback(NoteConfirmCallback callback) {
this.callback = callback;
}
public NoteInputDialog(@NonNull Context context) {
super(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_note_dialog);
etNote = findViewById(R.id.et_note_input);
btnCancel = findViewById(R.id.btn_note_cancel);
btnConfirm = findViewById(R.id.btn_note_confirm);
btnCancel.setOnClickListener(this);
btnConfirm.setOnClickListener(this);
adjustWindowSize();
}
private void adjustWindowSize() {
Window window = getWindow();
if (window != null) {
WindowManager.LayoutParams params = window.getAttributes();
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.gravity = Gravity.BOTTOM;
window.setBackgroundDrawableResource(android.R.color.transparent);
window.setAttributes(params);
// Aciona o teclado virtual com um leve atraso
etNote.postDelayed(() -> {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(etNote, InputMethodManager.SHOW_IMPLICIT);
}
}, 150);
}
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_note_cancel) {
dismiss();
} else if (id == R.id.btn_note_confirm) {
if (callback != null) {
callback.onConfirm(etNote.getText().toString().trim());
}
dismiss();
}
}
}
Diálogo para Seleção de Data e Hora
Para o horário, precisamos de um componente que permita escolher a data e ajustar as horas e minutos manualmente. O layout abaixo combina um DatePicker com campos de entrada numérica.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@android:color/white">
<DatePicker
android:id="@+id/dp_date_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:datePickerMode="spinner"
android:calendarViewShown="false"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Defina o horário:"
android:layout_marginTop="12dp"
android:textSize="16sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginTop="8dp">
<EditText
android:id="@+id/et_hour_input"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="2"
android:gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"/>
<EditText
android:id="@+id/et_minute_input"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="2"
android:gravity="center"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="end">
<Button
android:id="@+id/btn_time_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancelar"
android:background="?android:attr/selectableItemBackground"/>
<Button
android:id="@+id/btn_time_confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Confirmar"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
</LinearLayout>
A classe correspondente processa as entradas, valida os limites de horas e minutos e formata a string final. Utilizamos String.format para simplificar a formatação de números com dois dígitos, eliminando a necessidade de múltiplas verificações condicionais.
public class DateTimeSelectionDialog extends Dialog implements View.OnClickListener {
private EditText etHour;
private EditText etMinute;
private DatePicker datePicker;
private Button btnConfirm;
private Button btnCancel;
private DateTimeConfirmCallback callback;
public interface DateTimeConfirmCallback {
void onConfirm(String formattedTime, int year, int month, int day);
}
public void setCallback(DateTimeConfirmCallback callback) {
this.callback = callback;
}
public DateTimeSelectionDialog(@NonNull Context context) {
super(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_datetime_dialog);
etHour = findViewById(R.id.et_hour_input);
etMinute = findViewById(R.id.et_minute_input);
datePicker = findViewById(R.id.dp_date_selector);
btnCancel = findViewById(R.id.btn_time_cancel);
btnConfirm = findViewById(R.id.btn_time_confirm);
btnCancel.setOnClickListener(this);
btnConfirm.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_time_cancel) {
dismiss();
} else if (id == R.id.btn_time_confirm) {
int year = datePicker.getYear();
int month = datePicker.getMonth() + 1;
int day = datePicker.getDayOfMonth();
int hour = parseTimeComponent(etHour.getText().toString(), 23);
int minute = parseTimeComponent(etMinute.getText().toString(), 59);
String formattedTime = String.format("%04d-%02d-%02d %02d:%02d", year, month, day, hour, minute);
if (callback != null) {
callback.onConfirm(formattedTime, year, month, day);
}
dismiss();
}
}
private int parseTimeComponent(String input, int maxValue) {
if (input == null || input.trim().isEmpty()) {
return 0;
}
try {
int value = Integer.parseInt(input.trim());
return Math.max(0, Math.min(value, maxValue));
} catch (NumberFormatException e) {
return 0;
}
}
}
Integração na Interface de Registro
Por fim, conectamos os diálogos aos elementos de interface da tela de registro. Ao detectar o clique nos campos de texto, instanciamos os diálogos e atualizamos os modelos de dados e a UI após a confirmação do usuário.
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.tv_record_time) {
displayDateTimeDialog();
} else if (id == R.id.tv_record_note) {
displayNoteDialog();
}
}
private void displayNoteDialog() {
NoteInputDialog dialog = new NoteInputDialog(requireContext());
dialog.setCallback(noteText -> {
if (noteText != null && !noteText.isEmpty()) {
tvNote.setText(noteText);
// Atualizar o objeto de modelo de dados (Bean) com a nova nota
currentRecord.setNote(noteText);
}
});
dialog.show();
}
private void displayDateTimeDialog() {
DateTimeSelectionDialog dialog = new DateTimeSelectionDialog(requireContext());
dialog.setCallback((formattedTime, year, month, day) -> {
tvTime.setText(formattedTime);
// Atualizar o objeto de modelo de dados (Bean) com a nova data e hora
currentRecord.setYear(year);
currentRecord.setMonth(month);
currentRecord.setDay(day);
currentRecord.setFormattedTime(formattedTime);
});
dialog.show();
}
Com a interface de entrada dinâmica totalmente funcional, os dados coletados estão prontos para serem processados. A próxima etapa do desenvolvimento envolve a configuração da camada de persistência para salvar essas informações no banco de dados local.