Unity-UGUI太难用?!自定义适配触摸屏和鼠标的ScrollView组件

【原创文章】转载请标记出处。-----拉婓Laughing----

Unity自带的ScrollView组件实在太难用了!

在这里插入图片描述


槽点:

  1. 当用户把鼠标点在滚动窗体的内容Icon上时,无法滑动ScrollView
  2. 滚动窗体的Rect宽高需要根据实际内容调整,才能适配实际的滑动效果。(笔者每回都要调整很久 x_x ||| ,而且这部分如果涉及到动态修改Icon的数量和内容,十分不易扩展!)。
  3. 【滚动窗体+内容按钮】在滑动过程中,无法有效规避误点击按钮的事件触发。
  4. 种种原因,导致ScrollView在移动端(触摸屏),更加难用!

总结name多!还有什么理由阻止我们重新设计,编写一套属于自己的ScrollView组件呢!

我们的目标是:

1.改善:
  • ScrollView 装载内容的创建,以及动态创建装载内容时,能够根据实际的内容进行动态适配。
  • ScrollView 在滑动时,需要有效规避对动态装载内容的点击误判断。(滑动时,点选按钮无效;点选时,不滑动。)
  • ScrollView 能够良好支持PC端的鼠标事件,和触摸屏的触摸事件。
2.保持:
  • ScrollView 能够在滑动尽头,还原到指定头尾位置。(Elasticity)
  • ScrollView 能够通过Mask遮罩机制,显示指定范围内的装载内容。

执行与挑战!

  1. 制作滑动响应事件:

    原型设计:我们来仔细想象在一款正常交互作品中,对滑动框的控制方式。当鼠标或者手指在滑动框上按下时,滑动框被激活;激活状态下,用户滑动鼠标,或拖动手指,即可让滑动框跟着滑动。
    OK,功能需求都描绘出来了,接下来就要代码去实现吧!

    private RectTransform rt;  //需要控制的滑动窗体
    private Vector3 dragDelta; //滑动数据量
    public Camera renderCamera;//渲染滑动窗体的相机
    private bool isMouseDown;
    private bool isPointDown;
    //主要逻辑在Update里面
    private void Update()
    {
            //触摸
            if (Input.touchCount > 0)
            {
                Touch[] touches = Input.touches;
                int tCount = touches.Length;
                for(int i = 0; i < tCount; i++)
                {
                	//检测触摸及鼠标坐标是否在滑动窗口内
                    if (DetectedPosition(touches[i].position))
                    {
                        selectTouch = touches[i];
                        dragDelta = new Vector3(selectTouch.position.x, selectTouch.position.y, rt.anchoredPosition3D.z) - rt.anchoredPosition3D;
                        isPointDown = true;
                        break;
                    }
                }
            }
           //鼠标
            if(Input.GetMouseButtonDown(0))
            {
           		 //检测触摸及鼠标坐标是否在滑动窗口内
                if (DetectedPosition(Input.mousePosition))
                {   
                    dragDelta = Input.mousePosition- rt.anchoredPosition3D;
                    Debug.Log("MY");
                    //增加一层与Icon之间的触摸阻隔
                    isMouseDown = true;
                }
            }
            //这里开始实际控制滑动窗体的滑动
            if (isMouseDown&&!isPointDown)
            {  
            	//这是笔者自己封装在框架里的为transform修改localPosition的方法,因为项目实际只需要能在X轴上进行滑动。
              rt.SetLocalPos(Pos.x, Input.mousePosition.x - dragDelta.x);
            }
            else if (isPointDown&&!isMouseDown)
            {
                rt.SetLocalPos(Pos.x, selectTouch.position.x - dragDelta.x);   
            }
    }
    //检测触摸坐标是否在滑动窗体内
    //注意这里要指定一下渲染的相机是哪一个(以防在不同项目中,canvas的RenderMode不同,导致Rect失效)
    private bool DetectedPosition(Vector2 ScreenPosition)
    {
            if (RectTransformUtility.RectangleContainsScreenPoint
            (rt, ScreenPosition, renderCamera))
            {
                return true;
            }
            else
            {
                return false;
            }
    }
    ···
    

1.1. 一直到上一步,已经基本可以控制滑动窗口进行水平滑动了,接下来我们需要对窗口的显示画面

在这里插入图片描述
c_icon_group 附上刚刚的脚本,子物体都是滑动窗体实际的装载内容。
在这里插入图片描述
将渲染相机,赋给RenderCamera

在这里插入图片描述
2. 下一步,我们要让整个窗体的长度和起始位置,在编辑器运行时,能够自动适配装载内容的数量和内容。(数学问题,不过多解释原理,无非就是按照装载内容的位置坐标对窗体长度进行匹配计算。)

void Start(){
			rt = transform.GetComponent<RectTransform>();
            oldPos = rt.anchoredPosition3D;
            //设置content的宽高和位置
            int width =(int)(transform.GetChild(transform.childCount - 1).GetComponent<RectTransform>().anchoredPosition3D.x+ transform.GetChild(transform.childCount - 1).GetComponent<RectTransform>().sizeDelta.x);
            //这里笔者根据实际的项目分辨率,对宽度的上限与下限进行控制
            if (width <=1920)
            {
                width = 1920;
            }
            rt.sizeDelta = new Vector2(width, rt.sizeDelta.y);
            rt.SetLocalPos(Pos.x,width * 0.5f - Screen.width * 0.5f);
}
  1. ScrollView 能够在滑动尽头,还原到指定头尾位置。(Elasticity)
    对于这一需求,总的思路为:
    3.1 设定两个滑动的极限值,一为左滑初始值(同滑动条的初始位置值),一为右滑初始值。(这两个值,同样自匹配)
    3.2 在滑动结束时(即鼠标或手指抬起时),检测滑动窗体的位置,如果位置超过滑动窗体的极限值,即用Dotween动画,将滑动条还原到极限值【动画1】。
    3.3 用【动画1】的持续时间,来表现滑动条的弹性。

整体代码如下:

/***
* Copyright(C) by #Laughing#
* All rights reserved.
* FileName:     #ScrollView_Custom#
* Author:       #Laughing#
* Version:      #v1.0#
* Date:         #20190527_1200#
* Description:  ##
* History:      ##
***/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RDFW;
using System.Linq;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using System;
using DG.Tweening;

namespace RDFW
{
    public class RDScrollRect : MonoBehaviour
    {
        private bool isMouseDown;
        private bool isPointDown;
        private float endX;
        private float startX;
        public float endReserved;
        public Camera renderCamera;
        private GameObject mask;
        private Vector3 dragDelta=Vector3.zero;
        private RectTransform rt;
        private Vector3 oldPos;
        private Touch selectTouch;
        //补间动画对象
        private Tweener tweener;
        //窗体弹性
        [Range(0,1)]
        public float sensitive;
        private void Start()
        {  
            rt = transform.GetComponent<RectTransform>();
            rt.anchoredPosition3D = new Vector3(startX, rt.anchoredPosition3D.y, rt.anchoredPosition3D.z);
            oldPos = rt.anchoredPosition3D;
            //设置content的宽高和位置
            int width =(int)(transform.GetChild(transform.childCount - 1).GetComponent<RectTransform>().anchoredPosition3D.x+ transform.GetChild(transform.childCount - 1).GetComponent<RectTransform>().sizeDelta.x);
            if (width <=1920)
            {
                width = 1920;
            }
            rt.sizeDelta = new Vector2(width, rt.sizeDelta.y);
            rt.SetLocalPos(Pos.x,width * 0.5f - Screen.width * 0.5f);
            startX = width * 0.5f - Screen.width * 0.5f; 
            endX = ((width - Screen.width)*0.5f+endReserved)*-1;
            if (width == 1920)
            {
                endX = 0;
            }
        }
        private void Update()
        {
            //触摸
            if (Input.touchCount > 0)
            {
                Touch[] touches = Input.touches;
                int tCount = touches.Length;
                for(int i = 0; i < tCount; i++)
                {
                    if (DetectedPosition(touches[i].position))
                    {
                        selectTouch = touches[i];
                        dragDelta = new Vector3(selectTouch.position.x, selectTouch.position.y, rt.anchoredPosition3D.z) - rt.anchoredPosition3D;
                        isPointDown = true;
                        break;
                    }
                }
            }

            if(Input.GetMouseButtonDown(0))
            {
                if (DetectedPosition(Input.mousePosition))
                {   
                    dragDelta = Input.mousePosition- rt.anchoredPosition3D;
                    Debug.Log("MY");
                    //增加一层与Icon之间的触摸阻隔
                    isMouseDown = true;
                }
            }
            if (Input.GetMouseButtonUp(0))
            {
                isMouseDown = false;
                isPointDown = false;
                if (rt.anchoredPosition3D.x < endX)
                {
                    tweener?.Kill();
                    tweener=rt.DOLocalMoveX(endX,1- sensitive);
                }
                else if(rt.anchoredPosition3D.x > startX)
                {
                    tweener?.Kill();
                    tweener =rt.DOLocalMoveX(startX,1- sensitive);
                }
                
            }

            if (isMouseDown&&!isPointDown)
            {
                tweener?.Kill();
                rt.SetLocalPos(Pos.x, Input.mousePosition.x - dragDelta.x);
            }
            else if (isPointDown&&!isMouseDown)
            {     
                tweener?.Kill();
                rt.SetLocalPos(Pos.x, selectTouch.position.x - dragDelta.x);
            }

        } 

        private bool DetectedPosition(Vector2 ScreenPosition)
        {
            if (RectTransformUtility.RectangleContainsScreenPoint(transform.GetComponent<RectTransform>(), ScreenPosition, renderCamera))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        
    }
}
效果如下:

在这里插入图片描述
在这里插入图片描述

这里的EndReserved 可以根据具体需求来设定,意为末端位置的预留长度。
  1. 其实到这一步,我们的滑动条已经完成大半了,明显区别于Unity自带的ScrollView的地方,你会发现即便鼠标放在装载内容,即Icon上去拖动,也能控制滑动条。接下来,我们来处理
    “ScrollView 在滑动时,需要有效规避对动态装载内容的点击误判断。(滑动时,点选按钮无效;点选时,不滑动。)”

实现这个需求其实很简单,只要在为装载内容,即Icon按钮注册点击事件时,对鼠标滑动和触摸滑动事件做过滤即可,即当鼠标滑动,触摸滑动时,不触发按钮注册事件。

private void AddIcon(PointerEventData eventData)
{
        if (!eventData.dragging && !eventData.IsPointerMoving())
        {
            //这里做按钮按下,你想要做的事
        }
}
  1. 最后一步,我们为整个窗体在Hierarchy面板上的结构,添加一个父物体,并添加ugui遮罩组件,让窗口局部显示。
    在这里插入图片描述

最终效果如下:
在这里插入图片描述

【原创文章】转载请标记出处。-----拉婓Laughing----

Unity-UGUIUnity游戏引擎中的一个UI系统,可以用来创建和管理用户界面。它提供了丰富的功能和工具,使得开发者能够轻松地制作各种表格。 使用Unity-UGUI制作表格的步骤如下: 1. 创建Canvas对象:在Unity中,首先需要创建一个Canvas对象,作为UI渲染的容器。选择GameObject -> UI -> Canvas,即可创建一个Canvas对象。 2. 添加Table组件:选择Canvas对象,在Inspector面板中点击"Add Component"按钮,然后在搜索栏中输入"Table",选择适合的Table组件,点击添加。 3. 设置表格的行列数:在Table组件的Inspector面板中,设置表格所需的行数和列数。 4. 设置表格样式:可以在Inspector面板中设置表格的颜色、大小等属性,以满足具体需求。 5. 添加表格内容:可以通过代码或者拖拽方式,向表格中添加所需的文本或图片。可以通过操作表格的行列索引,将内容放置在特定的位置。 6. 设置表格的交互性:可以为表格中的每个单元格添加点击事件或其他交互效果,提升用户体验。 7. 调整表格布局:可以通过调整Canvas的大小、位置,或者改变组件之间的层次关系,来调整表格的布局。 8. 完善表格功能:可以根据具体需求,添加更多表格的功能,比如排序、过滤、搜索等。 9. 测试和优化:在表格制作完成后,可以进行测试,查看表格的显示效果和交互效果,并进行优化。 总之,使用Unity-UGUI制作表格,只需简单的操作和设置,就能够创建出各种样式、功能丰富的表格,满足游戏或应用程序的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值