mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Re-implementation of PR #172441 <!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> ## Add non-uniform border radius support to TableBorder This PR extends TableBorder to support border radius even when the outer border sides have different widths but the same color, similar to the non-uniform border support added to Border in #121921. ### Changes - Enhanced border rendering logic: TableBorder can now apply border radius when outer border sides have uniform colors but non-uniform widths/styles - New helper methods: Added `outerBorderIsUniform` property and `distinctVisibleOuterColors()` method to determine border uniformity - Optimized paint method: Refactored the paint logic to handle three scenarios: 1. Fully uniform borders (existing optimized path) 2. Outer borders with uniform colors but non-uniform widths (new non-uniform border radius support) 3. Completely non-uniform borders (fallback to standard border painting) ### Before/After - Before: TableBorder with border radius required all sides (including inner borders) to be completely identical. - After: TableBorder with border radius only requires outer border colors to be the same, allowing different widths per side. ### Example Usage ```dart TableBorder( top: BorderSide(color: Colors.blue, width: 3.0), right: BorderSide(color: Colors.blue, width: 1.0), // Different width bottom: BorderSide(color: Colors.blue, width: 2.0), // Different width left: BorderSide.none, // No border horizontalInside: BorderSide(color: Colors.red, width: 1.0), // Different color OK verticalInside: BorderSide(color: Colors.red, width: 1.0), // Different color OK borderRadius: BorderRadius.circular(8), // ✅ Now works! ) ``` Fixes: #173193 ## 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]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] 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. <!-- 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
199 lines
7.9 KiB
Dart
199 lines
7.9 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 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
test('TableBorder constructor', () {
|
|
const TableBorder border1 = TableBorder(
|
|
left: BorderSide(),
|
|
right: BorderSide(color: Color(0xFF00FF00)),
|
|
verticalInside: BorderSide(),
|
|
);
|
|
expect(border1.top, BorderSide.none);
|
|
expect(border1.right, const BorderSide(color: Color(0xFF00FF00)));
|
|
expect(border1.bottom, BorderSide.none);
|
|
expect(border1.left, const BorderSide());
|
|
expect(border1.horizontalInside, BorderSide.none);
|
|
expect(border1.verticalInside, const BorderSide());
|
|
expect(border1.dimensions, const EdgeInsets.symmetric(horizontal: 1.0));
|
|
expect(border1.isUniform, isFalse);
|
|
expect(
|
|
border1.scale(2.0),
|
|
const TableBorder(
|
|
left: BorderSide(width: 2.0),
|
|
right: BorderSide(width: 2.0, color: Color(0xFF00FF00)),
|
|
verticalInside: BorderSide(width: 2.0),
|
|
),
|
|
);
|
|
});
|
|
|
|
test('TableBorder.all constructor', () {
|
|
final TableBorder border2 = TableBorder.all(width: 2.0, color: const Color(0xFF00FFFF));
|
|
expect(border2.top, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
|
|
expect(border2.right, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
|
|
expect(border2.bottom, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
|
|
expect(border2.left, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
|
|
expect(border2.horizontalInside, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
|
|
expect(border2.verticalInside, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
|
|
expect(border2.dimensions, const EdgeInsets.symmetric(horizontal: 2.0, vertical: 2.0));
|
|
expect(border2.isUniform, isTrue);
|
|
expect(border2.scale(0.5), TableBorder.all(color: const Color(0xFF00FFFF)));
|
|
});
|
|
|
|
test('TableBorder.symmetric constructor', () {
|
|
const TableBorder border3 = TableBorder.symmetric(
|
|
inside: BorderSide(width: 3.0),
|
|
outside: BorderSide(color: Color(0xFFFF0000)),
|
|
);
|
|
expect(border3.top, const BorderSide(color: Color(0xFFFF0000)));
|
|
expect(border3.right, const BorderSide(color: Color(0xFFFF0000)));
|
|
expect(border3.bottom, const BorderSide(color: Color(0xFFFF0000)));
|
|
expect(border3.left, const BorderSide(color: Color(0xFFFF0000)));
|
|
expect(border3.horizontalInside, const BorderSide(width: 3.0));
|
|
expect(border3.verticalInside, const BorderSide(width: 3.0));
|
|
expect(border3.dimensions, const EdgeInsets.symmetric(horizontal: 1.0, vertical: 1.0));
|
|
expect(border3.isUniform, isFalse);
|
|
expect(
|
|
border3.scale(0.0),
|
|
const TableBorder.symmetric(
|
|
outside: BorderSide(width: 0.0, color: Color(0xFFFF0000), style: BorderStyle.none),
|
|
),
|
|
);
|
|
});
|
|
|
|
test('TableBorder.lerp', () {
|
|
const BorderSide side1 = BorderSide(color: Color(0x00000001));
|
|
const BorderSide side2 = BorderSide(width: 2.0, color: Color(0x00000002));
|
|
const BorderSide side3 = BorderSide(width: 3.0, color: Color(0x00000003));
|
|
const BorderSide side4 = BorderSide(width: 4.0, color: Color(0x00000004));
|
|
const BorderSide side5 = BorderSide(width: 5.0, color: Color(0x00000005));
|
|
const BorderSide side6 = BorderSide(width: 6.0, color: Color(0x00000006));
|
|
const TableBorder tableA = TableBorder(
|
|
top: side1,
|
|
right: side2,
|
|
bottom: side3,
|
|
left: side4,
|
|
horizontalInside: side5,
|
|
verticalInside: side6,
|
|
);
|
|
expect(tableA.isUniform, isFalse);
|
|
expect(tableA.dimensions, const EdgeInsets.fromLTRB(4.0, 1.0, 2.0, 3.0));
|
|
final TableBorder tableB = TableBorder(
|
|
top: side1.scale(2.0),
|
|
right: side2.scale(2.0),
|
|
bottom: side3.scale(2.0),
|
|
left: side4.scale(2.0),
|
|
horizontalInside: side5.scale(2.0),
|
|
verticalInside: side6.scale(2.0),
|
|
);
|
|
expect(tableB.isUniform, isFalse);
|
|
expect(tableB.dimensions, const EdgeInsets.fromLTRB(4.0, 1.0, 2.0, 3.0) * 2.0);
|
|
final TableBorder tableC = TableBorder(
|
|
top: side1.scale(3.0),
|
|
right: side2.scale(3.0),
|
|
bottom: side3.scale(3.0),
|
|
left: side4.scale(3.0),
|
|
horizontalInside: side5.scale(3.0),
|
|
verticalInside: side6.scale(3.0),
|
|
);
|
|
expect(tableC.isUniform, isFalse);
|
|
expect(tableC.dimensions, const EdgeInsets.fromLTRB(4.0, 1.0, 2.0, 3.0) * 3.0);
|
|
expect(TableBorder.lerp(tableA, tableC, 0.5), tableB);
|
|
expect(TableBorder.lerp(tableA, tableB, 2.0), tableC);
|
|
expect(TableBorder.lerp(tableB, tableC, -1.0), tableA);
|
|
expect(TableBorder.lerp(tableA, tableC, 0.9195)!.isUniform, isFalse);
|
|
expect(
|
|
TableBorder.lerp(tableA, tableC, 0.9195)!.dimensions,
|
|
EdgeInsets.lerp(tableA.dimensions, tableC.dimensions, 0.9195),
|
|
);
|
|
});
|
|
|
|
test('TableBorder.lerp identical a,b', () {
|
|
expect(TableBorder.lerp(null, null, 0), null);
|
|
const TableBorder border = TableBorder();
|
|
expect(identical(TableBorder.lerp(border, border, 0.5), border), true);
|
|
});
|
|
|
|
test('TableBorder.lerp with nulls', () {
|
|
final TableBorder table2 = TableBorder.all(width: 2.0);
|
|
final TableBorder table1 = TableBorder.all();
|
|
expect(TableBorder.lerp(table2, null, 0.5), table1);
|
|
expect(TableBorder.lerp(null, table2, 0.5), table1);
|
|
expect(TableBorder.lerp(null, null, 0.5), null);
|
|
});
|
|
|
|
test('TableBorder Object API', () {
|
|
expect(const TableBorder(), isNot(1.0));
|
|
expect(
|
|
const TableBorder().hashCode,
|
|
isNot(const TableBorder(top: BorderSide(width: 0.0)).hashCode),
|
|
);
|
|
});
|
|
|
|
test('TableBorder Object API', () {
|
|
final String none = BorderSide.none.toString();
|
|
final String zeroRadius = BorderRadius.zero.toString();
|
|
expect(
|
|
const TableBorder().toString(),
|
|
'TableBorder($none, $none, $none, $none, $none, $none, $zeroRadius)',
|
|
);
|
|
});
|
|
|
|
test('TableBorder.all with a borderRadius', () {
|
|
final TableBorder tableA = TableBorder.all(
|
|
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
|
|
);
|
|
expect(tableA.borderRadius, const BorderRadius.all(Radius.circular(8.0)));
|
|
});
|
|
|
|
test('TableBorder outer border uniformity', () {
|
|
const TableBorder uniformOuter = TableBorder(
|
|
top: BorderSide(width: 2.0),
|
|
right: BorderSide(width: 2.0),
|
|
bottom: BorderSide(width: 2.0),
|
|
left: BorderSide(width: 2.0),
|
|
horizontalInside: BorderSide(color: Color(0xFF0000FF)),
|
|
verticalInside: BorderSide(color: Color(0xFF0000FF)),
|
|
);
|
|
|
|
expect(uniformOuter.isUniform, isFalse);
|
|
|
|
final BorderSide topSide = uniformOuter.top;
|
|
expect(uniformOuter.right, equals(topSide));
|
|
expect(uniformOuter.bottom, equals(topSide));
|
|
expect(uniformOuter.left, equals(topSide));
|
|
|
|
const TableBorder nonUniformOuter = TableBorder(
|
|
top: BorderSide(width: 2.0),
|
|
right: BorderSide(color: Color(0xFF00FF00), width: 2.0),
|
|
bottom: BorderSide(width: 2.0),
|
|
left: BorderSide(width: 2.0),
|
|
);
|
|
|
|
expect(nonUniformOuter.right, isNot(equals(nonUniformOuter.top)));
|
|
});
|
|
|
|
test('TableBorder with non-uniform widths but uniform colors applies border radius', () {
|
|
const TableBorder borderWithRadius = TableBorder(
|
|
top: BorderSide(width: 3.0, color: Color(0xFF0000FF)),
|
|
right: BorderSide(color: Color(0xFF0000FF)),
|
|
bottom: BorderSide(width: 2.0, color: Color(0xFF0000FF)),
|
|
left: BorderSide(width: 1.5, color: Color(0xFF0000FF)),
|
|
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
|
);
|
|
|
|
expect(borderWithRadius.top.width, isNot(equals(borderWithRadius.bottom.width)));
|
|
expect(borderWithRadius.left.width, isNot(equals(borderWithRadius.right.width)));
|
|
|
|
final Color topColor = borderWithRadius.top.color;
|
|
expect(borderWithRadius.right.color, equals(topColor));
|
|
expect(borderWithRadius.bottom.color, equals(topColor));
|
|
expect(borderWithRadius.left.color, equals(topColor));
|
|
|
|
expect(borderWithRadius.borderRadius, const BorderRadius.all(Radius.circular(8.0)));
|
|
});
|
|
}
|