34_Flutter之自定义侧滑菜单组件

Flutter之自定义侧滑菜单组件

一.页面布局

  • 采用横向滚动的ListView实现,ListView的第一个元素为实际显示的item,并且其宽度为整个侧滑组件的宽度,后边的元素为侧滑菜单项。

  • 由于ListView在指定滑动方向为横向时,其宽度是不受限制的,所以需要为侧滑组件提供一个属性,用于确定ListView的宽度,并且使ListView中的第一个元素填充ListView的宽度。

  • 由于ListView在指定滑动方向为横向时,ListView的高度被限制为显示区域的最大值,并且其内部的子组件的高度会填充ListView的高度,因此需要结合Stack组件以及ListView的第一个item的实际高度来确定ListView的高度。

    34_Flutter之自定义侧滑菜单组件_第1张图片

    import 'package:flutter/material.dart';
    
    class SwipeMemuWidget extends StatefulWidget {
    
      final double itemWidth;
      final Widget child;
      final List menus;
    
      const SwipeMemuWidget({
        Key key,
        @required this.child,
        this.menus = const [],
        this.itemWidth
      }):super(key:key);
    
      @override
      State createState() {
        // TODO: implement createState
        return _SwipeMenuWidgetState();
      }
    
    }
    
    class _SwipeMenuWidgetState extends State {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        print(MediaQuery.of(context).size.width);
        return Stack(
    //      fit: StackFit.expand,
          children: [
            Container(
              width: widget.itemWidth,
              child: widget.child,
            ),
            Positioned(
              left: 0,
              top: 0,
              right: 0,
              bottom: 0,
              child: ListView(
                scrollDirection: Axis.horizontal,
                shrinkWrap: true,
                children: [
                  Container(
                    width: widget.itemWidth,
                    child: widget.child,
                  ),
                  Column(
                    mainAxisSize: MainAxisSize.max,
                    mainAxisAlignment: MainAxisAlignment.start,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Expanded(
                        child: Row(
                          mainAxisSize: MainAxisSize.min,
                          mainAxisAlignment: MainAxisAlignment.start,
                          crossAxisAlignment: CrossAxisAlignment.center,
                          children: widget.menus,
                        ),
                      )
                    ],
                  )
                ],
              ),
            )
          ],
        );
      }
    
    }
    
    import 'package:flutter/material.dart';
    import './widget/swipe_menu_widget.dart';
    import 'dart:ui';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        MediaQueryData mediaQueryData = MediaQueryData.fromWindow(window);
        double screenWidth = mediaQueryData.size.width;
        return MaterialApp(
          title: 'SwipeMenuWidget Demo',
          home: Scaffold(
            appBar: AppBar(
              title: Text("SwipeMenuWidget Demo"),
            ),
            body: Container(
              child: SwipeMemuWidget(
                itemWidth: screenWidth,
                child: Container(
                  color: Color(0xffffffff),
                  padding: EdgeInsets.all(40),
                  child: Center(
                    heightFactor: 1.0,
                    child: Text("item"),
                  ),
                ),
                menus: [
                  Container(
                    color: Color(0xffff0000),
                    padding: EdgeInsets.all(40),
                    child: Text("action"),
                  )
                ],
              ),
            ),
          ),
        );
      }
    }
    

    34_Flutter之自定义侧滑菜单组件_第2张图片

二.禁用ListView在滑到边界时ios平台的回弹滑动效果和android平台的微光效果

  • 将滑动布局用ScrollConfiguration包裹,设置自定义behavior

    ScrollConfiguration(
        behavior: _MyBehavior(),
        child: ListView(),
    );
    
    class _MyBehavior extends ScrollBehavior{
     @override
     Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
        if(Platform.isAndroid||Platform.isFuchsia){
         return child;
      }else{
        return super.buildViewportChrome(context,child,axisDirection);
        }
     }
    }
    
  • 为ListView设置自定义的ScrollPhysics继承自ClampingScrollPhysics

    ScrollConfiguration(
        behavior: _MyBehavior(),
        child: ListView(
          physics: _MyScrollPhysics(),
        ),
    );
    class _MyScrollPhysics extends ClampingScrollPhysics{
      const _MyScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
    
      @override
      _MyScrollPhysics applyTo(ScrollPhysics ancestor) {
        return _MyScrollPhysics(parent: buildParent(ancestor));
      }
    }
    

三.实现ListView阻尼滑动效果

  • 重写_MyScrollPhysics的createBallisticSimulation方法,并判断滑动到的位置是否大于滑动范围的一半,如果滑动到的位置大于滑动范围的一半,则返回ScrollSpringSimulation自动滑动打开菜单栏,否则自动滑动关闭菜单栏

    class _MyScrollPhysics extends ClampingScrollPhysics{
    
      const _MyScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
    
      @override
      _MyScrollPhysics applyTo(ScrollPhysics ancestor) {
        return _MyScrollPhysics(parent: buildParent(ancestor));
      }
    
      double _getValue(ScrollPosition position) {
        if(position.pixels < position.maxScrollExtent/2.0) {
          return 0;
        } else {
          return 1;
        }
      }
    
      double _getPixels(ScrollPosition position, double value) {
        return value * position.viewportDimension;
      }
    
      double _getTargetPixels(ScrollPosition position, Tolerance tolerance, double velocity) {
        double value = _getValue(position);
        if (velocity < -tolerance.velocity)
          value -= 0.5;
        else if (velocity > tolerance.velocity)
          value += 0.5;
        return _getPixels(position, value.roundToDouble());
      }
    
      @override
      Simulation createBallisticSimulation(ScrollMetrics position, double velocity) {
        // If we're out of range and not headed back in range, defer to the parent
        // ballistics, which should put us back in range at a page boundary.
        if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
            (velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
          return super.createBallisticSimulation(position, velocity);
        final Tolerance tolerance = this.tolerance;
        final double target = _getTargetPixels(position, tolerance, velocity);
        if (target != position.pixels)
          return ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
        return null;
      }
    }
    

    34_Flutter之自定义侧滑菜单组件_第3张图片

你可能感兴趣的:(Flutter)