[{"data":1,"prerenderedAt":1417},["ShallowReactive",2],{"page-\u002Fcsharp\u002Favalonia\u002Favalonia通过继承templatedcontrol完成跳转动画":3},{"id":4,"title":5,"body":6,"description":12,"extension":1411,"meta":1412,"navigation":156,"path":1413,"seo":1414,"stem":1415,"__hash__":1416},"content\u002Fcsharp\u002FAvalonia\u002FAvalonia通过继承TemplatedControl完成跳转动画.md","Avalonia通过继承TemplatedControl完成跳转动画",{"type":7,"value":8,"toc":1409},"minimark",[9,13,37,41,51,54,71,74,1163,1166,1169,1179,1182,1210,1213,1233,1236,1239,1259,1262,1310,1313,1328,1331,1334,1337,1370,1373,1376,1405],[10,11,12],"p",{},"我们知道一点，就是：",[10,14,15,19,20,24,25,28,29,32,33,36],{},[16,17,18],"code",{},"TemplatedControl"," 是一个",[21,22,23],"strong",{},"无外观控件","(lookless control),其",[21,26,27],{},"视觉外观","由 ",[16,30,31],{},"Template"," 属性定义。 TemplatedControl.cs:14-17 它直接继承自 ",[16,34,35],{},"Control"," 类。 TemplatedControl.cs:17",[38,39,40],"h4",{"id":40},"附加属性",[10,42,43,46,47,50],{},[16,44,45],{},"IsTemplateFocusTarget"," - 附加属性**,用于指定模板中哪个元素应该接收焦点装饰器**。 TemplatedControl.cs:106-107 当控件获得焦点时,",[21,48,49],{},"焦点装饰器会显示在标记了此属性的元素周围,而不是控件本身","。 TemplatedControl.cs:280-284",[38,52,53],{"id":53},"备注",[10,55,56,58,59,62,63,66,67,70],{},[16,57,18],{}," 是许多 Avalonia 控件的基类,包括之前讨论的 ",[16,60,61],{},"ContentControl","。 它提供了模板化控件的基础架构,使得控件的逻辑和外观可以完全分离。 所有模板子元素的 ",[16,64,65],{},"TemplatedParent"," ",[21,68,69],{},"属性都会被设置为该控件实例。因此，他很适合用来当做动画组件等使用","。",[10,72,73],{},"因此我们这里可以模仿sukiUi实现一个双缓冲的FadeIn FadeOut动画。",[75,76,81],"pre",{"className":77,"code":78,"language":79,"meta":80,"style":80},"language-csharp shiki shiki-themes github-light github-dark","using System;\nusing System.Threading;\nusing Avalonia;\nusing Avalonia.Animation;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Presenters;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Interactivity;\nusing Avalonia.Media;\nusing Avalonia.Styling;\nusing Avalonia.Threading;\n\nnamespace SukiUI.Controls\n{\n    \u002F\u002F TODO: This needs fairly significant work to make a bit more bomb proof\n    \u002F\u002F There are probably some more gains that can be made in terms of performance.\n    \u002F\u002F Unfortunately we're still bound by the arrange of controls having to happen on the main thread.\n    public class SukiTransitioningContentControl : TemplatedControl\n    {\n        internal static readonly StyledProperty\u003Cobject?> FirstBufferProperty =\n            AvaloniaProperty.Register\u003CSukiTransitioningContentControl, object?>(nameof(FirstBuffer));\n\n        internal object? FirstBuffer\n        {\n            get => GetValue(FirstBufferProperty);\n            set => SetValue(FirstBufferProperty, value);\n        }\n\n        internal static readonly StyledProperty\u003Cobject?> SecondBufferProperty =\n            AvaloniaProperty.Register\u003CSukiTransitioningContentControl, object?>(nameof(SecondBuffer));\n\n        internal object? SecondBuffer\n        {\n            get => GetValue(SecondBufferProperty);\n            set => SetValue(SecondBufferProperty, value);\n        }\n\n        public static readonly StyledProperty\u003Cobject?> ContentProperty = AvaloniaProperty.Register\u003CSukiTransitioningContentControl, object?>(nameof(Content));\n\n        public object? Content\n        {\n            get => GetValue(ContentProperty);\n            set => SetValue(ContentProperty, value);\n        }\n\n        private bool _isFirstBufferActive;\n\n        private ContentPresenter? _firstBuffer = null;\n        private ContentPresenter? _secondBuffer = null;\n\n        private static readonly Animation FadeIn;\n        private static readonly Animation FadeOut;\n        \n        private ContentPresenter? To => _isFirstBufferActive ? _firstBuffer : _secondBuffer;\n        private ContentPresenter? From => _isFirstBufferActive ? _secondBuffer : _firstBuffer;\n\n        private object? _contentBeforeApplied;\n\n        static SukiTransitioningContentControl()\n        {\n            FadeIn = new Animation\n            {\n                Duration = TimeSpan.FromMilliseconds(400),\n                Children =\n                {\n                    new KeyFrame()\n                    {\n                        Setters =\n                        {\n                            new Setter\n                            {\n                                Property = OpacityProperty,\n                                Value = 0d\n                            }\n                        },\n                        Cue = new Cue(0d)\n                    },\n                    new KeyFrame()\n                    {\n                        Setters =\n                        {\n                            new Setter\n                            {\n                                Property = OpacityProperty,\n                                Value = 1d\n                            }\n                        },\n                        Cue = new Cue(1d)\n                    }\n                },\n                FillMode = FillMode.Forward\n            };\n            FadeOut = new Animation\n            {\n                Duration = TimeSpan.FromMilliseconds(400),\n                Children =\n                {\n                    new KeyFrame()\n                    {\n                        Setters =\n                        {\n                            new Setter\n                            {\n                                Property = OpacityProperty,\n                                Value = 1d\n                            }\n                        },\n                        Cue = new Cue(0d)\n                    },\n                    new KeyFrame()\n                    {\n                        Setters =\n                        {\n                            new Setter\n                            {\n                                Property = OpacityProperty,\n                                Value = 0d\n                            }\n                        },\n                        Cue = new Cue(1d)\n                    }\n                },\n                FillMode = FillMode.Forward\n            };\n            FadeIn.Duration = FadeOut.Duration = TimeSpan.FromMilliseconds(250);\n        }\n\n        private CancellationTokenSource _animCancellationToken = new();\n        \n\n        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n        {\n            base.OnPropertyChanged(change);\n            if(change.Property == ContentProperty)\n                PushContent(change.NewValue);\n        }\n\n        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n        {\n            base.OnApplyTemplate(e);\n            if (e.NameScope.Get\u003CContentPresenter>(\"PART_FirstBufferControl\") is { } fBuff)\n                _firstBuffer = fBuff;\n            if (e.NameScope.Get\u003CContentPresenter>(\"PART_SecondBufferControl\") is { } sBuff)\n                _secondBuffer = sBuff;\n            if (_contentBeforeApplied != null)\n            {\n                PushContent(_contentBeforeApplied);\n                _contentBeforeApplied = null;\n            }\n        }\n\n        private void PushContent(object? content)\n        {\n            if (To is null || From is null)\n            {\n                _contentBeforeApplied = content;\n                return;\n            }\n            \n            _animCancellationToken.Cancel();\n            _animCancellationToken.Dispose();\n            _animCancellationToken = new CancellationTokenSource();\n            \n            if (_isFirstBufferActive) SecondBuffer = content;\n            else FirstBuffer = content;\n            _isFirstBufferActive = !_isFirstBufferActive;\n            try\n            {\n                FadeOut.RunAsync(From, _animCancellationToken.Token).ContinueWith(_ =>\n                {\n                    Dispatcher.UIThread.Invoke(() =>\n                    {\n                        From.IsHitTestVisible = false;\n                        if (_isFirstBufferActive) SecondBuffer = null;\n                        else FirstBuffer = null;\n                    });\n                });\n                FadeIn.RunAsync(To, _animCancellationToken.Token).ContinueWith(_ => \n                    Dispatcher.UIThread.Invoke(() => To.IsHitTestVisible = true));\n            }\n            catch\n            {\n                \u002F\u002F ignored\n            }\n        }\n\n        protected override void OnUnloaded(RoutedEventArgs e)\n        {\n            base.OnUnloaded(e);\n            _animCancellationToken.Dispose();\n        }\n    }\n}\n","csharp","",[16,82,83,91,97,103,109,115,121,127,133,139,145,151,158,164,170,176,182,188,194,200,206,212,217,223,229,235,241,247,252,258,264,269,275,280,286,292,297,302,308,313,319,324,330,336,341,346,352,357,363,369,374,380,386,392,398,404,409,415,420,426,431,437,443,449,455,461,467,473,479,485,491,497,503,509,515,521,527,533,538,543,548,553,558,563,568,574,579,584,590,596,602,608,614,620,625,630,635,640,645,650,655,660,665,670,675,680,685,690,695,700,705,710,715,720,725,730,735,740,745,750,755,760,765,770,775,781,786,791,797,802,807,813,818,824,830,836,841,846,852,857,863,869,875,881,887,893,898,904,910,916,921,926,932,937,943,948,954,960,965,971,977,983,989,994,1000,1006,1012,1018,1023,1029,1034,1040,1045,1051,1057,1063,1069,1075,1081,1087,1092,1098,1103,1109,1114,1119,1124,1130,1135,1141,1146,1151,1157],{"__ignoreMap":80},[84,85,88],"span",{"class":86,"line":87},"line",1,[84,89,90],{},"using System;\n",[84,92,94],{"class":86,"line":93},2,[84,95,96],{},"using System.Threading;\n",[84,98,100],{"class":86,"line":99},3,[84,101,102],{},"using Avalonia;\n",[84,104,106],{"class":86,"line":105},4,[84,107,108],{},"using Avalonia.Animation;\n",[84,110,112],{"class":86,"line":111},5,[84,113,114],{},"using Avalonia.Controls;\n",[84,116,118],{"class":86,"line":117},6,[84,119,120],{},"using Avalonia.Controls.Presenters;\n",[84,122,124],{"class":86,"line":123},7,[84,125,126],{},"using Avalonia.Controls.Primitives;\n",[84,128,130],{"class":86,"line":129},8,[84,131,132],{},"using Avalonia.Interactivity;\n",[84,134,136],{"class":86,"line":135},9,[84,137,138],{},"using Avalonia.Media;\n",[84,140,142],{"class":86,"line":141},10,[84,143,144],{},"using Avalonia.Styling;\n",[84,146,148],{"class":86,"line":147},11,[84,149,150],{},"using Avalonia.Threading;\n",[84,152,154],{"class":86,"line":153},12,[84,155,157],{"emptyLinePlaceholder":156},true,"\n",[84,159,161],{"class":86,"line":160},13,[84,162,163],{},"namespace SukiUI.Controls\n",[84,165,167],{"class":86,"line":166},14,[84,168,169],{},"{\n",[84,171,173],{"class":86,"line":172},15,[84,174,175],{},"    \u002F\u002F TODO: This needs fairly significant work to make a bit more bomb proof\n",[84,177,179],{"class":86,"line":178},16,[84,180,181],{},"    \u002F\u002F There are probably some more gains that can be made in terms of performance.\n",[84,183,185],{"class":86,"line":184},17,[84,186,187],{},"    \u002F\u002F Unfortunately we're still bound by the arrange of controls having to happen on the main thread.\n",[84,189,191],{"class":86,"line":190},18,[84,192,193],{},"    public class SukiTransitioningContentControl : TemplatedControl\n",[84,195,197],{"class":86,"line":196},19,[84,198,199],{},"    {\n",[84,201,203],{"class":86,"line":202},20,[84,204,205],{},"        internal static readonly StyledProperty\u003Cobject?> FirstBufferProperty =\n",[84,207,209],{"class":86,"line":208},21,[84,210,211],{},"            AvaloniaProperty.Register\u003CSukiTransitioningContentControl, object?>(nameof(FirstBuffer));\n",[84,213,215],{"class":86,"line":214},22,[84,216,157],{"emptyLinePlaceholder":156},[84,218,220],{"class":86,"line":219},23,[84,221,222],{},"        internal object? FirstBuffer\n",[84,224,226],{"class":86,"line":225},24,[84,227,228],{},"        {\n",[84,230,232],{"class":86,"line":231},25,[84,233,234],{},"            get => GetValue(FirstBufferProperty);\n",[84,236,238],{"class":86,"line":237},26,[84,239,240],{},"            set => SetValue(FirstBufferProperty, value);\n",[84,242,244],{"class":86,"line":243},27,[84,245,246],{},"        }\n",[84,248,250],{"class":86,"line":249},28,[84,251,157],{"emptyLinePlaceholder":156},[84,253,255],{"class":86,"line":254},29,[84,256,257],{},"        internal static readonly StyledProperty\u003Cobject?> SecondBufferProperty =\n",[84,259,261],{"class":86,"line":260},30,[84,262,263],{},"            AvaloniaProperty.Register\u003CSukiTransitioningContentControl, object?>(nameof(SecondBuffer));\n",[84,265,267],{"class":86,"line":266},31,[84,268,157],{"emptyLinePlaceholder":156},[84,270,272],{"class":86,"line":271},32,[84,273,274],{},"        internal object? SecondBuffer\n",[84,276,278],{"class":86,"line":277},33,[84,279,228],{},[84,281,283],{"class":86,"line":282},34,[84,284,285],{},"            get => GetValue(SecondBufferProperty);\n",[84,287,289],{"class":86,"line":288},35,[84,290,291],{},"            set => SetValue(SecondBufferProperty, value);\n",[84,293,295],{"class":86,"line":294},36,[84,296,246],{},[84,298,300],{"class":86,"line":299},37,[84,301,157],{"emptyLinePlaceholder":156},[84,303,305],{"class":86,"line":304},38,[84,306,307],{},"        public static readonly StyledProperty\u003Cobject?> ContentProperty = AvaloniaProperty.Register\u003CSukiTransitioningContentControl, object?>(nameof(Content));\n",[84,309,311],{"class":86,"line":310},39,[84,312,157],{"emptyLinePlaceholder":156},[84,314,316],{"class":86,"line":315},40,[84,317,318],{},"        public object? Content\n",[84,320,322],{"class":86,"line":321},41,[84,323,228],{},[84,325,327],{"class":86,"line":326},42,[84,328,329],{},"            get => GetValue(ContentProperty);\n",[84,331,333],{"class":86,"line":332},43,[84,334,335],{},"            set => SetValue(ContentProperty, value);\n",[84,337,339],{"class":86,"line":338},44,[84,340,246],{},[84,342,344],{"class":86,"line":343},45,[84,345,157],{"emptyLinePlaceholder":156},[84,347,349],{"class":86,"line":348},46,[84,350,351],{},"        private bool _isFirstBufferActive;\n",[84,353,355],{"class":86,"line":354},47,[84,356,157],{"emptyLinePlaceholder":156},[84,358,360],{"class":86,"line":359},48,[84,361,362],{},"        private ContentPresenter? _firstBuffer = null;\n",[84,364,366],{"class":86,"line":365},49,[84,367,368],{},"        private ContentPresenter? _secondBuffer = null;\n",[84,370,372],{"class":86,"line":371},50,[84,373,157],{"emptyLinePlaceholder":156},[84,375,377],{"class":86,"line":376},51,[84,378,379],{},"        private static readonly Animation FadeIn;\n",[84,381,383],{"class":86,"line":382},52,[84,384,385],{},"        private static readonly Animation FadeOut;\n",[84,387,389],{"class":86,"line":388},53,[84,390,391],{},"        \n",[84,393,395],{"class":86,"line":394},54,[84,396,397],{},"        private ContentPresenter? To => _isFirstBufferActive ? _firstBuffer : _secondBuffer;\n",[84,399,401],{"class":86,"line":400},55,[84,402,403],{},"        private ContentPresenter? From => _isFirstBufferActive ? _secondBuffer : _firstBuffer;\n",[84,405,407],{"class":86,"line":406},56,[84,408,157],{"emptyLinePlaceholder":156},[84,410,412],{"class":86,"line":411},57,[84,413,414],{},"        private object? _contentBeforeApplied;\n",[84,416,418],{"class":86,"line":417},58,[84,419,157],{"emptyLinePlaceholder":156},[84,421,423],{"class":86,"line":422},59,[84,424,425],{},"        static SukiTransitioningContentControl()\n",[84,427,429],{"class":86,"line":428},60,[84,430,228],{},[84,432,434],{"class":86,"line":433},61,[84,435,436],{},"            FadeIn = new Animation\n",[84,438,440],{"class":86,"line":439},62,[84,441,442],{},"            {\n",[84,444,446],{"class":86,"line":445},63,[84,447,448],{},"                Duration = TimeSpan.FromMilliseconds(400),\n",[84,450,452],{"class":86,"line":451},64,[84,453,454],{},"                Children =\n",[84,456,458],{"class":86,"line":457},65,[84,459,460],{},"                {\n",[84,462,464],{"class":86,"line":463},66,[84,465,466],{},"                    new KeyFrame()\n",[84,468,470],{"class":86,"line":469},67,[84,471,472],{},"                    {\n",[84,474,476],{"class":86,"line":475},68,[84,477,478],{},"                        Setters =\n",[84,480,482],{"class":86,"line":481},69,[84,483,484],{},"                        {\n",[84,486,488],{"class":86,"line":487},70,[84,489,490],{},"                            new Setter\n",[84,492,494],{"class":86,"line":493},71,[84,495,496],{},"                            {\n",[84,498,500],{"class":86,"line":499},72,[84,501,502],{},"                                Property = OpacityProperty,\n",[84,504,506],{"class":86,"line":505},73,[84,507,508],{},"                                Value = 0d\n",[84,510,512],{"class":86,"line":511},74,[84,513,514],{},"                            }\n",[84,516,518],{"class":86,"line":517},75,[84,519,520],{},"                        },\n",[84,522,524],{"class":86,"line":523},76,[84,525,526],{},"                        Cue = new Cue(0d)\n",[84,528,530],{"class":86,"line":529},77,[84,531,532],{},"                    },\n",[84,534,536],{"class":86,"line":535},78,[84,537,466],{},[84,539,541],{"class":86,"line":540},79,[84,542,472],{},[84,544,546],{"class":86,"line":545},80,[84,547,478],{},[84,549,551],{"class":86,"line":550},81,[84,552,484],{},[84,554,556],{"class":86,"line":555},82,[84,557,490],{},[84,559,561],{"class":86,"line":560},83,[84,562,496],{},[84,564,566],{"class":86,"line":565},84,[84,567,502],{},[84,569,571],{"class":86,"line":570},85,[84,572,573],{},"                                Value = 1d\n",[84,575,577],{"class":86,"line":576},86,[84,578,514],{},[84,580,582],{"class":86,"line":581},87,[84,583,520],{},[84,585,587],{"class":86,"line":586},88,[84,588,589],{},"                        Cue = new Cue(1d)\n",[84,591,593],{"class":86,"line":592},89,[84,594,595],{},"                    }\n",[84,597,599],{"class":86,"line":598},90,[84,600,601],{},"                },\n",[84,603,605],{"class":86,"line":604},91,[84,606,607],{},"                FillMode = FillMode.Forward\n",[84,609,611],{"class":86,"line":610},92,[84,612,613],{},"            };\n",[84,615,617],{"class":86,"line":616},93,[84,618,619],{},"            FadeOut = new Animation\n",[84,621,623],{"class":86,"line":622},94,[84,624,442],{},[84,626,628],{"class":86,"line":627},95,[84,629,448],{},[84,631,633],{"class":86,"line":632},96,[84,634,454],{},[84,636,638],{"class":86,"line":637},97,[84,639,460],{},[84,641,643],{"class":86,"line":642},98,[84,644,466],{},[84,646,648],{"class":86,"line":647},99,[84,649,472],{},[84,651,653],{"class":86,"line":652},100,[84,654,478],{},[84,656,658],{"class":86,"line":657},101,[84,659,484],{},[84,661,663],{"class":86,"line":662},102,[84,664,490],{},[84,666,668],{"class":86,"line":667},103,[84,669,496],{},[84,671,673],{"class":86,"line":672},104,[84,674,502],{},[84,676,678],{"class":86,"line":677},105,[84,679,573],{},[84,681,683],{"class":86,"line":682},106,[84,684,514],{},[84,686,688],{"class":86,"line":687},107,[84,689,520],{},[84,691,693],{"class":86,"line":692},108,[84,694,526],{},[84,696,698],{"class":86,"line":697},109,[84,699,532],{},[84,701,703],{"class":86,"line":702},110,[84,704,466],{},[84,706,708],{"class":86,"line":707},111,[84,709,472],{},[84,711,713],{"class":86,"line":712},112,[84,714,478],{},[84,716,718],{"class":86,"line":717},113,[84,719,484],{},[84,721,723],{"class":86,"line":722},114,[84,724,490],{},[84,726,728],{"class":86,"line":727},115,[84,729,496],{},[84,731,733],{"class":86,"line":732},116,[84,734,502],{},[84,736,738],{"class":86,"line":737},117,[84,739,508],{},[84,741,743],{"class":86,"line":742},118,[84,744,514],{},[84,746,748],{"class":86,"line":747},119,[84,749,520],{},[84,751,753],{"class":86,"line":752},120,[84,754,589],{},[84,756,758],{"class":86,"line":757},121,[84,759,595],{},[84,761,763],{"class":86,"line":762},122,[84,764,601],{},[84,766,768],{"class":86,"line":767},123,[84,769,607],{},[84,771,773],{"class":86,"line":772},124,[84,774,613],{},[84,776,778],{"class":86,"line":777},125,[84,779,780],{},"            FadeIn.Duration = FadeOut.Duration = TimeSpan.FromMilliseconds(250);\n",[84,782,784],{"class":86,"line":783},126,[84,785,246],{},[84,787,789],{"class":86,"line":788},127,[84,790,157],{"emptyLinePlaceholder":156},[84,792,794],{"class":86,"line":793},128,[84,795,796],{},"        private CancellationTokenSource _animCancellationToken = new();\n",[84,798,800],{"class":86,"line":799},129,[84,801,391],{},[84,803,805],{"class":86,"line":804},130,[84,806,157],{"emptyLinePlaceholder":156},[84,808,810],{"class":86,"line":809},131,[84,811,812],{},"        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n",[84,814,816],{"class":86,"line":815},132,[84,817,228],{},[84,819,821],{"class":86,"line":820},133,[84,822,823],{},"            base.OnPropertyChanged(change);\n",[84,825,827],{"class":86,"line":826},134,[84,828,829],{},"            if(change.Property == ContentProperty)\n",[84,831,833],{"class":86,"line":832},135,[84,834,835],{},"                PushContent(change.NewValue);\n",[84,837,839],{"class":86,"line":838},136,[84,840,246],{},[84,842,844],{"class":86,"line":843},137,[84,845,157],{"emptyLinePlaceholder":156},[84,847,849],{"class":86,"line":848},138,[84,850,851],{},"        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n",[84,853,855],{"class":86,"line":854},139,[84,856,228],{},[84,858,860],{"class":86,"line":859},140,[84,861,862],{},"            base.OnApplyTemplate(e);\n",[84,864,866],{"class":86,"line":865},141,[84,867,868],{},"            if (e.NameScope.Get\u003CContentPresenter>(\"PART_FirstBufferControl\") is { } fBuff)\n",[84,870,872],{"class":86,"line":871},142,[84,873,874],{},"                _firstBuffer = fBuff;\n",[84,876,878],{"class":86,"line":877},143,[84,879,880],{},"            if (e.NameScope.Get\u003CContentPresenter>(\"PART_SecondBufferControl\") is { } sBuff)\n",[84,882,884],{"class":86,"line":883},144,[84,885,886],{},"                _secondBuffer = sBuff;\n",[84,888,890],{"class":86,"line":889},145,[84,891,892],{},"            if (_contentBeforeApplied != null)\n",[84,894,896],{"class":86,"line":895},146,[84,897,442],{},[84,899,901],{"class":86,"line":900},147,[84,902,903],{},"                PushContent(_contentBeforeApplied);\n",[84,905,907],{"class":86,"line":906},148,[84,908,909],{},"                _contentBeforeApplied = null;\n",[84,911,913],{"class":86,"line":912},149,[84,914,915],{},"            }\n",[84,917,919],{"class":86,"line":918},150,[84,920,246],{},[84,922,924],{"class":86,"line":923},151,[84,925,157],{"emptyLinePlaceholder":156},[84,927,929],{"class":86,"line":928},152,[84,930,931],{},"        private void PushContent(object? content)\n",[84,933,935],{"class":86,"line":934},153,[84,936,228],{},[84,938,940],{"class":86,"line":939},154,[84,941,942],{},"            if (To is null || From is null)\n",[84,944,946],{"class":86,"line":945},155,[84,947,442],{},[84,949,951],{"class":86,"line":950},156,[84,952,953],{},"                _contentBeforeApplied = content;\n",[84,955,957],{"class":86,"line":956},157,[84,958,959],{},"                return;\n",[84,961,963],{"class":86,"line":962},158,[84,964,915],{},[84,966,968],{"class":86,"line":967},159,[84,969,970],{},"            \n",[84,972,974],{"class":86,"line":973},160,[84,975,976],{},"            _animCancellationToken.Cancel();\n",[84,978,980],{"class":86,"line":979},161,[84,981,982],{},"            _animCancellationToken.Dispose();\n",[84,984,986],{"class":86,"line":985},162,[84,987,988],{},"            _animCancellationToken = new CancellationTokenSource();\n",[84,990,992],{"class":86,"line":991},163,[84,993,970],{},[84,995,997],{"class":86,"line":996},164,[84,998,999],{},"            if (_isFirstBufferActive) SecondBuffer = content;\n",[84,1001,1003],{"class":86,"line":1002},165,[84,1004,1005],{},"            else FirstBuffer = content;\n",[84,1007,1009],{"class":86,"line":1008},166,[84,1010,1011],{},"            _isFirstBufferActive = !_isFirstBufferActive;\n",[84,1013,1015],{"class":86,"line":1014},167,[84,1016,1017],{},"            try\n",[84,1019,1021],{"class":86,"line":1020},168,[84,1022,442],{},[84,1024,1026],{"class":86,"line":1025},169,[84,1027,1028],{},"                FadeOut.RunAsync(From, _animCancellationToken.Token).ContinueWith(_ =>\n",[84,1030,1032],{"class":86,"line":1031},170,[84,1033,460],{},[84,1035,1037],{"class":86,"line":1036},171,[84,1038,1039],{},"                    Dispatcher.UIThread.Invoke(() =>\n",[84,1041,1043],{"class":86,"line":1042},172,[84,1044,472],{},[84,1046,1048],{"class":86,"line":1047},173,[84,1049,1050],{},"                        From.IsHitTestVisible = false;\n",[84,1052,1054],{"class":86,"line":1053},174,[84,1055,1056],{},"                        if (_isFirstBufferActive) SecondBuffer = null;\n",[84,1058,1060],{"class":86,"line":1059},175,[84,1061,1062],{},"                        else FirstBuffer = null;\n",[84,1064,1066],{"class":86,"line":1065},176,[84,1067,1068],{},"                    });\n",[84,1070,1072],{"class":86,"line":1071},177,[84,1073,1074],{},"                });\n",[84,1076,1078],{"class":86,"line":1077},178,[84,1079,1080],{},"                FadeIn.RunAsync(To, _animCancellationToken.Token).ContinueWith(_ => \n",[84,1082,1084],{"class":86,"line":1083},179,[84,1085,1086],{},"                    Dispatcher.UIThread.Invoke(() => To.IsHitTestVisible = true));\n",[84,1088,1090],{"class":86,"line":1089},180,[84,1091,915],{},[84,1093,1095],{"class":86,"line":1094},181,[84,1096,1097],{},"            catch\n",[84,1099,1101],{"class":86,"line":1100},182,[84,1102,442],{},[84,1104,1106],{"class":86,"line":1105},183,[84,1107,1108],{},"                \u002F\u002F ignored\n",[84,1110,1112],{"class":86,"line":1111},184,[84,1113,915],{},[84,1115,1117],{"class":86,"line":1116},185,[84,1118,246],{},[84,1120,1122],{"class":86,"line":1121},186,[84,1123,157],{"emptyLinePlaceholder":156},[84,1125,1127],{"class":86,"line":1126},187,[84,1128,1129],{},"        protected override void OnUnloaded(RoutedEventArgs e)\n",[84,1131,1133],{"class":86,"line":1132},188,[84,1134,228],{},[84,1136,1138],{"class":86,"line":1137},189,[84,1139,1140],{},"            base.OnUnloaded(e);\n",[84,1142,1144],{"class":86,"line":1143},190,[84,1145,982],{},[84,1147,1149],{"class":86,"line":1148},191,[84,1150,246],{},[84,1152,1154],{"class":86,"line":1153},192,[84,1155,1156],{},"    }\n",[84,1158,1160],{"class":86,"line":1159},193,[84,1161,1162],{},"}\n",[10,1164,1165],{},"我们注册了三个属性，其中有两个私有属性来当做缓冲器，一个Content属性来当做渲染的子组件内容。",[10,1167,1168],{},"我们重载了OnApplyTemplate，当模板应用完成了初始化之后就会自动调用，他会自动获取模板中的命名元素，建立控件与模板的连接，从 XAML 模板中获取两个 ContentPresenter 控件",[1170,1171,1172,1176],"ul",{},[1173,1174,1175],"li",{},"PART_FirstBufferControl 和 PART_SecondBufferControl 是在 XAML 中定义的名称",[1173,1177,1178],{},"使用模式匹配 is { } 确保获取成功",[10,1180,1181],{},"之后他会调用调用PushContent去处理延迟的内容设置，然后再来讲一下为什么PushContent可以处理延迟：",[75,1183,1185],{"className":77,"code":1184,"language":79,"meta":80,"style":80},"if (To is null || From is null)\n{\n    _contentBeforeApplied = content;\n    return;\n}\n",[16,1186,1187,1192,1196,1201,1206],{"__ignoreMap":80},[84,1188,1189],{"class":86,"line":87},[84,1190,1191],{},"if (To is null || From is null)\n",[84,1193,1194],{"class":86,"line":93},[84,1195,169],{},[84,1197,1198],{"class":86,"line":99},[84,1199,1200],{},"    _contentBeforeApplied = content;\n",[84,1202,1203],{"class":86,"line":105},[84,1204,1205],{},"    return;\n",[84,1207,1208],{"class":86,"line":111},[84,1209,1162],{},[10,1211,1212],{},"我们使用了前置检查，检查缓冲区是否已经初始化，而To和From是活跃和非活跃的ContentPresenter，如果缓冲区没有准备好，就会将内容暂存到_contentBeforeApplied。",[75,1214,1216],{"className":77,"code":1215,"language":79,"meta":80,"style":80},"        _animationCancellationToken.Cancel();\n        _animationCancellationToken.Dispose();\n        _animationCancellationToken = new CancellationTokenSource();\n",[16,1217,1218,1223,1228],{"__ignoreMap":80},[84,1219,1220],{"class":86,"line":87},[84,1221,1222],{},"        _animationCancellationToken.Cancel();\n",[84,1224,1225],{"class":86,"line":93},[84,1226,1227],{},"        _animationCancellationToken.Dispose();\n",[84,1229,1230],{"class":86,"line":99},[84,1231,1232],{},"        _animationCancellationToken = new CancellationTokenSource();\n",[10,1234,1235],{},"这段代码是确保没有并发的动画，取消正在进行的动画，释放旧的取消令牌，然后创建新的令牌用于当前动画，这一段我们会自己讲述。",[10,1237,1238],{},"然后是缓冲区切换的逻辑：",[75,1240,1242],{"className":77,"code":1241,"language":79,"meta":80,"style":80},"if (_isFirstBufferActive) SecondBuffer = content;\nelse FirstBuffer = content;\n_isFirstBufferActive = !_isFirstBufferActive;\n",[16,1243,1244,1249,1254],{"__ignoreMap":80},[84,1245,1246],{"class":86,"line":87},[84,1247,1248],{},"if (_isFirstBufferActive) SecondBuffer = content;\n",[84,1250,1251],{"class":86,"line":93},[84,1252,1253],{},"else FirstBuffer = content;\n",[84,1255,1256],{"class":86,"line":99},[84,1257,1258],{},"_isFirstBufferActive = !_isFirstBufferActive;\n",[10,1260,1261],{},"这样To和From会自动指向正确的缓冲区。接下来是我们设置的动画：",[75,1263,1265],{"className":77,"code":1264,"language":79,"meta":80,"style":80},"FadeOut.RunAsync(From, _animCancellationToken.Token).ContinueWith(_ =>\n{\n    Dispatcher.UIThread.Invoke(() =>\n    {\n        From.IsHitTestVisible = false;\n        if (_isFirstBufferActive) SecondBuffer = null;\n        else FirstBuffer = null;\n    });\n});\n",[16,1266,1267,1272,1276,1281,1285,1290,1295,1300,1305],{"__ignoreMap":80},[84,1268,1269],{"class":86,"line":87},[84,1270,1271],{},"FadeOut.RunAsync(From, _animCancellationToken.Token).ContinueWith(_ =>\n",[84,1273,1274],{"class":86,"line":93},[84,1275,169],{},[84,1277,1278],{"class":86,"line":99},[84,1279,1280],{},"    Dispatcher.UIThread.Invoke(() =>\n",[84,1282,1283],{"class":86,"line":105},[84,1284,199],{},[84,1286,1287],{"class":86,"line":111},[84,1288,1289],{},"        From.IsHitTestVisible = false;\n",[84,1291,1292],{"class":86,"line":117},[84,1293,1294],{},"        if (_isFirstBufferActive) SecondBuffer = null;\n",[84,1296,1297],{"class":86,"line":123},[84,1298,1299],{},"        else FirstBuffer = null;\n",[84,1301,1302],{"class":86,"line":129},[84,1303,1304],{},"    });\n",[84,1306,1307],{"class":86,"line":135},[84,1308,1309],{},"});\n",[10,1311,1312],{},"这个作用是在旧内容上进行淡出动画，动画完成之后禁用旧内容的鼠标交互，清空缓冲区内容。",[75,1314,1316],{"className":77,"code":1315,"language":79,"meta":80,"style":80},"FadeIn.RunAsync(To, _animCancellationToken.Token).ContinueWith(_ => \n    Dispatcher.UIThread.Invoke(() => To.IsHitTestVisible = true));\n",[16,1317,1318,1323],{"__ignoreMap":80},[84,1319,1320],{"class":86,"line":87},[84,1321,1322],{},"FadeIn.RunAsync(To, _animCancellationToken.Token).ContinueWith(_ => \n",[84,1324,1325],{"class":86,"line":93},[84,1326,1327],{},"    Dispatcher.UIThread.Invoke(() => To.IsHitTestVisible = true));\n",[10,1329,1330],{},"然后是淡入动画，动画完成就会启用鼠标交互。",[10,1332,1333],{},"新内容到达\n↓\n检查缓冲区是否就绪\n↓\n取消之前的动画\n↓\n将新内容放入非活跃缓冲区\n↓\n切换活跃缓冲区标志\n↓\n同时启动淡出和淡入动画\n↓\n淡出完成 → 清理旧缓冲区\n淡入完成 → 启用新内容交互",[10,1335,1336],{},"重载了属性改变的方法：",[75,1338,1340],{"className":77,"code":1339,"language":79,"meta":80,"style":80},"\n        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n        {\n            base.OnPropertyChanged(change);\n            if(change.Property == ContentProperty)\n                PushContent(change.NewValue);\n        }\n",[16,1341,1342,1346,1350,1354,1358,1362,1366],{"__ignoreMap":80},[84,1343,1344],{"class":86,"line":87},[84,1345,157],{"emptyLinePlaceholder":156},[84,1347,1348],{"class":86,"line":93},[84,1349,812],{},[84,1351,1352],{"class":86,"line":99},[84,1353,228],{},[84,1355,1356],{"class":86,"line":105},[84,1357,823],{},[84,1359,1360],{"class":86,"line":111},[84,1361,829],{},[84,1363,1364],{"class":86,"line":117},[84,1365,835],{},[84,1367,1368],{"class":86,"line":123},[84,1369,246],{},[10,1371,1372],{},"当属性改变之后，就会自动触发UI更新。",[10,1374,1375],{},"组件卸载的时候自动执行的重载方法：",[75,1377,1379],{"className":77,"code":1378,"language":79,"meta":80,"style":80},"        protected override void OnUnloaded(RoutedEventArgs e)\n        {\n            base.OnUnloaded(e);\n            _animCancellationToken.Dispose();\n        }\n    }\n",[16,1380,1381,1385,1389,1393,1397,1401],{"__ignoreMap":80},[84,1382,1383],{"class":86,"line":87},[84,1384,1129],{},[84,1386,1387],{"class":86,"line":93},[84,1388,228],{},[84,1390,1391],{"class":86,"line":99},[84,1392,1140],{},[84,1394,1395],{"class":86,"line":105},[84,1396,982],{},[84,1398,1399],{"class":86,"line":111},[84,1400,246],{},[84,1402,1403],{"class":86,"line":117},[84,1404,1156],{},[1406,1407,1408],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":80,"searchDepth":93,"depth":93,"links":1410},[],"md",{},"\u002Fcsharp\u002Favalonia\u002Favalonia通过继承templatedcontrol完成跳转动画",{"description":12},"csharp\u002FAvalonia\u002FAvalonia通过继承TemplatedControl完成跳转动画","nB6m0puk81IsbFh6q4cCVGVBdAHbyU-QrkIw5lSv2K4",1791042464866]