7
7
using System . Windows . Controls ;
8
8
using System . Windows . Input ;
9
9
using System . Windows . Media ;
10
+ using System . Windows . Media . Animation ;
10
11
using System . Windows . Threading ;
11
12
12
13
namespace MaterialDesignThemes . Wpf
@@ -16,13 +17,17 @@ namespace MaterialDesignThemes.Wpf
16
17
/// </summary>
17
18
public class Snackbar : ContentControl
18
19
{
19
- public const string PartActionButtonName = "PART_actionButton" ;
20
+ private const string PartActionButtonName = "PART_actionButton" ;
21
+ private const string PartContentGridName = "PART_contentGrid" ;
22
+ private const string PartContentPanelName = "PART_contentPanel" ;
20
23
21
24
/// <summary>
22
- /// The duration of the animation in milliseconds.
25
+ /// The duration of the open and close animations in milliseconds.
23
26
/// </summary>
24
27
public const int AnimationDuration = 300 ;
25
28
29
+ private const int OpacityAnimationHintOffset = 50 ;
30
+
26
31
/// <summary>
27
32
/// The minimum timeout for a visible <see cref="Snackbar" /> in milliseconds.
28
33
/// </summary>
@@ -150,22 +155,45 @@ public bool IsOpen
150
155
}
151
156
}
152
157
153
- private async static void IsOpenChangedHandler ( DependencyObject sender , DependencyPropertyChangedEventArgs args )
158
+ private static void IsOpenChangedHandler ( DependencyObject sender , DependencyPropertyChangedEventArgs args )
154
159
{
155
- // the animations are triggered by the value of the IsOpen property
156
-
160
+ // trigger the animations
157
161
Snackbar snackbar = ( Snackbar ) sender ;
158
162
159
- // stop the timer because it may mess up the behaviour of the Snackbar
160
- snackbar . _timer ? . Stop ( ) ;
161
-
162
163
if ( ( bool ) args . NewValue )
163
164
{
164
- await snackbar . ShowAsync ( ) ;
165
+ if ( snackbar . _openStoryboard != null )
166
+ {
167
+ // set the duration of the dummy visibility timeout animation as it may has changed since the last call
168
+ if ( snackbar . _dummyVisibilityAnimation != null )
169
+ {
170
+ int timeout = snackbar . VisibilityTimeout ;
171
+
172
+ if ( timeout < MinimumVisibilityTimeout )
173
+ {
174
+ timeout = MinimumVisibilityTimeout ;
175
+ }
176
+
177
+ snackbar . _dummyVisibilityAnimation . Duration = TimeSpan . FromMilliseconds ( timeout ) ;
178
+ }
179
+
180
+ // start the open animation
181
+ snackbar . _openStoryboard . Begin ( snackbar , true ) ;
182
+ }
165
183
}
166
184
else
167
185
{
168
- await snackbar . HideAsync ( ) ;
186
+ // stop the open animation if the Snackbar should close before the visibility timeout is reached
187
+ if ( snackbar . _openStoryboard != null )
188
+ {
189
+ snackbar . _openStoryboard . Stop ( snackbar ) ;
190
+ }
191
+
192
+ // start the close animation
193
+ if ( snackbar . _closeStoryboard != null )
194
+ {
195
+ snackbar . _closeStoryboard . Begin ( snackbar , true ) ;
196
+ }
169
197
}
170
198
}
171
199
@@ -209,8 +237,9 @@ public int VisibilityTimeout
209
237
}
210
238
}
211
239
212
- // used to implement the behaviour of the HalfAutomatic mode defined in the Material Design specs
213
- private DispatcherTimer _timer ;
240
+ private Storyboard _openStoryboard ;
241
+ private Storyboard _closeStoryboard ;
242
+ private DoubleAnimation _dummyVisibilityAnimation ;
214
243
215
244
private Button _actionButton ;
216
245
@@ -219,13 +248,14 @@ static Snackbar()
219
248
DefaultStyleKeyProperty . OverrideMetadata ( typeof ( Snackbar ) , new FrameworkPropertyMetadata ( typeof ( Snackbar ) ) ) ;
220
249
221
250
ContentProperty . OverrideMetadata ( typeof ( Snackbar ) , new FrameworkPropertyMetadata ( ContentChangedHandler ) ) ;
222
- TagProperty . OverrideMetadata ( typeof ( Snackbar ) , new FrameworkPropertyMetadata ( "0" ) ) ;
251
+ TagProperty . OverrideMetadata ( typeof ( Snackbar ) , new FrameworkPropertyMetadata ( 0.0 ) ) ;
223
252
}
224
253
225
254
public Snackbar ( ) : base ( ) { }
226
255
227
256
public override void OnApplyTemplate ( )
228
257
{
258
+ // set the event handler for the action button from the template
229
259
if ( _actionButton != null )
230
260
{
231
261
_actionButton . Click -= ActionButtonClickHandler ;
@@ -238,28 +268,80 @@ public override void OnApplyTemplate()
238
268
_actionButton . Click += ActionButtonClickHandler ;
239
269
}
240
270
271
+ // Storyboard for the open animation
272
+ _openStoryboard = new Storyboard ( ) ;
273
+
274
+ // height
275
+ DoubleAnimation contentPanelTagAnimation = new DoubleAnimation ( 0.0 , 1.0 , TimeSpan . FromMilliseconds ( AnimationDuration ) ) ;
276
+ contentPanelTagAnimation . BeginTime = TimeSpan . FromMilliseconds ( 0.0 ) ;
277
+ contentPanelTagAnimation . EasingFunction = new QuarticEase ( ) { EasingMode = EasingMode . EaseOut } ;
278
+ Storyboard . SetTarget ( contentPanelTagAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
279
+ Storyboard . SetTargetProperty ( contentPanelTagAnimation , new PropertyPath ( StackPanel . TagProperty ) ) ;
280
+ _openStoryboard . Children . Add ( contentPanelTagAnimation ) ;
281
+
282
+ // opacity of the content
283
+ DoubleAnimation contentGridOpacityAnimation = new DoubleAnimation ( 0.0 , TimeSpan . FromMilliseconds ( 0.0 ) ) ;
284
+ contentGridOpacityAnimation . BeginTime = TimeSpan . FromMilliseconds ( 0.0 ) ;
285
+ Storyboard . SetTarget ( contentGridOpacityAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
286
+ Storyboard . SetTargetProperty ( contentGridOpacityAnimation , new PropertyPath ( Grid . OpacityProperty ) ) ;
287
+ _openStoryboard . Children . Add ( contentGridOpacityAnimation ) ;
288
+
289
+ contentGridOpacityAnimation = new DoubleAnimation ( 0.0 , 1.0 , TimeSpan . FromMilliseconds ( AnimationDuration - OpacityAnimationHintOffset ) ) ;
290
+ contentGridOpacityAnimation . BeginTime = TimeSpan . FromMilliseconds ( OpacityAnimationHintOffset ) ;
291
+ contentGridOpacityAnimation . EasingFunction = new QuarticEase ( ) { EasingMode = EasingMode . EaseOut } ;
292
+ Storyboard . SetTarget ( contentGridOpacityAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
293
+ Storyboard . SetTargetProperty ( contentGridOpacityAnimation , new PropertyPath ( Grid . OpacityProperty ) ) ;
294
+ _openStoryboard . Children . Add ( contentGridOpacityAnimation ) ;
295
+
296
+ // dummy animation to keep the HalfAutomatic mode Snackbar open during the visibility timeout
297
+ _dummyVisibilityAnimation = new DoubleAnimation ( 1.0 , 1.0 , TimeSpan . FromMilliseconds ( VisibilityTimeout ) ) ;
298
+ _dummyVisibilityAnimation . BeginTime = TimeSpan . FromMilliseconds ( AnimationDuration ) ;
299
+ Storyboard . SetTarget ( _dummyVisibilityAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
300
+ Storyboard . SetTargetProperty ( _dummyVisibilityAnimation , new PropertyPath ( StackPanel . TagProperty ) ) ;
301
+ _openStoryboard . Children . Add ( _dummyVisibilityAnimation ) ;
302
+
303
+ _openStoryboard . Completed += ( object sender , EventArgs args ) =>
304
+ {
305
+ // close the Snackbar after the animation in the HalfAutomatic mode
306
+ if ( Mode == SnackbarMode . HalfAutomatic )
307
+ {
308
+ IsOpen = false ;
309
+ }
310
+ } ;
311
+
312
+ // Storyboard for the close animation
313
+ _closeStoryboard = new Storyboard ( ) ;
314
+
315
+ // height
316
+ contentPanelTagAnimation = new DoubleAnimation ( 1.0 , 0.0 , TimeSpan . FromMilliseconds ( AnimationDuration ) ) ;
317
+ contentPanelTagAnimation . BeginTime = TimeSpan . FromMilliseconds ( 0.0 ) ;
318
+ contentPanelTagAnimation . EasingFunction = new QuarticEase ( ) { EasingMode = EasingMode . EaseOut } ;
319
+ Storyboard . SetTarget ( contentPanelTagAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
320
+ Storyboard . SetTargetProperty ( contentPanelTagAnimation , new PropertyPath ( StackPanel . TagProperty ) ) ;
321
+ _closeStoryboard . Children . Add ( contentPanelTagAnimation ) ;
322
+
241
323
base . OnApplyTemplate ( ) ;
242
324
}
243
325
244
326
private async Task ShowContentAsync ( object content )
245
327
{
246
328
if ( Mode == SnackbarMode . HalfAutomatic )
247
329
{
248
- // first hide the Snackbar if its already visible
330
+ // first close the Snackbar if it is already visible
249
331
if ( IsOpen )
250
332
{
251
333
IsOpen = false ;
252
334
253
- // wait for the animation, otherwise the new content will already be shown in the hide animation
335
+ // wait for the animation, otherwise the new content will already be shown in the close animation
254
336
await Task . Delay ( AnimationDuration ) ;
255
337
}
256
338
257
- // now set the new content and show the Snackbar
339
+ // now set the new content and open the Snackbar
258
340
InternalContent = content ;
259
341
260
342
if ( content != null )
261
343
{
262
- // only show it with content
344
+ // only open it with content
263
345
IsOpen = true ;
264
346
265
347
await Task . Delay ( AnimationDuration ) ;
@@ -272,44 +354,6 @@ private async Task ShowContentAsync(object content)
272
354
}
273
355
}
274
356
275
- private async Task ShowAsync ( )
276
- {
277
- // wait for the animation
278
- await Task . Delay ( AnimationDuration ) ;
279
-
280
- // start the timeout in HalfAutomatic mode
281
- if ( Mode == SnackbarMode . HalfAutomatic )
282
- {
283
- // start the timer which will hide the Snackbar
284
- int timeout = VisibilityTimeout ;
285
-
286
- if ( timeout < MinimumVisibilityTimeout )
287
- {
288
- timeout = MinimumVisibilityTimeout ;
289
- }
290
-
291
- if ( _timer == null )
292
- {
293
- _timer = new DispatcherTimer ( ) ;
294
- }
295
-
296
- _timer . Tick += async ( object sender , EventArgs args ) =>
297
- {
298
- IsOpen = false ;
299
-
300
- await Task . Delay ( AnimationDuration ) ;
301
- } ;
302
- _timer . Interval = new TimeSpan ( 0 , 0 , 0 , 0 , timeout ) ;
303
- _timer . Start ( ) ;
304
- }
305
- }
306
-
307
- private async Task HideAsync ( )
308
- {
309
- // wait for the animation
310
- await Task . Delay ( AnimationDuration ) ;
311
- }
312
-
313
357
private async void ActionButtonClickHandler ( object sender , RoutedEventArgs args )
314
358
{
315
359
// do not you raise the event if the Snackbar is not fully visible
@@ -320,7 +364,7 @@ private async void ActionButtonClickHandler(object sender, RoutedEventArgs args)
320
364
321
365
Task task = null ;
322
366
323
- // hide the Snackbar in HalfAutomatic mode
367
+ // close the Snackbar in HalfAutomatic mode
324
368
if ( Mode == SnackbarMode . HalfAutomatic )
325
369
{
326
370
IsOpen = false ;
0 commit comments