Проблема с ограничениями размера виджета при изменении размера окна.

Я новичок в разработке Flutter и для начала следую учебным пособиям и лабораториям кода, рекомендованным на flutter.dev. В качестве первого упражнения я решил воссоздать страницу приветствия Spotify Desktop, стремясь обеспечить максимально точное представление с точки зрения отзывчивости и размеров элементов. Я считаю, что это отличный способ по-настоящему глубоко изучить фреймворк.

В настоящее время я сосредоточен на нижней панели приложения. Однако я столкнулся с трудностями в достижении желаемого поведения нижней панели при изменении размера окна приложения. Чтобы лучше проиллюстрировать проблему, я создал короткую гифку, демонстрирующую ожидаемое поведение (реальная запись настольного приложения Spotify Linux): .

В частности, я изо всех сил пытаюсь точно изменить размер ползунка плеера, чтобы он соответствовал минимальному и максимальному размерам, найденным в приложении Spotify. Когда я сжимаю окно приложения, размер ползунка не прекращается, когда он достигает установленного мной минимального размера. Я ожидаю, что виджет переполнится с предупреждением, когда я уменьшу размер окна ниже минимального размера виджета.

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

      import 'package:flutter/material.dart';
import 'dart:developer' as developer;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Spotify Clone',
      theme: ThemeData(
        primarySwatch: Colors.green,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: AppBottomBar(),
    );
  }
}

class AppBottomBar extends StatefulWidget {
  @override
  _AppBottomBarState createState() => _AppBottomBarState();
}

class _AppBottomBarState extends State<AppBottomBar> {
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Row(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        // This correctly centers the middle element.
        // However, when resizing, both left and right elements are considered to be equal in width
        // and the some overlapping appears before for the right element.
        // Also, in the Spotify app, the middle element is supposed to be the one being expanded, no the other way around.
        children: [
          Expanded(child: BottomBarSongOverview()),
          MusicController(),
          Expanded(
              child: Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              AppController(),
            ],
          )),
        ],
      ),
    );
  }
}

String formatDuration(int totalSeconds) {
  final duration = Duration(seconds: totalSeconds);
  final hours = duration.inHours;
  var minutes = duration.inMinutes.remainder(60).toString();
  final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');

  if (hours > 0) {
    minutes = minutes.padLeft(2, '0');
    return '$hours:$minutes:$seconds';
  } else {
    return '$minutes:$seconds';
  }
}

class MusicController extends StatefulWidget {
  @override
  _MusicControllerState createState() => _MusicControllerState();
}

class _MusicControllerState extends State<MusicController> {
  double duration = 340;
  var currentTime = 0.0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: const BoxConstraints(
          minWidth: 300,
          maxWidth: 700,
        ),
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                IconButton(
                    onPressed: () => {developer.log('Enable shuffle')},
                    icon: const Icon(
                      Icons.shuffle,
                      semanticLabel: "Enable shuffle",
                    )),
                IconButton(
                    onPressed: () => {developer.log('Previous')},
                    icon: const Icon(
                      Icons.skip_previous,
                      semanticLabel: "Previous",
                    )),
                IconButton(
                    onPressed: () => {developer.log('Play')},
                    icon: const Icon(Icons.play_arrow, semanticLabel: "Play")),
                IconButton(
                    onPressed: () => {developer.log('Next')},
                    icon: const Icon(Icons.skip_next, semanticLabel: "Next")),
                IconButton(
                    onPressed: () => {developer.log('Enable repeat')},
                    icon: const Icon(Icons.repeat, semanticLabel: "Repeat")),
              ],
            ),
            Row(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(formatDuration(currentTime.ceil())),
                Expanded(
                  child: Slider.adaptive(
                    value: currentTime,
                    min: 0.0,
                    max: duration,
                    onChanged: (value) {
                      setState(() {
                        currentTime = value;
                      });
                    },
                  ),
                ),
                Text(formatDuration(duration.ceil())),
              ],
            )
          ],
        ),
      ),
    );
  }
}

class BottomBarSongOverview extends StatefulWidget {
  @override
  State<BottomBarSongOverview> createState() => _BottomBarSongOverviewState();
}

class _BottomBarSongOverviewState extends State<BottomBarSongOverview> {
  var songName = "Please help me understand Flutter";
  var artistName = "MetaHG";
  var isFavorite = false;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        children: [
          Image.network(
            'https://picsum.photos/200',
            width: 50,
            height: 50,
          ),
          const SizedBox(width: 8),
          Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(songName),
              const SizedBox(height: 8),
              Text(artistName),
            ],
          ),
          const SizedBox(width: 8),
          IconButton(
              onPressed: () => {developer.log('Save to your library')},
              icon: isFavorite
                  ? const Icon(Icons.favorite)
                  : const Icon(Icons.favorite_border))
        ],
      ),
    );
  }
}

class AppController extends StatefulWidget {
  @override
  _AppControllerState createState() => _AppControllerState();
}

class _AppControllerState extends State<AppController> {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        children: [
          IconButton(
            icon: const Icon(Icons.mic, semanticLabel: "Lyrics"),
            onPressed: () => {developer.log("Lyrics")},
          ),
          IconButton(
            icon: const Icon(Icons.queue_music, semanticLabel: "Queue"),
            onPressed: () => {developer.log("Queue")},
          ),
          IconButton(
            icon: const Icon(Icons.phonelink,
                semanticLabel: "Connect to a device"),
            onPressed: () => {developer.log("Connect to a device")},
          ),
          SoundController(),
          IconButton(
            icon: const Icon(Icons.fullscreen, semanticLabel: "Full screen"),
            onPressed: () => {developer.log("Full screen")},
          ),
        ],
      ),
    );
  }
}

class SoundController extends StatefulWidget {
  @override
  _SoundControllerState createState() => _SoundControllerState();
}

class _SoundControllerState extends State<SoundController> {
  var volume = 0.5;
  var isMute = false;

  IconData getVolumeIcon(double value) {
    if (isMute) {
      return Icons.volume_off;
    }

    if (value == 0) {
      return Icons.volume_mute;
    } else if (value <= 0.5) {
      return Icons.volume_down;
    } else {
      return Icons.volume_up;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        IconButton(
          icon: Icon(
            getVolumeIcon(volume),
            semanticLabel: isMute ? 'Unmute' : 'Mute',
          ),
          onPressed: () {
            developer.log("Mute");
            setState(() {
              isMute = !isMute;
            });
          },
        ),
        SizedBox(
          height: 24.0,
          child: Slider.adaptive(
              value: volume,
              onChanged: (value) {
                setState(() {
                  volume = value;
                });
              }),
        ),
      ],
    );
  }
}

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

      import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Spotify Clone',
      theme: ThemeData(
        primarySwatch: Colors.green,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Material(child: SliderTest()),
    );
  }
}

class SliderTest extends StatefulWidget {
  @override
  _SliderTestState createState() => _SliderTestState();
}

class _SliderTestState extends State<SliderTest> {
  double duration = 340;
  var currentTime = 0.0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        padding: EdgeInsets.all(8.0),
        constraints: const BoxConstraints(
          minWidth: 300,
          maxWidth: 700,
        ),
        child: Row(
          children: [
            Text(currentTime.ceil().toString()),
            Expanded(
              child: SizedBox(
                child: Slider.adaptive(
                  value: currentTime,
                  min: 0.0,
                  max: duration,
                  onChanged: (value) {
                    setState(() {
                      currentTime = value;
                    });
                  },
                ),
              ),
            ),
            Text(duration.ceil().toString()),
          ],
        ),
      ),
    );
  }
}

Теперь о моих вопросах:

  1. Чего мне не хватает в отношении ограничений? Насколько я понимаю, ползунок адаптируется к максимальному размеру родительского виджета, который меньше указанного минимального размера виджета ползунка. Может ли это быть проблемой? И если да, то почему в этом случае родитель имеет приоритет над ребенком?
  2. Как я могу заставить мой средний виджет вести себя правильно с точки зрения минимального и максимального размеров?

Я был бы очень признателен за любые идеи или предложения о том, как преодолеть эту проблему и добиться желаемого поведения. Заранее спасибо за вашу помощь!

С наилучшими пожеланиями,

МетаХГ

0 ответов

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