Мне нужна помощь в создании пользовательского интерфейса для моего приложения. Просматривая Dribbble, я наткнулся на дизайн, который, как мне кажется, будет легко и быстро воспроизвести. Однако я пытаюсь создать контейнер с уникальным borderRadius во Flutter.
Вот ссылка на дизайн:
Я планирую добавить черную панель приложений с ведущими элементами и действиями для двух значков, а затем создать контейнер в теле. Однако я не могу понять, как добиться нестандартного borderRadius в правом нижнем углу.
Можете ли вы помочь мне с этим? Спасибо заранее за вашу помощь.
Есть несколько способов добиться этого. Можно было бы использовать CustomClipper
, но я думаю, что тот же эффект можно получить, используя Stack
и виджеты для углов с простым радиусом границы.
https://dartpad.dev/?id=51c04550077960bdad72c0b3bf5063c9
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: TestPage(),
);
}
}
class TestPage extends StatelessWidget {
const TestPage({super.key});
@override
Widget build(BuildContext context) {
const borderRadius = 48.0;
return Scaffold(
backgroundColor: Colors.grey,
body: Column(
children: [
Stack(
clipBehavior: Clip.none,
children: [
Positioned(
bottom: -borderRadius,
left: 0,
child: Container(
height: borderRadius * 2,
width: borderRadius * 2,
color: Colors.white,
),
),
Positioned(
bottom: -borderRadius,
right: 0,
child: Container(
height: borderRadius * 2,
width: borderRadius * 2,
color: Colors.black,
),
),
Container(
height: 200,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(borderRadius),
),
),
],
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(borderRadius),
),
),
),
],
),
);
}
}
Я добился подобной функции при разработке «окна сообщений» в своем приложении для чата. Вот как я создал изгиб с одной стороны контейнера.
Container(
decoration: BoxDecoration(
color: Colors.black, // I put black here for you
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(1),
),
),
child: Column(
children: [
// Add your widgets here
],
),
),
Ваш ответ, вероятно, не работает, потому что, если вы посмотрите на дизайн, который я хочу, нижний правый угол имеет «отрицательный радиус».
Существует несколько подходов для достижения этой цели. Вот один из способов сделать это:
CustomClipper
— мощный инструмент во Flutter для создания фигур. Здесь, в другом файле, вы можете определить класс CustomClipper
, который расширяется CustomClipper<Path>
для создания изогнутой формы. Затем примените этот CustomClipper к AppBar, чтобы добиться желаемого изогнутого эффекта.
Панель приложений:
appBar: PreferredSize(
preferredSize: Size.fromHeight(150),
child: ClipPath(
clipper: CurveAppBar(),
child: AppBar(
backgroundColor: Colors.black,
leading: Icon(Icons.menu, color: Colors.white),
actions: [
Icon(Icons.person, color: Colors.white),
],
),
),
),
Файл CustomClipper:
import 'package:flutter/material.dart';
class CurveAppBar extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final Path path = Path();
path.moveTo(0, 0);
path.lineTo(0, size.height - 50);
path.quadraticBezierTo(
size.width / 5, size.height / 2.5, size.width / 2, size.height - 50);//Adjust Your curves here
path.quadraticBezierTo(
size.width * 3 / 4, size.height, size.width, size.height - 50);//Adjust your curves here
path.lineTo(size.width, 0);
path.close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
Не стесняйтесь настраивать параметры CustomClipper
, чтобы настроить кривые в соответствии с вашим конкретным дизайном.
В своем ответе я опубликовал, как наконец воспроизвел дизайн, посмотрите, думаю, новичку легче воспроизвести.
BoxBorder
BoxBorder
используется с Flutter Container
BoxDecoration
, поэтому написание собственной реализации дополняет этот API и позволяет аккуратно контролировать размеры вставки края, чтобы дочерний элемент Widgets
был правильно выровнен, и в целом пользовательский BoxBorder является гибким для тех проектов, которые он поддерживает, поскольку он использует холст для рисования фигуры на экране, пределом является только математика.
class ChatBubbleBorder extends BoxBorder {
final double borderWidth;
final Radius radius;
const ChatBubbleBorder(
{this.borderWidth = 2, this.radius = const Radius.circular(16)});
@override
void paint(
Canvas canvas,
Rect rect, {
TextDirection? textDirection,
BoxShape shape = BoxShape.rectangle,
BorderRadius? borderRadius,
}) {
var paint = Paint();
paint.color = Colors.yellow;
paint.style = PaintingStyle.fill;
var modifiedRect =
Rect.fromLTWH(rect.left, rect.top, rect.width, rect.height - radius.y);
RRect rRect = RRect.fromRectAndCorners(
modifiedRect,
bottomLeft: radius,
topLeft: radius,
topRight: radius,
bottomRight: Radius.zero,
);
canvas.drawRRect(rRect, paint);
var path = Path();
path.moveTo(rect.width - radius.x, rect.height - radius.y);
path.quadraticBezierTo(
rect.width, rect.height - radius.y, rect.width, rect.height);
path.lineTo(rect.width, rect.height - radius.y);
path.close();
// In case of margin move the path.
path = path.shift(rect.topLeft);
canvas.drawPath(path, paint);
}
@override
BorderSide get top => BorderSide(width: borderWidth);
@override
BorderSide get bottom => BorderSide(width: borderWidth + radius.y);
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.fromLTRB(
borderWidth,
borderWidth,
borderWidth,
borderWidth + radius.y,
);
@override
bool get isUniform => false;
@override
ShapeBorder scale(double t) => ChatBubbleBorder(
borderWidth: t * borderWidth,
radius: Radius.circular(t * radius.y),
);
}
Здесь представление прокрутки передается в качестве дочернего элемента контейнеру с использованием пользовательского ChatBubbleBorder
, это показывает, что контент отображается в пределах ожидаемого dimensions
.
import 'package:flutter/material.dart';
main() => runApp(const MaterialApp(home: BorderTestScreen()));
class BorderTestScreen extends StatelessWidget {
const BorderTestScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: const BoxDecoration(
border: ChatBubbleBorder(),
),
child: const SizedBox(
width: 300,
height: 100,
child: SingleChildScrollView(
child: Text(
"Testing1\nTesting2\nTesting3\nTesting4\nTesting5\nTesting6",
),
),
),
),
);
}
}
В своем ответе я опубликовал, как наконец воспроизвел дизайн, посмотрите, думаю, новичку легче воспроизвести.
Изучив ваши ответы, я обнаружил, что этот код легче всего воспроизвести и он соответствует желаемому дизайну.
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:ui_blackandwhite/components/course_card.dart';
import 'package:ui_blackandwhite/components/langage_card.dart';
import 'package:ui_blackandwhite/components/my_search_bar.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.black,
leading: const Icon(
Icons.menu_rounded,
color: Colors.white,
size: 30,
),
actions: const [
Icon(
Icons.person,
color: Colors.white,
size: 30,
),
],
),
body: Column(
children: [
// Barre de recherche
Container(
height: 100,
decoration: const BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(50.0),
),
),
child: const Center(child: MySearchBar()),
),
// Reste de la page
Expanded(
child: Container(
color: Colors.black,
child: Container(
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(50.0),
),
),
child: Padding(
padding: const EdgeInsets.only(
top: 50.0,
left: 25.0,
right: 25.0,
bottom: 25.0,
),
child: Column(
children: [
// Texte "recommendé pour vous"
Align(
alignment: Alignment.centerLeft,
child: Text(
"Recommendé pour vous",
style: GoogleFonts.kanit(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
),
const Gap(15.0),
// List View de quelques langages de programmations
SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return const LangageCard();
},
),
),
const Gap(75),
Align(
alignment: Alignment.centerLeft,
child: Text(
"Dernières formations vues",
style: GoogleFonts.kanit(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
),
const Gap(15.0),
Expanded(
child: ListView(
children: const [
CourseCard(title: "Front-End"),
CourseCard(title: "Back-End"),
CourseCard(title: "JavaScript"),
],
),
)
],
),
),
),
),
),
],
),
);
}
}
Я создал контейнер внутри другого контейнера, придав им разные радиусы границ и цвета.
В своем ответе я опубликовал, как наконец воспроизвел дизайн, посмотрите, думаю, новичку легче воспроизвести.