mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Bumps the Dart version to 3.8 across the repo (excluding engine/src/flutter/third_party) and applies formatting updates from Dart 3.8. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
284 lines
7.5 KiB
Dart
284 lines
7.5 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:math' as math;
|
|
import 'dart:ui';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
// This returns render paragraph of the Tab label text.
|
|
RenderParagraph getTabText(WidgetTester tester, String text) {
|
|
return tester.renderObject<RenderParagraph>(
|
|
find.descendant(
|
|
of: find.byWidgetPredicate((Widget widget) => widget.runtimeType.toString() == '_TabStyle'),
|
|
matching: find.text(text),
|
|
),
|
|
);
|
|
}
|
|
|
|
// This creates and returns a TabController.
|
|
TabController createTabController({
|
|
required int length,
|
|
required TickerProvider vsync,
|
|
int initialIndex = 0,
|
|
Duration? animationDuration,
|
|
}) {
|
|
final TabController result = TabController(
|
|
length: length,
|
|
vsync: vsync,
|
|
initialIndex: initialIndex,
|
|
animationDuration: animationDuration,
|
|
);
|
|
addTearDown(result.dispose);
|
|
return result;
|
|
}
|
|
|
|
// This widget is used to test widget state in the tabs_test.dart file.
|
|
class TabStateMarker extends StatefulWidget {
|
|
const TabStateMarker({super.key, this.child});
|
|
|
|
final Widget? child;
|
|
|
|
@override
|
|
TabStateMarkerState createState() => TabStateMarkerState();
|
|
}
|
|
|
|
class TabStateMarkerState extends State<TabStateMarker> {
|
|
String? marker;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return widget.child ?? Container();
|
|
}
|
|
}
|
|
|
|
// Tab controller builder for TabControllerFrame widget.
|
|
typedef TabControllerFrameBuilder = Widget Function(BuildContext context, TabController controller);
|
|
|
|
// This widget creates a TabController and passes it to the builder.
|
|
class TabControllerFrame extends StatefulWidget {
|
|
const TabControllerFrame({
|
|
super.key,
|
|
required this.length,
|
|
this.initialIndex = 0,
|
|
required this.builder,
|
|
});
|
|
|
|
final int length;
|
|
final int initialIndex;
|
|
final TabControllerFrameBuilder builder;
|
|
|
|
@override
|
|
TabControllerFrameState createState() => TabControllerFrameState();
|
|
}
|
|
|
|
class TabControllerFrameState extends State<TabControllerFrame>
|
|
with SingleTickerProviderStateMixin {
|
|
late TabController _controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = TabController(
|
|
vsync: this,
|
|
length: widget.length,
|
|
initialIndex: widget.initialIndex,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return widget.builder(context, _controller);
|
|
}
|
|
}
|
|
|
|
// Test utility class to test tab indicator drawing.
|
|
class TabIndicatorRecordingCanvas extends TestRecordingCanvas {
|
|
TabIndicatorRecordingCanvas(this.indicatorColor);
|
|
|
|
final Color indicatorColor;
|
|
late Rect indicatorRect;
|
|
|
|
@override
|
|
void drawLine(Offset p1, Offset p2, Paint paint) {
|
|
// Assuming that the indicatorWeight is 2.0, the default.
|
|
const double indicatorWeight = 2.0;
|
|
if (paint.color == indicatorColor) {
|
|
indicatorRect = Rect.fromPoints(p1, p2).inflate(indicatorWeight / 2.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This creates a Fake implementation of ScrollMetrics.
|
|
class TabMockScrollMetrics extends Fake implements ScrollMetrics {}
|
|
|
|
class TabBarTestScrollPhysics extends ScrollPhysics {
|
|
const TabBarTestScrollPhysics({super.parent});
|
|
|
|
@override
|
|
TabBarTestScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
|
return TabBarTestScrollPhysics(parent: buildParent(ancestor));
|
|
}
|
|
|
|
@override
|
|
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
|
|
return offset == 10 ? 20 : offset;
|
|
}
|
|
|
|
static final SpringDescription _kDefaultSpring = SpringDescription.withDampingRatio(
|
|
mass: 0.5,
|
|
stiffness: 500.0,
|
|
ratio: 1.1,
|
|
);
|
|
|
|
@override
|
|
SpringDescription get spring => _kDefaultSpring;
|
|
}
|
|
|
|
// This widget is used to log the lifecycle of the TabBarView children.
|
|
class TabBody extends StatefulWidget {
|
|
const TabBody({super.key, required this.index, required this.log, this.marker = ''});
|
|
|
|
final int index;
|
|
final List<String> log;
|
|
final String marker;
|
|
|
|
@override
|
|
State<TabBody> createState() => TabBodyState();
|
|
}
|
|
|
|
class TabBodyState extends State<TabBody> {
|
|
@override
|
|
void initState() {
|
|
widget.log.add('init: ${widget.index}');
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(TabBody oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
// To keep the logging straight, widgets must not change their index.
|
|
assert(oldWidget.index == widget.index);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
widget.log.add('dispose: ${widget.index}');
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Center(
|
|
child: widget.marker.isEmpty
|
|
? Text('${widget.index}')
|
|
: Text('${widget.index}-${widget.marker}'),
|
|
);
|
|
}
|
|
}
|
|
|
|
// This widget is used to test the lifecycle of the TabBarView children with Ink widget.
|
|
class TabKeepAliveInk extends StatefulWidget {
|
|
const TabKeepAliveInk({super.key, required this.title});
|
|
|
|
final String title;
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _TabKeepAliveInkState();
|
|
}
|
|
|
|
class _TabKeepAliveInkState extends State<TabKeepAliveInk> with AutomaticKeepAliveClientMixin {
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
return Ink(child: Text(widget.title));
|
|
}
|
|
}
|
|
|
|
// This widget is used to test the lifecycle of the TabBarView children.
|
|
class TabAlwaysKeepAliveWidget extends StatefulWidget {
|
|
const TabAlwaysKeepAliveWidget({super.key});
|
|
|
|
static String text = 'AlwaysKeepAlive';
|
|
|
|
@override
|
|
State<TabAlwaysKeepAliveWidget> createState() => _TabAlwaysKeepAliveWidgetState();
|
|
}
|
|
|
|
class _TabAlwaysKeepAliveWidgetState extends State<TabAlwaysKeepAliveWidget>
|
|
with AutomaticKeepAliveClientMixin {
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
return Text(TabAlwaysKeepAliveWidget.text);
|
|
}
|
|
}
|
|
|
|
// This decoration is used to test the indicator decoration image configuration.
|
|
class TestIndicatorDecoration extends Decoration {
|
|
final List<TestIndicatorBoxPainter> painters = <TestIndicatorBoxPainter>[];
|
|
|
|
@override
|
|
BoxPainter createBoxPainter([VoidCallback? onChanged]) {
|
|
final TestIndicatorBoxPainter painter = TestIndicatorBoxPainter();
|
|
painters.add(painter);
|
|
return painter;
|
|
}
|
|
}
|
|
|
|
class TestIndicatorBoxPainter extends BoxPainter {
|
|
ImageConfiguration? lastConfiguration;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
|
|
lastConfiguration = configuration;
|
|
}
|
|
}
|
|
|
|
// Ease out sine (decelerating).
|
|
double _decelerateInterpolation(double fraction) {
|
|
return math.sin((fraction * math.pi) / 2.0);
|
|
}
|
|
|
|
// Ease in sine (accelerating).
|
|
double _accelerateInterpolation(double fraction) {
|
|
return 1.0 - math.cos((fraction * math.pi) / 2.0);
|
|
}
|
|
|
|
// Returns Tab indicator RRect with elastic animation.
|
|
RRect tabIndicatorRRectElasticAnimation(
|
|
RenderBox tabBarBox,
|
|
Rect currentRect,
|
|
Rect fromRect,
|
|
Rect toRect,
|
|
double progress,
|
|
) {
|
|
const double indicatorWeight = 3.0;
|
|
final double leftFraction = _accelerateInterpolation(progress);
|
|
final double rightFraction = _decelerateInterpolation(progress);
|
|
|
|
return RRect.fromLTRBAndCorners(
|
|
lerpDouble(fromRect.left, toRect.left, leftFraction)!,
|
|
tabBarBox.size.height - indicatorWeight,
|
|
lerpDouble(fromRect.right, toRect.right, rightFraction)!,
|
|
tabBarBox.size.height,
|
|
topLeft: const Radius.circular(indicatorWeight),
|
|
topRight: const Radius.circular(indicatorWeight),
|
|
);
|
|
}
|