Реализуйте разметку с помощью Flutter

Flutter
Реализуйте разметку с помощью Flutter

Эффект

Вращающийся фонарь — распространенный эффект, о том, как его использовать, поговорим в этой статье.PageViewсуществуетFlutterРеализовать marquee lantern, эффект следующий, высота текущей страницы выше остальных страниц, и есть анимация изменения высоты при переключении страниц. Для достижения этого эффекта в основном используетсяPageView.builderчасть.

развивать

Создать домашнюю страницу

Сначала создайтеIndexPageкомпонент, который используется для размещенияPageView, потому что вам нужно использоватьsetStateметод обновляет пользовательский интерфейс, поэтому он сохраняет состояние.

import 'package:flutter/material.dart';

class IndexPage extends StatefulWidget {
  @override
  _IndexPageState createState() => _IndexPageState();
}

class _IndexPageState extends State<IndexPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 0.0,
        backgroundColor: Colors.white,
      ),
      body: Column(
        children: <Widget>[],
      ),
    );
  }
}

Затем внутри компонента объявите_pageIndexПеременная используется для сохранения индекса текущей отображаемой страницы, вinitStateИнициализировать один в жизненном циклеPageControllerиспользуется для настройкиPageViewчасть.

существуетbodyизColumnсоздатьPageView.builder, с помощьюSizedBoxОбозначение деталиPageViewвысота, воляcontrollerУстановить как_pageController,существуетonPageChangedВ случае текущей отображаемой страницыindexприсвоить значение_pageIndexПеременная.

int _pageIndex = 0;
PageController _pageController;

@override
void initState() {
  super.initState();
  _pageController = PageController(
    initialPage: 0,
    viewportFraction: 0.8,
  );
}

body: Column(
  children: <Widget>[
    SizedBox(
      height: 580.0,
      child: PageView.builder(
        itemCount: 3,
        pageSnapping: true,
        controller: _pageController,
        onPageChanged: (int index) {
          setState(() {
            _pageIndex = index;
          });
        },
        itemBuilder: (BuildContext ctx, int index) {
          return _buildItem(_pageIndex, index);
        },
      ),
    ),
  ],
),

ключевой момент: настройкаPageControllerизviewportFractionЕсли параметр меньше 1, это значение используется для установки доли каждой страницы, отображаемой на экране, если меньше 1, содержимое других страниц может отображаться на текущей странице одновременно.

/// The fraction of the viewport that each page should occupy.
/// Defaults to 1.0, which means each page fills the viewport in the scrolling direction.
final double viewportFraction;

выполнить_buildItem

Затем реализуйте_buildItemметод, который возвращаетPageView.builderКонтент, отображаемый на каждой странице, первый параметрactiveIndexстраница, отображаемая в данный момент на экранеindex, второй параметрindexу каждого своеindex.

использоватьCenterвиджет для центрирования содержимого, затем используйтеAnimatedContainerДобавить анимационный эффект изменения высоты при переключении страниц, который используется при переключении страницsetStateметод изменен_pageIndex,FlutterПерекрасьте каждый предмет. Ключевым моментом является определение, является ли текущая страница отображаемой, если да, то ее высота равна 500, если нет, то 450.

_buildItem(activeIndex, index) {
  return Center(
    child: AnimatedContainer(
      curve: Curves.easeInOut,
      duration: Duration(milliseconds: 300),
      height: activeIndex == index ? 500.0 : 450.0,
      margin: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
      decoration: BoxDecoration(
        color: heroes[index].color,
        borderRadius: BorderRadius.all(Radius.circular(12.0)),
      ),
      child: Stack(),
    ),
  );
}

effect

добавить содержимое

тогда дайAnimatedContainerДобавьте содержимое каждого элемента

child: Stack(
  fit: StackFit.expand,
  children: <Widget>[
    ClipRRect(
      borderRadius: BorderRadius.all(
        Radius.circular(12.0),
      ),
      child: Image.network(
        heroes[index].image,
        fit: BoxFit.cover,
      ),
    ),
    Align(
      alignment: Alignment.bottomCenter,
      child: Row(
        children: <Widget>[
          Expanded(
            child: Container(
              padding: EdgeInsets.all(12.0),
              decoration: BoxDecoration(
                color: Colors.black26,
                borderRadius: BorderRadius.only(
                  bottomRight: Radius.circular(12.0),
                  bottomLeft: Radius.circular(12.0),
                ),
              ),
              child: Text(
                heroes[index].title,
                textAlign: TextAlign.center,
                style: TextStyle(
                  fontSize: 20.0,
                  fontWeight: FontWeight.bold,
                  color: Colors.white,
                ),
              ),
            ),
          )
        ],
      ),
    ),
  ],
),

индикатор орудия

Затем реализуйте индикатор страницы, создавPageIndicatorкомпонент, который необходимо передать вpageCountпредставляет собой общее количество страниц, аcurrentIndexУказывает индекс количества отображаемых в данный момент страниц. собрать все индикаторы в одинRowВ компоненте судить о текущем показателеindexОтображается ли страницаindex, если да, отобразите более темный цвет.

class PageIndicator extends StatelessWidget {
  final int pageCount;
  final int currentIndex;

  const PageIndicator(this.currentIndex, this.pageCount);

  Widget _indicator(bool isActive) {
    return Container(
      width: 6.0,
      height: 6.0,
      margin: EdgeInsets.symmetric(horizontal: 3.0),
      decoration: BoxDecoration(
        color: isActive ? Color(0xff666a84) : Color(0xffb9bcca),
        shape: BoxShape.circle,
        boxShadow: [
          BoxShadow(
            color: Colors.black12,
            offset: Offset(0.0, 3.0),
            blurRadius: 3.0,
          ),
        ],
      ),
    );
  }

  List<Widget> _buildIndicators() {
    List<Widget> indicators = [];
    for (int i = 0; i < pageCount; i++) {
      indicators.add(i == currentIndex ? _indicator(true) : _indicator(false));
    }
    return indicators;
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: _buildIndicators(),
    );
  }
}

Добавить кPageIndicatorприбытьSizedBoxпод

упаковкаCarousel

Наконец, оптимизируйте код, инкапсулируйте компонент, сделайте его отдельным компонентом и создайтеCarouselчасти, открытыеitemsа такжеheightДва свойства, настроить данные и высоту соответственно.

class Carousel extends StatefulWidget {
  final List items;
  final double height;

  const Carousel({
    @required this.items,
    @required this.height,
  });

  @override
  _CarouselState createState() => _CarouselState();
}

class _CarouselState extends State<Carousel> {
  int _pageIndex = 0;
  PageController _pageController;

  Widget _buildItem(activeIndex, index) {
    final items = widget.items;

    return Center(
      child: AnimatedContainer(
        curve: Curves.easeInOut,
        duration: Duration(milliseconds: 300),
        height: activeIndex == index ? 500.0 : 450.0,
        margin: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
        decoration: BoxDecoration(
          color: items[index].color,
          borderRadius: BorderRadius.all(Radius.circular(12.0)),
        ),
        child: Stack(
          fit: StackFit.expand,
          children: <Widget>[
            ClipRRect(
              borderRadius: BorderRadius.all(
                Radius.circular(12.0),
              ),
              child: Image.network(
                items[index].image,
                fit: BoxFit.cover,
              ),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Row(
                children: <Widget>[
                  Expanded(
                    child: Container(
                      padding: EdgeInsets.all(12.0),
                      decoration: BoxDecoration(
                        color: Colors.black26,
                        borderRadius: BorderRadius.only(
                          bottomRight: Radius.circular(12.0),
                          bottomLeft: Radius.circular(12.0),
                        ),
                      ),
                      child: Text(
                        items[index].title,
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontSize: 20.0,
                          fontWeight: FontWeight.bold,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    _pageController = PageController(
      initialPage: 0,
      viewportFraction: 0.8,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Container(
          height: widget.height,
          child: PageView.builder(
            pageSnapping: true,
            itemCount: heroes.length,
            controller: _pageController,
            onPageChanged: (int index) {
              setState(() {
                _pageIndex = index;
              });
            },
            itemBuilder: (BuildContext ctx, int index) {
              return _buildItem(_pageIndex, index);
            },
          ),
        ),
        PageIndicator(_pageIndex, widget.items.length),
      ],
    );
  }
}

позжеIndexPageВ компоненте используется только один экземплярCarousel, и из-заIndexPageВместо того, чтобы управлять состоянием виджета, вы можете превратить его вStatelessWidget.

полный код

import 'package:flutter/material.dart';

class Hero {
  final Color color;
  final String image;
  final String title;

  Hero({
    @required this.color,
    @required this.image,
    @required this.title,
  });
}

List heroes = [
  Hero(
    color: Color(0xFF86F3FB),
    image: "https://game.gtimg.cn/images/lol/act/img/skin/big22009.jpg",
    title: '寒冰射手-艾希',
  ),
  Hero(
    color: Color(0xFF7D6588),
    image: "https://game.gtimg.cn/images/lol/act/img/skin/big39006.jpg",
    title: '刀锋舞者-艾瑞莉娅',
  ),
  Hero(
    color: Color(0xFF4C314D),
    image: "https://game.gtimg.cn/images/lol/act/img/skin/big103015.jpg",
    title: '九尾妖狐-阿狸',
  ),
];

class Carousel extends StatefulWidget {
  final List items;
  final double height;

  const Carousel({
    @required this.items,
    @required this.height,
  });

  @override
  _CarouselState createState() => _CarouselState();
}

class _CarouselState extends State<Carousel> {
  int _pageIndex = 0;
  PageController _pageController;

  Widget _buildItem(activeIndex, index) {
    final items = widget.items;

    return Center(
      child: AnimatedContainer(
        curve: Curves.easeInOut,
        duration: Duration(milliseconds: 300),
        height: activeIndex == index ? 500.0 : 450.0,
        margin: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
        decoration: BoxDecoration(
          color: items[index].color,
          borderRadius: BorderRadius.all(Radius.circular(12.0)),
        ),
        child: Stack(
          fit: StackFit.expand,
          children: <Widget>[
            ClipRRect(
              borderRadius: BorderRadius.all(
                Radius.circular(12.0),
              ),
              child: Image.network(
                items[index].image,
                fit: BoxFit.cover,
              ),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Row(
                children: <Widget>[
                  Expanded(
                    child: Container(
                      padding: EdgeInsets.all(12.0),
                      decoration: BoxDecoration(
                        color: Colors.black26,
                        borderRadius: BorderRadius.only(
                          bottomRight: Radius.circular(12.0),
                          bottomLeft: Radius.circular(12.0),
                        ),
                      ),
                      child: Text(
                        items[index].title,
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontSize: 20.0,
                          fontWeight: FontWeight.bold,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    _pageController = PageController(
      initialPage: 0,
      viewportFraction: 0.8,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Container(
          height: widget.height,
          child: PageView.builder(
            pageSnapping: true,
            itemCount: heroes.length,
            controller: _pageController,
            onPageChanged: (int index) {
              setState(() {
                _pageIndex = index;
              });
            },
            itemBuilder: (BuildContext ctx, int index) {
              return _buildItem(_pageIndex, index);
            },
          ),
        ),
        PageIndicator(_pageIndex, widget.items.length),
      ],
    );
  }
}

class PageIndicator extends StatelessWidget {
  final int currentIndex;
  final int pageCount;

  const PageIndicator(this.currentIndex, this.pageCount);

  Widget _indicator(bool isActive) {
    return Container(
      width: 6.0,
      height: 6.0,
      margin: EdgeInsets.symmetric(horizontal: 3.0),
      decoration: BoxDecoration(
        color: isActive ? Color(0xff666a84) : Color(0xffb9bcca),
        shape: BoxShape.circle,
        boxShadow: [
          BoxShadow(
            color: Colors.black12,
            offset: Offset(0.0, 3.0),
            blurRadius: 3.0,
          ),
        ],
      ),
    );
  }

  List<Widget> _buildIndicators() {
    List<Widget> indicators = [];
    for (int i = 0; i < pageCount; i++) {
      indicators.add(i == currentIndex ? _indicator(true) : _indicator(false));
    }
    return indicators;
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: _buildIndicators(),
    );
  }
}

class IndexPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 0.0,
        backgroundColor: Colors.white,
      ),
      body: Carousel(
        height: 540,
        items: heroes,
      ),
      backgroundColor: Colors.white,
    );
  }
}

На этом весь макет готов! 😎

адрес блога