简体   繁体   English

无法在 Android 9 上隐藏 Android 键盘

[英]Unable to hide Android Keyboard on Android 9

Creating an app that on tap of an webview input field, has to do an action.创建一个应用程序,点击 webview 输入字段,必须执行一个操作。 Catching and starting the selected action works fine, but due to it being started by clicking an input field, the keyboard is requested.捕获并启动选定的操作工作正常,但由于它是通过单击输入字段启动的,因此需要键盘。 On Android < Version 9, my currently code works just fine to hide the keyboard, but on Android Version 9, it doesn't.在 Android < 版本 9 上,我当前的代码可以很好地隐藏键盘,但在 Android 版本 9 上,它没有。

I have tried all manor or combination of what was deemed the top answer on this post , but none have worked for my app on Android 9我已经尝试了这篇文章中被认为是最佳答案的所有庄园或组合,但没有一个适用于我在 Android 9 上的应用程序

Below is a bit of my code from my MainActivity, where the instance of my keyboard service implementation is created.下面是我的 MainActivity 中的一些代码,其中创建了我的键盘服务实现的实例。 the MainActivity code is then followed by my Keyboard service implementation made for android. MainActivity 代码之后是我为 android 制作的键盘服务实现。

[Activity(Label = "Dental.App", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, 
        ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, WindowSoftInputMode = SoftInput.StateAlwaysHidden) ]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
protected override void OnCreate(Bundle savedInstanceState)
        {
            ...
            DependencyService.Get<IServiceCollection>().SetKeyboardService(new KeyboardService(this, GetInputMethodManager()));            
            ...
        }

public InputMethodManager GetInputMethodManager()
        {
            return (InputMethodManager)GetSystemService(Context.InputMethodService);
        }
    }
public class KeyboardService : IKeyboardService
    {
        private InputMethodManager inputMethodManager;
        private readonly object mainActivity;
        public KeyboardService(object activity, InputMethodManager methodManager)
        {
            mainActivity = activity;
            inputMethodManager = methodManager;
        }
        public bool IsKeyboardShown => inputMethodManager.IsAcceptingText;

        public void HideKeyboard()
        {
            if (inputMethodManager == null || !(mainActivity is Activity activity)) return;

            Logging.Log(LogType.Information, $"Attempting to Hide Keyboard via 1st method...");

            //var view = activity.CurrentFocus;
            var view = activity.FindViewById(Android.Resource.Id.Content).RootView;
            if (view == null) Logging.Log(LogType.Warning, $"Failed to get View from Activity...");

            var token = view?.WindowToken;
            if (token == null) Logging.Log(LogType.Warning, $"Failed to get Token from View...");

            var success = inputMethodManager.HideSoftInputFromWindow(token, HideSoftInputFlags.None);
            Logging.Log(LogType.Information,
                $"{nameof(inputMethodManager.HideSoftInputFromWindow)} returned => {success}");

            if(success) view?.ClearFocus();
            if (!IsKeyboardShown)
            {
                view?.ClearFocus();
                return;
            }

            Logging.Log(LogType.Warning,
                $"Failed to Hide Keyboard via {nameof(inputMethodManager.HideSoftInputFromWindow)}...");
            HideKeyboardAttemptTwo(activity);
        }

        private void HideKeyboardAttemptTwo(Activity activity)
        {
            Logging.Log(LogType.Information, $"Attempting to Hide Keyboard via 2nd method...");

            //var view = activity.CurrentFocus;
            var view = activity.FindViewById(Android.Resource.Id.Content).RootView;
            if (view == null) Logging.Log(LogType.Warning, $"Failed to get View from Activity...");

            var token = view?.WindowToken;
            if (token == null) Logging.Log(LogType.Warning, $"Failed to get Token from View...");

            inputMethodManager.ToggleSoftInputFromWindow(token, ShowSoftInputFlags.None, HideSoftInputFlags.None);

            if (!IsKeyboardShown)
            {
                view?.ClearFocus();
                return;
            }

            Logging.Log(LogType.Warning, $"Failed to Hide Keyboard via {nameof(inputMethodManager.ToggleSoftInputFromWindow)}...");
        }

        public void ReInitializeInputMethod()
        {
            inputMethodManager = InputMethodManager.FromContext((Context) mainActivity);
        }

None of the null check are coming back true, ie nothing is null.没有一个空检查返回真,即没有任何东西是空的。 The variable called success in the method HideKeyboard is returning false in 99% of all cases where it is called on a android version 9. In the 1% of the cases where it is true, the keyboard is still open.方法 HideKeyboard 中名为 success 的变量在 99% 的 Android 版本 9 上调用它的情况下返回 false。在 1% 的情况下为 true,键盘仍处于打开状态。 If the keyboard is still shown at the end of HideKeyboard, then the code attempts to close the keyboard via toggling it in the method HideKeyboardAttemptTwo.如果键盘仍然显示在 HideKeyboard 的末尾,则代码尝试通过在 HideKeyboardAttemptTwo 方法中切换它来关闭键盘。 Doing it either of theses ways on Android 9 does not work, however running the exact same code on an Android 7.1 works just fine.在 Android 9 上执行这些方法中的任何一种都不起作用,但是在 Android 7.1 上运行完全相同的代码可以正常工作。

I'm not entirely sure that i have implemented the use of ToggleSoftInputFromWindow correctly, it is intended to only be able to run when the keyboard is open, ie always used to hide the keyboard.我不完全确定我是否正确实现了 ToggleSoftInputFromWindow 的使用,它旨在仅在键盘打开时才能运行,即始终用于隐藏键盘。

To reiterate my question: How do it successfully hide the keyboard on an Android 9.重申我的问题:如何在 Android 9 上成功隐藏键盘。

If any additional information is needed, just ask, and i will attempt to find and supply it.如果需要任何其他信息,请询问,我会尝试找到并提供它。

I uses this for my app, give it a try我将它用于我的应用程序,试一试

Interface in main project主项目中的接口

namespace *.Services.Interfaces
{
    public interface IForceKeyboardDismissalService
    {
        void DismissKeyboard();
    }
}

Phone specific code电话特定代码

using Plugin.CurrentActivity;  //Nugget used to get activity

[assembly: Xamarin.Forms.Dependency(typeof(AndroidForceKeyboardDismissalService))]
namespace *.Droid.PhoneSpecific
{
    public class AndroidForceKeyboardDismissalService : IForceKeyboardDismissalService
    {
        public void DismissKeyboard()
        {
            var imm = InputMethodManager.FromContext(CrossCurrentActivity.Current.Activity.ApplicationContext);
            imm?.HideSoftInputFromWindow(CrossCurrentActivity.Current.Activity.Window.DecorView.WindowToken, HideSoftInputFlags.NotAlways);

            var currentFocus = CrossCurrentActivity.Current.Activity.CurrentFocus;
            if (currentFocus != null && currentFocus is EditText)
                currentFocus.ClearFocus();
        }
    }
}

Usage用法

DependencyService.Get<IForceKeyboardDismissalService>().DismissKeyboard();

Let me know if its working for you.让我知道它是否适合你。

To fix my problem i injected some JavaScript into the Webview, wherein i unfocused the input field, that was clicked.为了解决我的问题,我将一些 JavaScript 注入到 Webview 中,在其中我没有聚焦输入字段,然后单击该字段。

On my Webview class i created a method that, given the string id of an element, would toggle whether or not that element is focused.在我的 Webview 类中,我创建了一个方法,如果给定元素的字符串 id,它将切换该元素是否获得焦点。 As a second input, a boolean can be supplied, but defaulted to True, to indicate whether or not, you only want to unfocus the element.作为第二个输入,可以提供一个布尔值,但默认为 True,以指示您是否只想取消对元素的关注。

public class AdvancedWebView : HybridWebView
{
...
public void ToggleElementFocus(string elementId, bool onlyUnFocus = true)
        {
            var js = GetJsInvertFocus(elementId, onlyUnFocus);

            InjectJavaScript(js);

            // Logging.Logging.Log(LogType.Information, $"Injected Javascript => {js}");
        }
...
private string GetJsInvertFocus(string elementId, bool onlyUnFocus)
        {
            var builder = new StringBuilder();

            builder.Append($"if (document.getElementById('{elementId}'))");
            builder.Append("{");
            builder.Append($"var element = document.getElementById('{elementId}');");
            builder.Append($"if (element === document.activeElement)");
            builder.Append("{");
            builder.Append($"element.blur();");
            builder.Append("}");
            builder.Append($"else if({onlyUnFocus} == False)");
            builder.Append("{");
            builder.Append($"element.focus();");
            builder.Append("}");
            builder.Append("}");

            return builder.ToString();
        }
...
}

I'm extending the HybridWebview from XLabs, as it already has the functionality to inject JavaScript into the Webview.我正在从 XLabs 扩展 HybridWebview,因为它已经具有将 JavaScript 注入 Webview 的功能。 So that is where i get the InjectJavaScript method from.这就是我从中获取 InjectJavaScript 方法的地方。

On my page in my app, with the Webview, i then have a method that runs, when the element is clicked.在我的应用程序页面上,使用 Webview,然后当单击元素时,我有一个运行的方法。 To get a click event when clicking the Webview look at this link .要在单击 Webview 时获得单击事件,请查看此链接 During the method i figure out what the element id is from the event arguments, and then use this id to inject the JavaScript shown above, to unfocus the element, causing the keyboard to not appear at all.在这个方法中,我从事件参数中找出元素 id 是什么,然后使用这个 id 注入上面显示的 JavaScript,使元素失去焦点,导致键盘根本不出现。 Below my OnClicked method can be seen.在我的 OnClicked 方法下方可以看到。

public partial class DentalWebPage : AdvancedTabbedPage
{
...
 private void DentalWebView_OnClicked(object sender, ClickEvent e)
        {
            try
            {
                if (LogUserPosition(sender, e)) return;

                SwapToScanningTap();
            }
            catch (Exception ex)
            {
                Logging.Log(LogType.Exception,
                    ex.GetType().Namespace == typeof(BaseException).Namespace
                        ? $"{ex.GetType()} => {ex}"
                        : $"{ex.GetType()} => {ex.Message}; Stacktrace => {ex.StackTrace}");
            }
        }

private bool LogUserPosition(object sender, ClickEvent e)
        {
            if (Config.DebugMode) Logging.Log(LogType.Debug, $"WebView was clicked...");

            if (Config.DebugMode) Logging.Log(LogType.Debug, $"Element that was clicked is the following one => {e.Element}");

            var success = Enum.TryParse(e.Element.Split(' ')[1].Split('=')[1], out clickedInputId);

            if (!success && !(clickedInputId == InputId.MainContent_TextBoxInputStr ||
                              clickedInputId == InputId.MainContent_TextBoxScanOrder ||
                              clickedInputId == InputId.MainContent_TextBoxSelectProd ||
                              clickedInputId == InputId.MainContent_TextBoxStockReturn))
                return true;

            if (Config.DebugMode && webPageEnding == WebsiteControllers.Stock.ToString().ToLowerInvariant())
                Logging.Log(LogType.Debug, $"WebView was clicked while on the stock page...");

            return false;
        }

private void SwapToScanningTap()
        {
            PerformOnMainThread(() =>
            {
                CurrentPage = Children[1];

                ScanningToggle.IsToggled = true;

                try
                {
                    var isKeyboardShown = services.KeyboardService.IsKeyboardShown;
                    if (Config.DebugMode) Logging.Log(LogType.Debug, $"IsKeyboardShown returns => {isKeyboardShown}");

                    DentalWebView.ToggleElementFocus(clickedInputId.ToString());
                }
                catch (ObjectDisposedException)
                {
                    if (DisposedReattempt) throw;

                    if (Config.DebugMode)
                        Logging.Log(LogType.Debug,
                            $"Input Method has been Disposed; Attempting to reinitialize it and rerun the {nameof(SwapToScanningTap)} method ones again");

                    DisposedReattempt = true;
                    services.KeyboardService.ReInitializeInputMethod();
                    SwapToScanningTap();
                }
            });
        }
...
private void PerformOnMainThread(Action action)
        {
            try
            {
                Device.BeginInvokeOnMainThread(action);
            }
            catch (Exception ex)
            {
                Logging.Log(LogType.Exception,
                    ex.GetType().Namespace == typeof(BaseException).Namespace
                        ? $"{ex.GetType()} => {ex}"
                        : $"{ex.GetType()} => {ex.Message}; Stacktrace => {ex.StackTrace}");
            }
        }
}

If you wish to get a understanding of the format of the string contained in e.Element, then go and look at the link supplied earlier.如果您想了解 e.Element 中包含的字符串的格式,请查看之前提供的链接。

Fell free to ask further questions, in case i missed something.随意提出进一步的问题,以防我错过了什么。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM