4.2-样式外观:触发器Trigger

4.2-样式外观:触发器Trigger

MAUI的触发器,提供了在运行时动态更改控件样式的方法。在Blazor或Vue中,可以通过三元表达式或绑定class来轻松实现,而MAUI则相对麻烦些,需要通过触发器来实现。触发器,其实就是控件的一个属性,只要是可视化控件,都带有一个Triggers集合属性,在这个集合属性中,可以设置多个Trigger。Trigger包括两个组成部分,一是触发条件,二是改变目标(样式类属性及其值)。对触发条件来说,我们有时候需要某个属性或数据改变时就触发,或者直接用事件触发,还或者当控件状态发生改变时触发,MAUI相应为我们提供的属性触发器、数据触发器、事件触发器和状态触发器。而需要改变的样式类属性,大多数时候是通过Setter,当然也可以通过后台代码。

一、属性触发器Trigger

<!--属性触发器:直接在控件中定义=================================================================-->
<ContentPage
    ......>
    <StackLayout x:Name="stackLayout" Padding="10">
        <Entry Placeholder="请输入">
            <Entry.Triggers>
                <!--触发条件:当Entry的属性IsFocused的值为true时。-->
                <Trigger TargetType="Entry" Property="IsFocused" Value="True" >
                    <!--在Trigger的Setters集合属性中设置Setter,<Trigger.Setters>可以省略-->
                    <!--当触发条件达到时,Entry的背景颜色变为AliceBlue-->
                    <Trigger.Setters>
                        <Setter Property="BackgroundColor" Value="AliceBlue"/>
                    </Trigger.Setters>
                </Trigger>
            </Entry.Triggers>
        </Entry>
    </StackLayout>
</ContentPage>

<!--属性触发器:通过资源字典中的样式定义触发器,效果以上例一样========================================-->
<ContentPage
    ......>
    <!--在资源字典中定义样式触发器,隐式应用于Entry控件-->
    <ContentPage.Resources>
        <Style TargetType="Entry">
            <Style.Triggers>
                <!--Trigger的定义,和在控件中定义一模一样-->
                <Trigger TargetType="Entry" Property="IsFocused" Value="True">
                    <Setter Property="BackgroundColor" Value="AliceBlue" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </ContentPage.Resources>
    <StackLayout x:Name="stackLayout" Padding="10">
        <Entry Placeholder="请输入" />
    </StackLayout>
</ContentPage>

二、数据触发器DataTrigger

<ContentPage
    ......>
    <StackLayout x:Name="stackLayout" Padding="10">
        <Entry x:Name="entry" Placeholder="请输入" />
        <Button Text="确定">
            <Button.Triggers>
                <!--  数据触发器的类型为DataTrigger  -->
                <!--  通过Binding,绑定数据,数据源可以是当前页面的UI属性值,也可以是后台属性。也可以使用Converter  -->
                <DataTrigger
                    Binding="{Binding Source={x:Reference entry}, Path=Text.Length}"
                    TargetType="Button"
                    Value="10">
                    <!--设置了多个Setter,可以触发多个属性的变更-->
                    <Setter Property="IsEnabled" Value="False" />
                    <Setter Property="BackgroundColor" Value="Red" />
                </DataTrigger>
            </Button.Triggers>
        </Button>
    </StackLayout>
</ContentPage>

三、属性和数据触发器的多触发条件MultiTrigger

  • 触发器可以触发多个属性的变更,也可以通过和<MultiTrigger.Conditions>设置多触发条件。
  • 设置多触发条件时,必须所有条件都满足,才会触发
  • 只有属性和数据触发器,可以设置多条件。数据使用BindingCondition,属性使用PropertyCondition
<ContentPage
    ......>
    <StackLayout x:Name="stackLayout" Padding="10">
        <Entry x:Name="entry1" Placeholder="请输入email" Text=""/>
        <Entry x:Name="entry2" Placeholder="请输入phone" Text=""/>
        <Button Text="确定">
            <Button.Triggers>
                <!--单触发条件使用Trigger,多触发条件使用MultiTrigger-->
                <MultiTrigger TargetType="Button">
                    <!--设置多触发条件-->
                    <MultiTrigger.Conditions>
                        <!--通过BindingCondition设置多个数据触发条件-->
                        <BindingCondition Binding="{Binding Source={x:Reference entry1},Path=Text.Length}" Value="10"/>
                        <BindingCondition Binding="{Binding Source={x:Reference entry2},Path=Text.Length}" Value="10"/>
                        <!--也可以设置多个属性触发条件,如-->
                        <PropertyCondition Property="Text" Value="确定"/>
                    </MultiTrigger.Conditions>
                    <Setter Property="IsEnabled" Value="False"/>
                </MultiTrigger>
            </Button.Triggers>
        </Button>
    </StackLayout>
</ContentPage>

四、属性和数据触发器的EnterActions和ExitActions

  • 通过后台代码更改目标属性,如:<Trigger.EnterActions><local:FadeTriggerAction StartsFrom=“0” /></Trigger.EnterActions>
  • 当满足触发条件时,将执行上例中,FadeTriggerAction对象的Invoke方法,在这个方法中,可以更改控件属性
  • FadeTriggerAction是TriggerAction类的派生类,T为目标控件的类型,这个类中重写Invoke方法
<!--使用属性触发器的EnterActions和ExitActions-->
<ContentPage
    ......
    xmlns:triggeraction="clr-namespace:MauiApp12.TriggerActions">

    <StackLayout x:Name="stackLayout" Padding="10">
        <Entry x:Name="entry" Placeholder="请输入" Text="">
            <Entry.Triggers>
                <Trigger TargetType="Entry" Property="IsFocused" Value="True">
                    <!--
                    ①当聚焦条件满足(EnterActions)或不满足(ExitActions)时,创建立FadeTriggerAction对象,初始化StartsFrom属性
                    ②执行FadeTriggerAction对象的Invoke方法
                    ③本案例中,FadeTriggerAction对象的Invoke方法,将创建一个背景色的渐变动画
                    -->
                    <Trigger.EnterActions>
                        <triggeraction:FadeTriggerAction StartsFrom="0"/>
                    </Trigger.EnterActions>
                    <Trigger.ExitActions>
                        <triggeraction:FadeTriggerAction StartsFrom="1"/>
                    </Trigger.ExitActions>
                </Trigger>
            </Entry.Triggers>
        </Entry>
        
    </StackLayout>
</ContentPage>

<!--定义EnterActions和ExitActions-->
<!--在TriggerActions文件夹中定义FadeTriggerAction类,派生自TriggerAction<T>-->
public class FadeTriggerAction : TriggerAction<VisualElement>
{
    //StartsFrom属性值,在创建FadeTriggerAction对象时初始化
    public int StartsFrom { get; set; }
    //重写Invoke方法,参数sender为触发器的宿主控件
    protected override void Invoke(VisualElement sender)
    {
        //设置sender的Animate,自定义一个背景色渐变动画
        sender.Animate("FadeTriggerAction",new Animation((d) =>
        {
            var val = StartsFrom == 1 ? d : 1-d;
            sender.BackgroundColor = Color.FromRgb(1, val, 1);
        }),
        length:1000, //动画长度1000毫秒
        easing:Easing.Linear); //动画的速率-恒定速度
    }
}

五、事件触发器

  • 事件触发器的使用,和属性/数据触发器的EnterAcitons/ExitActions用法类似,都是调用TriggerAction派生类对象的Invoke方法,在后台代码中更改控件的样式属性
  • 事件触发器不能使用EnterAcitons/ExitActions,也不能使用Setter
//XAML文件中定义事件触发器=========================================================================================
<ContentPage
    ......
    xmlns:triggeraction="clr-namespace:MauiApp12.TriggerActions">
    <StackLayout x:Name="stackLayout" Padding="10">
        <Entry x:Name="entry" Placeholder="请输入" Text="">
            <Entry.Triggers>
                //在Triggers集合中,增加EventTrigger事件触发器
                <EventTrigger Event="TextChanged">
                    //当TextChanged事件发生时,调用NumericValidationTriggerAction对象的Invoke方法
                    //NumericValidationTriggerAction是TriggerAction<T>的派生类
                    <triggeraction:NumericValidationTriggerAction/>
                </EventTrigger>
            </Entry.Triggers>
        </Entry>
    </StackLayout>
</ContentPage>


//在后台代码中,更改控件的样式属性==================================================================================
//和EnterAcitons/ExitAcitons的用法一样,派生自泛型抽像类TriggerAction<T>,泛型参数为宿主类型
//重写抽像类的Invoke方法,但满足触发条件时(此例为Entry的TextChanged事件),调用Invoke方法
public class NumericValidationTriggerAction : TriggerAction<Entry>
{
    protected override void Invoke(Entry entry)
    {
        //尝试将输入值转换为double,如果可以转,则将转化成功后的值赋值给result,并返回true,否则返回false
        double result;
        bool isValid = Double.TryParse(entry.Text,out result); 
        //如果可以转(说明是数值),则entry的字体颜色为黑色,否则为红色
        entry.TextColor = isValid ? Colors.Black : Colors.Red;
    }
}

六、状态触发器:

  • 回顾控件状态章节,当我们自定义状态时,需要在后台代码中调用VisualStateManager.GoToState方法,来定义控件状态改变的逻辑。而使用触发器,可以直接在XAML文件中,通过触发器来触发控件状态的变更。当符合某个触发条件时,控件转到某个状态,使用更加方便。
  • 每个控件状态VisualState,有一个VisualState.StateTriggers集合属性,将触发器添加到这个集合中。
  • 触发器的触发条件,包括:绑定数据、绑定属性、窗口大小、设备类型、设备方向等。
  • 每个状态触发器,还有一个IsActiveChanged事件,当满足触发条件时,还可以执行这个事件的委托方法。

1、状态触发器:数据触发

<ContentPage
    ......>
    <ContentPage.Resources>
        <!--定义隐式样式,自动作用于Grid控件-->
        <Style TargetType="Grid">
            <!--设置附加属性VisualStateManager.VisualStateGroups,和控件状态的使用基本一样-->    
            <Setter Property="VisualStateManager.VisualStateGroups">
                <VisualStateGroupList>
                    <!--为Grid自定义了两个状态,Checked和UnChecked-->
                    <VisualStateGroup>
                        <VisualState x:Name="Checked">
                            <!--定义状态触发器,使用绑定数据的方式,IsToggled可以通过MVVM模式,绑定ViewModel的数据-->
                            <!--同时,在触发器上定义了IsActiveChanged事件的事件处理程序OnCheckedStateIsActiveChanged-->
                            <VisualState.StateTriggers>
                                <StateTrigger IsActive="{Binding IsToggled}"
                                      IsActiveChanged="OnCheckedStateIsActiveChanged" />
                            </VisualState.StateTriggers>
                            <!--状态的Setters设置,详见控件状态章节-->
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor"
                                Value="Black" />
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState x:Name="Unchecked">
                            <VisualState.StateTriggers>
                                <StateTrigger IsActive="{Binding IsToggled, Converter={StaticResource inverseBooleanConverter}}"
                                      IsActiveChanged="OnUncheckedStateIsActiveChanged" />
                            </VisualState.StateTriggers>
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor"
                                Value="White" />
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateGroupList>
            </Setter>
        </Style>
    </ContentPage.Resources>
    <Grid RowDefinitions="1*,2*,Auto" ColumnDefinitions="1*,1*,Auto">
        ......
    </Grid>
</ContentPage>

2、状态触发器:属性触发

<ContentPage
    ......>
    <ContentPage.Resources>
        <Style TargetType="Grid">
            <Setter Property="VisualStateManager.VisualStateGroups">
                <VisualStateGroupList>
                    <VisualStateGroup>
                        <!--自定义了两个控件状态,Checked和Unchecked-->
                        <VisualState x:Name="Checked">
                            <!--  定义触发器,使用绑定控件属性的方式  -->
                            <!--  StateTrigger和CompareStateTrigger,都派生自StateTriggerBase,也可以使用IsActiveChanged事件  -->
                            <!--CompareStateTrigger绑定当前页面的checkBox控件的IsChecked属性,当值为True时触发控件状态Checked-->
                            <VisualState.StateTriggers>
                                <CompareStateTrigger Property="{Binding Source={x:Reference checkBox}, Path=IsChecked}" Value="True" />
                            </VisualState.StateTriggers>
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="Black" />
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState x:Name="Unchecked">
                            <!--CompareStateTrigger绑定当前页面的checkBox控件的IsChecked属性,当值为False时触发控件状态Checked-->
                            <VisualState.StateTriggers>
                                <CompareStateTrigger Property="{Binding Source={x:Reference checkBox}, Path=IsChecked}" Value="False" />
                            </VisualState.StateTriggers>
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="White" />
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateGroupList>
            </Setter>
        </Style>
    </ContentPage.Resources>

    <Grid>
        <StackLayout Orientation="Horizontal">
            <CheckBox x:Name="checkBox" VerticalOptions="Center" />
            <Label Text="点击复选框,改变Grid的背景色" VerticalOptions="Center" />
        </StackLayout>
    </Grid>
</ContentPage>

3、状态触发器:窗口大小触发

<ContentPage
    ......>
    <ContentPage.Resources>
        <Style TargetType="StackLayout">
            <Setter Property="VisualStateManager.VisualStateGroups">
                <VisualStateGroupList>
                    <!--定义两个状态,状态Vertical,StackLayout的方向为Vertical。状态Horizontal,StackLayout的为Horizontal-->
                    <!--触发器为AdaptiveTrigger,触发条件为窗口大小,通过设置MinWindowWidth和MinWindowHeight-->
                    <!--MinWindowWidth和MinWindowHeight的使用逻辑,和CSS中的媒体查询类似-->
                    <VisualStateGroup>
                        <VisualState x:Name="Vertical">
                            <VisualState.StateTriggers>
                                
                                <AdaptiveTrigger MinWindowWidth="0" MinWindowHeight="0"/>
                            </VisualState.StateTriggers>
                            <VisualState.Setters>
                                <Setter Property="Orientation"
                                Value="Vertical" />
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState x:Name="Horizontal">
                            <VisualState.StateTriggers>
                                <AdaptiveTrigger MinWindowWidth="800" MinWindowHeight="800"/>
                            </VisualState.StateTriggers>
                            <VisualState.Setters>
                                <Setter Property="Orientation"
                                Value="Horizontal" />
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateGroupList>
            </Setter>
        </Style>
    </ContentPage.Resources>

    <StackLayout>
        <!--定义三个测试的盒子-->
        <Label Text="111" WidthRequest="100" HeightRequest="100" BackgroundColor="Red"/>
        <Label Text="222" WidthRequest="100" HeightRequest="100" BackgroundColor="Yellow"/>
        <Label Text="333" WidthRequest="100" HeightRequest="100" BackgroundColor="Green"/>
    </StackLayout>
</ContentPage>

4、状态触发器:设备类型触发

<!--通过DeviceStateTrigger,设置设备类型触发器-->
<!--目前支持的类型包括iOS、Android、WinUI、MacCatalyst、Tizen-->
<VisualState.StateTriggers>
    <DeviceStateTrigger Device="iOS" />
</VisualState.StateTriggers>

5、状态触发器:设备方向触发

<!--通过OrientationStateTrigger,设置设备方向触发器-->
<!--目前支持的方向包括Portrait(竖屏)、Landscape(横屏)-->
<VisualState.StateTriggers>
    <OrientationStateTrigger Orientation="Portrait" />
</VisualState.StateTriggers>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值