美文网首页
flutter 即兴改造 MyBottomSheet

flutter 即兴改造 MyBottomSheet

作者: 卢融霜 | 来源:发表于2021-11-26 15:56 被阅读0次

效果图

QQ录屏20211126154730202111261547581.gif

基于 "flutter 写一个BottomSheet" 里面的内容,进行深度学习,得到如上效果。
后加的效果,总计思路就是 是根据 垂直滑动距离,来改变 item 向左,向上的位置。

网页版(第一次打开网址加载会很慢)
网页版

完整代码:

使用

import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter1/base/base_routes_widget.dart';
import 'package:flutter1/widget/my_bottom_sheet2.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

// @description 作用:
// @date: 2021/11/26
// @author: 卢融霜

class BoottomSheetRoutes2 extends StatefulWidget {
  const BoottomSheetRoutes2({Key key}) : super(key: key);

  @override
  _BoottomSheetRoutes2State createState() => _BoottomSheetRoutes2State();
}

class _BoottomSheetRoutes2State extends State<BoottomSheetRoutes2> {
  @override
  Widget build(BuildContext context) {
    return BaseRoutesWidget(
        title: "flutter 即兴改造 MyBoottomSheet",
        child: SizedBox(
          width: double.infinity,
          child: Stack(
            children: [
              Column(
                children: const [Text("改造MyBoottomSheet")],
              ),
              MyBottomSheet2()
            ],
          ),
        ));
  }
}

组件

import 'dart:math';
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter1/widget/sheet_icon.dart';
import 'package:flutter1/widget/sheet_title.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

// @description 作用: MyBottomSheet 即兴改造
// @date: 2021/11/26
// @author: 卢融霜

class MyBottomSheet2 extends StatefulWidget {
  MyBottomSheet2({Key key}) : super(key: key);

  @override
  _MyBottomSheet2State createState() => _MyBottomSheet2State();
}

List<ItemData> itemDatas = [
  ItemData(
      "https://t12.baidu.com/it/u=2453715824,121854933&fm=173&app=25&f=JPEG?w=639&h=399&s=9CA0069D451243D21CA0B1DE030040A9",
      "大圣神威",
      "孙悟空每次释放技能后强化下次普攻,并且附带位移效果。简单来说,被动是孙悟空高伤害的基础,天生20%的暴击几率使得孙悟空注定要成为一个暴力流的英雄。另外需要注意的是孙悟空初始暴击效果只有150%,这就使得前期的孙悟空虽然可以出暴击,但伤害不是很高,因此要尽量避免与敌人正面交战,等自己核心装备无尽战刃出来再说。"),
  ItemData(
      "https://t12.baidu.com/it/u=1192333310,3230582637&fm=173&app=25&f=JPEG?w=640&h=338&s=E9D4538F784090530CB14FBF03005087",
      "护身咒法",
      "孙悟空释放该技能,可以在一秒内抵挡一次敌方技能,若抵挡成功,孙悟空将获得短暂的无敌效果以及持续4秒的护盾,该技能使得孙悟空在改版之后保命能力大大提升。从数值上可以看出,该技能加满点之后可为孙悟空获得高达1000点的护盾,大大提高了孙悟空击杀脆皮的成功率。另外,该技能前期冷却时间较长,作为辅助技能比较好,推荐为次点技能。"),
  ItemData(
      "https://t10.baidu.com/it/u=3989151291,3332133202&fm=173&app=25&f=JPEG?w=640&h=360&s=3E29A00B42030EF6F4A1FA8303007087",
      "斗战冲锋",
      "该技能为孙悟空的主要位移技能,朝指定方向位移,命中敌人后可以借力翻滚,该效果在一定程度上增加了孙悟空的位移距离,在不被控住的情况下可以灵活的的冲出包围圈,并且翻滚效果还可以在团战时跨越前排直击后排,增加了孙悟空切后排的方式,但是该方法难度较大,要多加练习方可掌握。相比一技能来说,该技能CD相对较短,推荐主点。"),
  ItemData(
      "https://t10.baidu.com/it/u=3350237058,4106918017&fm=173&app=25&f=JPEG?w=640&h=373&s=F38260A44C427AD40EB2C199030090BB",
      "如意金箍",
      "孙悟空将金箍棒插入大地,震飞范围内敌人并眩晕一秒,同时对命中敌人打上三层印记,再次攻击触发印记造成高额伤害。该技能是孙悟空主要的控制技能,命中脆皮后很容易秒杀,关键就在于是否能够命中敌人,一般来说在突进贴脸后命中率较大,这对于自己的手速也有一定的考验。该技能的三层印记是不容忽略的,因此在保证大招命中率的同时,要尽量先放大招,利用一二技能来收割。"),
  ItemData(
      "https://t11.baidu.com/it/u=1764226162,1265974581&fm=173&app=25&f=JPEG?w=640&h=360&s=9A1B6687160322F0008BA5B70300D06A",
      "大圣神威",
      "孙悟空将金箍棒插入大地,震飞范围内敌人并眩晕一秒,同时对命中敌人打上三层印记,再次攻击触发印记造成高额伤害。该技能是孙悟空主要的控制技能,命中脆皮后很容易秒杀,关键就在于是否能够命中敌人,一般来说在突进贴脸后命中率较大,这对于自己的手速也有一定的考验。该技能的三层印记是不容忽略的,因此在保证大招命中率的同时,要尽量先放大招,利用一二技能来收割。"),
];

AnimationController _controller;

//最小宽度
double iconMinWidth = 45.r;
//最大宽度
double iconMaxWidth = 100.r;

//向上最小高度
double iconMinTop = 60.r;
//向上最大高度
double iconManTop = 100.r;

class _MyBottomSheet2State extends State<MyBottomSheet2>
    with SingleTickerProviderStateMixin {
  //最小高度
  double minHeight = 120.r;

  //最大高度
  double get maxHeight => MediaQuery.of(context).size.height - 90.r;

  //数据长度
  int get itemDatasLength => itemDatas.length;

  //垂直间距
  double get iconVerticalSpa =>
      //页面最大高
      (maxHeight -
          //头部标题高度
          (60.r) -
          //纵向数量
          (iconMaxWidth * itemDatasLength)) /
      itemDatasLength;

  //横向间距
  double get iconHorizonSpa =>
      (MediaQuery.of(context).size.width -
          //左右 两个外边距  30.r * 2
          (60.r) -
          //横向数量
          (iconMinWidth * itemDatasLength)) /
      (itemDatasLength - 1);

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 1000),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Stack(
            children: [
              Positioned(
                  height: lerp(minHeight, maxHeight),
                  left: 0,
                  right: 0,
                  bottom: 0,
                  child: GestureDetector(
                    onVerticalDragUpdate: _dragUpdate,
                    onVerticalDragEnd: _handleDragEnd,
                    child: Container(
                        decoration: BoxDecoration(
                          color: Colors.blueAccent,
                          borderRadius:
                              BorderRadius.vertical(top: Radius.circular(30.r)),
                        ),
                        padding: EdgeInsets.symmetric(horizontal: 30.r),
                        child: Stack(
                          children: [
                            Positioned(
                              height: 60.r,
                              top: 20.r,
                              left: 0,
                              child: SheetTitle(
                                fontSize: lerp(14.sp, 20.sp),
                                title: "技能点",
                              ),
                            ),
                            Positioned(
                                height: 60.r,
                                top: 20.r,
                                right: 0,
                                child: InkWell(
                                  onTap: _toggle,
                                  child: SheetIcon(
                                    fontSize: lerp(20.sp, 30.sp),
                                    showList: _controller.status ==
                                        AnimationStatus.completed,
                                  ),
                                )),
                            for (ItemData itemData in itemDatas)
                              getIconList(itemData),
                            for (ItemData itemData in itemDatas)
                              getContentList(itemData),
                          ],
                        )),
                  ))
            ],
          );
        });
  }

  ///开关切换
  void _toggle() {
    //判断打开状态 进行切换
    bool isOpen = _controller.status == AnimationStatus.completed;
    //velocity  传递正数 执行到 upperBound 值  负数 执行到 lowerBound 值
    _controller.fling(velocity: isOpen ? -1 : 1);
  }

  ///滑动关键代码,监听纵向滑动阀值,设置响应高度
  void _dragUpdate(DragUpdateDetails details) {
    _controller.value -= details.primaryDelta / maxHeight;
  }

  /// 开关核心代码: 根据滑动速度,滑动高度,来判断 开关
  void _handleDragEnd(DragEndDetails details) {
    //排除 动画过程中,和完成状态。
    if (_controller.isAnimating ||
        _controller.status == AnimationStatus.completed) return;

    //获取滑动速度
    final double flingVelocity =
        details.velocity.pixelsPerSecond.dy / maxHeight;

    //设置滑动执行方向
    if (flingVelocity < 0.0) {
      _controller.fling(velocity: max(1.0, -flingVelocity));
    } else if (flingVelocity > 0.0) {
      _controller.fling(velocity: min(-1.0, -flingVelocity));
    } else {
      _controller.fling(velocity: _controller.value < 0.5 ? -1.0 : 1.0);
    }
  }

  //图片距离左侧数值
  double iconItemLeft(i) {
    //min: 第i个  * (间距 + 最小宽度)   max 靠最左  0
    return lerp(i * (iconHorizonSpa + iconMinWidth), 0);
  }

  //详细信息item 距离左侧数据
  double contentItemLeft(i) {
    //min: 第i个  * (间距 + 最小宽度)   max 靠最左  0
    return iconItemLeft(i) + lerp(iconMinWidth, iconMaxWidth);
  }

  double iconItemTop(i) {
    return lerp(iconMinTop, iconMinTop + i * (iconVerticalSpa + iconManTop));
  }

  ///底部横向 图片展示
  Widget getIconList(ItemData itemData) {
    int i = itemDatas.indexOf(itemData);
    return SheetIconWidget(
        height: lerp(iconMinWidth.r, iconMaxWidth),
        width: lerp(iconMinWidth.r, iconMaxWidth),
        top: iconItemTop(i),
        left: iconItemLeft(i),
        url: itemData.url);
  }

  ///展开后,显示 右侧详细信息
  Widget getContentList(ItemData itemData) {
    int i = itemDatas.indexOf(itemData);
    return SheetContentWidget(
        height: lerp(iconMinWidth.r, iconMaxWidth),
        width: lerp(
            0,
            MediaQuery.of(context).size.width -
                60.r -
                lerp(iconMinWidth.r, iconMaxWidth)),
        top: iconItemTop(i),
        left: contentItemLeft(i),
        itemData: itemData);
  }
}

/// 滑动核心代码, 根据 _controller.value 的0 - 1的值,返回 minHeight - maxHeight 相对于的值,完成高度设置
double lerp(double minHeight, double maxHeight) {
  var height = lerpDouble(minHeight, maxHeight, _controller.value);
  return height;
}

//详细信息子item
class ContentItem extends StatelessWidget {
  ItemData itemData;

  ContentItem(this.itemData, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Row(
          children: [
            Expanded(
                child: Text(
              itemData.title,
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15.r),
            ))
          ],
        ),
        Padding(
          padding: EdgeInsets.only(top: 2.r),
          child: Row(
            children: [
              Expanded(
                  child: Text(
                itemData.text,
                maxLines: 3,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(fontSize: 12.r, color: Colors.grey),
              ))
            ],
          ),
        )
      ],
    );
  }
}

//详细信息item
class SheetContentWidget extends StatelessWidget {
  double height;
  double width;
  double top;
  double left;
  ItemData itemData;

  SheetContentWidget(
      {this.height, this.width, this.top, this.left, this.itemData, Key key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Positioned(
        height: height,
        width: width,
        top: top,
        left: left,
        child: AnimatedOpacity(
            opacity: _controller.status == AnimationStatus.completed ? 1 : 0,
            duration: const Duration(milliseconds: 500),
            child: Container(
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.horizontal(
                    right: Radius.circular(lerp(5.r, 10.r))),
              ),
              height: iconMaxWidth,
              width: MediaQuery.of(context).size.width - 60.r - iconMaxWidth,
              padding: EdgeInsets.all(8.r),
              child: Visibility(
                  visible: _controller.status == AnimationStatus.completed,
                  child: ContentItem(itemData)),
            )));
  }
}

///图片
class SheetIconWidget extends StatelessWidget {
  double height;
  double width;
  double top;
  double left;
  String url;

  SheetIconWidget(
      {this.url, this.height, this.width, this.top, this.left, Key key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Positioned(
        height: height,
        width: width,
        top: top,
        left: left,
        child: ClipRRect(
          borderRadius: BorderRadius.horizontal(
              left: Radius.circular(lerp(5.r, 10.r)),
              right: Radius.circular(lerp(5.r, 0))),
          child: Image.network(
            url,
            fit: BoxFit.cover,
          ),
        ));
  }
}

///实体类
class ItemData {
  String url;
  String title;
  String text;

  ItemData(this.url, this.title, this.text);
}

头部

import 'dart:ui';

import 'package:flutter/material.dart';

// @description 作用:bottom_sheet标题
// @date: 2021/11/26
// @author: 卢融霜

class SheetTitle extends StatelessWidget {
  final String title;
  final double fontSize;

  const SheetTitle({Key key, this.title, this.fontSize}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      title,
      style: TextStyle(
          color: Colors.white, fontSize: fontSize, fontWeight: FontWeight.bold),
    );
  }
}

右上图标

import 'package:flutter/material.dart';

// @description 作用: sheet_bottom 右侧按钮
// @date: 2021/11/26
// @author: 卢融霜

class SheetIcon extends StatelessWidget {
  final bool showList;
  final double fontSize;

  const SheetIcon({Key key, this.showList, this.fontSize}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AnimatedCrossFade(
        firstChild: Icon(
          Icons.menu,
          color: Colors.white,
          size: fontSize,
        ),
        secondChild: Icon(
          Icons.close_outlined,
          color: Colors.white,
          size: fontSize,
        ),
        crossFadeState:
            showList ? CrossFadeState.showSecond : CrossFadeState.showFirst,
        duration: const Duration(milliseconds: 500));
  }
}

相关文章

网友评论

      本文标题:flutter 即兴改造 MyBottomSheet

      本文链接:https://www.haomeiwen.com/subject/aecxxrtx.html