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ợ
- KeyboardVisibilityEvent - Event-based, dễ dùng
- Android-Keyboard-Visibility-Event
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".