[MAUI]模仿iOS多任务切换卡片滑动的交互实现

这篇具有很好参考价值的文章主要介绍了[MAUI]模仿iOS多任务切换卡片滑动的交互实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

@

目录
  • 原理
  • 创建布局
  • 创建分布函数
  • 创建动效
  • 创建绑定数据
  • 细节调整
    • 首张卡片的处理
    • 为卡片添加裁剪
    • 跳转到最后一张卡片
  • 项目地址

看了上一篇博文的评论,大家对MAUI还是比较感兴趣的,非常感谢大家的关注,这个专栏我争取周更😉。

App之间的多任务切换相信你们都很熟悉。苹果设备从iOS9开始使用水平排列的叠层卡片来展现多任务

[MAUI]模仿iOS多任务切换卡片滑动的交互实现
动图来自iPhone 使用手册 - 在 iPhone 上的应用之间切换

这个设计利用屏幕深度(z方向)和水平空间(x轴方向)的平顺结合,在有限的屏幕空间内,展现了更多的卡片,滑动屏幕时,每一个卡片在屏幕中央的时候也能得到大面积的展示。

今天我们在.NET MAUI中实现这个优秀交互效果
,最终效果如下:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台。

原理

使用过的App将以屏幕截图的卡片方式展现,卡片从右到左依次排列,最近使用的app卡片将靠前,并叠层在其他久未使用的app卡片之上。

平铺分布

平铺分布是经典的卡片布局,它的卡片分部是均匀的

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

在有限的屏幕宽度内呈现6张卡片,叠层放置后每张卡片可显示部分的宽度为屏幕宽度的1/6

卡片在屏幕横轴的位置与其偏移量是一个线性关系,如下图:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

iOS多任务卡片分布

在iOS多任务卡片的布局中,卡片在屏幕范围内的布局由左向右的密度依次降低:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

它的布局位置是由4段二阶贝塞尔曲线拼接成的完整曲线函数计算而来的。

二阶贝塞尔曲线,可以通过三个点,来确定一条平滑的曲线。详情请参考这里

卡片在屏幕横轴的位置与其偏移量如下图:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

同样是在页面上从左至右呈现6张卡片。利用贝塞尔曲线函数的特性,编号靠前的卡片(1,2,3)的偏移量“滞后”,编号靠后的卡片(4,5,6)的偏移量“追赶”,这样保证了编号靠后的卡片(较新的App任务)布局密度降低,从而有更大面积的展示。

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

计算每一个卡片的偏移量,卡片的大小随偏移量成正比,效果如下图:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

接下来我们用几张App截图代替颜色交替的卡片并赋予其动效。

创建布局

新建.NET MAUI项目,命名MultitaskingCardList。将界面图片资源文件拷贝到项目\Resources\Images中并将他们包含在MauiImage资源清单中。

<MauiImage Include="Resources\Images\*" />

在MainPage.xaml中,创建一个横向StackLayout作为App后台任务卡片容器,我们将使用绑定集合的方式,将App后台任务添加到这个容器中。

代码如下:

<StackLayout Orientation="Horizontal"
    BindingContextChanged="BoxLayout_BindingContextChanged"
    x:Name="BoxLayout"
    BindableLayout.ItemsSource="{Binding AppTombStones}">

它的DataTemplate代表一个App后台任务,使用Grid布局,App的截图与名称分别位于Grid的第二行和第一行。

<BindableLayout.ItemTemplate>
    <DataTemplate>
        <Grid Style="{StaticResource BoxFrameStyle}" >
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Label Margin="25,0,0,0" TranslationY="30"  Text="{Binding AppName}" VerticalOptions="End"></Label>
            <Image  Aspect="AspectFill"
                    Grid.Row="1"
                    HeightRequest="550"
                    WidthRequest="250"
                    Source="{Binding AppScreen}">           
            </Image>

        </Grid>
    </DataTemplate>
</BindableLayout.ItemTemplate>

对卡片Grid的样式进行定义:

宽度300,高度550,左边距-220,这使得屏幕区域范围内有大概5-6个卡片可见。

<ContentPage.Resources>
    <Style TargetType="Grid"
            x:Key="BoxFrameStyle">

        <Setter Property="WidthRequest"
                Value="300"></Setter>
        <Setter Property="Margin"
                Value="0,0,-220,0"></Setter>
        <Setter Property="AnchorX"
                Value="0"></Setter>
    </Style>
</ContentPage.Resources>

效果如下:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

创建分布函数

为了快速映射位置与偏移量,我们在页面加载时计算出贝塞尔函数曲线上的离散点

二阶贝塞尔曲线由三个点确定,分别是:
起始点、终止点(也称锚点)、控制点

BezierSegments对象将描述4段连续的,首尾相连的二阶贝塞尔曲线

在MainPage.xaml.cs中订阅页面加载完毕事件PageLoaded,在事件方法中编写代码如下:

var p0 = new Point(0, 1);
var p1 = new Point(0.1, 0.9988);
var p2 = new Point(0.175, 0.9955);


var p3 = new Point(0.4, 0.99);
var p4 = new Point(0.575, 0.92);
var p5 = new Point(0.7, 0.88);

var p6 = new Point(0.775, 0.71);
var p7 = new Point(0.9, 0.4);
var p8 = new Point(1, 0);

this.BezierSegments = new Point[][] {

    new Point[]{p0,p1,p2},
    new Point[]{p2,p3,p4},
    new Point[]{p4,p5,p6},
    new Point[]{p6,p7,p8}
};

bezeirPointSubdivs,标示贝塞尔曲线上点的数量,值越大,曲线越平滑,但计算量也越大,这里取999

var bezeirPointSubdivs = 999;

根据二阶贝塞尔函数式:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

将点坐标带入表达式,则可以得出输入输出值之间的映射关系,代码如下:

X轴坐标

var bezeirPointX = Math.Pow(1 - (double)j / bezeirPointSubdivs, 2) * BezierSegments[i][0].X + 2 * (double)j / bezeirPointSubdivs * (1 - (double)j / bezeirPointSubdivs) * BezierSegments[i][1].X + Math.Pow((double)j / bezeirPointSubdivs, 2) * BezierSegments[i][2].X;

Y轴坐标:

var bezeirPointY = Math.Pow(1 - (double)j / bezeirPointSubdivs, 2) * BezierSegments[i][0].Y + 2 * (double)j / bezeirPointSubdivs * (1 - (double)j / bezeirPointSubdivs) * BezierSegments[i][1].Y + Math.Pow((double)j / bezeirPointSubdivs, 2) * BezierSegments[i][2].Y;

对每一段的贝塞尔曲线计算,拟合出一条完整曲线
计算而得的离散点存入BezeirPoints,代码如下:

for (int i = 0; i < this.BezierSegments.Length; i++)
    {
        for (int j = 0; j < bezeirPointSubdivs; j++)
        {
            var bezeirPointX = Math.Pow(1 - (double)j / bezeirPointSubdivs, 2) * BezierSegments[i][0].X + 2 * (double)j / bezeirPointSubdivs * (1 - (double)j / bezeirPointSubdivs) * BezierSegments[i][1].X + Math.Pow((double)j / bezeirPointSubdivs, 2) * BezierSegments[i][2].X;
            var bezeirPointY = Math.Pow(1 - (double)j / bezeirPointSubdivs, 2) * BezierSegments[i][0].Y + 2 * (double)j / bezeirPointSubdivs * (1 - (double)j / bezeirPointSubdivs) * BezierSegments[i][1].Y + Math.Pow((double)j / bezeirPointSubdivs, 2) * BezierSegments[i][2].Y;
            BezeirPoints.Add(new Point(bezeirPointX, bezeirPointY));

        }
    }

我们使用线性插值法(linear interpolation),计算平移手势进度,卡片的分布偏移量以及大小等值。

线性插值法是指使用连接两个已知量的直线来确定在这两个已知量之间的一个未知量的值的方法。具体请参考这里

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

假设我们已知坐标(x0,y0)与(x1,y1),要得到[x0,x1]区间内某一位置x在直线上的值。根据图中所示,我们得到两点式直线方程

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

创建调制方法Modulate,代码如下

public double Modulate(double value, double[] source, double[] target)
{
    if (source.Length != 2 || target.Length != 2)
    {
        throw new ArgumentOutOfRangeException();
    }

    var start = source[0];
    var end = source[1];
    var targetStart = target[0];
    var targetEnd = target[1];
    if (value < start || value > end)
    {
        return value;
    }
    var k = (value - start) / (end - start);
    var result = k * (targetEnd - targetStart) + targetStart;
    return result;
}

创建动效

我们将为App后台任务容器创建平移手势,实现各个卡片的滚动动效,当用户指尖在屏幕水平方向上滑动时,卡片内容也应该随之横向滚动。

原本的实现方式是控件自监听平移(Pan)事件,通过x轴方向的平移偏移量,计算卡片容器中各个卡片的偏移量,从而实现卡片滚动动效。但平移过后的惯性滑动要自行计算,滑动手感不够流畅,最终效果并不理想,因此改用MAUI的ScrollView控件作为滚动框架

因此滚动行为(滚动阻尼,滚动惯性等)由各平台的原生代码实现。

<ScrollView x:Name="MainScroller"
    Background="Transparent"
    Orientation="Horizontal"
    Scrolled="ScrollView_Scrolled">

    <!--App后台任务卡片容器-->
    <StackLayout>...</StackLayout>


</ScrollView> 

效果如下:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

创建RenderTransform方法,实现卡片的平移,缩放,透明度等动效。
relativeOffsetX为卡片去除了滚动的影响,相对于屏幕的X方向位置。即相位置

通过遍历BoxLayout中的各卡片相对位置计算进度值progress

再通过调制方法Modulate,计算卡片的缩放,透明度,偏移量等值。

private void RenderTransform(double scrollX)
{
    var layoutWidth = this.MainLayout.DesiredSize.Width;
    if (this.BezeirPoints == null)
    {
        return;
    }
    foreach (var item in this.BoxLayout.Children)
    {
        if (item is VisualElement)
        {
            var relativeOffsetX = (item as VisualElement).X-scrollX;
            var progress = this.Modulate(relativeOffsetX, new double[] { 0, layoutWidth }, new double[] { 0, 1 });
            (item as VisualElement).ScaleTo(Modulate(progress, new double[] { 0, 1 }, new double[] { 0.72, 0.84 }), 0);
            (item as VisualElement).FadeTo(Modulate(progress, new double[] { 0.2, 0.54 }, new double[] { 0, 1 }), 0);
            var modulatedX = Modulate(1 - GetMappingY(progress), new double[] { 0, 1 }, new double[] { 0, layoutWidth });
            var offsetX = modulatedX - relativeOffsetX;
            (item as VisualElement).TranslateTo(offsetX, 0, 0);
        }
    }
}

静态效果如下:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

RenderTransform方法的形参scrollX为滚动框架的滚动偏移量,即MainScroller.ScrollX。

订阅滚动事件Scrolled,在事件方法中调用RenderTransform。代码如下:

private void ScrollView_Scrolled(object sender, ScrolledEventArgs e)
{
    RenderTransform(e.ScrollX);
}

创建绑定数据

创建MainPageViewModel.cs,用于界面绑定数据源。

AppTombStone描述App进入后台时的状态(墓碑机制)

public class AppTombStone
{
    public AppTombStone() { }

    public string AppName { get; set; }
    public string AppScreen { get; set; }
    public double TestOffset { get; set; }
}

在MainPageViewModel构造函数中,初始化AppTombStone列表,代码如下:

public class MainPageViewModel : INotifyPropertyChanged
{
    public MainPageViewModel()
    {
        var list = new List<AppTombStone>
        {
            new AppTombStone() { AppName="Edge", AppScreen= "p1.png",TestOffset=0},
            new AppTombStone() { AppName="Map", AppScreen= "p2.png",TestOffset=-10 },
            new AppTombStone() { AppName="Photo", AppScreen= "p3.png",TestOffset=-70 },
            new AppTombStone() { AppName="App Store", AppScreen= "p4.png" ,TestOffset=-90},
            new AppTombStone() { AppName="Calculator", AppScreen= "p5.png",TestOffset=-70 },
            new AppTombStone() { AppName="Music", AppScreen= "p6.png" ,TestOffset=-30},
            new AppTombStone() { AppName="File", AppScreen= "p7.png" },
            new AppTombStone() { AppName="Note", AppScreen= "p8.png" },
            new AppTombStone() { AppName="Paint", AppScreen= "p9.png" },
            new AppTombStone() { AppName="Weather", AppScreen= "p10.png" },
            new AppTombStone() { AppName="Chrome", AppScreen= "p11.png" },
            new AppTombStone() { AppName="Book", AppScreen= "p12.png" },
            new AppTombStone() { AppName="Browser", AppScreen= "p13.png" }
        };

        AppTombStones = new ObservableCollection<AppTombStone>(list);
    }

细节调整

首张卡片的处理

这里遇到个问题,当滚动框架滚动到最左侧时,最下方的卡片会被叠层上方的卡片覆盖,如下图所示:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

当滚动框架滚动到最左侧时,我们希望首张卡片不被上方的卡片覆盖,那么它至少应当滚动到屏幕的中部,因此需要加一个虚拟的BoxView将首张卡前的空间“撑起来”。

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

订阅BoxView的BindingContextChanged事件,在事件方法中添加如下代码

private void BoxLayout_BindingContextChanged(object sender, EventArgs e)
    {
        this.BoxLayout.Children.Insert(0, new BoxView()
        {
            WidthRequest=300,
            HeightRequest=500,
            BackgroundColor=Colors.Red
        });
    }

效果:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

为卡片添加裁剪

使用Image.Clip和Image.Shadow属性,为卡片添加圆角裁剪和阴影效果。

<Image  Aspect="AspectFill"
        Grid.Row="1"
        HeightRequest="550"
        WidthRequest="250"
        Source="{Binding AppScreen}">
    <Image.Clip>
        <RoundRectangleGeometry
            CornerRadius="20"
            Rect="0,20,250,480">
        </RoundRectangleGeometry>
    </Image.Clip>
    <Image.Shadow>
        <Shadow Brush="Black"
                Radius="40"
                Offset="-20,0"
                Opacity="0.3" />
    </Image.Shadow>
</Image>

跳转到最后一张卡片

App后台任务是从右到左排列的,因此在App启动时,需要将滚动框架滚动到最后一张卡片,代码如下:

private async void ContentPage_SizeChanged(object sender, EventArgs e)
{
    var layoutWidth = this.MainLayout.DesiredSize.Width;

    var scrollY = this.MainScroller.ScrollY;
    var posX = this.MainScroller.ContentSize.Width-layoutWidth;
    await this.MainScroller.ScrollToAsync(posX, scrollY, false).ContinueWith((t) =>
    {
        RenderTransform(this.MainScroller.ScrollX);
    });

}

最终效果:

[MAUI]模仿iOS多任务切换卡片滑动的交互实现

项目地址

Github:maui-samples文章来源地址https://www.toymoban.com/news/detail-431862.html

到了这里,关于[MAUI]模仿iOS多任务切换卡片滑动的交互实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 利用uniapp中模仿抖音、滑动视频组件、首个视频自动播放、预加载、实现加载更多,超高性能

    抖音效果图 本内容主要实现了滑动视频组件、首个视频自动播放、预加载、实现加载更多,超高性能, 前言:最近在做短剧,于是就在网上找了很多不错的例子,但是不是很完美,基本上都比较卡顿,我也是在站在巨人的肩膀上优化了一下。本片主要基于vue3、setup和ts开发的

    2024年02月04日
    浏览(75)
  • 关于安卓viewpager实现堆叠卡片交互

    长江后浪推前浪,无聊的需求一浪接一浪。 最近做到一个关于卡片堆叠的需求,觉得挺有意思,所以特此记录一下。 文末将附上源码链接 首先看设计图: 可以看到,是一个卡片堆叠的效果,关于这种UI的实现,方法有很多,例如用recyclerview,viewpager,甚至说自定义view都可以

    2024年02月11日
    浏览(38)
  • CSS实现一个交互感不错的卡片列表

    横向滚动 鼠标悬停时突出显示 默认堆叠展示 鼠标悬停时,完整展示当前块+适当旋出效果 移动端样式优化、磁吸效果 美化滚动条 flex 布局 css 简单变换+过渡 transform、transition 渐变色函数 linear-gradient … 伪类、伪元素 滚动条、::after、 ::before …

    2024年02月11日
    浏览(41)
  • QT桌面(实现界面的滑动切换)

    在ARM Linux中使用QT如何实现滑动翻页切换界面的效果呢?在ARM中是没有自带的鼠标的,那么我们如何实现滑动翻页呢?经过测试发现在ARM中运行QT程序也是可以通过重写鼠标事件来捕获触屏动作的,在ARM中滑动屏幕被定义成了鼠标左键事件,那么这样就有思路了, 重写鼠标事

    2024年02月06日
    浏览(43)
  • 使用JavaScript实现页面滑动切换效果

      使用JavaScript实现页面滑动切换效果 在现代Web页面设计中,页面滑动切换效果已经成为了一种常见的设计要求,能够提升用户体验,增加页面的交互性。本文将通过JavaScript来实现这一效果。 首先,我们需要在HTML中添加一些基础结构和样式。以下是一个简单的例子:   在

    2024年02月13日
    浏览(56)
  • uniapp实现tabs切换(可滑动)

    继续加油呀~

    2024年02月11日
    浏览(43)
  • 微信小程序实现滑动/点击切换Tab

    👏 swiper+scroll-view实现滑动/点击切换Tab,以及scroll-left的使用~ 🥇文末分享源代码。记得点赞+关注+收藏! 2.1 scroll-view实现tab列表 scroll-view: 可滚动视图区域。使用竖向滚动时,需要给scroll-view一个固定高度,通过 WXSS 设置 height。组件属性的长度单位默认为px。 scroll-x(bool

    2024年01月19日
    浏览(114)
  • 快速掌握微信小程序 tab 切换的实现技巧(可滑动切换)

    微信小程序中的 tab 切换功能可以说是用户所需的一个基础功能。本文将介绍如何通过微信小程序实现 tab 切换功能,为用户带来更为便捷和高效的小程序体验。 其实这个小功能的实现非常简单,只需要通过一个标识控制选项的样式及显示的内容,当我们触发点击或者滑动事

    2024年02月11日
    浏览(84)
  • 如何快速生成一个H5滑动的卡片

    当要对滚动做一些处理的时候可以参考下面的代码  这里以vant的轮播图组件  结构  样式  方法函数 计算属性用于将原始的 comboArr 数组切分成多个子数组,每个子数组包含 itemsPerSwipe 个元素。这样可以确保每个轮播项内都有相同数量的组合套餐信息。这个方法返回一个包含

    2024年02月10日
    浏览(45)
  • Taro+vue3 实现滑动列表 时切换电影

    以上代码是滑动的组件 

    2024年01月16日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包