[{"data":1,"prerenderedAt":1661},["ShallowReactive",2],{"page-\u002Fcsharp\u002Favalonia\u002Favalonia的composition-api-动画":3},{"id":4,"title":5,"body":6,"description":1654,"extension":1655,"meta":1656,"navigation":97,"path":1657,"seo":1658,"stem":1659,"__hash__":1660},"content\u002Fcsharp\u002FAvalonia\u002Favalonia的composition api 动画.md","Avalonia的composition Api 动画",{"type":7,"value":8,"toc":1652},"minimark",[9,22,29,32,896,917,927,940,943,1032,1047,1062,1094,1097,1100,1136,1170,1173,1176,1179,1187,1200,1206,1232,1235,1568,1585,1597,1603,1645,1648],[10,11,12,13,17,18,21],"p",{},"composition api 可以为我们在所绑定的属性的值发生变化的时候自动产生隐式动画，这个可以为我们节省很多的时间，并且Composition API 是在",[14,15,16],"strong",{},"渲染线程","上执行的，而",[14,19,20],{},"不是UI线程上执行","的。",[10,23,24,25,28],{},"Compositor类也就是我们说的合成器， 管理着UI线程和渲染线程之前的通信，动画计算和渲染在独立的渲染线程上执行而不会阻塞UI线程。在Win平台上，composition api 可以利用DirectCompositon或者WINUI composition 进行硬件加速渲染，",[14,26,27],{},"它使用批量提交机制，将多个属性变化打包成一个批次发送到渲染线程上","，减少了线程之间的通信次数，提高了整体性能。",[10,30,31],{},"光说不看代码没用用处，我们直接上源码：",[33,34,39],"pre",{"className":35,"code":36,"language":37,"meta":38,"style":38},"language-cs shiki shiki-themes github-light github-dark","using System;\nusing System.Diagnostics;\nusing System.Runtime.CompilerServices;\nusing Avalonia.Rendering.Composition.Animations;\nusing Avalonia.Rendering.Composition.Expressions;\nusing Avalonia.Rendering.Composition.Server;\nusing Avalonia.Rendering.Composition.Transport;\nusing Avalonia.Utilities;\n\nnamespace Avalonia.Rendering.Composition\n{\n    \u002F\u002F\u002F \u003Csummary>\n    \u002F\u002F\u002F Base class of the composition API representing a node in the visual tree structure.\n    \u002F\u002F\u002F Composition objects are the visual tree structure on which all other features of the composition API use and build on.\n    \u002F\u002F\u002F The API allows developers to define and create one or many \u003Csee cref=\"CompositionVisual\" \u002F> objects each representing a single node in a Visual tree.\n    \u002F\u002F\u002F \u003C\u002Fsummary>\n    public abstract class CompositionObject : ICompositorSerializable\n    {\n        \u002F\u002F\u002F \u003Csummary>\n        \u002F\u002F\u002F The collection of implicit animations attached to this object.\n        \u002F\u002F\u002F \u003C\u002Fsummary>\n        public ImplicitAnimationCollection? ImplicitAnimations { get; set; }\n\n        private protected InlineDictionary\u003CCompositionProperty, IAnimationInstance> PendingAnimations;\n        internal CompositionObject(Compositor compositor, SimpleServerObject? server)\n        {\n            Compositor = compositor;\n            Server = server;\n        }\n        \n        \u002F\u002F\u002F \u003Csummary>\n        \u002F\u002F\u002F The associated Compositor\n        \u002F\u002F\u002F \u003C\u002Fsummary>\n        public Compositor Compositor { get; }\n\n        SimpleServerObject ICompositorSerializable.TryGetServer(Compositor c)\n        {\n            Debug.Assert(c == Compositor);\n            return Server ?? ThrowInvalidOperation();\n        }\n\n        internal SimpleServerObject? Server { get; }\n        public bool IsDisposed { get; private set; }\n        private bool _registeredForSerialization;\n\n        [MethodImpl(MethodImplOptions.NoInlining)]\n        private static SimpleServerObject ThrowInvalidOperation() =>\n            throw new InvalidOperationException(\"There is no server-side counterpart for this object\");\n\n        protected internal void Dispose()\n        {\n            if (!IsDisposed && Server != null)\n                Compositor.DisposeOnNextBatch(Server);\n            IsDisposed = true;\n        }\n\n        \u002F\u002F\u002F \u003Csummary>\n        \u002F\u002F\u002F Connects an animation with the specified property of the object and starts the animation.\n        \u002F\u002F\u002F \u003C\u002Fsummary>\n        public void StartAnimation(string propertyName, CompositionAnimation animation)\n            => StartAnimation(propertyName, animation, null);\n        \n        internal virtual void StartAnimation(string propertyName, CompositionAnimation animation, ExpressionVariant? finalValue)\n        {\n            throw new ArgumentException(\"Unknown property \" + propertyName);\n        }\n\n        \u002F\u002F\u002F \u003Csummary>\n        \u002F\u002F\u002F Starts an animation group.\n        \u002F\u002F\u002F The StartAnimationGroup method on CompositionObject lets you start CompositionAnimationGroup.\n        \u002F\u002F\u002F All the animations in the group will be started at the same time on the object.\n        \u002F\u002F\u002F \u003C\u002Fsummary>\n        public void StartAnimationGroup(ICompositionAnimationBase grp)\n        {\n            if (grp is CompositionAnimation animation)\n            {\n                if(animation.Target == null)\n                    throw new ArgumentException(\"Animation Target can't be null\");\n                StartAnimation(animation.Target, animation);\n            }\n            else if (grp is CompositionAnimationGroup group)\n            {\n                foreach (var a in group.Animations)\n                {\n                    if (a.Target == null)\n                        throw new ArgumentException(\"Animation Target can't be null\");\n                    StartAnimation(a.Target, a);\n                }\n            }\n        }\n\n        bool StartAnimationGroupPart(CompositionAnimation animation, string target, ExpressionVariant finalValue)\n        {\n            if(animation.Target == null)\n                throw new ArgumentException(\"Animation Target can't be null\");\n            if (animation.Target == target)\n            {\n                StartAnimation(animation.Target, animation, finalValue);\n                return true;\n            }\n            else\n            {\n                StartAnimation(animation.Target, animation);\n                return false;\n            }\n        }\n        \n        internal bool StartAnimationGroup(ICompositionAnimationBase grp, string target, ExpressionVariant finalValue)\n        {\n            if (grp is CompositionAnimation animation)\n                return StartAnimationGroupPart(animation, target, finalValue);\n            if (grp is CompositionAnimationGroup group)\n            {\n                var matched = false;\n                foreach (var a in group.Animations)\n                {\n                    if (a.Target == null)\n                        throw new ArgumentException(\"Animation Target can't be null\");\n                    if (StartAnimationGroupPart(a, target, finalValue))\n                        matched = true;\n                }\n\n                return matched;\n            }\n\n            throw new ArgumentException();\n        }\n\n        protected void RegisterForSerialization()\n        {\n            if (Server == null)\n                throw new InvalidOperationException(\"The object doesn't have an associated server counterpart\");\n            \n            if(_registeredForSerialization)\n                return;\n            _registeredForSerialization = true;\n            Compositor.RegisterForSerialization(this);\n        }\n\n        void ICompositorSerializable.SerializeChanges(Compositor c, BatchStreamWriter writer)\n        {\n            Debug.Assert(c == Compositor);\n            _registeredForSerialization = false;\n            SerializeChangesCore(writer);\n        }\n\n        private protected virtual void SerializeChangesCore(BatchStreamWriter writer)\n        {\n        }\n    }\n}\n\n","cs","",[40,41,42,50,56,62,68,74,80,86,92,99,105,111,117,123,129,135,141,147,153,159,165,171,177,182,188,194,200,206,212,218,224,229,235,240,246,251,257,262,268,274,279,284,290,296,302,307,313,319,325,330,336,341,347,353,359,364,369,374,380,385,391,397,402,408,413,419,424,429,434,440,446,452,457,463,468,474,480,486,492,498,504,510,515,521,527,533,539,545,551,556,561,566,572,577,583,589,595,600,606,612,617,623,628,633,639,644,649,654,660,665,670,676,682,687,693,698,703,708,713,719,725,730,735,741,746,751,757,762,767,773,778,784,790,796,802,808,814,820,825,830,836,841,846,852,858,863,868,874,879,884,890],"code",{"__ignoreMap":38},[43,44,47],"span",{"class":45,"line":46},"line",1,[43,48,49],{},"using System;\n",[43,51,53],{"class":45,"line":52},2,[43,54,55],{},"using System.Diagnostics;\n",[43,57,59],{"class":45,"line":58},3,[43,60,61],{},"using System.Runtime.CompilerServices;\n",[43,63,65],{"class":45,"line":64},4,[43,66,67],{},"using Avalonia.Rendering.Composition.Animations;\n",[43,69,71],{"class":45,"line":70},5,[43,72,73],{},"using Avalonia.Rendering.Composition.Expressions;\n",[43,75,77],{"class":45,"line":76},6,[43,78,79],{},"using Avalonia.Rendering.Composition.Server;\n",[43,81,83],{"class":45,"line":82},7,[43,84,85],{},"using Avalonia.Rendering.Composition.Transport;\n",[43,87,89],{"class":45,"line":88},8,[43,90,91],{},"using Avalonia.Utilities;\n",[43,93,95],{"class":45,"line":94},9,[43,96,98],{"emptyLinePlaceholder":97},true,"\n",[43,100,102],{"class":45,"line":101},10,[43,103,104],{},"namespace Avalonia.Rendering.Composition\n",[43,106,108],{"class":45,"line":107},11,[43,109,110],{},"{\n",[43,112,114],{"class":45,"line":113},12,[43,115,116],{},"    \u002F\u002F\u002F \u003Csummary>\n",[43,118,120],{"class":45,"line":119},13,[43,121,122],{},"    \u002F\u002F\u002F Base class of the composition API representing a node in the visual tree structure.\n",[43,124,126],{"class":45,"line":125},14,[43,127,128],{},"    \u002F\u002F\u002F Composition objects are the visual tree structure on which all other features of the composition API use and build on.\n",[43,130,132],{"class":45,"line":131},15,[43,133,134],{},"    \u002F\u002F\u002F The API allows developers to define and create one or many \u003Csee cref=\"CompositionVisual\" \u002F> objects each representing a single node in a Visual tree.\n",[43,136,138],{"class":45,"line":137},16,[43,139,140],{},"    \u002F\u002F\u002F \u003C\u002Fsummary>\n",[43,142,144],{"class":45,"line":143},17,[43,145,146],{},"    public abstract class CompositionObject : ICompositorSerializable\n",[43,148,150],{"class":45,"line":149},18,[43,151,152],{},"    {\n",[43,154,156],{"class":45,"line":155},19,[43,157,158],{},"        \u002F\u002F\u002F \u003Csummary>\n",[43,160,162],{"class":45,"line":161},20,[43,163,164],{},"        \u002F\u002F\u002F The collection of implicit animations attached to this object.\n",[43,166,168],{"class":45,"line":167},21,[43,169,170],{},"        \u002F\u002F\u002F \u003C\u002Fsummary>\n",[43,172,174],{"class":45,"line":173},22,[43,175,176],{},"        public ImplicitAnimationCollection? ImplicitAnimations { get; set; }\n",[43,178,180],{"class":45,"line":179},23,[43,181,98],{"emptyLinePlaceholder":97},[43,183,185],{"class":45,"line":184},24,[43,186,187],{},"        private protected InlineDictionary\u003CCompositionProperty, IAnimationInstance> PendingAnimations;\n",[43,189,191],{"class":45,"line":190},25,[43,192,193],{},"        internal CompositionObject(Compositor compositor, SimpleServerObject? server)\n",[43,195,197],{"class":45,"line":196},26,[43,198,199],{},"        {\n",[43,201,203],{"class":45,"line":202},27,[43,204,205],{},"            Compositor = compositor;\n",[43,207,209],{"class":45,"line":208},28,[43,210,211],{},"            Server = server;\n",[43,213,215],{"class":45,"line":214},29,[43,216,217],{},"        }\n",[43,219,221],{"class":45,"line":220},30,[43,222,223],{},"        \n",[43,225,227],{"class":45,"line":226},31,[43,228,158],{},[43,230,232],{"class":45,"line":231},32,[43,233,234],{},"        \u002F\u002F\u002F The associated Compositor\n",[43,236,238],{"class":45,"line":237},33,[43,239,170],{},[43,241,243],{"class":45,"line":242},34,[43,244,245],{},"        public Compositor Compositor { get; }\n",[43,247,249],{"class":45,"line":248},35,[43,250,98],{"emptyLinePlaceholder":97},[43,252,254],{"class":45,"line":253},36,[43,255,256],{},"        SimpleServerObject ICompositorSerializable.TryGetServer(Compositor c)\n",[43,258,260],{"class":45,"line":259},37,[43,261,199],{},[43,263,265],{"class":45,"line":264},38,[43,266,267],{},"            Debug.Assert(c == Compositor);\n",[43,269,271],{"class":45,"line":270},39,[43,272,273],{},"            return Server ?? ThrowInvalidOperation();\n",[43,275,277],{"class":45,"line":276},40,[43,278,217],{},[43,280,282],{"class":45,"line":281},41,[43,283,98],{"emptyLinePlaceholder":97},[43,285,287],{"class":45,"line":286},42,[43,288,289],{},"        internal SimpleServerObject? Server { get; }\n",[43,291,293],{"class":45,"line":292},43,[43,294,295],{},"        public bool IsDisposed { get; private set; }\n",[43,297,299],{"class":45,"line":298},44,[43,300,301],{},"        private bool _registeredForSerialization;\n",[43,303,305],{"class":45,"line":304},45,[43,306,98],{"emptyLinePlaceholder":97},[43,308,310],{"class":45,"line":309},46,[43,311,312],{},"        [MethodImpl(MethodImplOptions.NoInlining)]\n",[43,314,316],{"class":45,"line":315},47,[43,317,318],{},"        private static SimpleServerObject ThrowInvalidOperation() =>\n",[43,320,322],{"class":45,"line":321},48,[43,323,324],{},"            throw new InvalidOperationException(\"There is no server-side counterpart for this object\");\n",[43,326,328],{"class":45,"line":327},49,[43,329,98],{"emptyLinePlaceholder":97},[43,331,333],{"class":45,"line":332},50,[43,334,335],{},"        protected internal void Dispose()\n",[43,337,339],{"class":45,"line":338},51,[43,340,199],{},[43,342,344],{"class":45,"line":343},52,[43,345,346],{},"            if (!IsDisposed && Server != null)\n",[43,348,350],{"class":45,"line":349},53,[43,351,352],{},"                Compositor.DisposeOnNextBatch(Server);\n",[43,354,356],{"class":45,"line":355},54,[43,357,358],{},"            IsDisposed = true;\n",[43,360,362],{"class":45,"line":361},55,[43,363,217],{},[43,365,367],{"class":45,"line":366},56,[43,368,98],{"emptyLinePlaceholder":97},[43,370,372],{"class":45,"line":371},57,[43,373,158],{},[43,375,377],{"class":45,"line":376},58,[43,378,379],{},"        \u002F\u002F\u002F Connects an animation with the specified property of the object and starts the animation.\n",[43,381,383],{"class":45,"line":382},59,[43,384,170],{},[43,386,388],{"class":45,"line":387},60,[43,389,390],{},"        public void StartAnimation(string propertyName, CompositionAnimation animation)\n",[43,392,394],{"class":45,"line":393},61,[43,395,396],{},"            => StartAnimation(propertyName, animation, null);\n",[43,398,400],{"class":45,"line":399},62,[43,401,223],{},[43,403,405],{"class":45,"line":404},63,[43,406,407],{},"        internal virtual void StartAnimation(string propertyName, CompositionAnimation animation, ExpressionVariant? finalValue)\n",[43,409,411],{"class":45,"line":410},64,[43,412,199],{},[43,414,416],{"class":45,"line":415},65,[43,417,418],{},"            throw new ArgumentException(\"Unknown property \" + propertyName);\n",[43,420,422],{"class":45,"line":421},66,[43,423,217],{},[43,425,427],{"class":45,"line":426},67,[43,428,98],{"emptyLinePlaceholder":97},[43,430,432],{"class":45,"line":431},68,[43,433,158],{},[43,435,437],{"class":45,"line":436},69,[43,438,439],{},"        \u002F\u002F\u002F Starts an animation group.\n",[43,441,443],{"class":45,"line":442},70,[43,444,445],{},"        \u002F\u002F\u002F The StartAnimationGroup method on CompositionObject lets you start CompositionAnimationGroup.\n",[43,447,449],{"class":45,"line":448},71,[43,450,451],{},"        \u002F\u002F\u002F All the animations in the group will be started at the same time on the object.\n",[43,453,455],{"class":45,"line":454},72,[43,456,170],{},[43,458,460],{"class":45,"line":459},73,[43,461,462],{},"        public void StartAnimationGroup(ICompositionAnimationBase grp)\n",[43,464,466],{"class":45,"line":465},74,[43,467,199],{},[43,469,471],{"class":45,"line":470},75,[43,472,473],{},"            if (grp is CompositionAnimation animation)\n",[43,475,477],{"class":45,"line":476},76,[43,478,479],{},"            {\n",[43,481,483],{"class":45,"line":482},77,[43,484,485],{},"                if(animation.Target == null)\n",[43,487,489],{"class":45,"line":488},78,[43,490,491],{},"                    throw new ArgumentException(\"Animation Target can't be null\");\n",[43,493,495],{"class":45,"line":494},79,[43,496,497],{},"                StartAnimation(animation.Target, animation);\n",[43,499,501],{"class":45,"line":500},80,[43,502,503],{},"            }\n",[43,505,507],{"class":45,"line":506},81,[43,508,509],{},"            else if (grp is CompositionAnimationGroup group)\n",[43,511,513],{"class":45,"line":512},82,[43,514,479],{},[43,516,518],{"class":45,"line":517},83,[43,519,520],{},"                foreach (var a in group.Animations)\n",[43,522,524],{"class":45,"line":523},84,[43,525,526],{},"                {\n",[43,528,530],{"class":45,"line":529},85,[43,531,532],{},"                    if (a.Target == null)\n",[43,534,536],{"class":45,"line":535},86,[43,537,538],{},"                        throw new ArgumentException(\"Animation Target can't be null\");\n",[43,540,542],{"class":45,"line":541},87,[43,543,544],{},"                    StartAnimation(a.Target, a);\n",[43,546,548],{"class":45,"line":547},88,[43,549,550],{},"                }\n",[43,552,554],{"class":45,"line":553},89,[43,555,503],{},[43,557,559],{"class":45,"line":558},90,[43,560,217],{},[43,562,564],{"class":45,"line":563},91,[43,565,98],{"emptyLinePlaceholder":97},[43,567,569],{"class":45,"line":568},92,[43,570,571],{},"        bool StartAnimationGroupPart(CompositionAnimation animation, string target, ExpressionVariant finalValue)\n",[43,573,575],{"class":45,"line":574},93,[43,576,199],{},[43,578,580],{"class":45,"line":579},94,[43,581,582],{},"            if(animation.Target == null)\n",[43,584,586],{"class":45,"line":585},95,[43,587,588],{},"                throw new ArgumentException(\"Animation Target can't be null\");\n",[43,590,592],{"class":45,"line":591},96,[43,593,594],{},"            if (animation.Target == target)\n",[43,596,598],{"class":45,"line":597},97,[43,599,479],{},[43,601,603],{"class":45,"line":602},98,[43,604,605],{},"                StartAnimation(animation.Target, animation, finalValue);\n",[43,607,609],{"class":45,"line":608},99,[43,610,611],{},"                return true;\n",[43,613,615],{"class":45,"line":614},100,[43,616,503],{},[43,618,620],{"class":45,"line":619},101,[43,621,622],{},"            else\n",[43,624,626],{"class":45,"line":625},102,[43,627,479],{},[43,629,631],{"class":45,"line":630},103,[43,632,497],{},[43,634,636],{"class":45,"line":635},104,[43,637,638],{},"                return false;\n",[43,640,642],{"class":45,"line":641},105,[43,643,503],{},[43,645,647],{"class":45,"line":646},106,[43,648,217],{},[43,650,652],{"class":45,"line":651},107,[43,653,223],{},[43,655,657],{"class":45,"line":656},108,[43,658,659],{},"        internal bool StartAnimationGroup(ICompositionAnimationBase grp, string target, ExpressionVariant finalValue)\n",[43,661,663],{"class":45,"line":662},109,[43,664,199],{},[43,666,668],{"class":45,"line":667},110,[43,669,473],{},[43,671,673],{"class":45,"line":672},111,[43,674,675],{},"                return StartAnimationGroupPart(animation, target, finalValue);\n",[43,677,679],{"class":45,"line":678},112,[43,680,681],{},"            if (grp is CompositionAnimationGroup group)\n",[43,683,685],{"class":45,"line":684},113,[43,686,479],{},[43,688,690],{"class":45,"line":689},114,[43,691,692],{},"                var matched = false;\n",[43,694,696],{"class":45,"line":695},115,[43,697,520],{},[43,699,701],{"class":45,"line":700},116,[43,702,526],{},[43,704,706],{"class":45,"line":705},117,[43,707,532],{},[43,709,711],{"class":45,"line":710},118,[43,712,538],{},[43,714,716],{"class":45,"line":715},119,[43,717,718],{},"                    if (StartAnimationGroupPart(a, target, finalValue))\n",[43,720,722],{"class":45,"line":721},120,[43,723,724],{},"                        matched = true;\n",[43,726,728],{"class":45,"line":727},121,[43,729,550],{},[43,731,733],{"class":45,"line":732},122,[43,734,98],{"emptyLinePlaceholder":97},[43,736,738],{"class":45,"line":737},123,[43,739,740],{},"                return matched;\n",[43,742,744],{"class":45,"line":743},124,[43,745,503],{},[43,747,749],{"class":45,"line":748},125,[43,750,98],{"emptyLinePlaceholder":97},[43,752,754],{"class":45,"line":753},126,[43,755,756],{},"            throw new ArgumentException();\n",[43,758,760],{"class":45,"line":759},127,[43,761,217],{},[43,763,765],{"class":45,"line":764},128,[43,766,98],{"emptyLinePlaceholder":97},[43,768,770],{"class":45,"line":769},129,[43,771,772],{},"        protected void RegisterForSerialization()\n",[43,774,776],{"class":45,"line":775},130,[43,777,199],{},[43,779,781],{"class":45,"line":780},131,[43,782,783],{},"            if (Server == null)\n",[43,785,787],{"class":45,"line":786},132,[43,788,789],{},"                throw new InvalidOperationException(\"The object doesn't have an associated server counterpart\");\n",[43,791,793],{"class":45,"line":792},133,[43,794,795],{},"            \n",[43,797,799],{"class":45,"line":798},134,[43,800,801],{},"            if(_registeredForSerialization)\n",[43,803,805],{"class":45,"line":804},135,[43,806,807],{},"                return;\n",[43,809,811],{"class":45,"line":810},136,[43,812,813],{},"            _registeredForSerialization = true;\n",[43,815,817],{"class":45,"line":816},137,[43,818,819],{},"            Compositor.RegisterForSerialization(this);\n",[43,821,823],{"class":45,"line":822},138,[43,824,217],{},[43,826,828],{"class":45,"line":827},139,[43,829,98],{"emptyLinePlaceholder":97},[43,831,833],{"class":45,"line":832},140,[43,834,835],{},"        void ICompositorSerializable.SerializeChanges(Compositor c, BatchStreamWriter writer)\n",[43,837,839],{"class":45,"line":838},141,[43,840,199],{},[43,842,844],{"class":45,"line":843},142,[43,845,267],{},[43,847,849],{"class":45,"line":848},143,[43,850,851],{},"            _registeredForSerialization = false;\n",[43,853,855],{"class":45,"line":854},144,[43,856,857],{},"            SerializeChangesCore(writer);\n",[43,859,861],{"class":45,"line":860},145,[43,862,217],{},[43,864,866],{"class":45,"line":865},146,[43,867,98],{"emptyLinePlaceholder":97},[43,869,871],{"class":45,"line":870},147,[43,872,873],{},"        private protected virtual void SerializeChangesCore(BatchStreamWriter writer)\n",[43,875,877],{"class":45,"line":876},148,[43,878,199],{},[43,880,882],{"class":45,"line":881},149,[43,883,217],{},[43,885,887],{"class":45,"line":886},150,[43,888,889],{},"    }\n",[43,891,893],{"class":45,"line":892},151,[43,894,895],{},"}\n",[10,897,898,901,902,905,906,908,909,912,913,916],{},[40,899,900],{},"ImplicitAnimations"," 是 ",[40,903,904],{},"CompositionObject"," 的一个公开属性,可以直接设置。这正是我们之前使用的方式，每个 ",[40,907,904],{}," 都有一个关联的 ",[40,910,911],{},"Server"," 对象,这是",[14,914,915],{},"渲染线程上的对应实体","。这就是为什么 Composition API 能在渲染线程上执行动画的原因。",[10,918,919,922,923,926],{},[40,920,921],{},"RegisterForSerialization()"," 方法确保",[14,924,925],{},"对象的更改会被序列化并发送到渲染线程","。这是 UI 线程和渲染线程之间通信的关键机制。",[10,928,929,932,933,936,937,939],{},[40,930,931],{},"StartAnimation"," 和 ",[40,934,935],{},"StartAnimationGroup"," 方法提供了显式启动动画的方式,而 ",[40,938,900],{}," 则是在属性变化时自动触发。",[10,941,942],{},"所以我们应该推荐使用这种动画机制，下面我们使用一个简单的例子来看看，我们试着在loaded之后获取一下组件对象，然后获取组件的合成器来达到我们想要的效果。",[33,944,946],{"className":35,"code":945,"language":37,"meta":38,"style":38}," private void MyBorder_Loaded(object? sender, RoutedEventArgs e)\n {\n     var visual = ElementComposition.GetElementVisual(MyBorder);\n     if (visual == null) return; \n     var compositor = visual.Compositor;\n     if (compositor == null) return;\n     \n     var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();\n     offsetAnimation.Target = \"Offset\";\n     offsetAnimation.InsertExpressionKeyFrame(1, \"this.FinalValue\");\n     offsetAnimation.Duration = TimeSpan.FromMilliseconds(2000);\n     \n     var implicaitAnimation = compositor.CreateImplicitAnimationCollection();\n     implicaitAnimation[\"Offset\"] = offsetAnimation;\n     visual.ImplicitAnimations = implicaitAnimation;\n     MyBorder.Loaded -= MyBorder_Loaded;\n }\n",[40,947,948,953,958,963,968,973,978,983,988,993,998,1003,1007,1012,1017,1022,1027],{"__ignoreMap":38},[43,949,950],{"class":45,"line":46},[43,951,952],{}," private void MyBorder_Loaded(object? sender, RoutedEventArgs e)\n",[43,954,955],{"class":45,"line":52},[43,956,957],{}," {\n",[43,959,960],{"class":45,"line":58},[43,961,962],{},"     var visual = ElementComposition.GetElementVisual(MyBorder);\n",[43,964,965],{"class":45,"line":64},[43,966,967],{},"     if (visual == null) return; \n",[43,969,970],{"class":45,"line":70},[43,971,972],{},"     var compositor = visual.Compositor;\n",[43,974,975],{"class":45,"line":76},[43,976,977],{},"     if (compositor == null) return;\n",[43,979,980],{"class":45,"line":82},[43,981,982],{},"     \n",[43,984,985],{"class":45,"line":88},[43,986,987],{},"     var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();\n",[43,989,990],{"class":45,"line":94},[43,991,992],{},"     offsetAnimation.Target = \"Offset\";\n",[43,994,995],{"class":45,"line":101},[43,996,997],{},"     offsetAnimation.InsertExpressionKeyFrame(1, \"this.FinalValue\");\n",[43,999,1000],{"class":45,"line":107},[43,1001,1002],{},"     offsetAnimation.Duration = TimeSpan.FromMilliseconds(2000);\n",[43,1004,1005],{"class":45,"line":113},[43,1006,982],{},[43,1008,1009],{"class":45,"line":119},[43,1010,1011],{},"     var implicaitAnimation = compositor.CreateImplicitAnimationCollection();\n",[43,1013,1014],{"class":45,"line":125},[43,1015,1016],{},"     implicaitAnimation[\"Offset\"] = offsetAnimation;\n",[43,1018,1019],{"class":45,"line":131},[43,1020,1021],{},"     visual.ImplicitAnimations = implicaitAnimation;\n",[43,1023,1024],{"class":45,"line":137},[43,1025,1026],{},"     MyBorder.Loaded -= MyBorder_Loaded;\n",[43,1028,1029],{"class":45,"line":143},[43,1030,1031],{}," }\n",[10,1033,1034,1035,1038,1039,1042,1043,1046],{},"我们把这个事件给组件的Loaded去订阅，我们得到组件的合成器，然后利用它创建一个三维动画，绑定属性到Offset上面，",[14,1036,1037],{},"为什么这里是3keyframe","，因为它创建的是",[40,1040,1041],{},"Vector3DKeyFrameAnimation",",专门用于动画化 ",[40,1044,1045],{},"Vector3D"," 类型的属性。",[10,1048,1049,1050,1053,1054,1057,1058,1061],{},"在 Composition API 的关键帧动画中,",[40,1051,1052],{},"InsertExpressionKeyFrame"," 方法的第一个参数是 ",[40,1055,1056],{},"normalizedProgressKey",",它是一个 ",[14,1059,1060],{},"0.0 到 1.0 之间的浮点数",",表示动画时间轴上的位置:",[1063,1064,1065,1076,1085],"ul",{},[1066,1067,1068,1071,1072,1075],"li",{},[40,1069,1070],{},"0.0"," (或 ",[40,1073,1074],{},"0",") = 动画开始 (0%)",[1066,1077,1078,1071,1081,1084],{},[40,1079,1080],{},"0.5",[40,1082,1083],{},"0.5f",") = 动画中间 (50%)",[1066,1086,1087,1071,1090,1093],{},[40,1088,1089],{},"1.0",[40,1091,1092],{},"1",") = 动画结束 (100%) Generator.KeyFrameAnimation.cs:39-42\n后面的不用想就是设置持续时间，之后我们通过合成器去创建隐式动画，将隐式动画里加入我们之前创建的动画，然后将它传递给visual的隐式动画里去执行。",[10,1095,1096],{},"代码生成器里，每种类型都有对应的KeyFrameAnimation类，而这个方法就是创建这个类实例的工厂方法。",[10,1098,1099],{},"类似地,其他属性也有对应的动画类型:",[1063,1101,1102,1111,1120,1129],{},[1066,1103,1104,1107,1108],{},[40,1105,1106],{},"Opacity"," (float) → ",[40,1109,1110],{},"CreateScalarKeyFrameAnimation()",[1066,1112,1113,1116,1117],{},[40,1114,1115],{},"Color"," → ",[40,1118,1119],{},"CreateColorKeyFrameAnimation()",[1066,1121,1122,1125,1126],{},[40,1123,1124],{},"Scale"," (Vector3D) → ",[40,1127,1128],{},"CreateVector3KeyFrameAnimation()",[1066,1130,1131,1107,1134],{},[40,1132,1133],{},"RotationAngle",[40,1135,1110],{},[10,1137,1138,1139,1142,1143,1146,1147,1150,1151,1154,1155,1154,1157,1159,1160,1162,1163,1165,1166,1169],{},"如果想要添加颜色动画，我们需要创建一个",[40,1140,1141],{},"CompositionSolidColorVisual"," 作为子视觉元素，",[40,1144,1145],{},"Border"," 的 ",[40,1148,1149],{},"CompositionVisual"," 只支持 ",[40,1152,1153],{},"Offset","、",[40,1156,1106],{},[40,1158,1124],{}," 等通用属性,不支持 ",[40,1161,1115],{},"\n如果需要颜色动画,必须创建 ",[40,1164,1141],{}," 作为子视觉元素，我们可以使用",[40,1167,1168],{},"ElementComposition.SetElementChildVisual()"," 将子视觉元素附加到控件上。",[10,1171,1172],{},"我们可以知道的是，每个Visual都有一个ChildCompositionVisual的属性，这就是通过SetElementChildVisual()设置的子窗口元素。",[10,1174,1175],{},"ChildComositionVisual 会被添加到CompositionVisual.Children集合的最后，这就意味着他会渲染在其他的子元素之上。",[10,1177,1178],{},"一下是CompositionSolidColorVisual类可以设置的属性：",[33,1180,1185],{"className":1181,"code":1183,"language":1184},[1182],"language-text"," \u003CObject Name=\"CompositionVisual\" Abstract=\"true\">\n        \u003CProperty Name=\"Root\" Type=\"CompositionTarget?\" Internal=\"true\" \u002F>\n        \u003CProperty Name=\"Parent\" Type=\"CompositionVisual?\" Internal=\"true\"  \u002F>\n        \u003CProperty Name=\"Visible\" Type=\"bool\" Animated=\"true\" DefaultValue=\"true\"\u002F>\n        \u003CProperty Name=\"Opacity\" Type=\"float\" Animated=\"true\" DefaultValue=\"1\"\u002F>\n        \n        \u003CProperty Name=\"Clip\" Type=\"Avalonia.Platform.IGeometryImpl?\" Internal=\"true\" \u002F>\n        \u003CProperty Name=\"ClipToBounds\" Type=\"bool\" Animated=\"true\" DefaultValue=\"true\"\u002F>\n        \u003CProperty Name=\"Offset\" Type=\"Vector3D\" Animated=\"true\"\u002F>\n        \u003CProperty Name=\"Size\" Type=\"Vector\" Animated=\"true\"\u002F>\n        \u003CProperty Name=\"AnchorPoint\" Type=\"Vector\" Animated=\"true\"\u002F>\n        \u003CProperty Name=\"CenterPoint\" Type=\"Vector3D\" Animated=\"true\"\u002F>\n        \u003CProperty Name=\"RotationAngle\" Type=\"float\" Animated=\"true\"\u002F>\n        \u003CProperty Name=\"Orientation\" Type=\"Quaternion\" DefaultValue=\"Quaternion.Identity\" Animated=\"true\"\u002F>\n        \u003CProperty Name=\"Scale\" Type=\"Vector3D\" DefaultValue=\"new Avalonia.Vector3D(1, 1, 1)\" Animated=\"true\"\u002F>\n        \u003CProperty Name=\"TransformMatrix\" Type=\"Avalonia.Matrix\" DefaultValue=\"Avalonia.Matrix.Identity\" Animated=\"true\" Internal=\"true\"\u002F>\n        \u003CProperty Name=\"AdornedVisual\" Type=\"CompositionVisual?\" Internal=\"true\" \u002F>\n        \u003CProperty Name=\"AdornerIsClipped\" Type=\"bool\" Internal=\"true\" \u002F>\n        \u003CProperty Name=\"OpacityMaskBrush\" ClientName=\"OpacityMaskBrushTransportField\" Type=\"Avalonia.Media.IBrush?\" Private=\"true\"  \u002F>\n        \u003CProperty Name=\"Effect\" Type=\"Avalonia.Media.IImmutableEffect?\" Internal=\"true\" \u002F>\n        \u003CProperty Name=\"RenderOptions\" Type=\"Avalonia.Media.RenderOptions\" \u002F>\n        \u003CProperty Name=\"ShouldExtendDirtyRect\" Type=\"bool\" Internal=\"true\" \u002F>\n    \u003C\u002FObject>\n    \u003CObject Name=\"CompositionContainerVisual\" Inherits=\"CompositionVisual\"\u002F>\n    \u003CObject Name=\"CompositionSolidColorVisual\" Inherits=\"CompositionContainerVisual\">\n        \u003CProperty Name=\"Color\" Type=\"Avalonia.Media.Color\" Animated=\"true\" \u002F>\n    \u003C\u002FObject>\n    \u003CObject Name=\"CompositionSurfaceVisual\" Inherits=\"CompositionContainerVisual\">\n        \u003CProperty Name=\"Surface\" Type=\"CompositionSurface?\" \u002F>\n    \u003C\u002FObject>\n","text",[40,1186,1183],{"__ignoreMap":38},[10,1188,1189,1191,1192,1195,1196,1199],{},[40,1190,1141],{}," ",[14,1193,1194],{},"不是","其他组件的基类，而是一个",[14,1197,1198],{},"具体的实现类","。继承关系如下：",[33,1201,1204],{"className":1202,"code":1203,"language":1184},[1182],"CompositionObject (抽象基类)  \n    └── CompositionVisual (抽象基类)  \n            └── CompositionContainerVisual  \n                    ├── CompositionSolidColorVisual (具体类)  \n                    └── CompositionSurfaceVisual (具体类) \n",[40,1205,1203],{"__ignoreMap":38},[10,1207,1208,1209,1212,1213,1216,1217,1220,1221,1224,1225,1227,1228,1231],{},"和他同级的",[40,1210,1211],{},"CompositionSurfaceVisual"," 是用于",[14,1214,1215],{},"显示自定义渲染内容","的视觉元素,它通过 ",[40,1218,1219],{},"Surface"," 属性关联一个 ",[40,1222,1223],{},"CompositionSurface"," 对象来显示复杂的图形内容。它只有一个特有属性 ",[40,1226,1219],{},",类型为 ",[40,1229,1230],{},"CompositionSurface?","。",[10,1233,1234],{},"具体实现如下：",[33,1236,1238],{"className":35,"code":1237,"language":37,"meta":38,"style":38},"using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Interactivity;\nusing Avalonia.Media;\nusing Avalonia.Rendering.Composition;\nusing Avalonia.VisualTree;\nusing System;\n\nnamespace AvaloniaTestDemo.Views;\n\npublic partial class MainView : UserControl\n{\n    private CompositionSolidColorVisual _colorVisual;\n    public MainView()\n    {\n        InitializeComponent();\n        MyBorder.Loaded += MyBorder_Loaded;\n    }\n\n    private void MyBorder_Loaded(object? sender, RoutedEventArgs e)\n    {\n        var visual = ElementComposition.GetElementVisual(MyBorder);\n        if (visual == null) return;\n        var compositor = visual.Compositor;\n        if (compositor == null) return;\n\n        _colorVisual = compositor.CreateSolidColorVisual();\n        _colorVisual.Size = new Vector(MyBorder.Bounds.Width, MyBorder.Bounds.Height);\n        _colorVisual.Color = Colors.Transparent;\n        _colorVisual.ClipToBounds = true;\n        ElementComposition.SetElementChildVisual(MyBorder, _colorVisual);\n\n        \u002F\u002F 创建 Offset 动画  \n        var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();\n        offsetAnimation.Target = \"Offset\";\n        offsetAnimation.InsertExpressionKeyFrame(1, \"this.FinalValue\");\n        offsetAnimation.Duration = TimeSpan.FromMilliseconds(2000);\n\n        \u002F\u002F 创建 Color 动画  \n        var colorAnimation = compositor.CreateColorKeyFrameAnimation();\n        colorAnimation.Target = \"Color\";\n        colorAnimation.InsertExpressionKeyFrame(1, \"this.FinalValue\");\n        colorAnimation.Duration = TimeSpan.FromMilliseconds(2000);\n\n        \u002F\u002F 将两个动画添加到同一个 ImplicitAnimationCollection  \n        var implicitAnimations = compositor.CreateImplicitAnimationCollection();\n        implicitAnimations[\"Offset\"] = offsetAnimation;\n\n        \u002F\u002F 为 visual 设置 Offset 动画  \n        visual.ImplicitAnimations = implicitAnimations;\n\n        \u002F\u002F 为 _colorVisual 设置 Color 动画  \n        var colorImplicitAnimations = compositor.CreateImplicitAnimationCollection();\n        colorImplicitAnimations[\"Color\"] = colorAnimation;\n        _colorVisual.ImplicitAnimations = colorImplicitAnimations;\n\n        MyBorder.Loaded -= MyBorder_Loaded;\n    }\n\n    private void Border_Click(object? sender, Avalonia.Input.PointerPressedEventArgs e)\n    {\n        if (sender is Border border)\n        {\n            var visual = ElementComposition.GetElementVisual(border);\n            if (visual == null) return; \u002F\u002F Check if visual is null before accessing its properties\n            visual.Offset += new Vector3D(100, 100, 0); \u002F\u002F x=100, y=100\n            _colorVisual.Color = Colors.Red;\n        }\n    }\n}\n",[40,1239,1240,1245,1250,1255,1260,1265,1270,1274,1278,1283,1287,1292,1296,1301,1306,1310,1315,1320,1324,1328,1333,1337,1342,1347,1352,1357,1361,1366,1371,1376,1381,1386,1390,1395,1400,1405,1410,1415,1419,1424,1429,1434,1439,1444,1448,1453,1458,1463,1467,1472,1477,1481,1486,1491,1496,1501,1505,1510,1514,1518,1523,1527,1532,1536,1541,1546,1551,1556,1560,1564],{"__ignoreMap":38},[43,1241,1242],{"class":45,"line":46},[43,1243,1244],{},"using Avalonia;\n",[43,1246,1247],{"class":45,"line":52},[43,1248,1249],{},"using Avalonia.Controls;\n",[43,1251,1252],{"class":45,"line":58},[43,1253,1254],{},"using Avalonia.Interactivity;\n",[43,1256,1257],{"class":45,"line":64},[43,1258,1259],{},"using Avalonia.Media;\n",[43,1261,1262],{"class":45,"line":70},[43,1263,1264],{},"using Avalonia.Rendering.Composition;\n",[43,1266,1267],{"class":45,"line":76},[43,1268,1269],{},"using Avalonia.VisualTree;\n",[43,1271,1272],{"class":45,"line":82},[43,1273,49],{},[43,1275,1276],{"class":45,"line":88},[43,1277,98],{"emptyLinePlaceholder":97},[43,1279,1280],{"class":45,"line":94},[43,1281,1282],{},"namespace AvaloniaTestDemo.Views;\n",[43,1284,1285],{"class":45,"line":101},[43,1286,98],{"emptyLinePlaceholder":97},[43,1288,1289],{"class":45,"line":107},[43,1290,1291],{},"public partial class MainView : UserControl\n",[43,1293,1294],{"class":45,"line":113},[43,1295,110],{},[43,1297,1298],{"class":45,"line":119},[43,1299,1300],{},"    private CompositionSolidColorVisual _colorVisual;\n",[43,1302,1303],{"class":45,"line":125},[43,1304,1305],{},"    public MainView()\n",[43,1307,1308],{"class":45,"line":131},[43,1309,152],{},[43,1311,1312],{"class":45,"line":137},[43,1313,1314],{},"        InitializeComponent();\n",[43,1316,1317],{"class":45,"line":143},[43,1318,1319],{},"        MyBorder.Loaded += MyBorder_Loaded;\n",[43,1321,1322],{"class":45,"line":149},[43,1323,889],{},[43,1325,1326],{"class":45,"line":155},[43,1327,98],{"emptyLinePlaceholder":97},[43,1329,1330],{"class":45,"line":161},[43,1331,1332],{},"    private void MyBorder_Loaded(object? sender, RoutedEventArgs e)\n",[43,1334,1335],{"class":45,"line":167},[43,1336,152],{},[43,1338,1339],{"class":45,"line":173},[43,1340,1341],{},"        var visual = ElementComposition.GetElementVisual(MyBorder);\n",[43,1343,1344],{"class":45,"line":179},[43,1345,1346],{},"        if (visual == null) return;\n",[43,1348,1349],{"class":45,"line":184},[43,1350,1351],{},"        var compositor = visual.Compositor;\n",[43,1353,1354],{"class":45,"line":190},[43,1355,1356],{},"        if (compositor == null) return;\n",[43,1358,1359],{"class":45,"line":196},[43,1360,98],{"emptyLinePlaceholder":97},[43,1362,1363],{"class":45,"line":202},[43,1364,1365],{},"        _colorVisual = compositor.CreateSolidColorVisual();\n",[43,1367,1368],{"class":45,"line":208},[43,1369,1370],{},"        _colorVisual.Size = new Vector(MyBorder.Bounds.Width, MyBorder.Bounds.Height);\n",[43,1372,1373],{"class":45,"line":214},[43,1374,1375],{},"        _colorVisual.Color = Colors.Transparent;\n",[43,1377,1378],{"class":45,"line":220},[43,1379,1380],{},"        _colorVisual.ClipToBounds = true;\n",[43,1382,1383],{"class":45,"line":226},[43,1384,1385],{},"        ElementComposition.SetElementChildVisual(MyBorder, _colorVisual);\n",[43,1387,1388],{"class":45,"line":231},[43,1389,98],{"emptyLinePlaceholder":97},[43,1391,1392],{"class":45,"line":237},[43,1393,1394],{},"        \u002F\u002F 创建 Offset 动画  \n",[43,1396,1397],{"class":45,"line":242},[43,1398,1399],{},"        var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();\n",[43,1401,1402],{"class":45,"line":248},[43,1403,1404],{},"        offsetAnimation.Target = \"Offset\";\n",[43,1406,1407],{"class":45,"line":253},[43,1408,1409],{},"        offsetAnimation.InsertExpressionKeyFrame(1, \"this.FinalValue\");\n",[43,1411,1412],{"class":45,"line":259},[43,1413,1414],{},"        offsetAnimation.Duration = TimeSpan.FromMilliseconds(2000);\n",[43,1416,1417],{"class":45,"line":264},[43,1418,98],{"emptyLinePlaceholder":97},[43,1420,1421],{"class":45,"line":270},[43,1422,1423],{},"        \u002F\u002F 创建 Color 动画  \n",[43,1425,1426],{"class":45,"line":276},[43,1427,1428],{},"        var colorAnimation = compositor.CreateColorKeyFrameAnimation();\n",[43,1430,1431],{"class":45,"line":281},[43,1432,1433],{},"        colorAnimation.Target = \"Color\";\n",[43,1435,1436],{"class":45,"line":286},[43,1437,1438],{},"        colorAnimation.InsertExpressionKeyFrame(1, \"this.FinalValue\");\n",[43,1440,1441],{"class":45,"line":292},[43,1442,1443],{},"        colorAnimation.Duration = TimeSpan.FromMilliseconds(2000);\n",[43,1445,1446],{"class":45,"line":298},[43,1447,98],{"emptyLinePlaceholder":97},[43,1449,1450],{"class":45,"line":304},[43,1451,1452],{},"        \u002F\u002F 将两个动画添加到同一个 ImplicitAnimationCollection  \n",[43,1454,1455],{"class":45,"line":309},[43,1456,1457],{},"        var implicitAnimations = compositor.CreateImplicitAnimationCollection();\n",[43,1459,1460],{"class":45,"line":315},[43,1461,1462],{},"        implicitAnimations[\"Offset\"] = offsetAnimation;\n",[43,1464,1465],{"class":45,"line":321},[43,1466,98],{"emptyLinePlaceholder":97},[43,1468,1469],{"class":45,"line":327},[43,1470,1471],{},"        \u002F\u002F 为 visual 设置 Offset 动画  \n",[43,1473,1474],{"class":45,"line":332},[43,1475,1476],{},"        visual.ImplicitAnimations = implicitAnimations;\n",[43,1478,1479],{"class":45,"line":338},[43,1480,98],{"emptyLinePlaceholder":97},[43,1482,1483],{"class":45,"line":343},[43,1484,1485],{},"        \u002F\u002F 为 _colorVisual 设置 Color 动画  \n",[43,1487,1488],{"class":45,"line":349},[43,1489,1490],{},"        var colorImplicitAnimations = compositor.CreateImplicitAnimationCollection();\n",[43,1492,1493],{"class":45,"line":355},[43,1494,1495],{},"        colorImplicitAnimations[\"Color\"] = colorAnimation;\n",[43,1497,1498],{"class":45,"line":361},[43,1499,1500],{},"        _colorVisual.ImplicitAnimations = colorImplicitAnimations;\n",[43,1502,1503],{"class":45,"line":366},[43,1504,98],{"emptyLinePlaceholder":97},[43,1506,1507],{"class":45,"line":371},[43,1508,1509],{},"        MyBorder.Loaded -= MyBorder_Loaded;\n",[43,1511,1512],{"class":45,"line":376},[43,1513,889],{},[43,1515,1516],{"class":45,"line":382},[43,1517,98],{"emptyLinePlaceholder":97},[43,1519,1520],{"class":45,"line":387},[43,1521,1522],{},"    private void Border_Click(object? sender, Avalonia.Input.PointerPressedEventArgs e)\n",[43,1524,1525],{"class":45,"line":393},[43,1526,152],{},[43,1528,1529],{"class":45,"line":399},[43,1530,1531],{},"        if (sender is Border border)\n",[43,1533,1534],{"class":45,"line":404},[43,1535,199],{},[43,1537,1538],{"class":45,"line":410},[43,1539,1540],{},"            var visual = ElementComposition.GetElementVisual(border);\n",[43,1542,1543],{"class":45,"line":415},[43,1544,1545],{},"            if (visual == null) return; \u002F\u002F Check if visual is null before accessing its properties\n",[43,1547,1548],{"class":45,"line":421},[43,1549,1550],{},"            visual.Offset += new Vector3D(100, 100, 0); \u002F\u002F x=100, y=100\n",[43,1552,1553],{"class":45,"line":426},[43,1554,1555],{},"            _colorVisual.Color = Colors.Red;\n",[43,1557,1558],{"class":45,"line":431},[43,1559,217],{},[43,1561,1562],{"class":45,"line":436},[43,1563,889],{},[43,1565,1566],{"class":45,"line":442},[43,1567,895],{},[10,1569,1570,1571,1573,1574,1576,1577,1580,1581,1584],{},"为什么Border的ElementVisual没有Color，根据根据 composition schema 的定义,",[40,1572,1145],{}," 对应的 ",[40,1575,1149],{}," 类型是 ",[40,1578,1579],{},"CompositionBorderVisual",",它继承自 ",[40,1582,1583],{},"CompositionDrawListVisual",": BorderVisual.cs:10-18",[10,1586,1587,1588,1590,1591,1596],{},"而 ",[40,1589,1149],{}," 基类只定义了通用的可动画属性,",[14,1592,1593,1594],{},"不包括 ",[40,1595,1115],{},": composition-schema.xml:16-37",[10,1598,1599,1600,1602],{},"因此,要为 ",[40,1601,1145],{}," 添加颜色动画,必须:",[1604,1605,1606,1614,1625,1633],"ol",{},[1066,1607,1608],{},[14,1609,1610,1611,1613],{},"创建一个 ",[40,1612,1141],{}," 作为子视觉元素",[1066,1615,1616],{},[14,1617,1618,1619,1146,1621,1624],{},"使用 ",[40,1620,1145],{},[40,1622,1623],{},"Compositor"," 来创建这个子视觉元素",[1066,1626,1627],{},[14,1628,1629,1630,1632],{},"为子视觉元素设置 ",[40,1631,1115],{}," 的隐式动画",[1066,1634,1635,1644],{},[14,1636,1637,1638,1640,1641,1643],{},"通过 ",[40,1639,1168],{}," 将其附加到 ",[40,1642,1145],{}," 上","\n所以我也推荐给父组件visual设置裁切子元素： visual.ClipToBounds = ture;",[10,1646,1647],{},"这个颜色变化就是邪教用法了，实际上我们很少这么操作，因为组件本身已经有了属性动画了。",[1649,1650,1651],"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":38,"searchDepth":52,"depth":52,"links":1653},[],"composition api 可以为我们在所绑定的属性的值发生变化的时候自动产生隐式动画，这个可以为我们节省很多的时间，并且Composition API 是在渲染线程上执行的，而不是UI线程上执行的。","md",{},"\u002Fcsharp\u002Favalonia\u002Favalonia的composition-api-动画",{"description":1654},"csharp\u002FAvalonia\u002Favalonia的composition api 动画","Gvaf4xayhZpsplVxe_2mc4nm1RA0hUujiSM__aktBVI",1791042464634]