Flutter — способ создать контейнер со странным borderRadius

Мне нужна помощь в создании пользовательского интерфейса для моего приложения. Просматривая Dribbble, я наткнулся на дизайн, который, как мне кажется, будет легко и быстро воспроизвести. Однако я пытаюсь создать контейнер с уникальным borderRadius во Flutter.

Вот ссылка на дизайн:

Я планирую добавить черную панель приложений с ведущими элементами и действиями для двух значков, а затем создать контейнер в теле. Однако я не могу понять, как добиться нестандартного borderRadius в правом нижнем углу.

Можете ли вы помочь мне с этим? Спасибо заранее за вашу помощь.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
92
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Есть несколько способов добиться этого. Можно было бы использовать 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),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

В своем ответе я опубликовал, как наконец воспроизвел дизайн, посмотрите, думаю, новичку легче воспроизвести.

Furiy 01.05.2024 16:11

Я добился подобной функции при разработке «окна сообщений» в своем приложении для чата. Вот как я создал изгиб с одной стороны контейнера.

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
        ],
      ),
    ),

Ваш ответ, вероятно, не работает, потому что, если вы посмотрите на дизайн, который я хочу, нижний правый угол имеет «отрицательный радиус».

Furiy 01.05.2024 15:11

Существует несколько подходов для достижения этой цели. Вот один из способов сделать это:

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, чтобы настроить кривые в соответствии с вашим конкретным дизайном.

В своем ответе я опубликовал, как наконец воспроизвел дизайн, посмотрите, думаю, новичку легче воспроизвести.

Furiy 01.05.2024 16:11

Реализуйте кастом BoxBorder

BoxBorder используется с Flutter ContainerBoxDecoration, поэтому написание собственной реализации дополняет этот 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",
            ),
          ),
        ),
      ),
    );
  }
}

В своем ответе я опубликовал, как наконец воспроизвел дизайн, посмотрите, думаю, новичку легче воспроизвести.

Furiy 01.05.2024 16:10
Ответ принят как подходящий

Изучив ваши ответы, я обнаружил, что этот код легче всего воспроизвести и он соответствует желаемому дизайну.

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"),
                          ],
                        ),
                      )
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Я создал контейнер внутри другого контейнера, придав им разные радиусы границ и цвета.

Другие вопросы по теме