-
-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ExtendedTabBar 在scrollDirection: Axis.vertical情况下indicator中 paint(Canvas canvas, Offset offset..) offset值异常 #28
Comments
tabbar_v_square_indicator.zip |
只有在快速滑动extendtabview 的时候点击extendtabbar才会出现,等待移动动画完成之后点击是没有问题的 |
|
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;
}
}
|
你可以自己debug下。出错的时候的堆栈信息,看看问题出在哪里 |
这个属性表示正在动画,主要是你写的 TabbarVSquareIndicator 逻辑过于复杂。你用自带的 ColorTabIndicator 有这个问题吗? |
抱歉 我这里弄了个简单的demo 也是会出现视频中item突然到第一个再回来的情况 |
https://github.com/fluttercandies/extended_tabs/assets/64937500/71d52bab-a9bb-4ebe-a4bf-78d353332147 |
web 的代码比较老了。用官方的 tab 会有这种问题吗? |
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 例子中换一下 官方的是没有问题的 |
你不是说水平也有问题吗? 水平跟官方的代码一模一样的 |
用你的例子在真机上面,没有重现。水平和垂直都没有重现 |
我就是用真机android平板测的, extended_tabs: ^4.0.2;然后那个异常好像必须得是连续移动两格移动未结束的时候点击tabbar会出现;你可以看那个视频都是同一套代码只是换了个widget |
Version
extended_tabs: ^4.0.2
Platforms
Android
Device Model
galaxy tab A7 (Android 11)
flutter info
How to reproduce?
30a5c109948d15a1788f9b3197b8a292.mp4
Logs
No response
Example code (optional)
No response
Contact
No response
The text was updated successfully, but these errors were encountered: