65.9K
CodeProject 正在变化。 阅读更多。
Home

Xamarin Android 中自动滚动到编辑文本框

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2018 年 12 月 25 日

CPOL

3分钟阅读

viewsIcon

4669

如何在 Xamarin Android 中自动滚动到编辑文本框

重要提示:这应该适用于较新版本的 Xamarin (2.3.3)。如果由于某种原因无法更新,本文将解释一种解决方法。

引言

在上一篇文章中,我解释了如何在 Xamarin Android 中检测软件键盘的显示和隐藏事件。我在我的项目中这样做是因为某些文本框被软件键盘隐藏,而这些文本框应该编辑其内容。在本文中,我将解释如何在一些较旧版本的 Xamarin 中解决这个问题。

解决方案

首先要做的是检测 Entry(单行编辑器)和 Editor(多行编辑器)上的焦点。最好的地方是在渲染器中。如果你的项目中没有这些,你可以创建新的。如果你已经有渲染器,它们可能用于其他目的。一个好主意是将渲染器中引入的所有功能限制在尽可能少的行中,以限制渲染器内部的代码依赖性。因此,我决定通过调用 static 类的 static 方法,将单行控件的滚动功能添加到控件中。以下是 2 个编辑器控件的渲染器。

public class ScrollEntryRenderer : EntryRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
    {
        base.OnElementChanged(e);
        EditorsScrollingHelper.AttachToControl(Control, this);
    }
}
public class ScrollEditorRenderer : EditorRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
    {
        base.OnElementChanged(e);
        EditorsScrollingHelper.AttachToControl(Control, this);
    }
}

EditorsScrollingHelper 中的 AttachToControl 方法应该在控件上发生焦点事件时添加滚动功能。EditorsScrollingHelper 负责此机制的所有繁重工作。

public static void AttachToControl(TextView control, ScrollEditorRenderer renderer)
{
    control.FocusChange += (s, e) =>
    {
        FocusChange(e, renderer.Element);
    };
}

public static void AttachToControl(TextView control, ScrollEntryRenderer renderer)
{
    control.FocusChange += (s, e) =>
    {
        FocusChange(e, renderer.Element);
    };
}

EditorsScrollingHelper 类的 FocusChange 方法保存实际滚动到焦点控件所需的所有数据。

private static void FocusChange(AndroidView.FocusChangeEventArgs e, XamarinView element)
{
    if (e.HasFocus)
    {
        _focusedElement = element;
        _elementHeight = element.Bounds.Height;
    }
    else _focusedElement = null;
}

需要保存对焦点控件的引用是不言自明的。元素高度将在下面解释。

实际滚动是在 ISoftwareKeyboardService.Show 事件的处理程序内部完成的。处理程序添加在 EditorsScrollingHelper 类的 static 构造函数内部。

static EditorsScrollingHelper()
{
    TinyIoCContainer.Current.Resolve<ISoftwareKeyboardService>().Show += OnKeyboardShow;
}

如果之前在 FocusChange 方法内部保存了 _focusedElement,则 OnKeyboardShow 处理程序会进行滚动。

private static void OnKeyboardShow(object sender, SoftwareKeyboardEventArgs args)
{
    if (_focusedElement != null)
    {
        ScrollIfNotVisible(_focusedElement);
    }
}

只有在有焦点控件时,if 条件才会触发滚动逻辑 - 因为 _focusedElement 字段不是 null。之所以这样做,是因为 Show 事件对于每个控件焦点触发不止一次(因为通过 GlobalLayoutListener 检测软件键盘显示事件并非万无一失)。保存的元素高度在 Show 事件内部用于计算是否需要滚动到焦点控件。

public static void ScrollIfNotVisible(XamarinView element)
{
    double translationY = 0;
    var parent = element;
    while (parent != null)
    {
        translationY -= parent.Y;
        parent = parent.Parent as XamarinView;
    }
    var height = Application.Current.MainPage.Bounds.Height;
    var elementHeight = _elementHeight;
    translationY -= elementHeight;
    if (-translationY > height)
    {
        if (Math.Abs(Application.Current.MainPage.TranslationY - translationY) > 0.99)
        {
            Application.Current.MainPage.SetTranslation(
                translationY + height / 2 - elementHeight / 2);
        }
    }
}

滚动是通过将焦点元素的所有元素高度加起来,从视觉树向上遍历到顶部 - 窗口根目录(没有父控件)。这是焦点控件的实际 Y 坐标。此值与保存的元素高度一起表示焦点控件底部在屏幕上的位置。有了这些信息,我们可以将其与可用的屏幕尺寸(Application.Current.MainPage.Bounds.Height)进行比较,此时,它应该大约是屏幕的一半(另一半被软件键盘占据)。如果 translationY 值大于应用程序可用的屏幕高度,这意味着控件底部不可见,则应用主页面的平移。平移是通过扩展方法完成的。

public static void SetTranslation(this VisualElement element, double y)
{
    element.TranslationY = y;
    var rectangle = new Rectangle
    {
        Left = element.X,
        Top = element.Y,
        Width = element.Width,
        Height = element.Height + (y < 0 ? -y : 0)
    };
    element.Layout(rectangle);
}

平移的工作原理是将控件向上移动 y 像素,并为此控件设置新的更高矩形。如果没有它,应用程序主页面只会向上移动,但在其下方不会呈现任何内容,除了空白。为了补救这种效果,主页面的矩形更高,并且应用程序会呈现可能位于焦点控件下方的任何控件。

这是滚动应用程序到焦点控件的解决方案的第一部分。另一半是在控件失去焦点时滚回。这是在 SoftwareKeyboardService 中通过在软件键盘隐藏事件上执行额外代码来完成的。

public void InvokeKeyboardHide(SoftwareKeyboardEventArgs args)
{
    OnHide();
    var handler = Hide;
    handler?.Invoke(this, args);
}

主页面平移回原始位置是在 OnHide 方法中完成的。

private void OnHide()
{
    if (Application.Current.MainPage != null)
    {
        Application.Current.MainPage.SetTranslation(0);
    }
}

将平移设置为零会将整个应用程序放回其应有的位置。 :)

就是这样!下面是包含工作解决方案的应用程序的 Gif

© . All rights reserved.