MyPageRight.vb 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. Public Class MyPageRight
  2. Inherits AdornerDecorator
  3. Public PageUuid As Integer = GetUuid()
  4. '当前状态
  5. Public Enum PageStates
  6. Empty '默认状态,页面全空
  7. LoaderWait '加载环初始等待
  8. LoaderEnter '加载环进入动画
  9. LoaderStayForce '加载环正常显示(强制等待)
  10. LoaderStay '加载环正常显示
  11. LoaderExit '加载环退出动画
  12. ContentEnter '内容进入动画
  13. ContentStay '内容正常显示
  14. ReloadExit '刷新导致的全部退出动画
  15. PageExit '切换页面导致的全部退出动画
  16. End Enum
  17. Private _PageState As PageStates = PageStates.Empty
  18. Public Property PageState As PageStates
  19. Get
  20. Return _PageState
  21. End Get
  22. Set(value As PageStates)
  23. If _PageState = value Then Exit Property
  24. _PageState = value
  25. If ModeDebug Then Log("[UI] 页面状态切换为 " & GetStringFromEnum(value))
  26. End Set
  27. End Property
  28. #Region "加载器"
  29. Private PageLoader As LoaderBase
  30. Private PageLoaderInputInvoke
  31. Private PageLoaderUi As MyLoading
  32. Private PanLoader As FrameworkElement
  33. Private PanContent As FrameworkElement
  34. Private PanAlways As FrameworkElement
  35. Private PageLoaderAutoRun As Boolean
  36. '初始化
  37. ''' <summary>
  38. ''' 表明页面存在需要在后台执行的加载器。
  39. ''' </summary>
  40. ''' <param name="LoaderUi">MyLoading 控件。</param>
  41. ''' <param name="PanLoader">MyLoading 控件对应的卡片。</param>
  42. ''' <param name="PanContent">加载结束后出现的内容容器。</param>
  43. ''' <param name="PanAlways">无论是否在加载总是要显示的容器。可以为 Nothing。</param>
  44. ''' <param name="RealLoader">在工作线程执行的加载器。</param>
  45. ''' <param name="FinishedInvoke">当加载器执行完成,在 UI 线程触发的 UI 初始化事件。</param>
  46. Public Sub PageLoaderInit(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
  47. RealLoader As LoaderBase, FinishedInvoke As Action(Of LoaderBase), Optional InputInvoke As Func(Of Object) = Nothing,
  48. Optional AutoRun As Boolean = True)
  49. '初始化参数
  50. Me.PanLoader = PanLoader
  51. Me.PanContent = PanContent
  52. Me.PanAlways = PanAlways
  53. Me.PageLoader = RealLoader
  54. Me.PageLoaderUi = LoaderUi
  55. Me.PageLoaderInputInvoke = InputInvoke
  56. Me.PageLoaderAutoRun = AutoRun
  57. '添加结束 Invoke
  58. AddHandler RealLoader.PreviewFinish,
  59. Sub()
  60. Do While PageState = MyPageRight.PageStates.PageExit OrElse PageState = MyPageRight.PageStates.ReloadExit
  61. Thread.Sleep(10) '不在退出动画时执行 UI 线程操作,避免退出动画被重置
  62. Loop
  63. RunInUiWait(Sub() FinishedInvoke(RealLoader))
  64. Thread.Sleep(20) '由于大量初始化控件会导致掉帧,延迟触发 State 改变事件
  65. End Sub
  66. AddHandler RealLoader.OnStateChangedUi, Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) RunInUi(Sub() PageLoaderState(Loader, NewState, OldState))
  67. '隐藏 UI
  68. PanLoader.Visibility = Visibility.Collapsed
  69. PanContent.Visibility = Visibility.Collapsed
  70. If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
  71. '初次运行加载器
  72. If PageLoaderAutoRun Then
  73. If PageLoader.GetType.Name.StartsWith("LoaderTask") Then
  74. PageLoader.Start(CType(PageLoader, Object).StartGetInput(Nothing, PageLoaderInputInvoke))
  75. Else
  76. Dim Input = Nothing
  77. If PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
  78. PageLoader.Start(Input)
  79. End If
  80. End If
  81. If PageLoader.State = LoadState.Finished Then
  82. RunInUiWait(Sub() FinishedInvoke(RealLoader)) '加载器已提前完成,直接触发事件
  83. End If
  84. '设置加载环
  85. PageLoaderUi.State = RealLoader
  86. AddHandler PageLoaderUi.Click, Sub() If RealLoader.State = LoadState.Failed Then PageLoaderRestart() '点击重试事件
  87. End Sub
  88. '重试
  89. Public Sub PageLoaderRestart(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = True) '由外部调用的重试
  90. If Not PageLoaderAutoRun Then Exit Sub
  91. If PageLoader.GetType.Name.StartsWith("LoaderTask") Then
  92. PageLoader.Start(CType(PageLoader, Object).StartGetInput(Input, PageLoaderInputInvoke), IsForceRestart:=IsForceRestart)
  93. Else
  94. If Input Is Nothing AndAlso PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
  95. PageLoader.Start(Input, IsForceRestart:=IsForceRestart)
  96. End If
  97. End Sub
  98. #End Region
  99. #Region "事件"
  100. '外部触发的事件
  101. ''' <summary>
  102. ''' 需要切换到当前页面,并且原本的 Loaded 事件已执行完成。
  103. ''' 需要根据加载器状态,从 Empty 切换到 ContentEnter、LoaderWait、LoaderEnter。
  104. ''' </summary>
  105. Public Sub PageOnEnter()
  106. If ModeDebug Then Log("[UI] 已触发 PageOnEnter")
  107. RaiseEvent OnPageEnter()
  108. Select Case PageState
  109. Case PageStates.Empty
  110. If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished Then
  111. PageState = PageStates.ContentEnter
  112. TriggerEnterAnimation(PanAlways, If(PanContent, Child))
  113. ElseIf PageLoader.State = LoadState.Loading Then
  114. PageState = PageStates.LoaderWait
  115. AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
  116. ElseIf PageLoader.State = LoadState.Failed Then
  117. PageState = PageStates.LoaderEnter
  118. TriggerEnterAnimation(PanAlways, PanLoader)
  119. ElseIf PageLoader.State = LoadState.Waiting Then
  120. '由于 “页面加载器 Minecraft Version List 不应在 PageOnEnter 事件出现 Waiting 状态” 的神秘反馈添加的临时修复
  121. PageState = PageStates.LoaderWait
  122. AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
  123. Log("[Anim] 警告:页面加载器 " & PageLoader.Name & " 不应在 PageOnEnter 事件出现 " & GetStringFromEnum(PageLoader.State) & " 状态", LogLevel.Debug)
  124. Else
  125. Throw New Exception("页面加载器 " & PageLoader.Name & " 不应在 PageOnEnter 事件出现 " & GetStringFromEnum(PageLoader.State) & " 状态。")
  126. End If
  127. Case Else
  128. Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnter 事件。")
  129. End Select
  130. End Sub
  131. Public Event OnPageEnter()
  132. ''' <summary>
  133. ''' 需要切换到其他页面。
  134. ''' 需要立即切换至 PageExit 或 Empty。
  135. ''' </summary>
  136. Public Sub PageOnExit()
  137. If ModeDebug Then Log("[UI] 已触发 PageOnExit")
  138. Select Case PageState
  139. Case PageStates.ContentEnter, PageStates.ContentStay
  140. PageState = PageStates.PageExit
  141. TriggerExitAnimation(PanAlways, If(PanContent, Child))
  142. Case PageStates.LoaderEnter, PageStates.LoaderStayForce, PageStates.LoaderStay
  143. PageState = PageStates.PageExit
  144. TriggerExitAnimation(PanAlways, PanLoader)
  145. Case PageStates.LoaderWait
  146. PageState = PageStates.PageExit
  147. TriggerExitAnimation(PanAlways)
  148. Case PageStates.LoaderExit, PageStates.ReloadExit
  149. PageState = PageStates.PageExit
  150. If PanAlways IsNot Nothing Then TriggerExitAnimation(PanAlways, If(PanContent, Child))
  151. Case PageStates.PageExit, PageStates.Empty
  152. End Select
  153. End Sub
  154. ''' <summary>
  155. ''' 即将切换到其他页面,需要强制完成页面状态清理。
  156. ''' 需要立即切换至 Empty。
  157. ''' </summary>
  158. Public Sub PageOnForceExit()
  159. If PageState = PageStates.Empty Then Exit Sub
  160. If ModeDebug Then Log("[UI] 已触发 PageOnForceExit")
  161. PageState = PageStates.Empty
  162. AniStop("PageRight PageChange " & PageUuid)
  163. '由于动画会被强制中止,所以需要手动进行隐藏
  164. If PageLoader Is Nothing Then
  165. Child.Visibility = Visibility.Collapsed
  166. Else
  167. PanContent.Visibility = Visibility.Collapsed
  168. PanLoader.Visibility = Visibility.Collapsed
  169. If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
  170. End If
  171. End Sub
  172. '内部触发的事件
  173. ''' <summary>
  174. ''' 逐个进入动画已执行完成。
  175. ''' 需要根据目前状态,从 ContentEnter 切换到 ContentStay,或从 LoaderEnter 切换到 LoaderStayForce。
  176. ''' </summary>
  177. Private Sub PageOnEnterAnimationFinished()
  178. If ModeDebug Then Log("[UI] 已触发 PageOnEnterAnimationFinished")
  179. Select Case PageState
  180. Case PageStates.ContentEnter
  181. PageState = PageStates.ContentStay
  182. Case PageStates.LoaderEnter
  183. PageState = PageStates.LoaderStayForce
  184. AniStart(AaCode(AddressOf PageOnLoaderStayFinished, 400), "PageRight PageChange " & PageUuid)
  185. Case Else
  186. Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnterAnimationFinished 事件。")
  187. End Select
  188. End Sub
  189. ''' <summary>
  190. ''' 逐个退出动画已执行完成。
  191. ''' 需要根据目前状态,从 AllExit 切换到 Empty,或从 LoaderExit 切换到 ContentEnter,或从 ContentExit 重新触发 PageOnEnter。
  192. ''' </summary>
  193. Private Sub PageOnExitAnimationFinished()
  194. If ModeDebug Then Log("[UI] 已触发 PageOnExitAnimationFinished")
  195. Select Case PageState
  196. Case PageStates.PageExit
  197. PageState = PageStates.Empty
  198. Case PageStates.ReloadExit
  199. PageState = PageStates.Empty
  200. PageOnEnter()
  201. Case PageStates.LoaderExit
  202. PageState = PageStates.ContentEnter
  203. TriggerEnterAnimation(PanContent)
  204. Case Else
  205. Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnExitAnimationFinished 事件。")
  206. End Select
  207. End Sub
  208. ''' <summary>
  209. ''' 加载环进入等待已结束。
  210. ''' 需要从 LoaderWait 切换到 LoaderEnter。
  211. ''' </summary>
  212. Private Sub PageOnLoaderWaitFinished()
  213. If ModeDebug Then Log("[UI] 已触发 PageOnLoaderWaitFinished")
  214. Select Case PageState
  215. Case PageStates.LoaderWait
  216. PageState = PageStates.LoaderEnter
  217. If PanAlways IsNot Nothing AndAlso PanAlways.Visibility = Visibility.Collapsed Then
  218. TriggerEnterAnimation(PanAlways, PanLoader)
  219. Else
  220. TriggerEnterAnimation(PanLoader)
  221. End If
  222. Case Else
  223. Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
  224. End Select
  225. End Sub
  226. ''' <summary>
  227. ''' 加载环展示等待已结束。
  228. ''' 需要从 LoaderStayForce 切换到 LoaderStay 或 LoaderExit。
  229. ''' </summary>
  230. Private Sub PageOnLoaderStayFinished()
  231. If ModeDebug Then Log("[UI] 已触发 PageOnLoaderStayFinished")
  232. Select Case PageState
  233. Case PageStates.LoaderStayForce
  234. If PageLoader.State = LoadState.Finished Then
  235. PageState = PageStates.LoaderExit
  236. TriggerExitAnimation(PanLoader)
  237. Else
  238. PageState = PageStates.LoaderStay
  239. End If
  240. Case Else
  241. Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
  242. End Select
  243. End Sub
  244. ''' <summary>
  245. ''' 全局加载状态已改变。
  246. ''' </summary>
  247. Private Sub PageLoaderState(sender As Object, NewState As LoadState, OldState As LoadState)
  248. Select Case NewState
  249. Case LoadState.Failed, LoadState.Loading
  250. If Not OldState = LoadState.Finished Then Exit Sub
  251. If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Refresh)")
  252. '重新开始运行
  253. '需要从部分状态切换到 ReloadExit
  254. Select Case PageState
  255. Case PageStates.ContentEnter, PageStates.ContentStay
  256. PageState = PageStates.ReloadExit
  257. TriggerExitAnimation(PanContent)
  258. Case PageStates.LoaderExit, PageStates.ReloadExit
  259. PageState = PageStates.ReloadExit
  260. End Select
  261. Case LoadState.Finished
  262. If Not OldState = LoadState.Loading Then Exit Sub
  263. If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop)")
  264. '运行结束
  265. '需要从 LoaderWait 切换到 ContentEnter,或从 LoaderStay 切换到 LoaderExit
  266. Select Case PageState
  267. Case PageStates.LoaderWait
  268. PageState = PageStates.ContentEnter
  269. If PanAlways IsNot Nothing AndAlso PanAlways.Visibility = Visibility.Collapsed Then
  270. TriggerEnterAnimation(PanAlways, PanContent)
  271. Else
  272. TriggerEnterAnimation(PanContent)
  273. End If
  274. Case PageStates.LoaderStay
  275. PageState = PageStates.LoaderExit
  276. TriggerExitAnimation(PanLoader)
  277. End Select
  278. End Select
  279. End Sub
  280. #End Region
  281. #Region "动画"
  282. '逐个进入动画
  283. Public Sub TriggerEnterAnimation(ParamArray Elements As FrameworkElement())
  284. '去除 Nothing 项
  285. Dim RealElements As New List(Of FrameworkElement)
  286. For Each Element In Elements
  287. If Element IsNot Nothing Then RealElements.Add(Element)
  288. Next
  289. For Each Element In RealElements
  290. Element.Visibility = Visibility.Visible '页面均处于默认的隐藏状态
  291. Next
  292. Dim AniList As New List(Of AniData)
  293. Dim Delay As Integer = 0
  294. For Each Element In RealElements
  295. For Each Control As FrameworkElement In GetAllAnimControls(Element, True)
  296. '还原被隐藏的卡片的消失动画
  297. Control.IsHitTestVisible = True
  298. If Control.RenderTransform IsNot Nothing AndAlso TypeOf Control.RenderTransform Is TranslateTransform Then Control.RenderTransform = Nothing
  299. Next
  300. For Each Control As FrameworkElement In GetAllAnimControls(Element)
  301. Control.Opacity = 0
  302. Control.RenderTransform = New TranslateTransform(0, -16)
  303. AniList.Add(AaOpacity(Control, 1, 150, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
  304. AniList.Add(AaTranslateY(Control, 5, 250, Delay, New AniEaseOutFluent))
  305. AniList.Add(AaTranslateY(Control, 11, 350, Delay, New AniEaseOutBack))
  306. Delay += 40
  307. Next
  308. Next
  309. AniList.Add(AaCode(Sub() PageOnEnterAnimationFinished(),, True))
  310. AniStart(AniList, "PageRight PageChange " & PageUuid)
  311. End Sub
  312. '逐个退出动画
  313. Public Sub TriggerExitAnimation(ParamArray Elements As FrameworkElement())
  314. '去除 Nothing 项
  315. Dim RealElements As New List(Of FrameworkElement)
  316. For Each Element In Elements
  317. If Element IsNot Nothing Then RealElements.Add(Element)
  318. Next
  319. Dim AniList As New List(Of AniData)
  320. Dim Delay As Integer = 0
  321. For Each Element In RealElements
  322. For Each Control As FrameworkElement In GetAllAnimControls(Element)
  323. Control.IsHitTestVisible = False
  324. AniList.Add(AaOpacity(Control, -1, 90, Delay))
  325. AniList.Add(AaTranslateY(Control, -6, 90, Delay))
  326. Delay += 20
  327. Next
  328. Next
  329. AniList.Add(AaCode(Sub()
  330. For Each Element In RealElements
  331. Element.Visibility = Visibility.Collapsed
  332. Next
  333. PageOnExitAnimationFinished()
  334. End Sub,, True))
  335. AniStart(AniList, "PageRight PageChange " & PageUuid)
  336. End Sub
  337. '遍历获取所有需要生成动画的控件
  338. Friend Function GetAllAnimControls(Element As FrameworkElement, Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement)
  339. Dim AllControls As New List(Of FrameworkElement)
  340. GetAllAnimControls(Element, AllControls, IgnoreInvisibility)
  341. Return AllControls
  342. End Function
  343. Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
  344. If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub
  345. If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint Then
  346. AllControls.Add(Element)
  347. ElseIf TypeOf Element Is ContentControl Then
  348. GetAllAnimControls(CType(Element, ContentControl).Content, AllControls, IgnoreInvisibility)
  349. ElseIf TypeOf Element Is Panel Then
  350. For Each Element2 As FrameworkElement In CType(Element, Panel).Children
  351. GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
  352. Next
  353. End If
  354. End Sub
  355. #End Region
  356. End Class