| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- Public Class MyPageRight
- Inherits AdornerDecorator
- Public PageUuid As Integer = GetUuid()
- '“返回顶部” 按钮检测的滚动区域
- Public Property PanScroll As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664)
- Get
- Return GetValue(PanScrollProperty)
- End Get
- Set(value As String)
- SetValue(PanScrollProperty, value)
- End Set
- End Property
- Private Shared ReadOnly PanScrollProperty =
- DependencyProperty.Register("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing))
- '当前状态
- Public Enum PageStates
- Empty '默认状态,页面全空
- LoaderWait '加载环初始等待
- LoaderEnter '加载环进入动画
- LoaderStayForce '加载环正常显示(强制等待)
- LoaderStay '加载环正常显示
- LoaderExit '加载环退出动画
- ContentEnter '内容进入动画
- ContentStay '内容正常显示
- ContentExit '刷新导致的全部退出动画,或页面内容退出(子页面更改)导致的全部退出动画
- PageExit '切换页面导致的全部退出动画
- End Enum
- Private _PageState As PageStates = PageStates.Empty
- Public Property PageState As PageStates
- Get
- Return _PageState
- End Get
- Set(value As PageStates)
- If _PageState = value Then Return
- _PageState = value
- If ModeDebug Then Log("[UI] 页面状态切换为 " & GetStringFromEnum(value))
- End Set
- End Property
- #Region "加载器"
- Private PageLoader As LoaderBase
- Private PageLoaderInputInvoke
- Private PageLoaderUi As MyLoading
- Private PanLoader As FrameworkElement
- Private PanContent As FrameworkElement
- Private PanAlways As FrameworkElement
- Private PageLoaderAutoRun As Boolean
- '初始化
- ''' <summary>
- ''' 表明页面存在需要在后台执行的加载器。
- ''' </summary>
- ''' <param name="LoaderUi">MyLoading 控件。</param>
- ''' <param name="PanLoader">MyLoading 控件对应的卡片。</param>
- ''' <param name="PanContent">加载结束后出现的内容容器。</param>
- ''' <param name="PanAlways">无论是否在加载总是要显示的容器。可以为 Nothing。</param>
- ''' <param name="RealLoader">在工作线程执行的加载器。</param>
- ''' <param name="FinishedInvoke">当加载器执行完成,在 UI 线程触发的 UI 初始化事件。</param>
- Public Sub PageLoaderInit(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
- RealLoader As LoaderBase,
- Optional FinishedInvoke As Action(Of LoaderBase) = Nothing, Optional InputInvoke As Func(Of Object) = Nothing,
- Optional AutoRun As Boolean = True)
- '初始化参数
- Me.PanLoader = PanLoader
- Me.PanContent = PanContent
- Me.PanAlways = PanAlways
- Me.PageLoader = RealLoader
- Me.PageLoaderUi = LoaderUi
- Me.PageLoaderInputInvoke = InputInvoke
- Me.PageLoaderAutoRun = AutoRun
- '添加结束 Invoke
- If FinishedInvoke IsNot Nothing Then
- AddHandler RealLoader.PreviewFinish,
- Sub()
- Do While PageState = MyPageRight.PageStates.PageExit OrElse PageState = MyPageRight.PageStates.ContentExit
- Thread.Sleep(10) '不在退出动画时执行 UI 线程操作,避免退出动画被重置
- Loop
- RunInUiWait(Sub() FinishedInvoke(RealLoader))
- Thread.Sleep(20) '由于大量初始化控件会导致掉帧,延迟触发 State 改变事件
- End Sub
- End If
- AddHandler RealLoader.OnStateChangedUi, Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) RunInUi(Sub() PageLoaderState(Loader, NewState, OldState))
- '隐藏 UI
- PanLoader.Visibility = Visibility.Collapsed
- PanContent.Visibility = Visibility.Collapsed
- If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
- '初次运行加载器
- If PageLoaderAutoRun Then
- If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
- PageLoader.Start(CType(PageLoader, Object).StartGetInput(Nothing, PageLoaderInputInvoke))
- Else
- Dim Input = Nothing
- If PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
- PageLoader.Start(Input)
- End If
- End If
- If PageLoader.State = LoadState.Finished AndAlso FinishedInvoke IsNot Nothing Then
- RunInUiWait(Sub() FinishedInvoke(RealLoader)) '加载器已提前完成,直接触发事件
- End If
- '设置加载环
- PageLoaderUi.State = RealLoader
- AddHandler PageLoaderUi.Click, Sub() If RealLoader.State = LoadState.Failed Then PageLoaderRestart() '点击重试事件
- End Sub
- '重试
- Public Sub PageLoaderRestart(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = True) '由外部调用的重试
- If Not PageLoaderAutoRun Then Return
- If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
- PageLoader.Start(CType(PageLoader, Object).StartGetInput(Input, PageLoaderInputInvoke), IsForceRestart:=IsForceRestart)
- Else
- If Input Is Nothing AndAlso PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
- PageLoader.Start(Input, IsForceRestart:=IsForceRestart)
- End If
- End Sub
- #End Region
- #Region "事件"
- '外部触发的事件
- ''' <summary>
- ''' 需要切换到当前页面,并且原本的 Loaded 事件已执行完成。
- ''' 需要根据加载器状态,从 Empty 切换到 ContentEnter、LoaderWait、LoaderEnter。
- ''' </summary>
- Public Sub PageOnEnter()
- If ModeDebug Then Log("[UI] 已触发 PageOnEnter")
- RaiseEvent PageEnter()
- Select Case PageState
- Case PageStates.Empty
- If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
- '如果加载器在进入页面时不启动(例如联机),那么在此时就会有 State = Waiting
- PageState = PageStates.ContentEnter
- TriggerEnterAnimation(PanAlways, If(PanContent, Child))
- ElseIf PageLoader.State = LoadState.Loading Then
- PageState = PageStates.LoaderWait
- AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 400), "PageRight PageChange " & PageUuid)
- Else 'PageLoader.State = LoadState.Failed
- PageState = PageStates.LoaderEnter
- TriggerEnterAnimation(PanAlways, PanLoader)
- End If
- Case PageStates.ContentExit
- '和上面的一样,但是不管 PanAlways
- If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
- PageState = PageStates.ContentEnter
- TriggerEnterAnimation(If(PanContent, Child))
- ElseIf PageLoader.State = LoadState.Loading Then
- PageState = PageStates.LoaderWait
- AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 400), "PageRight PageChange " & PageUuid)
- Else 'PageLoader.State = LoadState.Failed
- PageState = PageStates.LoaderEnter
- TriggerEnterAnimation(PanLoader)
- End If
- Case PageStates.ContentEnter
- '重复调用 PageOnEnter,直接忽略
- Case Else
- Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnter 事件。")
- End Select
- End Sub
- Public Event PageEnter()
- ''' <summary>
- ''' 需要切换到其他页面。
- ''' 需要立即切换至 PageExit 或 Empty。
- ''' </summary>
- Public Sub PageOnExit()
- If ModeDebug Then Log("[UI] 已触发 PageOnExit")
- RaiseEvent PageExit()
- Select Case PageState
- Case PageStates.ContentEnter, PageStates.ContentStay
- PageState = PageStates.PageExit
- TriggerExitAnimation(PanAlways, If(PanContent, Child))
- Case PageStates.LoaderEnter, PageStates.LoaderStayForce, PageStates.LoaderStay
- PageState = PageStates.PageExit
- TriggerExitAnimation(PanAlways, PanLoader)
- Case PageStates.LoaderWait
- PageState = PageStates.PageExit
- TriggerExitAnimation(PanAlways)
- Case PageStates.LoaderExit, PageStates.ContentExit
- PageState = PageStates.PageExit
- If PanAlways IsNot Nothing Then TriggerExitAnimation(PanAlways, If(PanContent, Child))
- Case PageStates.PageExit, PageStates.Empty
- End Select
- End Sub
- Public Event PageExit()
- ''' <summary>
- ''' 即将切换到其他页面,需要强制完成页面状态清理。
- ''' 需要立即切换至 Empty。
- ''' </summary>
- Public Sub PageOnForceExit()
- If PageState = PageStates.Empty Then Return
- If ModeDebug Then Log("[UI] 已触发 PageOnForceExit")
- PageState = PageStates.Empty
- AniStop("PageRight PageChange " & PageUuid)
- '由于动画会被强制中止,所以需要手动进行隐藏
- If PageLoader Is Nothing Then
- Child.Visibility = Visibility.Collapsed
- Else
- PanContent.Visibility = Visibility.Collapsed
- PanLoader.Visibility = Visibility.Collapsed
- If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
- End If
- End Sub
- ''' <summary>
- ''' PanContent 中的子页面改变,需要让当前内容退出,再显示新的内容。
- ''' 需要在 PageEnter 事件确认要显示的子页面有哪些。
- ''' </summary>
- Public Sub PageOnContentExit()
- If ModeDebug Then Log("[UI] 已触发 PageOnContentExit")
- If PageLoader IsNot Nothing AndAlso PageLoader.State = LoadState.Loading Then
- 'Loading 的加载器可能触发进一步变化,难以预测会触发子页面的动画还是加载器完成的动画
- Throw New Exception("在调用 PageOnContentExit 时,加载器不能为 Loading 状态")
- End If
- Select Case PageState
- Case PageStates.ContentEnter, PageStates.ContentStay
- PageState = PageStates.ContentExit
- TriggerExitAnimation(PanContent)
- Case PageStates.LoaderExit
- PageState = PageStates.ContentExit
- Case PageStates.LoaderEnter, PageStates.LoaderStayForce, PageStates.LoaderStay
- PageState = PageStates.ContentExit
- TriggerExitAnimation(PanLoader)
- Case PageStates.LoaderWait
- PageOnEnter()
- Case PageStates.Empty
- '可能是在离开页面后,其他后台线程调用了本函数,此时不应触发 PageOnEnter()
- '例如:在加入联机房间后,切换到其他页面,然后联机房间被解散
- If IsLoaded Then PageOnEnter()
- End Select
- End Sub
- '内部触发的事件
- ''' <summary>
- ''' 逐个进入动画已执行完成。
- ''' 需要根据目前状态,从 ContentEnter 切换到 ContentStay,或从 LoaderEnter 切换到 LoaderStayForce。
- ''' </summary>
- Private Sub PageOnEnterAnimationFinished()
- If ModeDebug Then Log("[UI] 已触发 PageOnEnterAnimationFinished")
- Select Case PageState
- Case PageStates.ContentEnter
- PageState = PageStates.ContentStay
- Case PageStates.LoaderEnter
- PageState = PageStates.LoaderStayForce
- AniStart(AaCode(AddressOf PageOnLoaderStayFinished, 400), "PageRight PageChange " & PageUuid)
- Case Else
- Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnterAnimationFinished 事件。")
- End Select
- End Sub
- ''' <summary>
- ''' 逐个退出动画已执行完成。
- ''' 需要根据目前状态,从 AllExit 切换到 Empty,或从 LoaderExit 切换到 ContentEnter,或从 ContentExit 重新触发 PageOnEnter。
- ''' </summary>
- Private Sub PageOnExitAnimationFinished()
- If ModeDebug Then Log("[UI] 已触发 PageOnExitAnimationFinished")
- Select Case PageState
- Case PageStates.PageExit
- PageState = PageStates.Empty
- Case PageStates.ContentExit
- PageOnEnter()
- Case PageStates.LoaderExit
- PageState = PageStates.ContentEnter
- TriggerEnterAnimation(PanContent)
- Case Else
- Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnExitAnimationFinished 事件。")
- End Select
- End Sub
- ''' <summary>
- ''' 加载环进入等待已结束。
- ''' 需要从 LoaderWait 切换到 LoaderEnter。
- ''' </summary>
- Private Sub PageOnLoaderWaitFinished()
- If ModeDebug Then Log("[UI] 已触发 PageOnLoaderWaitFinished")
- Select Case PageState
- Case PageStates.LoaderWait
- PageState = PageStates.LoaderEnter
- If PanAlways IsNot Nothing AndAlso PanAlways.Visibility = Visibility.Collapsed Then
- TriggerEnterAnimation(PanAlways, PanLoader)
- Else
- TriggerEnterAnimation(PanLoader)
- End If
- Case Else
- Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
- End Select
- End Sub
- ''' <summary>
- ''' 加载环展示等待已结束。
- ''' 需要从 LoaderStayForce 切换到 LoaderStay 或 LoaderExit。
- ''' </summary>
- Private Sub PageOnLoaderStayFinished()
- If ModeDebug Then Log("[UI] 已触发 PageOnLoaderStayFinished")
- Select Case PageState
- Case PageStates.LoaderStayForce
- If PageLoader.State = LoadState.Finished Then
- PageState = PageStates.LoaderExit
- TriggerExitAnimation(PanLoader)
- Else
- PageState = PageStates.LoaderStay
- End If
- Case Else
- Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
- End Select
- End Sub
- ''' <summary>
- ''' 全局加载状态已改变。
- ''' </summary>
- Private Sub PageLoaderState(sender As Object, NewState As LoadState, OldState As LoadState)
- Select Case NewState
- Case LoadState.Failed, LoadState.Loading
- If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Return
- If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Start/Refresh)")
- '(重新)开始运行
- '需要从部分状态切换到 ReloadExit
- Select Case PageState
- Case PageStates.ContentEnter, PageStates.ContentStay
- PageState = PageStates.ContentExit
- TriggerExitAnimation(PanContent)
- Case PageStates.LoaderExit
- PageState = PageStates.ContentExit
- End Select
- Case LoadState.Finished, LoadState.Aborted, LoadState.Waiting
- If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Return
- If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop/Abort)")
- '运行结束
- '需要从 LoaderWait 切换到 ContentEnter,或从 LoaderStay 切换到 LoaderExit
- Select Case PageState
- Case PageStates.LoaderWait
- PageState = PageStates.ContentEnter
- If PanAlways IsNot Nothing AndAlso PanAlways.Visibility = Visibility.Collapsed Then
- TriggerEnterAnimation(PanAlways, PanContent)
- Else
- TriggerEnterAnimation(PanContent)
- End If
- Case PageStates.LoaderStay
- PageState = PageStates.LoaderExit
- TriggerExitAnimation(PanLoader)
- End Select
- End Select
- End Sub
- #End Region
- #Region "动画"
- '逐个进入动画
- Public Sub TriggerEnterAnimation(ParamArray Elements As FrameworkElement())
- Dim RealElements = Elements.Where(Function(e) e IsNot Nothing)
- For Each Element In RealElements
- Element.Visibility = Visibility.Visible '页面均处于默认的隐藏状态
- Next
- Dim AniList As New List(Of AniData)
- Dim Delay As Integer = 0
- '基础动画
- For Each Element In RealElements
- For Each Control As FrameworkElement In GetAllAnimControls(Element, True)
- '还原被隐藏的卡片的消失动画
- Control.IsHitTestVisible = True
- If Control.RenderTransform IsNot Nothing AndAlso TypeOf Control.RenderTransform Is TranslateTransform Then Control.RenderTransform = Nothing
- Next
- For Each Control As FrameworkElement In GetAllAnimControls(Element)
- If TypeOf Control Is MyExtraTextButton Then
- CType(Control, MyExtraTextButton).Show = True
- Else
- Control.Opacity = 0
- Control.RenderTransform = New TranslateTransform(0, -16)
- AniList.Add(AaOpacity(Control, 1, 100, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
- AniList.Add(AaTranslateY(Control, 5, 250, Delay, New AniEaseOutFluent))
- AniList.Add(AaTranslateY(Control, 11, 350, Delay, New AniEaseOutBack))
- Delay += 25
- End If
- Next
- Next
- '滚动条动画
- Dim Scroll As MyScrollBar = GetFirstScrollViewer(RealElements)
- If Scroll IsNot Nothing Then
- If TypeOf Scroll.RenderTransform IsNot TranslateTransform Then Scroll.RenderTransform = New TranslateTransform(10, 0)
- AniList.Add(AaTranslateX(Scroll, -CType(Scroll.RenderTransform, TranslateTransform).X, 350, 0, New AniEaseOutFluent))
- End If
- '结束
- AniList.Add(AaCode(Sub() PageOnEnterAnimationFinished(),, True))
- AniStart(AniList, "PageRight PageChange " & PageUuid, True)
- End Sub
- '逐个退出动画
- Public Sub TriggerExitAnimation(ParamArray Elements As FrameworkElement())
- Dim RealElements = Elements.Where(Function(e) e IsNot Nothing)
- Dim AniList As New List(Of AniData)
- Dim Delay As Integer = 0
- For Each Element In RealElements
- For Each Control As FrameworkElement In GetAllAnimControls(Element)
- If TypeOf Control Is MyExtraTextButton Then
- CType(Control, MyExtraTextButton).Show = False
- Else
- Control.IsHitTestVisible = False
- AniList.Add(AaOpacity(Control, -1, 70, Delay))
- AniList.Add(AaTranslateY(Control, -6, 70, Delay))
- Delay += 15
- End If
- Next
- Next
- '滚动条动画
- Dim Scroll As MyScrollBar = GetFirstScrollViewer(RealElements)
- If Scroll IsNot Nothing Then
- If TypeOf Scroll.RenderTransform IsNot TranslateTransform Then Scroll.RenderTransform = New TranslateTransform
- AniList.Add(AaTranslateX(Scroll, 10 - CType(Scroll.RenderTransform, TranslateTransform).X, 90, 0, New AniEaseInFluent))
- End If
- '结束
- AniList.Add(AaCode(
- Sub()
- For Each Element In RealElements
- Element.Visibility = Visibility.Collapsed
- Next
- PageOnExitAnimationFinished()
- End Sub,, True))
- AniStart(AniList, "PageRight PageChange " & PageUuid)
- End Sub
- ''' <summary>
- ''' 禁用页面切换动画的控件列表。
- ''' </summary>
- Public DisabledPageAnimControls As New List(Of FrameworkElement)
- ''' <summary>
- ''' 遍历获取所有需要生成动画的控件。
- ''' </summary>
- Friend Function GetAllAnimControls(Element As FrameworkElement, Optional IgnoreInvisibility As Boolean = False) As IEnumerable(Of FrameworkElement)
- Dim AllControls As New List(Of FrameworkElement)
- _GetAllAnimControls(Element, AllControls, IgnoreInvisibility)
- Return AllControls.Except(DisabledPageAnimControls)
- End Function
- Private Sub _GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
- If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return
- If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is MyExtraTextButton OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then
- AllControls.Add(Element)
- ElseIf TypeOf Element Is ContentControl Then
- Dim Content = CType(Element, ContentControl).Content
- If Content IsNot Nothing AndAlso TypeOf Content Is FrameworkElement Then _GetAllAnimControls(Content, AllControls, IgnoreInvisibility)
- ElseIf TypeOf Element Is Panel Then
- For Each Element2 In CType(Element, Panel).Children
- If TypeOf Element2 Is FrameworkElement Then _GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
- Next
- End If
- End Sub
- '查找列表中的第一个滚动条
- Private Function GetFirstScrollViewer(Elements As IEnumerable(Of FrameworkElement)) As MyScrollBar
- Dim Viewer As MyScrollViewer = Nothing
- For Each Element In Elements
- If TypeOf Element Is MyScrollViewer Then
- Viewer = Element
- GoTo FindViewer
- End If
- For Each Control In LogicalTreeHelper.GetChildren(Element)
- If TypeOf Control Is MyScrollViewer Then
- Viewer = Control
- GoTo FindViewer
- End If
- Next
- Next
- Return Nothing
- FindViewer:
- If Viewer.ComputedVerticalScrollBarVisibility <> Visibility.Visible Then Return Nothing
- Return Viewer.ScrollBar
- End Function
- #End Region
- End Class
|