热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

今日份分享:Flutter自定义之旋转木马

今日份分享:Flutter自定义之旋转木马-先上图,带你回到童年时光:效果分析子布局按照圆形顺序放置且平分角度子布局旋转、支持手势滑动旋转、快速滑动抬手继续旋转、自动旋转支持X轴旋

先上图,带你回到童年时光:

效果分析

  • 子布局按照圆形顺序放置且平分角度
  • 子布局旋转、支持手势滑动旋转、快速滑动抬手继续旋转、自动旋转
  • 支持X轴旋转
  • 支持前后缩放子布局(起始角度为前,相对位置为后,最前面最大,反而越小)
  • 多个布局叠加时前面遮挡后面

效果难点问题

  • Flutter如何实现控件布局达到3D效果?
  • Flutter如何实现子控件旋转、自动旋转、手势滑动时关联子控件旋转滚动?快速滑动抬手继续旋转滚动?
  • Flutter如何实现多个布局叠加时前面遮挡后面?
1.子布局按照圆形顺序放置且平分角度

如上图所示:

如上图所示(参考系:最下方为0度,逆时针旋转角度增加)

第一个点
解:根据已知条件列方程式
x2=width/2+sin(a)*R
y2=height/2+cos(a)*R    

第二个点
解:根据已知条件列方程式①
① x=width/2-sin(b)*R 
   y=height/2-cos(b)*R
因为b=a-180,所以带入①方程得:
② x=width/2-sin(a-180)*R
   y=height/2-cos(a-180)*R 
又因为sin(k*360+a)=sin(a),所以②方式可以修改为:
③ x=width/2-sin(180+a)*R
   y=height/2-cos(180+a)*R
又又因为 sin(180+a)=-sin(a),cos(180+a)=-cosa 带入③方程式得:
④ x=width/2+sin(a)*R 
  y=height/2+cos(a)*R 

由上面2点计算得,每个子布局的中心点坐标公式统一为:
x=width/2+sin(a)*R 
y=height/2+cos(a)*R

以上所用三角函数公式表:

通过上面计算得出子控件的位置公式后,开始我们的代码。

实现子控件按照圆形布局及平分角度代码如下:

//所有子控件的位置数据
//count:子控件数量;  
//startAngle:开始角度默认为0;  
//rotateAngle:偏转角度默认为0;
List _childPointList({Size size = Size.zero}) {
    List childPointList = [];
    double averageAngle = 360 / count;
    double radius = size.width / 2 - childWidth / 2;   
    for (int i = 0; i < count; i++) {
       /********************子布局角度*****************/
      double angle = startAngle + averageAngle * i + rotateAngle;
      //子布局中心点坐标
      var centerX = size.width / 2 + sin(radian(angle)) * radius;
      var centerY = size.height / 2 + cos(radian(angle)) * radius;
      childPointList.add(Point(
        centerX,
        centerY,
        childWidth,
        childHeight,
        centerX - childWidth / 2,
        centerY - childHeight / 2,
        centerX + childWidth / 2,
        centerY + childHeight / 2,
        1,
        angle,
        i,
      ));
    }
    return childPointList;
  }

///角度转弧度
///弧度 =度数 * (π / 180)
///度数 =弧度 * (180 / π)
double radian(double angle) {
    return angle * pi / 180;
}
2.子布局如何旋转?自动旋转?支持手势滑动旋转?快速滑动抬手继续旋转?

子布局如何旋转

所谓的旋转就是所有的子布局绕着圆形移动,布局一旦移动就代表中间位置改变,根据上面我们计算的子布局位置的公式来看:

中心点坐标
x=width/2+sin(a)*R 
y=height/2+cos(a)*R

因为widthR都是已知并且定下来的尺寸,所以说,想要改变中心点坐标,只需修改 角度a就可以了。要想达到旋转效果的话就是让所有的子布局都同时移动相同的角度即可。

子布局原始角度值:
double angle = startAngle + averageAngle * i; 
我们可以在此基础上加上一个可变的角度值,通过改变这个值,所有的子布局都会同时加上此值同时移动了位置。如下:
double angle = startAngle + averageAngle * i + rotateAngle; 
其中 rotateAngle 就是可变的值。改变这个值就能让布局动起来
自动旋转

同理,我们只要搞个定时器,周期性修改这个rotateAngle值,并setState(() {})刷新下,看起来就自动旋转了。

支持手势滑动旋转

大家已经知道通过修改rotateAngle值去实现旋转,那么支持手势滑动旋转顾名思义就是通过手势修改这个rotateAngle值就OK,那么手势处理Flutter提供了GestureDetector组件,这个组件功能很强大,这里面我们使用了他的几个回调方法。

本次实现直接使用水平滑动监听,大家如果想兼容竖直滑动可以自己尝试修改就可以。

GestureDetector(
        ///水平滑动按下
        onHorizontalDragDown: (DragDownDetails details) {...},

        ///水平滑动开始
        onHorizontalDragStart: (DragStartDetails details) {
          //记录拖动开始时当前的选择角度值
          downAngle = rotateAngle;
          //记录拖动开始时的x坐标
          downX = details.globalPosition.dx;
        },

        ///水平滑动中
        onHorizontalDragUpdate: (DragUpdateDetails details) {
           //滑动中X坐标值
          var updateX = details.globalPosition.dx;
          //计算当前旋转角度值并刷新
          rotateAngle = (downX - updateX) * slipRatio + downAngle;
          if (mounted) setState(() {});
        },

        ///水平滑动结束
        onHorizontalDragEnd: (DragEndDetails details) {...},

        ///滑动取消
        onHorizontalDragCancel: () {...},

        behavior: HitTestBehavior.opaque,//deferToChild   translucent
        child: xxx,
);
快速滑动抬手继续旋转

抬手还能继续旋转,也就是当我们快速滑动抬手的时候只要继续修改旋转角度值rotateAngle就可以达到继续旋转的效果。当我们抬手的时候我们可以拿到什么呢?

例如:当我们骑着小黄单车在大路上快速的蹬着脚蹬子然后停止蹬,你的小黄已当时的速度飞驰在这个大路上,由于地面的摩擦力的影响,速度会越来越小,最后停止。

///水平滑动结束
onHorizontalDragEnd: (DragEndDetails details) {
          //x方向上每秒速度的像素数
          velocityX = details.velocity.pixelsPerSecond.dx; 
          _controller.reset();
          _controller.forward();
 },

  //动画设置rotateAngle
   _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 1000),
    );

    animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.linearToEaseOut,
    );

    animation = new Tween(begin: 1, end: 0).animate(animation)
      ..addListener(() {
        //当前速度
        var velocity = animation.value * -velocityX;
        var offsetX = radius != 0 ? velocity * 5 / (2 * pi * radius) : velocity;
        rotateAngle += offsetX;
        setState(() => {});
      })
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          rotateAngle = rotateAngle % 360;
          _startRotateTimer();
        }
      });
3.支持X轴旋转

上图是X轴方向查看旋转切面图,按照x轴旋转所有的x坐标都是相同的,y值从上往下不断增加。因为绕着X轴旋转时,X坐标是不变的,Y坐标值改变,当旋转了a角度时,现在的Y坐标如图所示为

Y坐标旋转后=height/2+y*cos(a)     y值我们已经在上面计算过了,y=cos(a)*R 
所以最终的计算公式是:
Y坐标值=height/2+cos(a)*R*cos(a)
cos(a)在a=[0,90]区间时对应的值是1-0   即是 a=0度时cos(a)=1,就是原始状态(与Y轴平行),a=90度时cos(a)=0,就是与Y轴垂直准状态。所以我们可以设置a在0-90之间即可。

4.支持前后缩放子布局(起始角度为前,相对位置为后,最前面最大,反而越小)

上图为cos余弦曲线图。0度和360度最大 ,180度最小,刚好与我们设计的初始值从0开始,然后逆时针绕一圈角度从0-360度。

所以缩放比scale计算公式可以写为:

var scale = (1 - minScale) / 2 * (1 + cos(radian(angle - startAngle))) + minScale;

minScale为最小缩放比,为了让缩放有个极限值,即 scale范围为:(minScale,1)

5.多个布局叠加时前面遮挡后面

从视觉感受,靠近前面的布局应该遮挡后面的布局,在Android当中bringToFront()方法可以让布局置于前面,Flutter没有提供此方法,我们该如何处理这种情况呢?

Flutter提供一个Stack布局,也叫层叠式布局,当我们添加子布局到Stack布局中时,后面添加的会遮住前面添加的,所以只要我们在添加子布局的时候按照由后到前来添加即可。话说怎么知道是前是后呢?

知道实现思路现在要解决的问题是:

如何区分前与后?有什么条件可以区分?

考虑中...

1、根据坐标值?Y坐标小就是后面,Y坐标大就是前面?

答案是不一定;因为当我启动角度不是0的时候,比如是90度,那么最右面是前面,最左边是后面,这个时候是X坐标的大小区分前后关系,所以说单独使用坐标值的大小来决定前后关系是不对的。

2、根据前大后小原则?根据缩放值排序来添加子布局?

答案是可行;因为我们已经实现了前面的布局缩放值是1,后面的缩放值越来越小,而且我们已经处理了启动角度问题,所以根据缩放值来实现是可行的。

///通过缩放值进行排序,从小到大
childPointList.sort((a, b) {
  return a.scale.compareTo(b.scale);
});

///遍历添加子布局
Stack(
  children: childPointList.map(
              (Point point) {
                return Positioned(
                    width: point.width,
                    left: point.left,
                    top: point.top,
                    child: this.widget.children[point.index]);
              },
            ).toList(),
   ),

通过上面方式即可实现前后遮挡效果了。

小知识点

Flutter 之Stack 组件Stack一个可以叠加子控件的布局,这里主要讲一下 Positioned,其他使用方式可以看下官网说明。

Positioned({
  Key key,
  this.left,
  this.top,
  this.right,
  this.bottom,
  this.width,
  this.height,
  @required Widget child,
})

使用Positioned控制Widget的位置,通过Positioned可以随意摆放一个组件,有点像绝对布局。其中lefttop 、right、 bottom分别代表离Stack左、上、右、底四边的距离。

Flutter之LayoutBuilder 组件

有时我们希望根据组件的大小确认组件的外观,比如竖屏的时候上下展示,横屏的时候左右展示,通过LayoutBuilder组件可以获取父组件的约束尺寸。

附:github链接:https://github.com/yixiaolunh...

原文链接:https://www.jianshu.com/p/451...

文末

您的点赞收藏就是对我最大的鼓励!
欢迎关注我,分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!


推荐阅读
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
author-avatar
月曳柳覀梢
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有