Skip to content

Commit 0c94a78

Browse files
Move FadeHeaderBehavior to new base class
Was much more aligned then it looked at first glance. Think it was first, so some things were just done locally in scope vs. cached from the other classes. At least now we can clean things up all together easily if we want to optimize things. Main difference is some of the event hooks, so if they're not needed we may want flags/options for those in the base class to enable.
1 parent 0f07550 commit 0c94a78

File tree

4 files changed

+27
-121
lines changed

4 files changed

+27
-121
lines changed

components/Behaviors/src/Headers/FadeHeaderBehavior.cs

Lines changed: 18 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using CommunityToolkit.WinUI.Animations.Expressions;
6+
using CommunityToolkit.WinUI.Behaviors.Internal;
67

78
#if WINUI3
89
using Microsoft.UI.Composition;
@@ -22,134 +23,35 @@ namespace CommunityToolkit.WinUI.Behaviors;
2223
/// <seealso>
2324
/// <cref>Microsoft.Xaml.Interactivity.Behavior{Windows.UI.Xaml.UIElement}</cref>
2425
/// </seealso>
25-
public class FadeHeaderBehavior : BehaviorBase<FrameworkElement>
26+
public class FadeHeaderBehavior : HeaderBehaviorBase
2627
{
27-
/// <summary>
28-
/// Attaches the behavior to the associated object.
29-
/// </summary>
30-
/// <returns>
31-
/// <c>true</c> if attaching succeeded; otherwise <c>false</c>.
32-
/// </returns>
33-
protected override bool Initialize()
28+
/// <inheritdoc/>
29+
protected override bool AssignAnimation()
3430
{
35-
var result = AssignFadeAnimation();
36-
return result;
37-
}
38-
39-
/// <summary>
40-
/// Detaches the behavior from the associated object.
41-
/// </summary>
42-
/// <returns>
43-
/// <c>true</c> if detaching succeeded; otherwise <c>false</c>.
44-
/// </returns>
45-
protected override bool Uninitialize()
46-
{
47-
RemoveFadeAnimation();
48-
return true;
49-
}
50-
51-
/// <summary>
52-
/// If any of the properties are changed then the animation is automatically started depending on the AutomaticallyStart property.
53-
/// </summary>
54-
/// <param name="d">The dependency object.</param>
55-
/// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
56-
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
57-
{
58-
var b = d as FadeHeaderBehavior;
59-
b?.AssignFadeAnimation();
60-
}
61-
62-
/// <summary>
63-
/// The UIElement that will be faded.
64-
/// </summary>
65-
public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
66-
nameof(HeaderElement), typeof(UIElement), typeof(FadeHeaderBehavior), new PropertyMetadata(null, PropertyChangedCallback));
67-
68-
/// <summary>
69-
/// Gets or sets the target element for the Fading behavior.
70-
/// </summary>
71-
/// <remarks>
72-
/// Set this using the header of a ListView or GridView. You can use the entire root of the header or an element within the header.
73-
///
74-
/// Using this example Header:
75-
/// <ListView.Header>
76-
/// <Grid Name="MyHeader">
77-
/// </Grid>
78-
/// </ListView.Header>
79-
///
80-
/// The behavior would be implemented like this
81-
/// <FadeHeaderBehavior HeaderElement="{Binding ElementName=HeaderPanel}" />
82-
/// </remarks>
83-
public UIElement HeaderElement
84-
{
85-
get { return (UIElement)GetValue(HeaderElementProperty); }
86-
set { SetValue(HeaderElementProperty, value); }
87-
}
88-
89-
/// <summary>
90-
/// Uses Composition API to get the UIElement and sets an ExpressionAnimation
91-
/// The ExpressionAnimation uses the height of the UIElement to calculate an opacity value
92-
/// for the Header as it is scrolling off-screen. The opacity reaches 0 when the Header
93-
/// is entirely scrolled off.
94-
/// </summary>
95-
/// <returns><c>true</c> if the assignment was successful; otherwise, <c>false</c>.</returns>
96-
private bool AssignFadeAnimation()
97-
{
98-
if (AssociatedObject == null)
31+
if (base.AssignAnimation())
9932
{
100-
return false;
101-
}
33+
// Get the ScrollViewer's ManipulationPropertySet
34+
var scrollPropSet = _scrollProperties!.GetSpecializedReference<ManipulationPropertySetReferenceNode>();
10235

103-
var scroller = AssociatedObject as ScrollViewer ?? AssociatedObject.FindDescendant<ScrollViewer>();
104-
if (scroller == null)
105-
{
106-
return false;
107-
}
36+
// Use the ScrollViewer's Y offset and the header's height to calculate the opacity percentage. Clamp it between 0% and 100%
37+
var headerHeight = (float)HeaderElement.RenderSize.Height;
38+
var opacityExpression = ExpressionFunctions.Clamp(1 - (-scrollPropSet.Translation.Y / headerHeight), 0, 1);
10839

109-
var listView = AssociatedObject as ListViewBase ?? AssociatedObject.FindDescendant<ListViewBase>();
40+
// Begin animating
41+
_headerVisual?.StartAnimation("Opacity", opacityExpression);
11042

111-
if (listView != null && listView.ItemsPanelRoot != null)
112-
{
113-
Canvas.SetZIndex(listView.ItemsPanelRoot, -1);
114-
}
115-
116-
// Implicit operation: Find the Header object of the control if it uses ListViewBase
117-
if (HeaderElement == null && listView != null)
118-
{
119-
HeaderElement = (listView.Header as UIElement)!;
43+
return true;
12044
}
12145

122-
// If no header is set or detected, return.
123-
if (HeaderElement == null || HeaderElement.RenderSize.Height == 0d)
124-
{
125-
return false;
126-
}
127-
128-
// Get the ScrollViewer's ManipulationPropertySet
129-
var scrollViewerManipulationPropSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller);
130-
var scrollPropSet = scrollViewerManipulationPropSet.GetSpecializedReference<ManipulationPropertySetReferenceNode>();
131-
132-
// Use the ScrollViewer's Y offset and the header's height to calculate the opacity percentage. Clamp it between 0% and 100%
133-
var headerHeight = (float)HeaderElement.RenderSize.Height;
134-
var opacityExpression = ExpressionFunctions.Clamp(1 - (-scrollPropSet.Translation.Y / headerHeight), 0, 1);
135-
136-
// Begin animating
137-
var targetElement = ElementCompositionPreview.GetElementVisual(HeaderElement);
138-
targetElement.StartAnimation("Opacity", opacityExpression);
139-
140-
return true;
46+
return false;
14147
}
14248

143-
/// <summary>
144-
/// Remove the opacity animation from the UIElement.
145-
/// </summary>
146-
private void RemoveFadeAnimation()
49+
protected override void StopAnimation()
14750
{
148-
if (HeaderElement != null)
51+
if (_headerVisual != null)
14952
{
150-
var targetElement = ElementCompositionPreview.GetElementVisual(HeaderElement);
151-
targetElement.StopAnimation("Opacity");
152-
targetElement.Opacity = 1.0f;
53+
_headerVisual.StopAnimation("Opacity");
54+
_headerVisual.Opacity = 1.0f;
15355
}
15456
}
15557
}

components/Behaviors/src/Headers/HeaderBehaviorBase.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,11 @@ protected override bool Uninitialize()
8080
}
8181

8282
/// <summary>
83-
/// Uses Composition API to get the UIElement and sets an ExpressionAnimation
83+
/// Uses Composition API to get the UIElement and sets an ExpressionAnimation.
8484
/// </summary>
85+
/// <remarks>
86+
/// If this method returns true, you should have access to all protected fields with assigned components to use.
87+
/// </remarks>
8588
/// <returns><c>true</c> if the assignment was successful; otherwise, <c>false</c>.</returns>
8689
protected virtual bool AssignAnimation()
8790
{
@@ -143,6 +146,7 @@ protected virtual bool AssignAnimation()
143146
return false;
144147
}
145148

149+
// TODO: Not sure if we need to provide an option to turn these events off, as FadeHeaderBehavior didn't use these two, unlike QuickReturn/Sticky did...
146150
headerElement.SizeChanged -= ScrollHeader_SizeChanged;
147151
headerElement.SizeChanged += ScrollHeader_SizeChanged;
148152

components/Behaviors/src/Headers/QuickReturnHeaderBehavior.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ protected override bool AssignAnimation()
6363
/// <inheritdoc/>
6464
protected override void StopAnimation()
6565
{
66-
_headerVisual?.StopAnimation("Offset.Y");
67-
6866
_animationProperties?.InsertScalar("OffsetY", 0.0f);
6967

7068
if (_headerVisual != null)
7169
{
70+
_headerVisual.StopAnimation("Offset.Y");
71+
7272
var offset = _headerVisual.Offset;
7373
offset.Y = 0.0f;
7474
_headerVisual.Offset = offset;

components/Behaviors/src/Headers/StickyHeaderBehavior.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ protected override bool AssignAnimation()
5858
/// <inheritdoc/>
5959
protected override void StopAnimation()
6060
{
61-
_headerVisual?.StopAnimation("Offset.Y");
62-
6361
_animationProperties?.InsertScalar("OffsetY", 0.0f);
6462

6563
if (_headerVisual != null)
6664
{
65+
_headerVisual.StopAnimation("Offset.Y");
66+
6767
var offset = _headerVisual.Offset;
6868
offset.Y = 0.0f;
6969
_headerVisual.Offset = offset;

0 commit comments

Comments
 (0)