Evolution and Practice of Virtual Keyboard Show/Hide Event Capture in Android

Nov 26, 2025 · Programming · 11 views · 7.8

Keywords: Android Virtual Keyboard | WindowInsetsAnimation | Keyboard Event Detection | ViewTreeObserver | Adaptive Layout

Abstract: This article provides an in-depth exploration of virtual keyboard state detection in Android systems, focusing on the WindowInsetsAnimation API introduced in Android 11 and its application in keyboard state management. By comparing traditional solutions like ViewTreeObserver listening and onMeasure overriding, it details the implementation principles, applicable scenarios, and limitations of each method. The article offers complete code examples and best practice recommendations to help developers choose the most appropriate keyboard event handling solution based on target API levels.

Technical Background and Evolution

In Android application development, accurately detecting the show/hide state of virtual keyboards is crucial for implementing adaptive interface layouts. For a long time, due to the lack of official standard API support, developers had to rely on various indirect methods to achieve this functionality. With the continuous evolution of the Android system, this issue was fundamentally resolved in Android 11.

Android 11 Standard Solution

Android 11 introduced the WindowInsetsAnimation.Callback API, providing an official standard solution for keyboard state detection. This API accurately identifies the display state of the Input Method Editor (IME) by monitoring window inset animation changes.

The core implementation code is as follows:

view.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback {
    override fun onEnd(animation: WindowInsetsAnimation) {
        super.onEnd(animation)
        val showingKeyboard = view.rootWindowInsets.isVisible(WindowInsets.Type.ime())
        // Update interface layout based on keyboard state
        updateLayoutBasedOnKeyboardState(showingKeyboard)
    }
})

This method offers the following advantages: high accuracy through direct IME visibility detection; timely response triggered immediately after animation completion; good compatibility designed specifically for modern Android systems.

Analysis of Traditional Solutions

Before Android 11, developers needed to employ various indirect methods to detect keyboard state. The most common approach was solutions based on ViewTreeObserver.OnGlobalLayoutListener.

The basic implementation principle involves monitoring global layout changes and calculating view height differences to determine keyboard state:

private void setupKeyboardDetection() {
    final View rootView = getWindow().getDecorView();
    rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        private final int keyboardHeightThreshold = 200;
        private boolean keyboardVisible = false;
        
        @Override
        public void onGlobalLayout() {
            Rect visibleArea = new Rect();
            rootView.getWindowVisibleDisplayFrame(visibleArea);
            int heightDifference = rootView.getHeight() - visibleArea.height();
            
            boolean currentlyVisible = heightDifference > keyboardHeightThreshold;
            if (currentlyVisible != keyboardVisible) {
                keyboardVisible = currentlyVisible;
                onKeyboardVisibilityChanged(keyboardVisible);
            }
        }
    });
}

Although widely used, this method has significant limitations: height thresholds require empirical setting and may be inaccurate across different devices; inability to distinguish between keyboards and other factors causing layout changes; unstable performance in full-screen modes.

Custom Layout Solutions

Another common approach involves detecting size changes by overriding the onMeasure method in custom layouts:

public class KeyboardAwareLayout extends LinearLayout {
    private KeyboardVisibilityListener listener;
    
    public KeyboardAwareLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int proposedHeight = MeasureSpec.getSize(heightMeasureSpec);
        int currentHeight = getHeight();
        
        if (currentHeight > proposedHeight && listener != null) {
            listener.onKeyboardShown();
        } else if (currentHeight < proposedHeight && listener != null) {
            listener.onKeyboardHidden();
        }
        
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    
    public void setKeyboardVisibilityListener(KeyboardVisibilityListener listener) {
        this.listener = listener;
    }
    
    public interface KeyboardVisibilityListener {
        void onKeyboardShown();
        void onKeyboardHidden();
    }
}

This solution requires setting android:windowSoftInputMode to adjustResize to function properly and is ineffective in adjustPan mode.

Configuration Change Handling

In early Android versions, some developers attempted to use onConfigurationChanged for keyboard state detection:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    
    if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
        // Physical keyboard visible
    } else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
        // Physical keyboard hidden
    }
}

It is particularly important to note that this method only applies to physical keyboard detection and is completely ineffective for virtual keyboards, which is a common misunderstanding.

Special Considerations for Full-Screen Mode

In full-screen Activities, keyboard detection faces additional challenges. Since full-screen mode typically disables standard layout adjustment mechanisms, traditional height comparison methods may fail. In such cases, consider the following alternatives: using ViewTreeObserver listening combined with more precise height calculations, or accepting functional limitations in certain scenarios.

Best Practice Recommendations

Based on different Android versions and device characteristics, a layered strategy is recommended: prioritize WindowInsetsAnimation.Callback for Android 11 and above devices; fall back to ViewTreeObserver solutions for older devices. Additionally, it is advised to: set reasonable height thresholds (typically 100-200dp); handle edge cases like floating keyboards; add appropriate animation transitions during layout changes.

Code Implementation Example

A complete compatibility solution should include version detection and fallback mechanisms:

public class KeyboardDetector {
    public interface KeyboardStateListener {
        void onKeyboardStateChanged(boolean visible);
    }
    
    public static void setupKeyboardDetection(Activity activity, KeyboardStateListener listener) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            setupModernDetection(activity, listener);
        } else {
            setupLegacyDetection(activity, listener);
        }
    }
    
    @RequiresApi(api = Build.VERSION_CODES.R)
    private static void setupModernDetection(Activity activity, KeyboardStateListener listener) {
        View decorView = activity.getWindow().getDecorView();
        decorView.setWindowInsetsAnimationCallback(new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
            @Override
            public void onEnd(WindowInsetsAnimation animation) {
                boolean keyboardVisible = decorView.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
                listener.onKeyboardStateChanged(keyboardVisible);
            }
            
            @Override
            public WindowInsets onProgress(WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) {
                return insets;
            }
        });
    }
    
    private static void setupLegacyDetection(Activity activity, KeyboardStateListener listener) {
        final View activityRoot = activity.findViewById(android.R.id.content);
        activityRoot.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            private final int estimatedKeyboardHeight = (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 100, activityRoot.getResources().getDisplayMetrics());
            private boolean lastKeyboardState = false;
            
            @Override
            public void onGlobalLayout() {
                Rect visibleFrame = new Rect();
                activityRoot.getWindowVisibleDisplayFrame(visibleFrame);
                int heightDiff = activityRoot.getRootView().getHeight() - visibleFrame.height();
                
                boolean keyboardVisible = heightDiff > estimatedKeyboardHeight;
                if (keyboardVisible != lastKeyboardState) {
                    lastKeyboardState = keyboardVisible;
                    listener.onKeyboardStateChanged(keyboardVisible);
                }
            }
        });
    }
}

Conclusion and Outlook

Android virtual keyboard state detection has evolved from indirect inference to direct detection. Developers should choose appropriate implementation solutions based on the device distribution of their target user base. With continuous updates to the Android system, more unified and simplified APIs may emerge in the future. The current best practice is to adopt conditional code paths that support advanced features on modern devices while providing reliable fallback solutions for older devices.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.