如何防止键盘覆盖我的 UI 而不是调整它的大小?

Posted

技术标签:

【中文标题】如何防止键盘覆盖我的 UI 而不是调整它的大小?【英文标题】:How do I keep the keyboard from covering my UI instead of resizing it? 【发布时间】:2015-09-19 06:30:50 【问题描述】:

ios 中,当根节点为 ScrollView 时,Xamarin.Forms 会在键盘出现时调整屏幕大小。但是当根节点不是ScrollView 时,键盘会隐藏部分 UI。你如何防止这种情况发生?

【问题讨论】:

【参考方案1】:

解决此问题的方法是使用自定义渲染器,该渲染器侦听键盘出现并在键盘出现时添加填充。

在您的 PCL 项目中,KeyboardResizingAwareContentPage.cs:

using Xamarin.Forms;

public class KeyboardResizingAwareContentPage : ContentPage 
    public bool CancelsTouchesInView = true;

在您的 iOS 项目中,IosKeyboardFixPageRenderer.cs:

using Foundation;
using MyProject.iOS.Renderers;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(KeyboardResizingAwareContentPage), typeof(IosKeyboardFixPageRenderer))]

namespace MyProject.iOS.Renderers 
    public class IosKeyboardFixPageRenderer : PageRenderer 
        NSObject observerHideKeyboard;
        NSObject observerShowKeyboard;

        public override void ViewDidLoad()
        
            base.ViewDidLoad();

            var cp = Element as KeyboardResizingAwareContentPage;
            if (cp != null && !cp.CancelsTouchesInView) 
                foreach (var g in View.GestureRecognizers) 
                    g.CancelsTouchesInView = false;
                
            
        

        public override void ViewWillAppear(bool animated)
        
            base.ViewWillAppear(animated);

            observerHideKeyboard = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, OnKeyboardNotification);
            observerShowKeyboard = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, OnKeyboardNotification);
        

        public override void ViewWillDisappear(bool animated)
        
            base.ViewWillDisappear(animated);

            NSNotificationCenter.DefaultCenter.RemoveObserver(observerHideKeyboard);
            NSNotificationCenter.DefaultCenter.RemoveObserver(observerShowKeyboard);
        

        void OnKeyboardNotification(NSNotification notification)
        
            if (!IsViewLoaded) return;

            var frameBegin = UIKeyboard.FrameBeginFromNotification(notification);
            var frameEnd = UIKeyboard.FrameEndFromNotification(notification);

            var page = Element as ContentPage;
            if (page != null && !(page.Content is ScrollView)) 
                var padding = page.Padding;
                page.Padding = new Thickness(padding.Left, padding.Top, padding.Right, padding.Bottom + frameBegin.Top - frameEnd.Top);
            
        
    

【讨论】:

结果很好,但是如何隐藏键盘呢?因为如果我在键盘外点击不会关闭... @Fran_gg7 使用 ResignFirstResponder()。所以“激活键盘的控件” ResignFirstResponder(); Xamarin Forms 应该在页面上有一个手势识别器,它会自动为您调用 ResignFirstResponder。有时您不想要这种行为,这就是 CancelsTouchesInView 布尔值的用途;将其设置为 false 以取消 Xamarin Forms 的默认行为。 我来自 Xamarin 论坛。我真的很感激你的一段代码。 :) @AnthonyMills 这个解决方案很棒,但是有一个问题。如果其中一个条目被标记为 IsPassword,则页面滚动不规律:)【参考方案2】:

我发现KeyboardOverlap plugin 比 Anthony 的解决方案效果更好。

我就是这样使用它的:

    创建自定义渲染器
public class KeyboardResizingAwareContentPage : ContentPage


    在 iOS 上实现自定义渲染器。这是paulpatarinski代码的重要部分:
[Preserve (AllMembers = true)]
public class KeyboardOverlapRenderer : PageRenderer

    NSObject _keyboardShowObserver;
    NSObject _keyboardHideObserver;
    private bool _pageWasShiftedUp;
    private double _activeViewBottom;
    private bool _isKeyboardShown;

    public static void Init ()
    
        var now = DateTime.Now;
        Debug.WriteLine ("Keyboard Overlap plugin initialized 0", now);
    

    public override void ViewWillAppear (bool animated)
    
        base.ViewWillAppear (animated);

        var page = Element as ContentPage;

        if (page != null) 
            var contentScrollView = page.Content as ScrollView;

            if (contentScrollView != null)
                return;

            RegisterForKeyboardNotifications ();
        
    

    public override void ViewWillDisappear (bool animated)
    
        base.ViewWillDisappear (animated);

        UnregisterForKeyboardNotifications ();
    

    void RegisterForKeyboardNotifications ()
    
        if (_keyboardShowObserver == null)
            _keyboardShowObserver = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillShowNotification, OnKeyboardShow);
        if (_keyboardHideObserver == null)
            _keyboardHideObserver = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillHideNotification, OnKeyboardHide);
    

    void UnregisterForKeyboardNotifications ()
    
        _isKeyboardShown = false;
        if (_keyboardShowObserver != null) 
            NSNotificationCenter.DefaultCenter.RemoveObserver (_keyboardShowObserver);
            _keyboardShowObserver.Dispose ();
            _keyboardShowObserver = null;
        

        if (_keyboardHideObserver != null) 
            NSNotificationCenter.DefaultCenter.RemoveObserver (_keyboardHideObserver);
            _keyboardHideObserver.Dispose ();
            _keyboardHideObserver = null;
        
    

    protected virtual void OnKeyboardShow (NSNotification notification)
    
        if (!IsViewLoaded || _isKeyboardShown)
            return;

        _isKeyboardShown = true;
        var activeView = View.FindFirstResponder ();

        if (activeView == null)
            return;

        var keyboardFrame = UIKeyboard.FrameEndFromNotification (notification);
        var isOverlapping = activeView.IsKeyboardOverlapping (View, keyboardFrame);

        if (!isOverlapping)
            return;

        if (isOverlapping) 
            _activeViewBottom = activeView.GetViewRelativeBottom (View);
            ShiftPageUp (keyboardFrame.Height, _activeViewBottom);
        
    

    private void OnKeyboardHide (NSNotification notification)
    
        if (!IsViewLoaded)
            return;

        _isKeyboardShown = false;
        var keyboardFrame = UIKeyboard.FrameEndFromNotification (notification);

        if (_pageWasShiftedUp) 
            ShiftPageDown (keyboardFrame.Height, _activeViewBottom);
        
    

    private void ShiftPageUp (nfloat keyboardHeight, double activeViewBottom)
    
        var pageFrame = Element.Bounds;

        var newY = pageFrame.Y + CalculateShiftByAmount (pageFrame.Height, keyboardHeight, activeViewBottom);

        Element.LayoutTo (new Rectangle (pageFrame.X, newY,
            pageFrame.Width, pageFrame.Height));

        _pageWasShiftedUp = true;
    

    private void ShiftPageDown (nfloat keyboardHeight, double activeViewBottom)
    
        var pageFrame = Element.Bounds;

        var newY = pageFrame.Y - CalculateShiftByAmount (pageFrame.Height, keyboardHeight, activeViewBottom);

        Element.LayoutTo (new Rectangle (pageFrame.X, newY,
            pageFrame.Width, pageFrame.Height));

        _pageWasShiftedUp = false;
    

    private double CalculateShiftByAmount (double pageHeight, nfloat keyboardHeight, double activeViewBottom)
    
        return (pageHeight - activeViewBottom) - keyboardHeight;
    

还有缺少的扩展:

public static class ViewExtensions

    /// <summary>
    /// Find the first responder in the <paramref name="view"/>'s subview hierarchy
    /// </summary>
    /// <param name="view">
    /// A <see cref="UIView"/>
    /// </param>
    /// <returns>
    /// A <see cref="UIView"/> that is the first responder or null if there is no first responder
    /// </returns>
    public static UIView FindFirstResponder (this UIView view)
    
        if (view.IsFirstResponder) 
            return view;
        
        foreach (UIView subView in view.Subviews) 
            var firstResponder = subView.FindFirstResponder ();
            if (firstResponder != null)
                return firstResponder;
        
        return null;
    

    /// <summary>
    /// Returns the new view Bottom (Y + Height) coordinates relative to the rootView
    /// </summary>
    /// <returns>The view relative bottom.</returns>
    /// <param name="view">View.</param>
    /// <param name="rootView">Root view.</param>
    public static double GetViewRelativeBottom (this UIView view, UIView rootView)
    
        var viewRelativeCoordinates = rootView.ConvertPointFromView (view.Frame.Location, view);
        var activeViewRoundedY = Math.Round (viewRelativeCoordinates.Y, 2);

        return activeViewRoundedY + view.Frame.Height;
    

    /// <summary>
    /// Determines if the UIView is overlapped by the keyboard
    /// </summary>
    /// <returns><c>true</c> if is keyboard overlapping the specified activeView rootView keyboardFrame; otherwise, <c>false</c>.</returns>
    /// <param name="activeView">Active view.</param>
    /// <param name="rootView">Root view.</param>
    /// <param name="keyboardFrame">Keyboard frame.</param>
    public static bool IsKeyboardOverlapping (this UIView activeView, UIView rootView, CGRect keyboardFrame)
    
        var activeViewBottom = activeView.GetViewRelativeBottom (rootView);
        var pageHeight = rootView.Frame.Height;
        var keyboardHeight = keyboardFrame.Height;

        var isOverlapping = activeViewBottom >= (pageHeight - keyboardHeight);

        return isOverlapping;
    

    使用自定义页面渲染器
public partial class LoginPage : KeyboardResizingAwareContentPage

    public LoginPage()
    
        // your content
        // note: you have to use base.Navigation.PushAsync(), base.DisplayAlert(), ...
    

<?xml version="1.0" encoding="utf-8" ?>
<renderer:KeyboardResizingAwareContentPage xmlns="http://xamarin.com/schemas/2014/forms"
     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
     x:Class="App.Pages.LoginPage"
     xmlns:renderer="clr-namespace:App.CustomRenderers;assembly=App">
    <!-- your content -->
</renderer:KeyboardResizingAwareContentPage>

这一切归功于保罗!谢谢!

【讨论】:

这个解决方案也会触发(来自生产日志):System.ObjectDisposedException:无法访问已处置的对象。对象名称:'IosKeyboardFixPageRenderer'。 _isKeyboardShown = false;应该在方法 UnregisterForKeyboardNotifications 的末尾移动【参考方案3】:

Xamarin Forum question 讨论它。

此外,如果您希望在 android 中使用 Xamarin / Forms,您可以在主 Activity 中进行设置:

[Activity(WindowSoftInputMode = Android.Views.SoftInput.AdjustResize)]
public class MainActivity
    ...

【讨论】:

【参考方案4】:

对于Android,在MainActivityLoadApplication(new App());之后添加以下代码

App.Current.On<Xamarin.Forms.PlatformConfiguration.Android>().
UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);

【讨论】:

以上是关于如何防止键盘覆盖我的 UI 而不是调整它的大小?的主要内容,如果未能解决你的问题,请参考以下文章

防止在 Android 浏览器中从键盘调整页面大小

显示键盘时防止 DialogFragment 调整大小/折叠

我们如何防止当我调整窗口大小时,我的图像使用 HTML-CSS 移动?

设置高 DPI 时防止我的 Win32 应用程序的 UI 元素按比例放大

如何防止 Android Firefox 浏览器在打开软键盘时调整窗口大小

如何防止用户调整表单的大小?