Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Kiểm tra trạng thái hiển thị bàn phím ảo Android

Android không cung cấp API trực tiếp để kiểm tra keyboard đang hiển thị hay không. Cách phổ biến là so sánh chiều cao vùng hiển thị (visible display frame) với chiều cao màn hình thực tế.

Nguyên lý

Khi keyboard hiện lên, nó che khuất một phần màn hình. Vùng hiển thị của app (getWindowVisibleDisplayFrame) sẽ giảm chiều cao. Nếu sự chênh lệch > ngưỡng (thường 15-25% chiều cao màn hình) → Keyboard đang hiển thị.

Triển khai

Layout activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="https://schemas.android.com/apk/res/android"
    android:id="@+id/rootView"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/etInput"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="Nhập text để hiện keyboard"
        android:imeOptions="actionDone"
        android:inputType="text"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_margin="16dp" />

    <Button
        android:id="@+id/btnHideKeyboard"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Ẩn bàn phím"
        app:layout_constraintTop_toBottomOf="@id/etInput"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp" />

    <TextView
        android:id="@+id/tvStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Trạng thái: Đang kiểm tra..."
        android:textSize="16sp"
        app:layout_constraintTop_toBottomOf="@id/btnHideKeyboard"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_margin="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java

import android.graphics.Rect;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private View rootView;
    private EditText etInput;
    private TextView tvStatus;
    private boolean isKeyboardVisible = false;

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

        rootView = findViewById(R.id.rootView);
        etInput = findViewById(R.id.etInput);
        tvStatus = findViewById(R.id.tvStatus);
        Button btnHide = findViewById(R.id.btnHideKeyboard);

        // 1. Listener thay đổi layout toàn cục (keyboard show/hide)
        rootView.getViewTreeObserver().addOnGlobalLayoutListener(this::onGlobalLayout);

        // 2. Nút ẩn bàn phím
        btnHide.setOnClickListener(v -> hideKeyboard());

        // 3. Focus EditText để hiện keyboard khi start (tùy chọn)
        etInput.requestFocus();
        // showKeyboard(etInput); // Gọi nếu cần auto-show
    }

    private void onGlobalLayout() {
        Rect r = new Rect();
        // Lấy visible display frame (khu vực không bị che bởi system bars/keyboard)
        rootView.getWindowVisibleDisplayFrame(r);
        
        int screenHeight = rootView.getRootView().getHeight();
        int visibleHeight = r.height(); // r.bottom - r.top
        int keyboardHeight = screenHeight - visibleHeight;

        // Ngưỡng: keyboard chiếm > 15% màn hình
        boolean keyboardVisible = keyboardHeight > screenHeight * 0.15;

        if (keyboardVisible != isKeyboardVisible) {
            isKeyboardVisible = keyboardVisible;
            runOnUiThread(() -> updateStatus(keyboardVisible, keyboardHeight, screenHeight));
        }
    }

    private void updateStatus(boolean visible, int kbHeight, int screenHeight) {
        String status = visible ? "ĐANG HIỂN THỊ" : "ĐÃ ẨN";
        tvStatus.setText(String.format("Trạng thái: %s\nChiều cao keyboard: %dpx\nMàn hình: %dpx", 
            status, kbHeight, screenHeight));
        tvStatus.setTextColor(visible ? 0xFF4CAF50 : 0xFFF44336);
    }

    private void hideKeyboard() {
        InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
        if (imm != null && getCurrentFocus() != null) {
            imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Remove listener để tránh leak
        rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this::onGlobalLayout);
    }
}

Cách khác: Extension Function (Kotlin)

// utils/KeyboardUtils.kt
fun View.isKeyboardVisible(): Boolean {
    val rect = Rect()
    getWindowVisibleDisplayFrame(rect)
    val screenHeight = rootView.height
    val keypadHeight = screenHeight - rect.bottom
    return keypadHeight > screenHeight * 0.15
}

// Usage
binding.root.doOnNextLayout { 
    if (it.isKeyboardVisible()) { /* ... */ }
}

// Hoặc observe
fun Activity.observeKeyboardVisibility(listener: (Boolean) -> Unit) {
    val rootView = findViewById(android.R.id.content)
    rootView.viewTreeObserver.addOnGlobalLayoutListener {
        val visible = rootView.isKeyboardVisible()
        listener(visible)
    }
}

Xử lý trong Fragment

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment, container, false);
    
    view.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
        Rect r = new Rect();
        view.getWindowVisibleDisplayFrame(r);
        int screenHeight = view.getRootView().getHeight();
        int kbHeight = screenHeight - r.bottom;
        boolean visible = kbHeight > screenHeight * 0.15;
        // Cập nhật UI fragment
    });
    return view;
}

Lưu ý quan trọng

  • Không tin cậy 100%: Có false positive khi navigation bar ẩn/hiện, split screen, multi-window
  • Threshold: 0.15 (15%) là kinh nghiệm, có thể điều chỉnh 0.1-0.25 tùy device
  • Listener gọi nhiều lần: Cần debounce hoặc check thay đổi state như ví dụ
  • Remove listener: Bắt buộc trong onDestroy/onDetach tránh memory leak
  • Không hoạt động nếu Activity không phải fullscreen hoặc có system window overlay

Thư viện hỗ trợ

Chạy ứng dụng

App khởi động → EditText focus → Keyboard hiện → TextView hiển thị "ĐANG HIỂN THỊ" + chiều cao keyboard. Nhấn "Ẩn bàn phím" hoặc back → Keyboard ẩn → TextView hiển thị "ĐÃ ẨN".