Animate Line under one button to another
You ask:
How is the animation done? Is the bar an imageview that moves?
More than likely it's just a UIView
whose background color is that light blue, and whose frame (or whose constraints that define the frame) are changed within a [UIView animateWithDuration:]
block.
For example, in Objective-C, you might have these properties:
@property (nonatomic, weak) UIView *underlineView;
@property (nonatomic, strong) NSArray *underlineConstraints;
@property (nonatomic, weak) IBOutlet UIButton *button1;
and this code:
- (void)addUnderline {
UIView *underlineView = [[UIView alloc] init];
underlineView.translatesAutoresizingMaskIntoConstraints = NO;
underlineView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:underlineView];
self.underlineView = underlineView;
[self updateConstraintsForUnderlineView:underlineView underButton:self.button1];
}
- (IBAction)didTapButton:(UIButton *)sender {
[self updateConstraintsForUnderlineView:self.underlineView underButton:sender];
[UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
- (void)updateConstraintsForUnderlineView:(UIView *)underlineView underButton:(UIButton *)button {
if (self.underlineConstraints) {
[self.view removeConstraints:self.underlineConstraints];
}
NSDictionary *views = NSDictionaryOfVariableBindings(underlineView, button);
self.underlineConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[button]-[underlineView(5)]" options:NSLayoutFormatAlignAllLeading | NSLayoutFormatAlignAllTrailing metrics:nil views:views];
[self.view addConstraints:self.underlineConstraints];
}
Or in Swift:
weak var underlineView: UIView!
var underlineConstraints: [AnyObject]!
@IBOutlet weak var button1: UIButton!
func addUnderline() {
let underlineView = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
underlineView.backgroundColor = UIColor.lightGrayColor()
view.addSubview(underlineView)
self.underlineView = underlineView
updateConstraintsForUnderlineView(underlineView, underButton:button1)
}
@IBAction func didTapButton(sender: UIButton) {
updateConstraintsForUnderlineView(underlineView, underButton:sender)
UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseOut, animations: { () -> Void in
self.view.layoutIfNeeded()
}, completion: nil)
}
func updateConstraintsForUnderlineView(underlineView: UIView, underButton button: UIButton) {
if underlineConstraints != nil {
view.removeConstraints(underlineConstraints)
}
let views = ["underlineView": underlineView, "button": button]
underlineConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:[button]-[underlineView(5)]", options: .AlignAllLeading | .AlignAllTrailing, metrics: nil, views: views)
view.addConstraints(underlineConstraints)
}
How to animate underline from left to right?
You can use gradient and adjust background-position
with a delay to obtain such effect:
.un {
display: inline-block;
padding-bottom:2px;
background-image: linear-gradient(#000 0 0);
background-position: 0 100%; /*OR bottom left*/
background-size: 0% 2px;
background-repeat: no-repeat;
transition:
background-size 0.3s,
background-position 0s 0.3s; /*change after the size immediately*/
}
.un:hover {
background-position: 100% 100%; /*OR bottom right*/
background-size: 100% 2px;
}
<span class="un">Underlined Text</span>
Animate button from one point to another
This is not a trivial thing to do.
Basically you're trying to animate a button between two completely different parts of the visual tree, so you're going to have to template the whole thing and wrap everything up in a parent layout that contains both your expander and the button you're trying to animate.
Templating your expander shouldn't be too difficult, just move your cursor to it's XAML, go to the properties tab, select "Template" and click the little square to the right to generate all the code for the new ControlTemplate. I've done it for you in the code below, but the theming will probably be all wrong so you'll want to do it yourself.
Next step is to create two rectangles, one in the expanders header and one in its content area. Set the size of these to whatever you want your button to be but set the fill to Transparent; they're only placeholders to help with layout, and you'll use them just to align your button control later on.
Now you'll need to go back to your ControlTemplate and wrap all the content (i.e. the top-level border) in a grid. You'll also want to place your actual button as a child of this grid.
Finally, you'll need a behaviour that binds to the two rectangles you created (so it can calculate From and To points), the parent grid (so that it has a parent control it can animate your button relative to) and the expander (so it can trigger animations in response to the expander expanding and collapsing). The actual animation itself can then be a ThicknessAnimation which animates the button's Margin.
Put all that together and you get this:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:WpfApp1.Behaviors"
<UserControl.Resources>
<ControlTemplate x:Key="ExpanderControlTemplate1" TargetType="{x:Type Expander}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3" SnapsToDevicePixels="True">
<Grid x:Name="Grid">
<DockPanel>
<ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="{TemplateBinding Foreground}" FontWeight="{TemplateBinding FontWeight}" FontStyle="{TemplateBinding FontStyle}" FontStretch="{TemplateBinding FontStretch}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" MinWidth="0" MinHeight="0" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
<ToggleButton.FocusVisualStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Rectangle Margin="0" SnapsToDevicePixels="True" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.FocusVisualStyle>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse x:Name="circle" Fill="White" HorizontalAlignment="Center" Height="19" Stroke="#FF333333" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF333333" StrokeThickness="2" VerticalAlignment="Center"/>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="1" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF5593FF"/>
<Setter Property="Fill" TargetName="circle" Value="#FFF3F9FF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C77DD"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="#FFD9ECFF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="#FFBCBCBC"/>
<Setter Property="Fill" TargetName="circle" Value="#FFE6E6E6"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF707070"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
<ContentPresenter x:Name="ExpandSite" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" DockPanel.Dock="Bottom" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</DockPanel>
<Button Width="100" Height="50" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top">
<i:Interaction.Behaviors>
<behaviors:AnimatedMarginBehavior ElementA="{Binding ElementName=ElementA}" ElementB="{Binding ElementName=ElementB}" Grid="{Binding ElementName=Grid}" Parent="{Binding RelativeSource={RelativeSource AncestorType=Expander}}" />
</i:Interaction.Behaviors>
</Button>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Right">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Right"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Left"/>
<Setter Property="Style" TargetName="HeaderSite">
<Setter.Value>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.RowDefinitions>
<RowDefinition Height="19"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="-90"/>
</TransformGroup>
</Grid.LayoutTransform>
<Ellipse x:Name="circle" Fill="White" HorizontalAlignment="Center" Height="19" Stroke="#FF333333" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF333333" StrokeThickness="2" VerticalAlignment="Center"/>
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF5593FF"/>
<Setter Property="Fill" TargetName="circle" Value="#FFF3F9FF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C77DD"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="#FFD9ECFF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="#FFBCBCBC"/>
<Setter Property="Fill" TargetName="circle" Value="#FFE6E6E6"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF707070"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="ExpandDirection" Value="Up">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Top"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Bottom"/>
<Setter Property="Style" TargetName="HeaderSite">
<Setter.Value>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="180"/>
</TransformGroup>
</Grid.LayoutTransform>
<Ellipse x:Name="circle" Fill="White" HorizontalAlignment="Center" Height="19" Stroke="#FF333333" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF333333" StrokeThickness="2" VerticalAlignment="Center"/>
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="1" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF5593FF"/>
<Setter Property="Fill" TargetName="circle" Value="#FFF3F9FF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C77DD"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="#FFD9ECFF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="#FFBCBCBC"/>
<Setter Property="Fill" TargetName="circle" Value="#FFE6E6E6"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF707070"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="ExpandDirection" Value="Left">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Left"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Right"/>
<Setter Property="Style" TargetName="HeaderSite">
<Setter.Value>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<Grid.RowDefinitions>
<RowDefinition Height="19"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="90"/>
</TransformGroup>
</Grid.LayoutTransform>
<Ellipse x:Name="circle" Fill="White" HorizontalAlignment="Center" Height="19" Stroke="#FF333333" VerticalAlignment="Center" Width="19"/>
<Path x:Name="arrow" Data="M1,1.5L4.5,5 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="False" Stroke="#FF333333" StrokeThickness="2" VerticalAlignment="Center"/>
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Data" TargetName="arrow" Value="M1,4.5L4.5,1 8,4.5"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF5593FF"/>
<Setter Property="Fill" TargetName="circle" Value="#FFF3F9FF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" TargetName="circle" Value="#FF3C77DD"/>
<Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
<Setter Property="Fill" TargetName="circle" Value="#FFD9ECFF"/>
<Setter Property="Stroke" TargetName="arrow" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Stroke" TargetName="circle" Value="#FFBCBCBC"/>
<Setter Property="Fill" TargetName="circle" Value="#FFE6E6E6"/>
<Setter Property="Stroke" TargetName="arrow" Value="#FF707070"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<Grid>
<Expander Margin="5" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Stretch" IsExpanded="True" Template="{DynamicResource ExpanderControlTemplate1}">
<Expander.Header>
<DockPanel Margin="5">
<TextBlock Margin="5" VerticalAlignment="Center">Hello World</TextBlock>
<Rectangle x:Name="ElementA" Width="100" Height="50" Fill="Transparent" />
</DockPanel>
</Expander.Header>
<Expander.Content>
<StackPanel Orientation="Horizontal">
<Canvas Margin="5" Width="300" Background="Black" />
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type RadioButton}">
<Setter Property="Margin" Value="5" />
</Style>
</StackPanel.Resources>
<RadioButton IsChecked="True">Option 1</RadioButton>
<RadioButton>Option 2</RadioButton>
<RadioButton>Option 3</RadioButton>
<RadioButton>Option 4</RadioButton>
<Rectangle x:Name="ElementB" Width="100" Height="50" Fill="Transparent" />
</StackPanel>
</StackPanel>
</Expander.Content>
</Expander>
</Grid>
And the behavior:
public class AnimatedMarginBehavior : Behavior<FrameworkElement>
{
private ThicknessAnimation Animation = new ThicknessAnimation();
public FrameworkElement ElementA
{
get { return GetValue(ElementAProperty) as FrameworkElement; }
set { SetValue(ElementAProperty, value); }
}
public static readonly DependencyProperty ElementAProperty =
DependencyProperty.Register("ElementA", typeof(FrameworkElement), typeof(AnimatedMarginBehavior),
new PropertyMetadata(default(FrameworkElement), (d, e) => (d as AnimatedMarginBehavior).OnBindingChanged(e)));
public FrameworkElement ElementB
{
get { return GetValue(ElementBProperty) as FrameworkElement; }
set { SetValue(ElementBProperty, value); }
}
public static readonly DependencyProperty ElementBProperty =
DependencyProperty.Register("ElementB", typeof(FrameworkElement), typeof(AnimatedMarginBehavior),
Related Topics
Closure (With Default Value) as Function Parameter
Why Is a Firestore Listener Returning .Added Twice When a Single Document Is Added
Change The 2Nd and 3Rd Pickerview Acording to What Row from The 1St Picker Is Selected
Cloudkit: How to Access Main User's Attributes
Why Run Loop Is Needed When Using Dispatchqueue.Main.Async in MAC Command Line Tool in Swift
Swiftui Sheet Shows Sheet with Wrong Data
Can't Unwrap Optional.None When Setting Window Background Color
How to Bend a Rectangle in Sprite Kit
Animate Line Under One Button to Another
What Is The Intended Use of Optional Variable/Constant in Swift
Why Do I Keep Getting The Error "No Such Module 'Realmswift'"
Midiclientcreate Does Not Work for 32 Bit Processors in Swift
Swift: How to Update UI When Listening to a Stream
Should I Invalidate Timer When My Application Is Suspended or in Background