Aprimoramento da Tela de Registros com Diálogos Personalizados no Android

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.

Tags: android java XML custom-dialog DatePicker

Publicado em 7-2 04:05