介绍
类似的玩意当我还是个安卓程序员的时候就用过很多了。只不过ugui里没有viewpaper可以用,需要我们自己写个组件。然后动画要配合animaiton了。
实现过程
1.滑动事件封装
基本上原封不动把滑动相关参数封装起来做成事件,把回调方法预留出来。
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
public class UISpriteDraggable : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler {
public class UISpriteDraggableDragEvent : UnityEvent<PointerEventData> {
}
public UISpriteDraggableDragEvent onBeginDrag = new UISpriteDraggableDragEvent(); //开始滑动事件
public UISpriteDraggableDragEvent onEndDrag = new UISpriteDraggableDragEvent(); //结束滑动事件
public UISpriteDraggableDragEvent onDragging = new UISpriteDraggableDragEvent(); //滑动中事件
public void OnBeginDrag(PointerEventData eventData) {
int inputCount = Input.touchCount;
if (inputCount > 1) {
return;
}
onBeginDrag?.Invoke(eventData);
}
public void OnEndDrag(PointerEventData eventData) {
int inputCount = Input.touchCount;
if (inputCount > 1) {
return;
}
onEndDrag?.Invoke(eventData);
}
public void OnDrag(PointerEventData eventData) {
int inputCount = Input.touchCount;
if (inputCount > 1) {
return;
}
onDragging?.Invoke(eventData);
}
}
2.轮播组件实现
因为我们项目是手游,从这里开始就写在lua了。
我们也起了个ViewPaper的名字,把UISpriteDraggable传来的参数稍微封装一下,改成了左划成功,优化成功,滑动中,滑动取消,这4个事件。
--------------------------------------------------------------
-- @file ViewPaper.lua
-- @brief 滑动切换页面
--
-- @author Shadowrabbit
--
-- @Modified 2020/07/24 10:10
-- @Copyright Copyright (c) 2020, Shadowrabbit
--============================================================
local assert = assert
local UIDef = class.get("UIDef") --- @type UIDef
local Logger = class.get("Logger") --- @type Logger
--- @class ViewPaper : UIBasePanel
local ViewPaper = assert(self)
ViewPaper.KeepAlive = true -- 对于UILayer表示是否游戏状态切换时需要保留,对于其它panel表示是否要放入缓存
ViewPaper.FullScreen = false -- 是否全屏,比全屏界面层级低的界面需要隐藏
ViewPaper.PanelType = UIDef.PanelType.UIRepeatable -- panel类型
ViewPaper.Reuse = true
ViewPaper.Modules = {}
ViewPaper.Bindings = {}
local kTriggerDistance = 1000
--- @brief 构造函数
--- @protected
function ViewPaper:Initialize()
end
--- @brief 析构函数
--- @protected
function ViewPaper:Finalize()
end
--- @brief 打开时回调
--- @protected
function ViewPaper:OnOpen()
local compSpriteDraggable = self.transform:GetComponent("UISpriteDraggable")
compSpriteDraggable.onBeginDrag:AddListener(self:CoSlot(self, self.OnBeginDrag))
compSpriteDraggable.onEndDrag:AddListener(self:CoSlot(self, self.OnEndDrag))
compSpriteDraggable.onDragging:AddListener(self:CoSlot(self, self.OnDragging))
end
--- @brief 关闭时回调
--- @protected
function ViewPaper:OnClose()
local compSpriteDraggable = self.transform:GetComponent("UISpriteDraggable")
compSpriteDraggable.onBeginDrag:RemoveAllListeners()
compSpriteDraggable.onEndDrag:RemoveAllListeners()
compSpriteDraggable.onDragging:RemoveAllListeners()
self.beginDragPos = nil
self.currentDragPos = nil
self.endDragPos = nil
self.onDragging = nil
self.onCancelDrag = nil
self.onDragToLeft = nil
self.onDragToRight = nil
end
--- @brief 更新时回调
--- @protected
function ViewPaper:OnRefresh()
end
--- @brief 滑动中事件
--- @public
function ViewPaper:SetOnDraggingListener(listener)
assert(listener, "listener == nil")
self.onDragging = listener
end
--- @brief 左划事件
--- @public
function ViewPaper:SetOnDragToLeftListener(listener)
assert(listener, "listener == nil")
self.onDragToLeft = listener
end
--- @brief 右划事件
--- @public
function ViewPaper:SetOnDragToRightListener(listener)
assert(listener, "listener == nil")
self.onDragToRight = listener
end
--- @brief 取消滑动事件
--- @public
function ViewPaper:SetOnCancelDragListener(listener)
assert(listener, "listener == nil")
self.onCancelDrag = listener
end
--- @brief
--- @public
function ViewPaper:RemoveOnDraggingListener()
self.onDragging = nil
end
--- @brief
--- @public
function ViewPaper:RemoveOnDragToLeftListener()
self.onDragToLeft = nil
end
--- @brief
--- @public
function ViewPaper:RemoveOnDragToRightListener()
self.onDragToRight = nil
end
--- @brief
--- @public
function ViewPaper:RemoveOnCancelDragListener()
self.onCancelDrag = nil
end
--- @brief
--- @public
function ViewPaper:RemoveAllListeners()
self:RemoveOnCancelDragListener()
self:RemoveOnDraggingListener()
self:RemoveOnDragToLeftListener()
self:RemoveOnDragToRightListener()
end
--- @brief 开始滑动
--- @private
--- @param pointerEventData UnityEngine.EventSystems.PointerEventData
function ViewPaper:OnBeginDrag(pointerEventData)
self.isDragging = true
self.beginDragPos = pointerEventData.position
end
--- @brief 结束滑动
--- @private
function ViewPaper:OnEndDrag(pointerEventData)
if not self.isDragging then
return
end
self.isDragging = false
self.endDragPos = pointerEventData.position
--进度百分比 正数为右划
local progressValue = (self.endDragPos.x - self.beginDragPos.x) / kTriggerDistance
--右划事件
if self.endDragPos.x - self.beginDragPos.x > kTriggerDistance / 4 then
if not self.onDragToRight then
return
end
self.onDragToRight(progressValue)
return
end
--左划事件
if self.beginDragPos.x - self.endDragPos.x > kTriggerDistance / 4 then
if not self.onDragToLeft then
return
end
self.onDragToLeft(progressValue)
return
end
--取消滑动事件
if not self.onCancelDrag then
return
end
self.onCancelDrag(progressValue)
end
--- @brief 滑动中
--- @private
function ViewPaper:OnDragging(pointerEventData)
self.currentDragPos = pointerEventData.position
--滑动中回调
if not self.onDragging then
return
end
--进度百分比 正数为右划
local progressValue = (self.currentDragPos.x - self.beginDragPos.x) / kTriggerDistance
self.onDragging(progressValue)
end
3.实现类
把对应事件实现一下,panel2就是viewpaper的组件类,一共3个panel,分别是左中右三个页面,我们只对panel2操作,滑动结束后重置动画panel2就回到屏幕中间了。
---@brief 激活
---private
function XXXXX:OnActivate(gameObject)
self.panel2:SetOnCancelDragListener(self:CoSlot(self, self.OnCancelDrag))
self.panel2:SetOnDraggingListener(self:CoSlot(self, self.OnDragging))
self.panel2:SetOnDragToLeftListener(self:CoSlot(self, self.OnDragToLeft))
self.panel2:SetOnDragToRightListener(self:CoSlot(self, self.OnDragToRight))
end
关闭的时候别忘了取消监听
function XXXXX:OnClose()
self.panel2:RemoveAllListeners()
end
对应几个事件的实现方法,我们在这里多实现了两个方法CoAutoPlayDraggingAnimationToEnd()和
CoAutoPlayDraggingAnimationToStart(),这两个方法都是跑在协成里的,滑动一半时松手,对应的补充滑动。
--- @private
function XXXXX:OnCancelDrag(progressValue)
if self.isAutoPlaying then
return
end
self:CoParameterizedSlot(self, self.CoAutoPlayDraggingAnimationToStart, progressValue)()
end
--- @private
function XXXXX:OnDragging(progressValue)
if self.isAutoPlaying then
return
end
local isDragToRight = progressValue > 0
--右划没数据了
if isDragToRight and self:CheckIfOutOfBound(self.currentPage - 1) then
return
end
if not isDragToRight and self:CheckIfOutOfBound(self.currentPage + 1) then
return
end
--修正透明度和缩放 当前有可能正在播放静态动画
self.panelPageView.localScale = Vector3(1, 1, 1)
local fixProgressValue = math.abs(progressValue)
--左右滑动时关闭没有使用的立绘 防止暴力滑动导致左右立绘留在屏幕内
self.panel1.gameObject:SetActive(progressValue >= 0)
self.panel3.gameObject:SetActive(progressValue < 0)
self:SetClip(self.panelPageView, isDragToRight and "youhua" or "zuohua")
self:SetAnimationProgress(self.panelPageView, fixProgressValue)
end
--- @private
function XXXXX:OnDragToLeft(progressValue)
if self:CheckIfOutOfBound(self.currentPage + 1) then
return
end
self:CoParameterizedSlot(self, self.CoAutoPlayDraggingAnimationToEnd, progressValue)()
end
--- @private
function XXXXX:OnDragToRight(progressValue)
if self:CheckIfOutOfBound(self.currentPage - 1) then
return
end
self:CoParameterizedSlot(self, self.CoAutoPlayDraggingAnimationToEnd, progressValue)()
end
--- @brief 检测数据是否越界
--- @private
function XXXXX:CheckIfOutOfBound(chapterId)
--范围越界
if chapterId <= 0 then
return true
end
local config = sq.facade:GetConfigManager():TryGetConfig("ChapterConf_Catalog", "ChapterId", chapterId)
if config == nil then
return true
end
return false
end
--- @brief
--- @private
function XXXXX:CoAutoPlayDraggingAnimationToStart(progressValue)
self.isAutoPlaying = true
local isDragToRight = progressValue > 0
local fixProgressValue = math.abs(progressValue)
--当前进度没到0% 补充滑动
while fixProgressValue > 0 do
--动画20帧 按原速度进度为0.05
fixProgressValue = Mathf.Clamp01(fixProgressValue - 0.05)
self:SetClip(self.panelPageView, isDragToRight and "youhua" or "zuohua")
self:SetAnimationProgress(self.panelPageView, fixProgressValue)
self:WaitForFrames(1)
end
--重置动画还原状态
self:ResetAnimation(self.panelPageView, true)
self.isAutoPlaying = false
end
--- @brief
--- @private
function XXXXX:CoAutoPlayDraggingAnimationToEnd(progressValue)
self.isAutoPlaying = true
local isDragToRight = progressValue > 0
local fixProgressValue = math.abs(progressValue)
--当前进度没到100% 补充滑动
while fixProgressValue < 1 do
--动画20帧 按原速度进度为0.05
fixProgressValue = Mathf.Clamp01(fixProgressValue + 0.05)
self:SetClip(self.panelPageView, isDragToRight and "youhua" or "zuohua")
self:SetAnimationProgress(self.panelPageView, fixProgressValue)
self:WaitForFrames(1)
end
--重置动画还原状态
self:ResetAnimation(self.panelPageView, true)
--刷新滑动后的数据
self.currentPage = isDragToRight and self.currentPage - 1 or self.currentPage + 1
self:RefreshView()
self.isAutoPlaying = false
end
4.动画控制
原理是把动画的播放速度设置成0,完全移交给程序来控制进度。程序这边通过协成在每一帧改变动画的进度
--- @brief 设置动画进度
--- @public
function AnimationUtils.SetProgress(transform, progressValue)
--- @type UnityEngine.Animation
local compAni = transform:GetComponent("Animation")
compAni:Play()
--- @type UnityEngine.AnimationState get_Item为C#的this[]用法
local state = compAni:get_Item(compAni.clip.name)
state.normalizedTime = progressValue
state.speed = 0
end
--- @brief 设置动画片段
--- @public
function AnimationUtils.SetClip(transform, name)
--- @type UnityEngine.Animation
local compAni = transform:GetComponent("Animation")
compAni.clip = compAni:GetClip(name) --设置当前片段
end