【原创文章】转载请标记出处。-----拉婓Laughing----
制作滑动响应事件:
原型设计:我们来仔细想象在一款正常交互作品中,对滑动框的控制方式。当鼠标或者手指在滑动框上按下时,滑动框被激活;激活状态下,用户滑动鼠标,或拖动手指,即可让滑动框跟着滑动。 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); } 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; } } } }
实现这个需求其实很简单,只要在为装载内容,即Icon按钮注册点击事件时,对鼠标滑动和触摸滑动事件做过滤即可,即当鼠标滑动,触摸滑动时,不触发按钮注册事件。
private void AddIcon(PointerEventData eventData) { if (!eventData.dragging && !eventData.IsPointerMoving()) { //这里做按钮按下,你想要做的事 } } 最后一步,我们为整个窗体在Hierarchy面板上的结构,添加一个父物体,并添加ugui遮罩组件,让窗口局部显示。最终效果如下:
【原创文章】转载请标记出处。-----拉婓Laughing----