利用Unity InputField实现文本编辑器窗体效果

利用Unity InputField实现文本编辑器窗体效果

包含知识点:
1.计算窗体大小
2.计算光标位置
3.设置光标位置到可视区域内
具体参考源代码,内有注释。

Unity工程github地址:

public class TextEdit : UIBehaviour {

    [SerializeField]
    private ScrollRect m_scrollView;
    [SerializeField]
    private InputField m_inputField;

    [SerializeField]
    private bool m_AutoHeight=true;
    [SerializeField]
    private bool m_AutoWidth=true;

    public bool autoHeight
    {
        get
        {
            return m_AutoHeight;
        }
        set
        {
            m_AutoHeight = true;
            CalculationInputPanelSize();
            WaitEndFrameMakeCaretInViewport();
        }
    }

    public bool autoWidth
    {
        get { return m_AutoWidth; }
        set {
            m_AutoWidth = true;
            CalculationInputPanelSize();
            WaitEndFrameMakeCaretInViewport();
        }
    }

    bool initSuccess = false;
    bool onInputFieldSelect;
    private TextGenerator m_TextCacheForLayout;
    public TextGenerator cachedTextGeneratorForLayout
    {
        get { return m_TextCacheForLayout ?? (m_TextCacheForLayout = new TextGenerator()); }
    }
    private bool onWaittingMakeCaretInViewport = false;

    private int preCaretPostion=-1;
    protected override void Awake()
    {
        base.Awake();
        m_inputField.onValueChanged.AddListener(OnInputValueChanged);
    }

    protected override void Start()
    {
        base.Start();
        StartCoroutine(WaitStart());
    }
    IEnumerator WaitStart()
    {
        yield return new WaitForEndOfFrame();
        CalculationInputPanelSize();
    }

    protected override void OnEnable()
    {
        base.OnEnable();
        onWaittingMakeCaretInViewport = false;
        CalculationInputPanelSize();
        WaitEndFrameMakeCaretInViewport();
    }

    private void OnInputValueChanged(string value)
    {
        CalculationInputPanelSize();
        WaitEndFrameMakeCaretInViewport();
    }
    /// <summary>
    /// 计算InputField的大小
    /// </summary>
    private void CalculationInputPanelSize()
    {
        string textValue = m_inputField.text + "  ";
        Text inputTextComponent = m_inputField.textComponent;
        TextGenerator textGenerator = cachedTextGeneratorForLayout;
        RectTransform inputRectTrans = m_inputField.transform as RectTransform;
        TextGenerationSettings generationSettings = GetGenerationSettings();
        Vector2 textCompontentSize = inputTextComponent.rectTransform.rect.size;
        Vector2 inputSize = inputRectTrans.rect.size;
        float height;
        float width;
        if (m_AutoHeight)
        {
            height = textGenerator.GetPreferredHeight(textValue, generationSettings) / inputTextComponent.pixelsPerUnit;
            height += inputSize.y - textCompontentSize.y;
            if (height < m_scrollView.viewport.rect.size.y)
            {
                height = m_scrollView.viewport.rect.size.y;
            }
        }
        else 
            height = inputRectTrans.rect.height;
        if (m_AutoWidth)
        {
            width = textGenerator.GetPreferredWidth(textValue, generationSettings) / inputTextComponent.pixelsPerUnit;
            width += inputSize.x - textCompontentSize.x;
            if (width < m_scrollView.viewport.rect.size.x)
            {
                width = m_scrollView.viewport.rect.size.x;
            }
        }
        else
            width = inputRectTrans.rect.width;
        
        inputRectTrans.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
        inputRectTrans.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
        LayoutRebuilder.ForceRebuildLayoutImmediate(m_scrollView.content);
    }

    /// <summary>
    /// 计算光标位置,使光标位于可视区域内
    /// </summary>
    private void MakeCaretInViewport()
    {
        if (!m_inputField.isFocused)
        {
            return;
        }


        if (m_scrollView.horizontalScrollbar.size>=1&&m_scrollView.verticalScrollbar.size>=1)
        {
            //编辑区域未超过视口大小,光标一定在可视区域内
            return;
        }
        //获取光标位置
        int caretIndex = m_inputField.caretPosition;
        preCaretPostion = caretIndex;
        Text textComponent = m_inputField.textComponent;
        TextGenerator textGenerator = textComponent.cachedTextGenerator;
        //光标底部位置
        Vector3 caretPosBottom = Vector3.zero;
        //光标顶部位置
        Vector3 caretPosTop = Vector3.zero;
        if (textGenerator.lineCount == 0)
        {
            //没有内容
            return;
        }
        if (caretIndex < textGenerator.characterCount)
        {
            UICharInfo cusorChar = textGenerator.characters[caretIndex];
            caretPosBottom.x = cusorChar.cursorPos.x;
        }
        else
        {
            Debug.LogError("caretIndex out of textGenerator.characterCount");
        }
        caretPosBottom.x /= textComponent.pixelsPerUnit;
        if (caretPosBottom.x > textComponent.rectTransform.rect.xMax)
        {
            caretPosBottom.x = textComponent.rectTransform.rect.xMax;
        }

        int characterLine = DetermineCharacterLine(caretIndex, textGenerator);

        caretPosBottom.y = textGenerator.lines[characterLine].topY / textComponent.pixelsPerUnit;
        caretPosTop = caretPosBottom;
        float height = textGenerator.lines[characterLine].height / textComponent.pixelsPerUnit;
        caretPosBottom.y -= height;
        caretPosBottom = textComponent.transform.TransformPoint(caretPosBottom);
        caretPosTop = textComponent.transform.TransformPoint(caretPosTop);
        caretPosBottom = RectTransformUtility.WorldToScreenPoint(null, caretPosBottom);
        caretPosTop = RectTransformUtility.WorldToScreenPoint(null, caretPosTop);
        Vector2 caretPosBottomInView;
        Vector2 caretPosTopInView;
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(m_scrollView.viewport, caretPosBottom, null, out caretPosBottomInView))
        {
            return;
        }
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(m_scrollView.viewport, caretPosTop, null, out caretPosTopInView))
        {
            return;
        }
        if (m_scrollView.verticalScrollbar.size < 1)
        {
            float heightDif = m_scrollView.content.rect.height - m_scrollView.viewport.rect.height;
            float lineTop = caretPosTopInView.y;

            float lineBottom = caretPosBottomInView.y;

            if (lineTop > 0)
            {
                //超出上边界需要界面向下移动
                float move = lineTop / heightDif;
                float barValue = Mathf.Clamp01(m_scrollView.verticalNormalizedPosition + move);
                m_scrollView.verticalNormalizedPosition = barValue;
            }
            else if (lineBottom < -m_scrollView.viewport.rect.height)
            {
                float move = -m_scrollView.viewport.rect.height - lineBottom;
                float barValue = Mathf.Clamp01(m_scrollView.verticalNormalizedPosition - move / heightDif);
                m_scrollView.verticalNormalizedPosition = barValue;
            }
        }
        if (m_scrollView.horizontalScrollbar.size < 1)
        {
            float caretP = caretPosTopInView.x;
            float widthDif = m_scrollView.content.rect.width - m_scrollView.viewport.rect.width;
          
            if (caretP < 0)
            {
                //超出做边界
                float move = -caretP / widthDif;
                float barValue = Mathf.Clamp01(m_scrollView.horizontalNormalizedPosition - move);
                m_scrollView.horizontalNormalizedPosition = barValue;
            }
            else if (caretP > m_scrollView.viewport.rect.width)
            {
                float move = caretP - m_scrollView.viewport.rect.width;
                float barValue = Mathf.Clamp01(m_scrollView.horizontalNormalizedPosition + move);
                m_scrollView.horizontalNormalizedPosition = barValue;
            }
        }
    }

    /// <summary>
    /// 等待到帧结束时计算光标位置
    /// </summary>
    private void WaitEndFrameMakeCaretInViewport()
    {
        if (!onWaittingMakeCaretInViewport)
        {
            onWaittingMakeCaretInViewport = true;
            StartCoroutine(IEWaitEndFrameMakeCaretInViewport());
        }
    }

    private IEnumerator IEWaitEndFrameMakeCaretInViewport()
    {
        yield return new WaitForEndOfFrame();
        MakeCaretInViewport();
        onWaittingMakeCaretInViewport = false;
    }
    /// <summary>
    /// 确定字符在第多少行
    /// </summary>
    /// <param name="charPos">字符的位置</param>
    /// <param name="generator">文本生成器</param>
    /// <returns>返回行下标</returns>
    private int DetermineCharacterLine(int charPos, TextGenerator generator)
    {
        for (int i = 0; i < generator.lineCount - 1; ++i)
        {
            if (generator.lines[i + 1].startCharIdx > charPos)
                return i;
        }

        return generator.lineCount - 1;
    }

    private TextGenerationSettings GetGenerationSettings()
    {
       return m_inputField.textComponent.GetGenerationSettings(m_inputField.textComponent.rectTransform.rect.size);
    }

    void LateUpdate()
    {
        if (preCaretPostion != m_inputField.caretPosition && !onWaittingMakeCaretInViewport)
        {
            MakeCaretInViewport();
        }
    }


}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值