Эмуляция горизонтального списка изображений, как в приложении Google Play

Я хотел бы создать виджет, который ведет себя так же, как списки горизонтальной прокрутки (например, для рекомендуемых приложений) в приложении Google Play.

Пример того, что я имею в виду:

введите здесь описание изображения

Я пробовал использовать виджет Page Viewer, но он не делает то же самое. Некоторые примечательные факты:

1) 3 полностью квадратичных изображения видны полностью, а еще одно показано частично. Второй (зеленоватый) прямо в центре.

2) Крайнее левое изображение (синее) идеально совмещено с надписью «Рекомендовано для вас».

3) Все 4 изображения в квадрате имеют закругленные углы.

4) Он поддерживает привязку элементов

Это лучшее, что мне удалось воспроизвести с помощью виджета PageView:

введите здесь описание изображения

  • Красный - это цвет фона контейнера, содержащего просмотр страницы.
  • Зеленый - это цвет для четных элементов, а синий - для нечетных элементов.
  • Внутри каждого предмета добавляется изображение размером 100х100.

Это код:

  return Container(
      width: double.infinity,
      color: Colors.red,
      height: 100,
      child:
          PageView(
              pageSnapping: true,
              controller:
                  PageController(initialPage: 1, viewportFraction: 0.315),
              children: List<Widget>.generate(10, (index) {
                return Container(
                  color: index%2 ==0 ? Colors.green : Colors.blue,
                  child:Image.network("http://via.placeholder.com/100x100",
                      fit: BoxFit.fitHeight)
                );
              })));

В моем коде есть 2 вещи, которые не сработают:

1) Я не могу отобразить частично видимый элемент с правой стороны, сохраняя 3 полностью видимых элемента, а второй идеально центрированный, как в исходной форме изображения в Google Play, просто используя viewportFraction.

2) Я не могу применить закругленные углы к изображениям предметов, потому что, как вы видите, контейнер не квадратный, а изображение. Поэтому, когда я применяю ClipRRect, он применяется неправильно. Я попытался добавить еще один контейнер, увеличив его размер до 100x100, и применить ClipRRect к изображению внутри этого контейнера, но когда он находится внутри PageView, кажется, что указанная ширина / высота не имеют никакого эффекта. Кажется, что они контролируются внутри PageView.

Итак, может ли кто-нибудь помочь в решении этих проблем, чтобы получить что-то с таким же поведением, что и горизонтальный прокручиваемый список Google Play?

Ваше здоровье!


person Notbad    schedule 21.12.2019    source источник
comment
Просто используйте ListView с ScrollDirection: Horizontal, создайте собственный виджет для ваших изображений с формой, которая вам нравится   -  person Ahmed I. Elsayed    schedule 22.12.2019
comment
Привязка - это функция этого горизонтального списка Google Play. То, что вы предлагаете, этого не поддерживает.   -  person Notbad    schedule 22.12.2019


Ответы (2)


class SO extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    double edge = 120.0;
    double padding = edge / 10.0;
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        color: Colors.red,
        padding: const EdgeInsets.symmetric(vertical: 8),
        child: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: Row(
            children: [
              for (int i = 0; i < 10; i++)
                Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Padding(
                      padding: EdgeInsets.all(padding),
                      child: ClipRRect(
                        borderRadius: BorderRadius.all(Radius.circular(edge * .2)),
                        child: Container(
                          width: edge,
                          height: edge,
                          color: Colors.blue,
                          child: Image.network("http://via.placeholder.com/${edge.round()}x${edge.round()}", fit: BoxFit.fitHeight),
                        ),
                      ),
                    ),
                    Container(
                      width: edge + padding,
                      padding: EdgeInsets.only(left: padding),
                      child: Text(
                        'foo app bar baz app apk',
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                    Padding(
                      padding: EdgeInsets.only(left: padding),
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: <Widget>[
                          Text('4.2'),
                          Icon(
                            Icons.star,
                            size: 16,
                          )
                        ],
                      ),
                    ),
                  ],
                )
            ],
          ),
        ),
      ),
    );
  }
}

который дает

скриншот

person Doc    schedule 21.12.2019
comment
Да, но ... Это, например, не поддерживает привязку. Одна из моих первых мыслей, когда я реализовывал это, заключалась в том, что я выбрал не тот виджет и собирался использовать, например, горизонтальный список, но привязка не работает для таких элементов управления. - person Notbad; 22.12.2019

Вы можете изменить PageScrollPhysics, чтобы создать эффект привязки.

Это то, что я сделал,

import 'package:flutter/material.dart';

Future<void> main() async {
  runApp(
    MaterialApp(
      home: new Main(),
    ),
  );
}

class Main extends StatefulWidget {
  @override
  _MainState createState() => _MainState();
}

class _MainState extends State<Main> {
  static const _scrollPhysics =
      const ExtentScrollPhysics(itemExtent: 80, separatorSpacing: 10);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Demo"),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                Text(
                  "Recommended for you",
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
                IconButton(
                  icon: Icon(Icons.arrow_forward),
                  onPressed: () {},
                )
              ],
            ),
            SizedBox.fromSize(
              size: Size.fromHeight(130),
              child: ListView.separated(
                scrollDirection: Axis.horizontal,
                itemCount: 30,
                physics: _scrollPhysics,
                separatorBuilder: (context, _) =>
                    SizedBox(width: _scrollPhysics.dividerSpacing),
                itemBuilder: (context, index) {
                  return SizedBox(
                    width: _scrollPhysics.itemExtent, // set height for vertical
                    child: CardItem(),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class CardItem extends StatefulWidget {
  @override
  _CardItemState createState() => _CardItemState();
}

class _CardItemState extends State<CardItem> {
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        AspectRatio(
          aspectRatio: 1,
          child: Container(
            decoration: BoxDecoration(
              color: Colors.blue,
              borderRadius: BorderRadius.circular(20.0),
            ),
          ),
        ),
        const SizedBox(height: 8.0),
        Text(
          "Tile Name",
          overflow: TextOverflow.ellipsis,
          maxLines: 2,
          style: const TextStyle(fontSize: 12.0),
        ),
        Text(
          "1 MB",
          style: const TextStyle(
            fontSize: 12.0,
            color: Colors.black54,
          ),
        ),
      ],
    );
  }
}

class ExtentScrollPhysics extends ScrollPhysics {
  final double itemExtent;
  final double dividerSpacing;

  const ExtentScrollPhysics(
      {ScrollPhysics parent, this.itemExtent, double separatorSpacing})
      : assert(itemExtent != null && itemExtent > 0),
        dividerSpacing = separatorSpacing ?? 0,
        super(parent: parent);

  @override
  ExtentScrollPhysics applyTo(ScrollPhysics ancestor) {
    return ExtentScrollPhysics(
      parent: buildParent(ancestor),
      itemExtent: itemExtent,
      separatorSpacing: dividerSpacing,
    );
  }

  double _getItem(ScrollPosition position) {
    return position.pixels / (itemExtent + dividerSpacing);
  }

  double _getPixels(ScrollPosition position, double item) {
    return item * (itemExtent + dividerSpacing);
  }

  double _getTargetPixels(
      ScrollPosition position, Tolerance tolerance, double velocity) {
    double page = _getItem(position);
    if (velocity < -tolerance.velocity)
      page -= 0.5;
    else if (velocity > tolerance.velocity) page += 0.5;
    return _getPixels(position, page.roundToDouble());
  }

  @override
  Simulation createBallisticSimulation(
      ScrollMetrics position, double velocity) {
    // If we're out of range and not headed back in range, defer to the parent
    // ballistics, which should put us back in range at a page boundary.
    if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
        (velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
      return super.createBallisticSimulation(position, velocity);

    final Tolerance tolerance = this.tolerance;
    final double target = _getTargetPixels(position, tolerance, velocity);
    if (target != position.pixels)
      return ScrollSpringSimulation(spring, position.pixels, target, velocity,
          tolerance: tolerance);
    return null;
  }

  @override
  bool get allowImplicitScrolling => false;
}
person Crazy Lazy Cat    schedule 22.12.2019