[MAUI]模仿微信“按住-說話”的互動實現

林曉lx發表於2023-04-15

@


.NET MAUI 跨平臺框架包含了識別平移手勢的功能,在之前的博文[MAUI 專案實戰] 手勢控制音樂播放器(二): 手勢互動中利用此功能實現了pan-pit拖拽系統。

簡單來說就是拖拽物(pan)體到坑(pit)中,手勢容器控制元件PanContainer描述了pan運動和pit位置的關係,並在手勢運動中產生一系列訊息事件。

今天使用這個控制元件,做一個模仿微信“按住-說話”的小功能,最終效果如下:

在這裡插入圖片描述

使用.NET MAUI實現跨平臺支援,本專案可執行於Android、iOS平臺。

建立頁面佈局

新建.NET MAUI專案,命名HoldAndSpeak
MainPage.xaml中建立一個PitContentLayoutGrid容器,並對Grid容器進行如下佈局:

在手機螢幕的底部設定兩行兩列的佈局:

第一行第一列,對應取消傳送手勢區域,
第一行第二列,對應語音轉文字手勢區域,
第二行獨佔兩列,對應傳送手勢區域。

佈局如下圖所示

<Grid x:Name="PitContentLayout"
        Opacity="1">
    <Grid.RowDefinitions>
        <RowDefinition Height="1*" />
        <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
</Grid>

建立三個PitGrid控制元件,並對這三個功能區域的PitGrid控制元件命名,CancelPitTransliterationPit,分別對應了取消傳送、語音轉文字、傳送。

為每個PitGrid控制元件新增內容:

傳送區域是一個底部弧形區域,我們用一個巨大的圓形+Y軸方向的偏移,透過只保留螢幕底部往上的一部分圓形區域來實現底部弧形區域的效果,程式碼如下:

<BoxView TranslationY="450"
        x:Name="SendBox"
        HeightRequest="1000"
        WidthRequest="1000"
        CornerRadius="500">
</BoxView>

取消傳送和語音轉文字區域是一個圓形區域,我們用一個正常大小的圓形來實現。

PitContentLayout區域整體程式碼如下

<Grid x:Name="PitContentLayout"
        Opacity="1">
    <Grid.RowDefinitions>
        <RowDefinition Height="1*" />
        <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
    <controls1:PitGrid x:Name="CancelPit"
                        TranslationX="-40"
                        
                        PitName="CancelPit">

        <BoxView x:Name="CancelBox"
                    HeightRequest="80"
                    WidthRequest="80"
                    CornerRadius="50"
                    Margin="7.5"
                    Color="{StaticResource PhoneContrastBackgroundBrush}"
                    VerticalOptions="CenterAndExpand"
                    HorizontalOptions="CenterAndExpand"></BoxView>
        <Label   x:Name="CancelLabel"
                    TextColor="{StaticResource PhoneContrastForegroundBrush}"
                    FontFamily="FontAwesome"
                    FontSize="28"
                    Rotation="-10"
                    HorizontalOptions="CenterAndExpand"
                    Margin="0"></Label>


    </controls1:PitGrid>
    <controls1:PitGrid x:Name="TransliterationPit"
                        PitName="TransliterationPit"
                        TranslationX="40"
                        Grid.Column="1">

        <BoxView x:Name="TransliterationBox"
                    HeightRequest="80"
                    WidthRequest="80"
                    CornerRadius="50"
                    Margin="7.5"
                    Color="{StaticResource PhoneContrastBackgroundBrush}"
                    VerticalOptions="CenterAndExpand"
                    HorizontalOptions="CenterAndExpand"></BoxView>
        <Label  x:Name="TransliterationLabel"
                TextColor="{StaticResource PhoneContrastForegroundBrush}"
                FontSize="28"
                Text="文"
                Rotation="10"
                HorizontalOptions="CenterAndExpand"
                Margin="0"></Label>


    </controls1:PitGrid>
    <controls1:PitGrid x:Name="SendPit"
                        PitName="SendPit"
                        Grid.ColumnSpan="2"
                        Grid.Row="1">

        <BoxView TranslationY="450"
                    x:Name="SendBox"
                    HeightRequest="1000"
                    WidthRequest="1000"
                    CornerRadius="500"
                    Margin="7.5"
                    Color="{StaticResource PhoneContrastBackgroundBrush}"
                    VerticalOptions="CenterAndExpand"
                    HorizontalOptions="CenterAndExpand"></BoxView>
        <Label x:Name="SendLabel"
                TranslationY="30"
                FontSize="28"
                Rotation="45"
                TextColor="{StaticResource PhoneContrastForegroundBrush}"
                FontFamily="FontAwesome"
                HorizontalOptions="CenterAndExpand"
                Margin="0"></Label>


    </controls1:PitGrid>

</Grid>

效果如下

在這裡插入圖片描述

建立手勢控制元件

建立一個手勢控制元件。他包裹的內容。是一個帶有按住說話的按鈕。

在這裡插入圖片描述

<controls1:PanContainer BackgroundColor="Transparent"
        x:Name="DefaultPanContainer"
        OnTapped="DefaultPanContainer_OnOnTapped"
        AutoAdsorption="False"
        OnfinishedChoise="DefaultPanContainer_OnOnfinishedChoise">

    <Grid PropertyChanged="BindableObject_OnPropertyChanged"
            VerticalOptions="Start"
            HorizontalOptions="Start">

        <BoxView HeightRequest="80"
                    WidthRequest="250"
                    Margin="7.5"
                    Color="{StaticResource PhoneContrastBackgroundBrush}"></BoxView>
        <Label  x:Name="PauseLabel"
                HorizontalOptions="CenterAndExpand"
                FontSize="28"
                TextColor="{StaticResource PhoneForegroundBrush}"
                Text="按住 說話"
                Margin="0"></Label>

    </Grid>


</controls1:PanContainer>

此時應該是可以拖動,並且在拖拽開始,進入pit,離開pit,釋放時,分別觸發Start,In,Out,Over四個狀態。

在這裡插入圖片描述

但我們希望在拖拽時隱藏這個按鈕,這將在建立動畫章節將介紹。

建立TalkBox

建立一個圓角矩形,用來顯示正在說話的動畫。

<Grid Grid.Row="1"
            Opacity="1"
            x:Name="TalkBoxLayout">
        <BoxView x:Name="TalkBox"
                    HeightRequest="80"
                    WidthRequest="200"
                    CornerRadius="20"
                    Margin="7.5"
                    Color="{StaticResource PhoneAccentBrush}"
                    VerticalOptions="CenterAndExpand"
                    HorizontalOptions="CenterAndExpand"></BoxView>

        <controls:PlayingMotionView   HorizontalOptions="CenterAndExpand"
                                        x:Name="MotionView"
                                        Margin="0"></controls:PlayingMotionView>

    </Grid>
</Grid>

效果如下

在這裡插入圖片描述

建立動畫

拖拽物動畫

在拖拽時我們希望可以隱藏拖拽物,設定 PanScalePanScaleAnimationLength屬性為0,程式碼如下:

<controls1:PanContainer BackgroundColor="Transparent"
        x:Name="DefaultPanContainer"
        OnTapped="DefaultPanContainer_OnOnTapped"
        AutoAdsorption="False"
        PanScale="0.0"
        PanScaleAnimationLength="0">

按鈕啟用動畫

Codebeind程式碼中,配置Active和DeActive方法,用於啟用和取消啟用功能區域按鈕的樣式。

啟用時,對應功能區域按鈕背景顏色變為白色,字型顏色變為黑色,並且放大到1.2倍。
取消啟用時,恢復到原來的樣式。

在這裡插入圖片描述

程式碼如下



private void Active(BoxView currentContent, Label text, Color toColor, Color txtToColor, double scaleTo = 1.2)
{
    currentContent.AbortAnimation("ActivateFunctionAnimations");
    var parentAnimation = new Animation();


    var txtFromColor = text.TextColor;

    var animation2 = new Animation(t => text.TextColor = GetColor(t, txtFromColor, txtToColor), 0, 1, Easing.SpringOut);



    var fromColor = currentContent.Color;

    var animation4 = new Animation(t => currentContent.Color = GetColor(t, fromColor, toColor), 0, 1, Easing.SpringOut);
    var animation5 = new Animation(v => currentContent.Scale = v, currentContent.Scale, scaleTo);


    parentAnimation.Add(0, 1, animation2);
    parentAnimation.Add(0, 1, animation4);
    parentAnimation.Add(0, 1, animation5);

    parentAnimation.Commit(this, "ActivateFunctionAnimations", 16, 300);
}



private void DeActive(BoxView currentContent, Label text)
{
    currentContent.AbortAnimation("DeactivateFunctionAnimations");
    var parentAnimation = new Animation();


    var txtFromColor = text.TextColor;
    var txtToColor = (Color)Application.Current.Resources["PhoneContrastForegroundBrush"];

    var animation2 = new Animation(t => text.TextColor = GetColor(t, txtFromColor, txtToColor), 0, 1, Easing.SpringOut);



    var fromColor = currentContent.Color;
    var toColor = (Color)Application.Current.Resources["PhoneContrastBackgroundBrush"];

    var animation4 = new Animation(t => currentContent.Color = GetColor(t, fromColor, toColor), 0, 1, Easing.SpringOut);
    var animation5 = new Animation(v => currentContent.Scale = v, currentContent.Scale, 1.0);


    parentAnimation.Add(0, 1, animation2);
    parentAnimation.Add(0, 1, animation4);
    parentAnimation.Add(0, 1, animation5);

    parentAnimation.Commit(this, "DeactivateFunctionAnimations", 16, 300);
}

在拖拽進入pit的事件中設定啟用狀態,在拖拽離開pit的事件中設定取消啟用狀態。


case PanType.Out:
    switch (args.CurrentPit?.PitName)
    {
        case "CancelPit":
            DeActive(this.CancelBox, this.CancelLabel);
            break;

        case "SendPit":
            DeActive(this.SendBox, this.SendLabel);
            break;

        case "TransliterationPit":
            DeActive(this.TransliterationBox, this.TransliterationLabel);
            break;

        default:
            break;
    }
    break;
case PanType.In:
    var parentAnimation = new Animation();

    Color toColor = default;
    double translationX = default;
    double width = default;
    switch (args.CurrentPit?.PitName)
    {
        case "CancelPit":
            Active(this.CancelBox, this.CancelLabel, Colors.White, Colors.Black);

            this.TalkBox.AbortAnimation("TalkBoxAnimations");

            break;

        case "SendPit":
            Active(this.SendBox, this.SendLabel, Colors.Gray, Colors.Black, 1.0);
            break;

        case "TransliterationPit":
            Active(this.TransliterationBox, this.TransliterationLabel, Colors.White, Colors.Black);
            break;

        default:
            break;
    }

在這裡插入圖片描述

TalkBox動畫

建立GetColor方法,使用插值法用於獲取漸變過程中獲取當前進度的顏色

    private Color GetColor(double t, Color fromColor, Color toColor)
    {
        return Color.FromRgba(fromColor.Red + t * (toColor.Red - fromColor.Red),
                           fromColor.Green + t * (toColor.Green - fromColor.Green),
                           fromColor.Blue + t * (toColor.Blue - fromColor.Blue),
                           fromColor.Alpha + t * (toColor.Alpha - fromColor.Alpha));
    }

在這裡插入圖片描述

在進入功能區域時,TalkBox的顏色,偏移量和寬度都會發生變化,建立一個複合動畫TalkBoxAnimations,用於觸發TalkBox的動畫效果。

this.TalkBox.AbortAnimation("TalkBoxAnimations");

var fromColor = this.TalkBox.Color;

var animation2 = new Animation(t => this.TalkBox.Color = GetColor(t, fromColor, toColor), 0, 1, Easing.SpringOut);
var animation4 = new Animation(v => this.TalkBoxLayout.TranslationX = v, this.TalkBoxLayout.TranslationX, translationX);
var animation5 = new Animation(v => this.TalkBox.WidthRequest = v, this.TalkBox.Width, width);


parentAnimation.Add(0, 1, animation2);
parentAnimation.Add(0, 1, animation4);
parentAnimation.Add(0, 1, animation5);

parentAnimation.Commit(this, "TalkBoxAnimations", 16, 300);

最終效果如下:

Layout動畫

建立一個用於顯示功能區域和TalkBox的漸變動畫,用於在拖拽開始和結束時,顯示和隱藏這兩個控制元件。

private void ShowLayout(double opacity = 1)
{
    this.PitContentLayout.FadeTo(opacity);
    this.TalkBoxLayout.FadeTo(opacity);
}
case PanType.Over:
    ShowLayout(0);
    break;
case PanType.Start:
    ShowLayout();
    break;

專案地址

Github:maui-samples

相關文章