源码10:InputField
InputField就是输入框 提供给用户输入文字内容的 是一个重要的交互手段。比如常见的用来输入用户名 ,密码等等。
public class InputField
: Selectable,
IUpdateSelectedHandler,
IBeginDragHandler,
IDragHandler,
IEndDragHandler,
IPointerClickHandler,
ISubmitHandler,
ICanvasElement,
ILayoutElement
{
...
}
上面就是InputField在实现过程中继承的类和接口 具体的接口描述就不再解释了,前面的文章说的比较多。
protected override void OnEnable()
{
base.OnEnable();
if (m_Text == null)
m_Text = string.Empty;
m_DrawStart = 0;
m_DrawEnd = m_Text.Length;
// If we have a cached renderer then we had OnDisable called so just restore the material.
if (m_CachedInputRenderer != null)
m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture);
if (m_TextComponent != null)
{
m_TextComponent.RegisterDirtyVerticesCallback(MarkGeometryAsDirty);
m_TextComponent.RegisterDirtyVerticesCallback(UpdateLabel);
m_TextComponent.RegisterDirtyMaterialCallback(UpdateCaretMaterial);
UpdateLabel();
}
}
OnEnable 组件刚激活
1.设置了输入文字绘制开始和结束的位置
2.给文字组件添加了了两个RegisterDirtyVerticesCallback 事件MarkGeometryAsDirty 和UpdateLabel
调用了RegisterDirtyMaterialCallback,添加UpdateCaretMaterial回调
最后调用UpdateLable
private void MarkGeometryAsDirty()
{
#if UNITY_EDITOR
if (!Application.isPlaying || UnityEditor.PrefabUtility.IsPartOfPrefabAsset(gameObject))
return;
#endif
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
}
MarkGeometryAsDirty 在CanvasUpdateRegistry里把自己注册到图形重建序列。(后面文章分析)
UpdateLable 方法主要就是输入的时候更新显示
/// </summary>
protected void UpdateLabel()
{
if (m_TextComponent != null && m_TextComponent.font != null && !m_PreventFontCallback)
{
m_PreventFontCallback = true;
//获取全部的显示文字字串
//当有IME组合字符串的时候就需要根据光标位置把组合串接入计算进去
//https://docs.unity3d.com/cn/2018.4/ScriptReference/Input-compositionString.html
string fullText;
if (EventSystem.current != null && gameObject == EventSystem.current.currentSelectedGameObject && 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;
//无输入就显示Placeholder
bool isEmpty = string.IsNullOrEmpty(fullText);
if (m_Placeholder != null)
m_Placeholder.enabled = isEmpty;
// If not currently editing the text, set the visible range to the whole text.
// The UpdateLabel method will then truncate it to the part that fits inside the Text area.
// We can't do this when text is being edited since it would discard the current scroll,
// which is defined by means of the m_DrawStart and m_DrawEnd indices.
if (!m_AllowInput)
{
m_DrawStart = 0;
m_DrawEnd = m_Text.Length;
}
if (!isEmpty)
{
//获取文本生成设置(Text组件功能)
// Determine what will actually fit into the given line
Vector2 extents = m_TextComponent.rectTransform.rect.size;
var settings = m_TextComponent.GetGenerationSettings(extents);
settings.generateOutOfBounds = true;
//生成Mesh的顶点数据和文本字符串数据,
cachedInputTextGenerator.PopulateWithErrors(processed, settings, gameObject);
//根据TextGenerator和caretPos(光标位置)来计算m_DrawStart和m_DrawEnd,并根据这两个值取字符串的子字符串,设置光标可见(使用协程闪烁)。
SetDrawRangeToContainCaretPosition(caretSelectPositionInternal);
//获取实际上能绘制显示的字符串
processed = processed.Substring(m_DrawStart, Mathf.Min(m_DrawEnd, processed.Length) - m_DrawStart);
//设置光标可见(使用协程闪烁)。
SetCaretVisible();
}
//加工过的字符串给到Text组件 注册到图形重建列表中
m_TextComponent.text = processed;
MarkGeometryAsDirty();
m_PreventFontCallback = false;
}
}
private void UpdateCaretMaterial()
{
if (m_TextComponent != null && m_CachedInputRenderer != null)
m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture);
}
- 调用m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture);创建一个默认的材质
- 调用m_CachedInputRenderer.SetMaterial把创建的材质赋值给光标,更新光标的材质
public void DeactivateInputField()
{
// Not activated do nothing.
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;
SendOnSubmit();
if (m_Keyboard != null)
{
m_Keyboard.active = false;
m_Keyboard = null;
}
m_CaretPosition = m_CaretSelectPosition = 0;
if (input != null)
input.imeCompositionMode = IMECompositionMode.Auto;
}
MarkGeometryAsDirty();
}
停用Input 事件 通常在输入结束 或者失去Focus disable等情况调用
- 设置m_Keyboard.active为false,
- 设置光标位置为0,
- 如果IsInteractable为true,调用SendOnSubmit,发送onEndEdit事件, onEndEdit.Invoke(m_Text),
- 最后调用MarkGeometryAsDirty等待重建。
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;
}
激活输入 ,通常是在Click的时候 选中状态下激活键盘进行输入
public override void OnSelect(BaseEventData eventData)
{
base.OnSelect(eventData);
if (shouldActivateOnSelect)
ActivateInputField();
}
重写Selectable 中的OnSelect 选中 激活键盘输入
public virtual void OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
ActivateInputField();
}
和上面一样当点击的时候激活键盘输入
public override void OnDeselect(BaseEventData eventData)
{
DeactivateInputField();
base.OnDeselect(eventData);
}
取消选中停用输入
public virtual void OnUpdateSelected(BaseEventData eventData)
{
if (!isFocused)
return;
bool consumedEvent = false;
while (Event.PopEvent(m_ProcessingEvent))
{
if (m_ProcessingEvent.rawType == EventType.KeyDown)
{
consumedEvent = true;
var shouldContinue = KeyPressed(m_ProcessingEvent);
if (shouldContinue == EditState.Finish)
{
DeactivateInputField();
break;
}
}
switch (m_ProcessingEvent.type)
{
case EventType.ValidateCommand:
case EventType.ExecuteCommand:
switch (m_ProcessingEvent.commandName)
{
case "SelectAll":
SelectAll();
consumedEvent = true;
break;
}
break;
}
}
if (consumedEvent)
UpdateLabel();
eventData.Use();
}
实现了IUpdateSelectedHandler 的方法 是由StandaloneInputModule 每帧检测是否有选中对象 进行事件发送
这里主要是处理一系列键盘输入的操作,比如退格键,复制粘贴(Ctrl+ c Ctrl+v)。
private void OnFillVBO(Mesh vbo)
{
using (var helper = new VertexHelper())
{
if (!isFocused)
{
helper.FillMesh(vbo);
return;
}
Vector2 roundingOffset = m_TextComponent.PixelAdjustPoint(Vector2.zero);
if (!hasSelection)
GenerateCaret(helper, roundingOffset);
else
GenerateHighlight(helper, roundingOffset);
helper.FillMesh(vbo);
}
}
如果没有选择区域,调用GenerateCaret生成光标的Mesh,否则,调用GenerateHightlight生成选中区域的Mesh。然后调用VertexHelper.FillMesh,填充Mesh。
总结
InpuFiled源码涉及到很多显示 键盘操作相关的东西 ,看起来确实比较凌乱 。但是包含了UGUI的很多特性功能,值得仔细揣摩
参考文章:https://zhuanlan.zhihu.com/p/340600190
后面要开始分析比较重头的内容 Graphic UGUI的的显示核心