fluttercandies / extended_tabs

A powerful official extension library of Tab/TabBar/TabView, which support to scroll ancestor or child Tabs when current is overscroll, and set scroll direction and cache extent.
https://fluttercandies.github.io/extended_tabs/
MIT License
268 stars 49 forks source link

ExtendedTabBar 在scrollDirection: Axis.vertical情况下indicator中 paint(Canvas canvas, Offset offset..) offset值异常 #28

Open xietian0908 opened 1 year ago

xietian0908 commented 1 year ago

Version

extended_tabs: ^4.0.2

Platforms

Android

Device Model

galaxy tab A7 (Android 11)

flutter info

[!] Flutter (Channel unknown, 3.3.10, on Microsoft Windows [版本 10.0.19045.2965], locale zh-CN)
    ! Flutter version 3.3.10 on channel unknown at E:\flutter sdk\flutter
    ! Upstream repository unknown
    • Framework revision 135454af32 (6 months ago), 2022-12-15 07:36:55 -0800
    • Engine revision 3316dd8728
    • Dart version 2.18.6
    • DevTools version 2.15.0
    • Pub download mirror https://pub.flutter-io.cn
    • Flutter download mirror https://storage.flutter-io.cn

[√] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
    • Android SDK at C:\Users\Administrator\AppData\Local\Android\Sdk
    • Platform android-33, build-tools 33.0.2
    • ANDROID_HOME = C:\Users\Administrator\AppData\Local\Android\Sdk
    • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java      
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-9586694)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Users\Administrator\AppData\Local\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop for Windows (Visual Studio Community 2022 17.5.4)
    • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
    • Visual Studio Community 2022 version 17.5.33530.505
    • Windows 10 SDK version 10.0.22000.0

[√] Android Studio (version 2022.2)
    • Android Studio at C:\Program Files\Android\Android Studio
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-9586694)

[√] Connected device (4 available)
    • VRD W10 (mobile)  • RCJ6R20721000566 • android-arm64  • Android 10 (API 29)
    • Windows (desktop) • windows          • windows-x64    • Microsoft Windows [版本 10.0.19045.2965]
    • Chrome (web)      • chrome           • web-javascript • Google Chrome 112.0.5615.121
    • Edge (web)        • edge             • web-javascript • Microsoft Edge 113.0.1774.57

[√] HTTP Host Availability
    • All required HTTP hosts are available

How to reproduce?

https://github.com/fluttercandies/extended_tabs/assets/64937500/063f7772-2b35-4563-84ca-fb948a37e20c

4939ed8c35baf1a90a903c16804e5fc

Logs

No response

Example code (optional)

No response

Contact

No response

xietian0908 commented 1 year ago

tabbar_v_square_indicator.zip ExtendedTabBar( // labelPadding: EdgeInsets.symmetric(horizontal: 50.w), indicatorPadding: const EdgeInsets.all(0), labelPadding: const EdgeInsets.only(bottom: 0), isScrollable: true, indicatorWeight: 0, scrollDirection: Axis.vertical, tabs: _tabs(read.airDevicesModelList), indicator: TabbarVSquareIndicator( length: read.airDevicesModelList.length, ), );

xietian0908 commented 1 year ago

只有在快速滑动extendtabview 的时候点击extendtabbar才会出现,等待移动动画完成之后点击是没有问题的

zmtzawqlp commented 1 year ago
  1. 错误信息呢?
  2. 提供可以运行的最小的例子
xietian0908 commented 1 year ago
  1. 错误信息呢?
  2. 提供可以运行的最小的例子
import 'package:extended_tabs/extended_tabs.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/src/widgets/placeholder.dart';

class ExtendedTabsBug extends StatefulWidget {
  const ExtendedTabsBug({super.key});

  @override
  State<ExtendedTabsBug> createState() => _ExtendedTabsBugState();
}

class _ExtendedTabsBugState extends State<ExtendedTabsBug> {
  int length = 16;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: DefaultTabController(
        length: length,
        child: Row(
          children: [
            ExtendedTabBar(
              // labelPadding: EdgeInsets.symmetric(horizontal: 50.w),
              indicatorPadding: const EdgeInsets.all(0),
              labelPadding: const EdgeInsets.only(bottom: 0),
              isScrollable: true,
              indicatorWeight: 0,
              scrollDirection: Axis.vertical,
              tabs: _tabs(),
              indicator: TabbarVSquareIndicator(
                length: length,
              ),
            ),
            Expanded(
              child: ExtendedTabBarView(
                scrollDirection: Axis.vertical,
                children: [
                  for (int i = 0; i < length; i++)
                    Container(
                      color: Colors.primaries[i],
                      alignment: Alignment.center,
                      child: Text('tabview $i'),
                    )
                ],
              ),
            )
          ],
        ),
      ),
    );
  }

  List<Widget> _tabs() {
    return List.generate(
      length,
      (index) => Container(
        height: 100,
        margin: const EdgeInsets.only(bottom: 16),
        alignment: Alignment.center,
        child: Text('tab $index'),
      ),
    );
  }
}

class TabbarVSquareIndicator extends Decoration {
  /// Height of the indicator. Defaults to 4
  final double height;
  final double width;

  /// topRight radius of the indicator, default to 5.
  final double topRightRadius;

  /// topLeft radius of the indicator, default to 5.
  final double topLeftRadius;

  /// bottomRight radius of the indicator, default to 0.
  final double bottomRightRadius;

  /// bottomLeft radius of the indicator, default to 0
  final double bottomLeftRadius;

  /// Color of the indicator, default set to [Colors.black]
  final Color color;

  /// Horizontal padding of the indicator, default set 0
  final double horizontalPadding;

  /// [PagingStyle] determines if the indicator should be fill or stroke, default to fill
  final PaintingStyle paintingStyle;

  /// StrokeWidth, used for [PaintingStyle.stroke], default set to 2
  final double strokeWidth;
  final int length;

  const TabbarVSquareIndicator({
    this.length = 0,
    this.height = 4,
    this.width = 10,
    this.topRightRadius = 2,
    this.topLeftRadius = 2,
    this.bottomRightRadius = 2,
    this.bottomLeftRadius = 2,
    this.color = Colors.red,
    this.horizontalPadding = 0,
    this.paintingStyle = PaintingStyle.fill,
    this.strokeWidth = 4,
  });

  @override
  CustomPainter createBoxPainter([VoidCallback? onChanged]) {
    return CustomPainter(
      this,
      onChanged,
      bottomLeftRadius: bottomLeftRadius,
      bottomRightRadius: bottomRightRadius,
      color: color,
      height: height,
      width: width,
      horizontalPadding: horizontalPadding,
      topLeftRadius: topLeftRadius,
      topRightRadius: topRightRadius,
      paintingStyle: paintingStyle,
      strokeWidth: strokeWidth,
      length: length,
    );
  }
}

class CustomPainter extends BoxPainter {
  final TabbarVSquareIndicator decoration;
  final double height;
  final double topRightRadius;
  final double topLeftRadius;
  final double bottomRightRadius;
  final double bottomLeftRadius;
  final Color color;
  final double horizontalPadding;
  final double strokeWidth;
  final PaintingStyle paintingStyle;
  final double width;
  final gradient = const LinearGradient(
    colors: [Color(0xff0C87F3), Color(0xff0961AE)],
    begin: Alignment.centerLeft,
    end: Alignment.centerRight,
  );
  final int length;

  CustomPainter(
    this.decoration,
    VoidCallback? onChanged, {
    required this.height,
    required this.topRightRadius,
    required this.topLeftRadius,
    required this.bottomRightRadius,
    required this.bottomLeftRadius,
    required this.color,
    required this.horizontalPadding,
    required this.paintingStyle,
    required this.strokeWidth,
    required this.width,
    required this.length,
  }) : super(onChanged);

  double yfun = 0;
  double yfun2 = 0;
  int index2 = 0;
  Path path = Path();

  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    Size mysize = Size(configuration.size!.width, configuration.size!.height - 16);
    Size mysizeReal = Size(configuration.size!.width, configuration.size!.height);
    radius = Size(configuration.size!.width, (configuration.size!.height - 16));

    // Offset myoffset = Offset(offset.dx, offset.dy);

    // final Rect rect = myoffset & mysize;
    final Paint paint = Paint()
      ..strokeCap = StrokeCap.round
      ..color = color
      ..style = paintingStyle
      ..strokeWidth = strokeWidth;

    int curIndex = (offset.dy / mysizeReal.height).round();
    if (axis == Axis.horizontal) {
      for (int i = 0; i < length; i++) {
        Rect rect = Rect.fromCenter(center: Offset(0, 0 + i * configuration.size!.width), width: radius.width, height: radius.height);
        RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(10));
        canvas.drawRRect(rrect, paint..color = const Color(0xff38424B));
      }
      curIndex = (offset.dx / mysizeReal.width).round();
      canvas.translate(mysize.width / 2, mysize.height / 2);
      // print('yfun index2:$index2 curIndex:$curIndex ${offset.dy} before:$before');
      if (yfun == 0) {
        yfun = mysizeReal.width;
      }
      if (curIndex != index2) {
        yfun2 = (curIndex) * mysizeReal.width;
        yfun = (curIndex - index2).abs() * mysizeReal.width;
        index2 = curIndex;
      }
      _canvasInitRectangle(
        canvas,
        offset.dx,
        yfun,
        paint: paint..color = Colors.green,
        path: path,
      );
    } else {
      canvas.translate(mysize.width / 2, mysize.height / 2);
      for (int i = 0; i < length; i++) {
        Rect rect = Rect.fromCenter(center: Offset(0, 0 + i * configuration.size!.height), width: radius.width, height: radius.height);
        RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(10));
        canvas.drawRRect(rrect, paint..color = const Color(0xff38424B));
      }
      curIndex = (offset.dy / mysizeReal.height).round();
      print('${offset.dy}');
      if (yfun == 0) {
        yfun = mysizeReal.height;
        before = (curIndex) * mysizeReal.height;
      }
      // if (!_isUsePosition(offset.dy, mysizeReal)) return;
      if (curIndex != index2) {
        yfun2 = (curIndex) * mysizeReal.height;
        yfun = (curIndex - index2).abs() * mysizeReal.height;
        index2 = curIndex;
      }
      _canvasInitRectangle(
        canvas,
        offset.dy,
        yfun,
        paint: paint..color = Colors.green,
        path: path,
      );
    }

    // print('=====y:${offset.dy} ${(offset.dy - yfun2).abs() / yfun} $yfun $yfun2');
    // if (flag) yfun = 0;
    // if (index2 != index.value) index2 = index.value;
    // path = Path()
    //   ..moveTo(0, y)
    //   ..relativeLineTo(mysize.width, 0)
    //   ..relativeLineTo(0, mysize.height)
    //   ..relativeLineTo(-mysize.width + yfun, 0)
    //   ..close();
    // canvas.drawPath(path, paint..color = color);
    // canvas.translate(0, mysize.height);
    // _canvasInit(
    //   canvas,
    //   offset.dy,
    //   yfun,
    //   paint: paint..color = Colors.green,
    //   path: path,
    // );
  }

  late double changeValue = radius.width / 2; //变化值
  Size radius = Size(60, 20);
  Size get ctrlRadius => Size(radius.width * mX, radius.height * mY); //控制点radius
  // final double mX = 0.551915024494; // 圆形
  final double mY = 1; // 圆形
  final double mX = 1; // 矩形
  // final double mY = 1.1; // 矩形
  Axis axis = Axis.vertical;
  double before = 0;
  bool isLeft(double offsetMove) {
    bool flag = true;
    if (before > offsetMove) flag = false;
    before = offsetMove;
    return flag;
  }

  Future<void> _canvasInitRectangle(
    Canvas canvas,
    double offsetMove,
    double offsetEnd, {
    required Path path,
    required Paint paint,
  }) async {
    double percent = 0;
    double cutOffsetLeft = 0; // 左 下
    double cutOffsetRight = 0;
    percent = (offsetMove - yfun2).abs() / yfun;
    if (axis == Axis.horizontal) {}
    if (!isLeft(offsetMove)) {
      if (percent > 0 && percent <= 0.5) {
        cutOffsetLeft = changeValue * percent;
      } else if (percent > 0.5 && percent < 1.0) {
        ///坐标恢复,原本的位置 + 位移距离✖系数,系数为: 0.5 ~ 0
        cutOffsetLeft = changeValue * (1 - percent);
      }
      radius = Size(radius.width - cutOffsetLeft, radius.height);
    } else {
      if (percent > 0 && percent <= 0.5) {
        cutOffsetRight = changeValue * percent;
      } else if (percent > 0.5 && percent < 1.0) {
        ///坐标恢复,原本的位置 + 位移距离✖系数,系数为: 0.5 ~ 0
        cutOffsetRight = changeValue * (1 - percent);
      }
      radius = Size(radius.width - cutOffsetRight, radius.height);
    }
    cutOffsetLeft = 0; // 左 下
    cutOffsetRight = 0;
    // print('percent ${isLeft(offsetMove)} $percent cutOffsetRight:$cutOffsetRight cutOffsetLeft:$cutOffsetLeft }');
    if (axis == Axis.vertical) {
      Rect rect = Rect.fromCenter(center: Offset(0, 0 + offsetMove), width: radius.width, height: radius.height);
      RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(10));
      canvas.drawRRect(rrect, paint);
    } else {
      Rect rect = Rect.fromCenter(center: Offset(0 + offsetMove, 0), width: radius.width, height: radius.height);
      RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(10));
      canvas.drawRRect(rrect, paint);
    }
  }

  Future<void> _canvasInit(
    Canvas canvas,
    double offsetMove,
    double offsetEnd, {
    required Path path,
    required Paint paint,
  }) async {
    double percent = 0;
    double cutOffsetLeft = 0; // 左 下
    double cutOffsetRight = 0;
    percent = (offsetMove - yfun2).abs() / yfun;
    if (axis == Axis.horizontal) {}
    if (!isLeft(offsetMove)) {
      if (percent > 0 && percent <= 0.5) {
        cutOffsetLeft = changeValue * percent;
      } else if (percent > 0.5 && percent < 1.0) {
        ///坐标恢复,原本的位置 + 位移距离✖系数,系数为: 0.5 ~ 0
        cutOffsetLeft = changeValue * (1 - percent);
      }
      radius = Size(radius.width - cutOffsetLeft, radius.height);
    } else {
      if (percent > 0 && percent <= 0.5) {
        cutOffsetRight = changeValue * percent;
      } else if (percent > 0.5 && percent < 1.0) {
        ///坐标恢复,原本的位置 + 位移距离✖系数,系数为: 0.5 ~ 0
        cutOffsetRight = changeValue * (1 - percent);
      }
      radius = Size(radius.width - cutOffsetRight, radius.height);
    }
    cutOffsetLeft = 0; // 左 下
    cutOffsetRight = 0;
    // print('percent ${isLeft(offsetMove)} $percent cutOffsetRight:$cutOffsetRight cutOffsetLeft:$cutOffsetLeft }');
    if (axis == Axis.vertical) {
      Offset topPoint = Offset(0, -radius.height + offsetMove - cutOffsetRight);
      Offset topPointEnd = Offset(radius.width, -radius.height - cutOffsetRight);
      Offset topPointLeft = Offset(radius.width - ctrlRadius.width, -radius.height);
      Offset topPointRight = Offset(ctrlRadius.width, 0 + cutOffsetRight);

      Offset rPoint = Offset(radius.width, radius.height + cutOffsetRight);
      Offset rPointTop = Offset(radius.width, radius.height - ctrlRadius.height + cutOffsetRight);
      Offset rPointBottom = Offset(0, ctrlRadius.height);

      Offset bottomPoint = Offset(-radius.width, radius.height + cutOffsetLeft);
      Offset bottomPointLeft = Offset(-ctrlRadius.width, 0 - cutOffsetLeft);
      Offset bottomPointRight = Offset(-(radius.width - ctrlRadius.width), radius.height);

      Offset lPoint = Offset(-radius.width, -radius.height - cutOffsetLeft);
      Offset lPointTop = Offset(0, -ctrlRadius.height);
      Offset lPointBottom = Offset(-radius.width, -(radius.height - ctrlRadius.height) - cutOffsetLeft);

      path.reset();
      path.moveTo(topPoint.dx, topPoint.dy);
      path.relativeCubicTo(topPointRight.dx, topPointRight.dy, rPointTop.dx, rPointTop.dy, rPoint.dx, rPoint.dy);
      path.relativeCubicTo(rPointBottom.dx, rPointBottom.dy, bottomPointRight.dx, bottomPointRight.dy, bottomPoint.dx, bottomPoint.dy);
      path.relativeCubicTo(bottomPointLeft.dx, bottomPointLeft.dy, lPointBottom.dx, lPointBottom.dy, lPoint.dx, lPoint.dy);
      path.relativeCubicTo(lPointTop.dx, lPointTop.dy, topPointLeft.dx, topPointLeft.dy, topPointEnd.dx, topPointEnd.dy);
      canvas.drawPath(path, paint);
    } else {
      Offset topPoint = Offset(0 + offsetMove, -radius.height);
      Offset topPointEnd = Offset(radius.width + cutOffsetRight, -radius.height);
      Offset topPointLeft = Offset(radius.width - ctrlRadius.width, -radius.height);
      Offset topPointRight = Offset(ctrlRadius.width, 0);

      Offset rPoint = Offset(radius.width + cutOffsetLeft, radius.height);
      Offset rPointTop = Offset(radius.width + cutOffsetLeft, radius.height - ctrlRadius.height);
      Offset rPointBottom = Offset(0, ctrlRadius.height);

      Offset bottomPoint = Offset(-radius.width - cutOffsetLeft, radius.height);
      Offset bottomPointLeft = Offset(-ctrlRadius.width, 0);
      Offset bottomPointRight = Offset(-(radius.width - ctrlRadius.width) - cutOffsetLeft, radius.height);

      Offset lPoint = Offset(-radius.width - cutOffsetRight, -radius.height);
      Offset lPointTop = Offset(0, -ctrlRadius.height);
      Offset lPointBottom = Offset(-radius.width - cutOffsetRight, -(radius.height - ctrlRadius.height));

      path.reset();
      path.moveTo(topPoint.dx, topPoint.dy);
      path.relativeCubicTo(topPointRight.dx, topPointRight.dy, rPointTop.dx, rPointTop.dy, rPoint.dx, rPoint.dy);
      path.relativeCubicTo(rPointBottom.dx, rPointBottom.dy, bottomPointRight.dx, bottomPointRight.dy, bottomPoint.dx, bottomPoint.dy);
      path.relativeCubicTo(bottomPointLeft.dx, bottomPointLeft.dy, lPointBottom.dx, lPointBottom.dy, lPoint.dx, lPoint.dy);
      path.relativeCubicTo(lPointTop.dx, lPointTop.dy, topPointLeft.dx, topPointLeft.dy, topPointEnd.dx, topPointEnd.dy);
      canvas.drawPath(path, paint);
    }
  }

  /// 是否是有效的移动点
  bool _isUsePosition(double dy, Size mysizeReal) {
    bool flag = true;
    // print('$before $dy ${(before - dy).abs()} ${mysizeReal.height}');
    if ((before - dy).abs() > mysizeReal.height / 4) flag = false;
    before = dy;
    return flag;
  }
}
xietian0908 commented 1 year ago

这个问题只有在快速滑动tabview的时候,在移动动画未结束之前点击,tabbar才会发生,这个没有报错信息只有中间的值变化突然有问题了offset.dy image

zmtzawqlp commented 1 year ago

你可以自己debug下。出错的时候的堆栈信息,看看问题出在哪里

xietian0908 commented 1 year ago

a36d58a4c2332341c2d736a1d80e8e2 controller.indexIsChanging为true的情况下就会出错

zmtzawqlp commented 1 year ago

这个属性表示正在动画,主要是你写的 TabbarVSquareIndicator 逻辑过于复杂。你用自带的 ColorTabIndicator 有这个问题吗?

xietian0908 commented 1 year ago
class ExtendedTabsStackoverflow extends StatefulWidget {
  const ExtendedTabsStackoverflow({super.key});

  @override
  State<ExtendedTabsStackoverflow> createState() => _ExtendedTabsStackoverflowState();
}

class _ExtendedTabsStackoverflowState extends State<ExtendedTabsStackoverflow> {
  int length = 16;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: DefaultTabController(
        length: length,
        child: Row(
          children: [
            ExtendedTabBar(
              // labelPadding: EdgeInsets.symmetric(horizontal: 50.w),
              indicatorPadding: const EdgeInsets.all(0),
              labelPadding: const EdgeInsets.only(bottom: 0),
              isScrollable: true,
              indicatorWeight: 0,
              scrollDirection: Axis.vertical,
              tabs: _tabs(),
              indicator: TabbarVSquareIndicator(
                length: length,
              ),
            ),
            Expanded(
              child: ExtendedTabBarView(
                scrollDirection: Axis.vertical,
                children: [
                  for (int i = 0; i < length; i++)
                    Container(
                      color: Colors.primaries[i],
                      alignment: Alignment.center,
                      child: Text('tabview $i'),
                    )
                ],
              ),
            )
          ],
        ),
      ),
    );
  }

  List<Widget> _tabs() {
    return List.generate(
      length,
      (index) => Container(
        height: 100,
        margin: const EdgeInsets.only(bottom: 16),
        alignment: Alignment.center,
        child: Text('tab $index'),
      ),
    );
  }
}

class TabbarVSquareIndicator extends Decoration {
  final Color color;
  final PaintingStyle paintingStyle;
  final int length;

  const TabbarVSquareIndicator({
    this.length = 0,
    this.color = Colors.red,
    this.paintingStyle = PaintingStyle.fill,
  });

  @override
  CustomPainter createBoxPainter([VoidCallback? onChanged]) {
    return CustomPainter(
      this,
      onChanged,
      paintingStyle: paintingStyle,
      color: color,
      length: length,
    );
  }
}

class CustomPainter extends BoxPainter {
  final TabbarVSquareIndicator decoration;
  final Color color;
  final PaintingStyle paintingStyle;
  final int length;

  CustomPainter(
    this.decoration,
    VoidCallback? onChanged, {
    required this.color,
    required this.paintingStyle,
    required this.length,
  }) : super(onChanged);

  Path path = Path();
  Size radius = const Size(60, 20);

  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    Size mysize = Size(configuration.size!.width, configuration.size!.height - 16);
    radius = Size(configuration.size!.width, (configuration.size!.height - 16));
    // Offset myoffset = Offset(offset.dx, offset.dy);
    // final Rect rect = myoffset & mysize;
    final Paint paint = Paint()
      ..strokeCap = StrokeCap.round
      ..color = color
      ..style = paintingStyle
      ..strokeWidth = 4;

    canvas.translate(mysize.width / 2, mysize.height / 2);
    for (int i = 0; i < length; i++) {
      Rect rect = Rect.fromCenter(center: Offset(0, 0 + i * configuration.size!.height), width: radius.width, height: radius.height);
      RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(10));
      canvas.drawRRect(rrect, paint..color = const Color(0xff38424B));
    }
    print('offset ==> ${offset.dy}');
    Rect rect = Rect.fromCenter(center: Offset(0, 0 + offset.dy), width: radius.width, height: radius.height);
    RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(10));
    canvas.drawRRect(rrect, paint..color = color);
  }
}

抱歉 我这里弄了个简单的demo 也是会出现视频中item突然到第一个再回来的情况

xietian0908 commented 1 year ago

https://github.com/fluttercandies/extended_tabs/assets/64937500/71d52bab-a9bb-4ebe-a4bf-78d353332147 我用demo for web 里面的例子也复现了,这个问题在水平方向也同样存在;多tab情况下更明显

zmtzawqlp commented 1 year ago

web 的代码比较老了。用官方的 tab 会有这种问题吗?

xietian0908 commented 1 year ago
import 'package:flutter/material.dart';
import 'package:extended_tabs/extended_tabs.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const ExtendedTabsStackoverflow(),
    );
  }
}

class ExtendedTabsStackoverflow extends StatefulWidget {
  const ExtendedTabsStackoverflow({super.key});

  @override
  State<ExtendedTabsStackoverflow> createState() => _ExtendedTabsStackoverflowState();
}

class _ExtendedTabsStackoverflowState extends State<ExtendedTabsStackoverflow> {
  int length = 16;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: DefaultTabController(
        length: length,
        child: Column(
          children: [
            ExtendedTabBar(
              // labelPadding: EdgeInsets.symmetric(horizontal: 50.w),
              indicatorPadding: const EdgeInsets.all(0),
              labelPadding: const EdgeInsets.only(bottom: 0),
              isScrollable: true,
              indicatorWeight: 0,
              // scrollDirection: Axis.vertical,
              tabs: _tabs(),
              indicator: TabbarVSquareIndicator(
                length: length,
              ),
            ),
            Expanded(
              child: ExtendedTabBarView(
                // scrollDirection: Axis.vertical,
                children: [
                  for (int i = 0; i < length; i++)
                    Container(
                      color: Colors.primaries[i],
                      alignment: Alignment.center,
                      child: Text('tabview $i'),
                    )
                ],
              ),
            )
          ],
        ),
      ),
    );
  }

  List<Widget> _tabs() {
    return List.generate(
      length,
      (index) => Container(
        height: 100,
        width: 100,
        margin: const EdgeInsets.only(bottom: 16),
        alignment: Alignment.center,
        child: Text('tab $index'),
      ),
    );
  }
}

class TabbarVSquareIndicator extends Decoration {
  final Color color;
  final PaintingStyle paintingStyle;
  final int length;

  const TabbarVSquareIndicator({
    this.length = 0,
    this.color = Colors.red,
    this.paintingStyle = PaintingStyle.fill,
  });

  @override
  CustomPainter createBoxPainter([VoidCallback? onChanged]) {
    return CustomPainter(
      this,
      onChanged,
      paintingStyle: paintingStyle,
      color: color,
      length: length,
    );
  }
}

class CustomPainter extends BoxPainter {
  final TabbarVSquareIndicator decoration;
  final Color color;
  final PaintingStyle paintingStyle;
  final int length;

  CustomPainter(
    this.decoration,
    VoidCallback? onChanged, {
    required this.color,
    required this.paintingStyle,
    required this.length,
  }) : super(onChanged);

  Path path = Path();
  Size radius = const Size(60, 20);

  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    Size mysize = Size(configuration.size!.width, configuration.size!.height - 16);
    radius = Size(configuration.size!.width, (configuration.size!.height - 16));
    // Offset myoffset = Offset(offset.dx, offset.dy);
    // final Rect rect = myoffset & mysize;
    final Paint paint = Paint()
      ..strokeCap = StrokeCap.round
      ..color = color
      ..style = paintingStyle
      ..strokeWidth = 4;

    canvas.translate(mysize.width / 2, mysize.height / 2);
    for (int i = 0; i < length; i++) {
      Rect rect = Rect.fromCenter(center: Offset(0 + i * configuration.size!.width, 0), width: radius.width, height: radius.height);
      RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(10));
      canvas.drawRRect(rrect, paint..color = const Color(0xff38424B));
    }
    print('offset ==> ${offset.dy}');
    Rect rect = Rect.fromCenter(center: Offset(0 + offset.dx, 0), width: radius.width, height: radius.height);
    RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(10));
    canvas.drawRRect(rrect, paint..color = color);
  }
}
官方只有水平的 我测试了extendedtabbar extendedtabview 、和 tabbar tabview 例子中换一下 官方的是没有问题的
zmtzawqlp commented 1 year ago

你不是说水平也有问题吗? 水平跟官方的代码一模一样的

xietian0908 commented 1 year ago

tabbar tabview https://github.com/fluttercandies/extended_tabs/assets/64937500/bd9dc7f4-4862-4c53-84b7-86245865ffbd

extended_tabs https://github.com/fluttercandies/extended_tabs/assets/64937500/22d1d935-dee8-4cf4-9147-7431d7b83f86

xietian0908 commented 1 year ago

6a1964c00278d800860263ec2751cf9 这是水平的

zmtzawqlp commented 1 year ago

用你的例子在真机上面,没有重现。水平和垂直都没有重现

xietian0908 commented 1 year ago

我就是用真机android平板测的, extended_tabs: ^4.0.2;然后那个异常好像必须得是连续移动两格移动未结束的时候点击tabbar会出现;你可以看那个视频都是同一套代码只是换了个widget