转载请注明出处:http://blog.youkuaiyun.com/fuemocheng
Unity官方源码:https://bitbucket.org/Unity-Technologies/
需求:新建一个类 MyInputField 继承自 UnityEngine.UI 的 InputField ,即输入框,要求新类中实现整块字符的删除。比如输入一个表情(字符串代码是 #F20),要求删除时能整块删除。
首先找到 InputField 的源码(Unity5.6)。
分析源码:
- 当按键事件发生, OnUpdateSelected() 方法检测到,通过 KeyPressed() 来判断事件,并执行相应逻辑。当按下Backspace键时,执行 Backspace() 方法,然后根据情况Delete()。其中 Backspace() 和 Delete() 都是父类私有方法,不可由子类直接调用。
看下面源码:
/// <summary>
/// Handle the specified event.
/// </summary>
private Event m_ProcessingEvent = new Event();
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();
}
protected EditState KeyPressed(Event evt)
{
...
switch (evt.keyCode)
{
case KeyCode.Backspace:
{
Backspace();
return EditState.Continue;
}
case KeyCode.Delete:
{
ForwardSpace();
return EditState.Continue;
}
...
}
...
}
private void Backspace()
{
if (m_ReadOnly)
return;
if (hasSelection)
{
Delete();
SendOnValueChangedAndUpdateLabel();
}
else
{
if (caretPositionInternal > 0)
{
m_Text = text.Remove(caretPositionInternal - 1, 1);
caretSelectPositionInternal = caretPositionInternal = caretPositionInternal - 1;
SendOnValueChangedAndUpdateLabel();
}
}
}
private void Delete()
{
if (m_ReadOnly)
return;
if (caretPositionInternal == caretSelectPositionInternal)
return;
if (caretPositionInternal < caretSelectPositionInternal)
{
m_Text = text.Substring(0, caretPositionInternal) + text.Substring(caretSelectPositionInternal, text.Length - caretSelectPositionInternal);
caretSelectPositionInternal = caretPositionInternal;
}
else
{
m_Text = text.Substring(0, caretSelectPositionInternal) + text.Substring(caretPositionInternal, text.Length - caretPositionInternal);
caretPositionInternal = caretSelectPositionInternal;
}
}
- 如果要实现整块删除,则必然在 KeyPressed() 之前执行我们自定义删除函数。因为我们没法重写 KeyPressed(),但是Unity提供了 virtual void OnUpdateSelected() 方法,这就是我们可以重写的方法。于是重写如下:
private Event m_ProcessingEvent = new Event();
public override void OnUpdateSelected(BaseEventData eventData)
{
if (!isFocused)
return;
bool consumedEvent = false;
while (Event.PopEvent(m_ProcessingEvent))
{
if (m_ProcessingEvent.rawType == EventType.KeyDown)
{
consumedEvent = true;
/// <summary>
/// 如果是Backspace键,执行我们自定义删除方法
/// </summary>
if (m_ProcessingEvent.keyCode == KeyCode.Backspace)
{
DelNodeFace();
break;
}
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();
}
DelNodeFace() 就是我们自定义删除 整块内容 的函数。主要实现原理就是记录这一 整块输入内容 在输入框中开始和结束的位置,删除时和光标位置进行比较,一旦开始删除 整块内容 的最后位置,就将 整块内容 删除。
实现如下:
public bool DelNodeFace() //自定义删除函数
{
int currentPos = this.caretPosition;
for (int i=m_inputNodeList.Count-1; i>=0; i--)
{
TInputNode tempNode = m_inputNodeList[i];
//这里其实存在Bug,如果从showStr中间删除,也会删除同样长度的输入信息,应该限制只能从末尾删除(暂时不解决这个问题)
if (currentPos > tempNode.m_iCharBegin && currentPos <= tempNode.m_iCharEnd)
{
string showStr = "#f" + tempNode.m_myInfo.GetValueInt("faceId").ToString();
for (int j = 0; j < showStr.Length; j++)
{
//删除 字符串中 tempNode.m_iCharBegin 到 tempNode.m_iCharEnd
DeleteOne();
}
m_inputNodeList.RemoveAt(i);
return true;
}
}
//否则只删除一个字符信息
DeleteOne();
return false;
}
但是,仍然要实现其中的删除每一个字符的方法 DeleteOne() 。
这时就需要调用父类中的 Backspace() 方法了。如何调用父类中的私有方法呢?利用反射。
C#反射怎么用,可以百度。 这里给有两个链接:
1、子类用反射可以访问父类中的私有成员变量及方法
2、反射(C#编程指南)这里实现如下:
public void DeleteOne()
{
// 指明当前对象
object obj = (InputField)this;
// 获取对象的类型
Type type = obj.GetType();
// 对象的父类类型
type = type.BaseType;
//字段绑定标志
BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
//获取对象的私有方法print
MethodInfo mf = type.GetMethod("Backspace", flag);
// 实现对象中的方法
mf.Invoke(obj, null);
}
于是可以实现 整块删除。
当然,插入字符时,整块字符 的起始位置也要发生变化,所以也要重写插入字符的方法。
InputField 源码中插入字符实现
/// <summary>
/// Append the specified text to the end of the current.
/// </summary>
protected virtual void Append(string input)
{
if (m_ReadOnly)
return;
if (!InPlaceEditing())
return;
for (int i = 0, imax = input.Length; i < imax; ++i)
{
char c = input[i];
if (c >= ' ' || c == '\t' || c == '\r' || c == 10 || c == '\n')
{
Append(c);
}
}
}
protected virtual void Append(char input)
{
if (m_ReadOnly)
return;
if (!InPlaceEditing())
return;
// If we have an input validator, validate the input first
int insertionPoint = Math.Min(selectionFocusPosition, selectionAnchorPosition);
if (onValidateInput != null)
input = onValidateInput(text, insertionPoint, input);
else if (characterValidation != CharacterValidation.None)
input = Validate(text, insertionPoint, input);
// If the input is invalid, skip it
if (input == 0)
return;
// Append the character and update the label
Insert(input);
}
// Insert the character and update the label.
private void Insert(char c)
{
if (m_ReadOnly)
return;
string replaceString = c.ToString();
Delete();
// Can't go past the character limit
if (characterLimit > 0 && text.Length >= characterLimit)
return;
m_Text = text.Insert(m_CaretPosition, replaceString);
caretSelectPositionInternal = caretPositionInternal += replaceString.Length;
SendOnValueChanged();
}
我们进行重写,实现插入字符串时,块内容整体后移:
protected override void Append(string input)
{
if (readOnly)
return;
if (!InPlaceEditing())
return;
for (int i = 0, imax = input.Length; i < imax; ++i)
{
char c = input[i];
if (c >= ' ' || c == '\t' || c == '\r' || c == 10 || c == '\n')
{
Append(c);
}
}
}
protected override void Append(char input)
{
if (readOnly)
return;
if (!InPlaceEditing())
return;
// If we have an input validator, validate the input first
int insertionPoint = Math.Min(selectionFocusPosition, selectionAnchorPosition);
if (onValidateInput != null)
input = onValidateInput(text, insertionPoint, input);
else if (characterValidation != CharacterValidation.None)
input = Validate(text, insertionPoint, input);
// If the input is invalid, skip it
if (input == 0)
return;
#region 将列表里存储表情开始结束位置后移
for (int i = m_inputNodeList.Count - 1; i >= 0; i--)
{
TInputNode tempNode = m_inputNodeList[i];
if (tempNode.m_iCharBegin >= m_CaretPosition)
{
tempNode.m_iCharBegin++;
tempNode.m_iCharEnd++;
}
}
#endregion
// Append the character and update the label
InsertOne(input);
}
//利用反射调用父类私有方法Insert(),并传参;
public void InsertOne(char c)
{
// 指明当前对象
object obj = (InputField)this;
// 获取对象的类型
Type type = obj.GetType();
// 对象的父类类型
type = type.BaseType;
//字段绑定标志
BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
object[] parameters = new object[1];
parameters[0] = c;
//获取对象的私有方法print
MethodInfo mf = type.GetMethod("Insert", flag);
// 实现对象中的方法
mf.Invoke(obj, parameters);
}
最终实现要求,实现了自定义MyInputFiled类。
这里给出 MyInputField 类源码,完全继承 InputFiled 的功能,而且实现自定义删除 块内容 的功能。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Reflection;
using System;
using UnityEngine.EventSystems;
public class MyInputField : InputField {
private int m_nCurrentPos = 0;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public enum eInputNode
{
eInputNode_none = 0,
eInputNode_face = 1,
eInputNode_item = 2,
eInputNode_itemByType = 3,
eInputNode_pet = 4,
}
//输入节点,可以是表情、物品、宠物
public class TInputNode
{
public TInputNode()
{
}
public eInputNode m_eInputNode = eInputNode.eInputNode_none;
public int m_nID;
public int m_iCharBegin = 0;
public int m_iCharEnd = 0;
}
public List<TInputNode> m_inputNodeList = new List<TInputNode>();
public void AddNodeFace(int faceId)
{
//只能最多输入两个表情
if (m_inputNodeList.Count >= 2)
return;
TInputNode newNode = new TInputNode();
newNode.m_eInputNode = eInputNode.eInputNode_face;
newNode.m_nID = faceId;
string showStr = "#f" + faceId.ToString();
newNode.m_iCharBegin = this.caretPosition;
this.Append(showStr);
this.UpdateLabel();
newNode.m_iCharEnd = this.caretPosition;
m_inputNodeList.Add(newNode);
}
public bool DelNodeFace() //自定义删除函数
{
int currentPos = this.caretPosition;
for (int i=m_inputNodeList.Count-1; i>=0; i--)
{
TInputNode tempNode = m_inputNodeList[i];
//这里其实存在Bug,如果从showStr中间删除,也会删除同样长度的输入信息,应该只删除m_iCharBegin 到 m_iCharEnd 之间的字符(暂时不解决这个问题)
if (currentPos > tempNode.m_iCharBegin && currentPos <= tempNode.m_iCharEnd)
{
string showStr = "#f" + tempNode.m_myInfo.GetValueInt("faceId").ToString();
for (int j = 0; j < showStr.Length; j++)
{
//删除 字符串中 tempNode.m_iCharBegin 到 tempNode.m_iCharEnd
DeleteOne();
}
m_inputNodeList.RemoveAt(i);
return true;
}
}
//否则只删除一个字符信息
DeleteOne();
return false;
}
public string GetSendStr()
{
return "";
}
#region 重写父类方法
private Event m_ProcessingEvent = new Event();
public override void OnUpdateSelected(BaseEventData eventData)
{
if (!isFocused)
return;
bool consumedEvent = false;
while (Event.PopEvent(m_ProcessingEvent))
{
if (m_ProcessingEvent.rawType == EventType.KeyDown)
{
consumedEvent = true;
/// <summary>
/// 如果是Backspace键,执行我们自定义删除方法
/// </summary>
if (m_ProcessingEvent.keyCode == KeyCode.Backspace)
{
DelNodeFace();
break;
}
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();
}
protected override void Append(string input)
{
if (readOnly)
return;
if (!InPlaceEditing())
return;
for (int i = 0, imax = input.Length; i < imax; ++i)
{
char c = input[i];
if (c >= ' ' || c == '\t' || c == '\r' || c == 10 || c == '\n')
{
Append(c);
}
}
}
protected override void Append(char input)
{
if (readOnly)
return;
if (!InPlaceEditing())
return;
// If we have an input validator, validate the input first
int insertionPoint = Math.Min(selectionFocusPosition, selectionAnchorPosition);
if (onValidateInput != null)
input = onValidateInput(text, insertionPoint, input);
else if (characterValidation != CharacterValidation.None)
input = Validate(text, insertionPoint, input);
// If the input is invalid, skip it
if (input == 0)
return;
#region 将列表里存储表情开始结束位置后移
for (int i = m_inputNodeList.Count - 1; i >= 0; i--)
{
TInputNode tempNode = m_inputNodeList[i];
if (tempNode.m_iCharBegin >= m_CaretPosition)
{
tempNode.m_iCharBegin++;
tempNode.m_iCharEnd++;
}
}
#endregion
// Append the character and update the label
InsertOne(input);
}
private bool InPlaceEditing()
{
return !TouchScreenKeyboard.isSupported;
}
#endregion
#region 利用反射调用基类私有方法
public void DeleteOne()
{
// 指明当前对象
object obj = (InputField)this;
// 获取对象的类型
Type type = obj.GetType();
// 对象的父类类型
type = type.BaseType;
//字段绑定标志
BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
//获取对象的私有方法print
MethodInfo mf = type.GetMethod("Backspace", flag);
// 实现对象中的方法
mf.Invoke(obj, null);
}
public void InsertOne(char c)
{
// 指明当前对象
object obj = (InputField)this;
// 获取对象的类型
Type type = obj.GetType();
// 对象的父类类型
type = type.BaseType;
//字段绑定标志
BindingFlags flag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
object[] parameters = new object[1];
parameters[0] = c;
//获取对象的私有方法print
MethodInfo mf = type.GetMethod("Insert", flag);
// 实现对象中的方法
mf.Invoke(obj, parameters);
}
#endregion
}