UGUI源码分析:InputField文本输入框组件

系列

UGUI源码分析系列总览
相关前置:
UGUI EventSystem源码分析
UGUI源码分析:Selectable交互组件的基类
UGUI源码分析:Text与Outline的具体实现

文章目录

  • 系列
  • UML图一览
  • InputField
    • 属性介绍
    • 初始化
    • 事件接口
      • 第一步:点击激活InputField
      • 第二步:输入文字
      • 第三步:结束编辑


UML图一览

UGUI源码分析:InputField文本输入框组件_第1张图片


InputField

BaseClass: Selectable

Interface: IUpdateSelectedHandler,IXXXDragHandler,IPointerClickHandler,ISubmitHandler,ICanvasElement,ILayoutElement

Intro: UGUI中输入框组件组件

  • IUpdateSelectedHandler:刷新当前被选中物体事件监听,帧刷新
  • IXXXDragHandler:三个拖拽接口,这里就简写成这样,监听整个拖拽过程(开始,拖拽,结束)。
  • IPointerClickHandler: 点击事件接口
  • ISubmitHandlerSubmit按键点击事件的响应接口,Submit是可以在Project Settings中的Input输入设置。当组件被选中时(“选中”的详细介绍请看Selectable)可响应Submit事件。
  • ICanvasElement :Canvas元素(重建接口),当Canvas发生更新时重建(void Rebuild)
  • ILayoutElement:布局相关接口

InputField,是UGUI中输入文本框组件。它提供了丰富的输入文本属性来实现输入功能,并提供了两个监听事件OnValueChanged(文本变化时响应)OnEndEdit(完成编辑时响应)。

属性介绍

UGUI源码分析:InputField文本输入框组件_第2张图片

  • Interactable:是否可被交互(false时无法通过EventSystem进行交互)
  • Transition:状态变化过渡模式(相关详情)
  • Navigation:导航(相关详情)
  • Text Component :文本组件
  • Text :文本内容
  • Character Limit :字符长度限制
  • Content Type :内容类型(数字、邮箱、字母、密码共十种类型选择)
  • Line Type :行类型(单行、多行)
  • Placeholder :占位Text组件,当无内容时显示该组件
  • Caret Blink Rate :光标闪烁频率
  • Caret Width :光标宽度
  • CustomCaretColor :自定义光标颜色开关
  • Selection Color :选中区域颜色
  • Hide Mobile Input:隐藏输入框开关
  • On Value Changed:文本发生变化的事件监听
  • On End Edit :完成编辑的事件监听

为了呈现出输入框的交互效果,InputField的代码量非常的大(2400+行)是UGUI组件中内容量最大的组件,逻辑并不复杂,只在于它实现的输入功能细节很多(例如各种输入要求限制,光标效果,拖拽选中区域…)初看源码会有一种的感觉。所以本章就针对其基本的输入交互流程来分析其实现原理,不发散去关注它各个细节的实现。

初始化

一如既往,阅读源码的最好方式就是先从生命周期开始。

Enable阶段:主要是向Text组件注册了重建前(脏标记)时的事件监听。并执行了UpdateLabel方法,相应的事件中也包含了UpdateLabel方法,该方法便是我们重点关注的地方。

protected override void OnEnable()
{
    base.OnEnable();
    if (m_Text == null)
        m_Text = string.Empty;
    m_DrawStart = 0;
    m_DrawEnd = m_Text.Length;

    if (m_CachedInputRenderer != null)     m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture);

    //向Text组件注册事件监听,主要当Graphic进行顶点与材质的重建标记时会执行相应的监听事件
    if (m_TextComponent != null)
    {
        m_TextComponent.RegisterDirtyVerticesCallback(MarkGeometryAsDirty);
        m_TextComponent.RegisterDirtyVerticesCallback(UpdateLabel);
        m_TextComponent.RegisterDirtyMaterialCallback(UpdateCaretMaterial);
        UpdateLabel();//更新文本
    }
}

Disable阶段:非常普通地做了一些清理工作。

protected override void OnDisable()
{
    // 关闭携程
    m_BlinkCoroutine = null;
    //将各种属性做无效处理
    DeactivateInputField();
    //注销Text中的事件监听
    if (m_TextComponent != null)
    {
        m_TextComponent.UnregisterDirtyVerticesCallback(MarkGeometryAsDirty);
        m_TextComponent.UnregisterDirtyVerticesCallback(UpdateLabel);
        m_TextComponent.UnregisterDirtyMaterialCallback(UpdateCaretMaterial);
    }
    CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
    if (m_CachedInputRenderer != null)
        m_CachedInputRenderer.Clear();
    //清除网格
    if (m_Mesh != null)
        DestroyImmediate(m_Mesh);
    m_Mesh = null;
    base.OnDisable();
}

事件接口

UI组件的交互都是基于输入事件的,我们按照使用InputField的操作流程来一一分析各个事件。

第一步:点击激活InputField

public virtual void OnPointerClick(PointerEventData eventData)
{
    if (eventData.button != PointerEventData.InputButton.Left)
        return;
	//激活输入框组件
    ActivateInputField();
}

InputField中使用了Unity提供的键盘接口类型(TouchScreenKeyboard),该接口记录了我们的输入内容。

public void ActivateInputField()
{
    if (m_TextComponent == null || m_TextComponent.font == null || !IsActive() || !IsInteractable())
        return;

    if (isFocused)
    {
        if (m_Keyboard != null && !m_Keyboard.active)
        {
            //激活键盘接口,并将当前输入框设置进键盘接口中
            m_Keyboard.active = true;
            m_Keyboard.text = m_Text;
        }
    }

    m_ShouldActivateNextUpdate = true;
}

到此,激活操作就结束了。主要内容是激活了TouchScreenKeyboard,来保存我们的输入。

第二步:输入文字

InputField采用了每帧更新的方式来更新我们的输入(LateUpdate)。

获取键盘输入信息,并验证信息的有效字符,并更新。

protected virtual void LateUpdate()
{
    if (m_ShouldActivateNextUpdate)
    {
        if (!isFocused)
        {
            ActivateInputFieldInternal();
            m_ShouldActivateNextUpdate = false;
            return;
        }
        m_ShouldActivateNextUpdate = false;
    }
    if (InPlaceEditing() || !isFocused)
        return;
    AssignPositioningIfNeeded();
    if (m_Keyboard == null || m_Keyboard.done)
    {
        if (m_Keyboard != null)
        {
            if (!m_ReadOnly)
                text = m_Keyboard.text;
            if (m_Keyboard.wasCanceled)
                m_WasCanceled = true;
        }
        OnDeselect(null);
        return;
    }
    //获取输入信息
    string val = m_Keyboard.text;
    if (m_Text != val)
    {
        //只读情况则无法改变输入内容
        if (m_ReadOnly)
        {
            m_Keyboard.text = m_Text;
        }
        else
        {
            m_Text = "";
            for (int i = 0; i < val.Length; ++i)
            {
                char c = val[i];
                if (c == '\r' || (int)c == 3)
                    c = '\n';
                //验证输入内容
                if (onValidateInput != null)
                    c = onValidateInput(m_Text, m_Text.Length, c);
                else if (characterValidation != CharacterValidation.None)
                    c = Validate(m_Text, m_Text.Length, c);//默认的验证方法
                //限制行时当存在换行情况则停止更新
                if (lineType == LineType.MultiLineSubmit && c == '\n')
                {
                    m_Keyboard.text = m_Text;

                    OnDeselect(null);
                    return;
                }
                if (c != 0)
                    m_Text += c;
            }
            //字数限制的情况进行切割
            if (characterLimit > 0 && m_Text.Length > characterLimit)
                m_Text = m_Text.Substring(0, characterLimit);

            if (m_Keyboard.canGetSelection)
            {
                UpdateCaretFromKeyboard();
            }
            else
            {
                caretPositionInternal = caretSelectPositionInternal = m_Text.Length;
            }
            if (m_Text != val)
                m_Keyboard.text = m_Text;
            //执行事件并更新文本显示
            SendOnValueChangedAndUpdateLabel();
        }
    }
    else if (m_Keyboard.canGetSelection)
    {
        UpdateCaretFromKeyboard();
    }

    //当键盘输入完毕时,执行Deselect,会关闭键盘并执行编辑完成的事件
    if (m_Keyboard.done)
    {
        if (m_Keyboard.wasCanceled)
            m_WasCanceled = true;

        OnDeselect(null);
    }
}

更新显示内容

protected void UpdateLabel()
{
    if (m_TextComponent != null && m_TextComponent.font != null && !m_PreventFontCallback)
    {
        m_PreventFontCallback = true;

        string fullText;
        if (compositionString.Length > 0)
            fullText = text.Substring(0, m_CaretPosition) + compositionString + text.Substring(m_CaretPosition);
        else
            fullText = text;

        string processed;
        //输入类型为密码类型时,用*替换
        if (inputType == InputType.Password)
            processed = new string(asteriskChar, fullText.Length);
        else
            processed = fullText;

        bool isEmpty = string.IsNullOrEmpty(fullText);

        if (m_Placeholder != null)
            m_Placeholder.enabled = isEmpty;

        if (!m_AllowInput)
        {
            m_DrawStart = 0;
            m_DrawEnd = m_Text.Length;
        }

        if (!isEmpty)
        {
            Vector2 extents = m_TextComponent.rectTransform.rect.size;

            var settings = m_TextComponent.GetGenerationSettings(extents);
            settings.generateOutOfBounds = true;
            //生成mesh数据
            cachedInputTextGenerator.PopulateWithErrors(processed, settings, gameObject);
            //计算光标位置
            SetDrawRangeToContainCaretPosition(caretSelectPositionInternal);

            processed = processed.Substring(m_DrawStart, Mathf.Min(m_DrawEnd, processed.Length) - m_DrawStart);
            //通过携程显示光标
            SetCaretVisible();
        }
        m_TextComponent.text = processed;
        MarkGeometryAsDirty();
        m_PreventFontCallback = false;
    }
}

第三步:结束编辑

LateUpdate检测m_Keyboard.done,或者执行Deselect事件时(变更目标时执行)

//取消inputfield的激活
public void DeactivateInputField()
{
    if (!m_AllowInput)
        return;
    //处理相关输入事件的属性,将其重置
    m_HasDoneFocusTransition = false;
    m_AllowInput = false;
    if (m_Placeholder != null)
        m_Placeholder.enabled = string.IsNullOrEmpty(m_Text);
    if (m_TextComponent != null && IsInteractable())
    {
        if (m_WasCanceled)
            text = m_OriginalText;
        if (m_Keyboard != null)
        {
            m_Keyboard.active = false;
            m_Keyboard = null;
        }
        m_CaretPosition = m_CaretSelectPosition = 0;
        //执行编辑完毕的事件监听
        SendOnSubmit();
        input.imeCompositionMode = IMECompositionMode.Auto;
    }

    MarkGeometryAsDirty();
}

.
.
.
.
.


嗨,我是作者Vin129,逐儿时之梦正在游戏制作的技术海洋中漂泊。知道的越多,不知道的也越多。希望我的文章对你有所帮助:)


你可能感兴趣的:(UGUI源码分析)