airsdk / Adobe-Runtime-Support

Report, track and discuss issues in Adobe AIR. Monitored by Adobe - and HARMAN - and maintained by the AIR community.
206 stars 11 forks source link

[Android] [Bug] Stage is not properly shifted to align Textfield input with keyboard #443

Open neuronix opened 4 years ago

neuronix commented 4 years ago

When the soft keyboard is shown after focusing on a flash.display.Textfield input, the stage does not align properly on many devices, mostly Android 10.

Here are two devices, same build using and SDK AIR 33.1.1.217 and android:hardwareaccelerated="true" in xml:

Huawei device, Android 10 working fine 1

Samsung Galaxy S10, Android 10, not working 2

neuronix commented 4 years ago

On a side note, this bug has plagued AIR for years now, we used to have some hacks like delaying the focus or moving the textfield every key stroke (yeah... developer desperation...) but they no longer work and we actually had to remove them for the soft keyboard to actually show since last SDK (.217).

So we no longer have a workaround other than a proper fix in AIR.

rewb0rn commented 4 years ago

We are using a custom solution for this and I am not aware of any Android 10 related problems. Feel free to give it a try. First we have the base class that works on pure AS3, and based on that we have a inherited class that uses the Distriqt Application ANE that delivers more accurate results for the softkeyboard size. You need to instantiate one of them (preferably the SoftKeyboardPannerAne class) and keep a reference of it during the lifetime of your application.

Base class

    public class SoftKeyboardPanner
    {
        protected var _stage:Stage;
        private var _mainView:Sprite;

        private var _panning:Number = 0;
        private var _currentTextfield:TextField;

        public function SoftKeyboardPanner(mainView:Sprite)
        {
            _mainView = mainView;
            _stage = _mainView.stage;

            addSystemHandlers();

        }

        public function dispose():void
        {
            releaseCurrentTextfield();

            _stage.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
            removeSystemHandlers();

            _stage = null;
            _mainView = null;
        }
        // **************************************
        // GETTERS AND SETTERS
        // **************************************
        protected function get keyboardHeight():Number
        {
            return _stage.softKeyboardRect.height;
        }
        // **************************************
        // PUBLIC FUNCTIONS
        // **************************************

        // **************************************
        // PROTECTED FUNCTIONS
        // **************************************

        protected function addSystemHandlers():void 
        {
            _stage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, keyboardActivateHandler);
            _stage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, keyboardDeactivateHandler);
            _stage.addEventListener(FocusEvent.FOCUS_IN, focusChangedHandler);
        }

        protected function removeSystemHandlers():void 
        {

            _stage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, keyboardActivateHandler);
            _stage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, keyboardDeactivateHandler);
            _stage.removeEventListener(FocusEvent.FOCUS_IN, focusChangedHandler);
        }

        protected function setPanning(value:Number):void
        {
            _panning = value;

            //you could also use a Tween here:
            _mainView.y = -_panning;
        }
        // **************************************
        // PRIVATE FUNCTIONS
        // **************************************
        private function releaseCurrentTextfield():void 
        {
            if (_currentTextfield)
            {
                _currentTextfield.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, keyboardDeactivateHandler);
                _currentTextfield.removeEventListener(Event.REMOVED_FROM_STAGE, keyboardDeactivateHandler);
                _currentTextfield = null;
            }
        }

        private function checkPanning():void 
        {

            var textfield:Rectangle = _currentTextfield.getRect(_stage);
            var kh:Number = keyboardHeight;

            //Keyboard ANE will report correct keyboard height with a delay if renderMode is direct
            if (kh == 0)
            {
                _stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
                return;
            }

            //make sure we receive keyboard resize updates, e.g. when word suggestions pop in
            _stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);

            var keyboardStart:Number = _stage.stageHeight - kh;
            var textfieldEnd:Number = textfield.y + textfield.height + _panning;

            if (keyboardStart < textfieldEnd)
            {
                setPanning(textfieldEnd - keyboardStart);

                _currentTextfield.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, keyboardDeactivateHandler);
                _currentTextfield.addEventListener(Event.REMOVED_FROM_STAGE, keyboardDeactivateHandler);
            }
        }

        // **************************************
        // EVENT LISTENERS
        // **************************************

        protected function keyboardActivateHandler(e:SoftKeyboardEvent):void 
        {
            _currentTextfield = e.target as TextField;
            if (!_currentTextfield) return;

            checkPanning();
        }

        protected function keyboardDeactivateHandler(e:Event = null):void 
        {       
            _stage.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
            if (_panning == 0) return;

            releaseCurrentTextfield();

            setPanning(0);
        }

        private function enterFrameHandler(e:Event):void 
        {
            checkPanning();
        }

        protected function focusChangedHandler(e:FocusEvent):void 
        {
            //if user clicks another Textfield, Softkeyboard stays open but panning has to be recalculated
            if (_panning != 0)
            {
                keyboardDeactivateHandler();

                if (_stage.focus is TextField && TextField(_stage.focus).type == TextFieldType.INPUT)
                {
                    _currentTextfield = _stage.focus as TextField;

                    checkPanning(); 
                }
            }
        }

    }

ANE Subclass:

    public class SoftKeyboardPannerAne extends SoftKeyboardPanner
    {
        private var _keyboardOpen:Boolean = false;

        public function SoftKeyboardPannerAne(mainView:Sprite)
        {
            super(mainView);

            try
            {
                Application.service.display.setDisplayMode(DisplayMode.IMMERSIVE);
            } catch (e:Error)
            {
                trace(this, e.message);
            }
        }

        // **************************************
        // GETTERS AND SETTERS
        // **************************************
        override protected function get keyboardHeight():Number 
        {
            try
            {
                var n:Number = Application.service.keyboard.height;
                //n = n * 1.15;
                return n;
            } catch (e:Error)
            {
                return super.keyboardHeight;
            }

            return super.keyboardHeight;
        }

        // **************************************
        // PUBLIC FUNCTIONS
        // **************************************

        // **************************************
        // PROTECTED FUNCTIONS
        // **************************************

        protected override function addSystemHandlers():void 
        {
            _stage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, openKeyboardHandler);
            _stage.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, removeKeyboardHandler);
            _stage.addEventListener(FocusEvent.FOCUS_IN, focusChangedHandler);
        }

        protected override function removeSystemHandlers():void 
        {
            _stage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, openKeyboardHandler);
            _stage.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, removeKeyboardHandler);
            _stage.removeEventListener(FocusEvent.FOCUS_IN, focusChangedHandler);
        }

        // **************************************
        // PRIVATE FUNCTIONS
        // **************************************

        // **************************************
        // EVENT LISTENERS
        // **************************************
        private function keyboardActivatingHandler(e:SoftKeyboardEvent):void 
        {

        }

        protected function openKeyboardHandler(e:SoftKeyboardEvent):void 
        {
            if (!_keyboardOpen)
            {
                _keyboardOpen = true;
                //Show soft navigation in soft keyboard
                var visibility:int = Application.service.display.getSystemUiVisibility();
                Application.service.display.setSystemUiVisibility(
                    visibility & ~AndroidSystemUiFlags.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                );
            }

            super.keyboardActivateHandler(e);
        }

        protected function removeKeyboardHandler(e:Event = null):void 
        {
            if (_keyboardOpen)
            {
                _keyboardOpen = false;
                //go back to previous displaymode
                Application.service.display.setDisplayMode( DisplayMode.IMMERSIVE );
            }

            super.keyboardDeactivateHandler(e);
        }
    }

I am not saying this should not be fixed in Air, but maybe this workaround is helpful. Let me know

neuronix commented 4 years ago

Thanks @rewb0rn for the tip, will give it a try!