본문 바로가기

개발하자

수평으로 스크롤할 수 있으며 스냅 효과가 있는 카드(스냅 효과 포함)

반응형

수평으로 스크롤할 수 있으며 스냅 효과가 있는 카드(스냅 효과 포함)

왼쪽 또는 오른쪽에서 스와이프할 때 스냅과 함께 수평으로 스크롤하는 카드 목록을 만들고 싶습니다.

각 카드 사이에는 약간의 간격이 있으며 아래 이미지와 유사한 화면에 맞춥니다

enter image description here

그 외에도 이러한 수평 스크롤 가능한 목록 요소는 수직 스크롤 가능한 목록 안에 포함되어야 한다.

내가 할 수 있는 모든 것은 플러터 문서에서 예시를 따른 후 수평 스크롤 카드 목록을 표시하는 것이다.

class SnapCarousel extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final title = 'Horizontal List';

    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Container(
          margin: EdgeInsets.symmetric(vertical: 20.0),
          height: 200.0,
          child: ListView(
            scrollDirection: Axis.horizontal,
            children: <Widget>[
              Container(
                width: 160.0,
                color: Colors.red,
              ),
              Container(
                width: 160.0,
                color: Colors.blue,
              ),
              Container(
                width: 160.0,
                color: Colors.green,
              ),
              Container(
                width: 160.0,
                color: Colors.yellow,
              ),
              Container(
                width: 160.0,
                color: Colors.orange,
              ),
            ],
          ),
        ),
      ),
    );
  }
}



및 사용:

import 'package:flutter/material.dart';

main() => runApp(MaterialApp(home: MyHomePage()));

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Carousel in vertical scrollable'),
      ),
      body: ListView.builder(
        padding: EdgeInsets.symmetric(vertical: 16.0),
        itemBuilder: (BuildContext context, int index) {
          if(index % 2 == 0) {
            return _buildCarousel(context, index ~/ 2);
          }
          else {
            return Divider();
          }
        },
      ),
    );
  }

  Widget _buildCarousel(BuildContext context, int carouselIndex) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Text('Carousel $carouselIndex'),
        SizedBox(
          // you may want to use an aspect ratio here for tablet support
          height: 200.0,
          child: PageView.builder(
            // store this controller in a State to save the carousel scroll position
            controller: PageController(viewportFraction: 0.8),
            itemBuilder: (BuildContext context, int itemIndex) {
              return _buildCarouselItem(context, carouselIndex, itemIndex);
            },
          ),
        )
      ],
    );
  }

  Widget _buildCarouselItem(BuildContext context, int carouselIndex, int itemIndex) {
    return Padding(
      padding: EdgeInsets.symmetric(horizontal: 4.0),
      child: Container(
        decoration: BoxDecoration(
          color: Colors.grey,
          borderRadius: BorderRadius.all(Radius.circular(4.0)),
        ),
      ),
    );
  }
}



이것은 오래된 질문이며, 저는 다른 것을 찾으러 여기에 왔습니다 ;-). 하지만 이 패키지를 사용하면 WiVault의 외관상으로는 쉽게 해결할 수 있습니다:

Demo image

구현:

종속성을 pubsec.yaml에 넣습니다:

dependencies:
   flutter_swiper: ^1.1.6

필요한 페이지에서 가져오기:

import 'package:flutter_swiper/flutter_swiper.dart';

레이아웃:

new Swiper(
  itemBuilder: (BuildContext context, int index) {
    return new Image.network(
      "http://via.placeholder.com/288x188",
      fit: BoxFit.fill,
    );
  },
  itemCount: 10,
  viewportFraction: 0.8,
  scale: 0.9,
)



스크린샷:

enter image description here


타사 패키지를 사용하지 않으려는 경우 다음과 같이 시도할 수 있습니다:

class _HomePageState extends State<HomePage> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: SizedBox(
          height: 200, // card height
          child: PageView.builder(
            itemCount: 10,
            controller: PageController(viewportFraction: 0.7),
            onPageChanged: (int index) => setState(() => _index = index),
            itemBuilder: (_, i) {
              return Transform.scale(
                scale: i == _index ? 1 : 0.9,
                child: Card(
                  elevation: 6,
                  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
                  child: Center(
                    child: Text(
                      "Card ${i + 1}",
                      style: TextStyle(fontSize: 32),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}



ListView를 통해 스냅 효과를 얻으려면 물리학을 PageScrollPhysics로 설정하십시오

const List<Widget> children = [
  ContainerCard(),
  ContainerCard(),
  ContainerCard(),
];
ListView.builder(
    scrollDirection: Axis.horizontal,
    physics: const PageScrollPhysics(), // this for snapping
    itemCount: children.length,
    itemBuilder: (_, index) => children[index],
  )



고급 스냅 목록

동적 항목 크기, 구성 가능한 스냅 지점, 항목 시각화 및 필수 제어(예: ScrollToIndex, 애니메이션)와 같은 고급 용도를 찾고 있는 경우에는 훨씬 더 많은 기능과 함께 기본 기반을 사용해야 합니다.

SnappyListView(
  itemCount: Colors.accents.length,
  itemBuilder: (context, index) {
    return Container(
        height: 100,
        color: Colors.accents.elementAt(index),
        child: Text("Index: $index"),
    ),
);



enter image description here


서드파티 라이브러리를 사용하고 싶지 않은 사람에게는 더 좋고 간단하다고 생각합니다. . 그래서 처음 페이지가 로드될 때마다 첫 번째와 두 번째 카드에는 애니메이션이 없고 카드를 스와이프할 때 이전 카드와 현재 카드에만 스케일 애니메이션이 적용됩니다. 다음은 제가 구현한 것입니다:

class MyHomePage extends StatefulWidget {


const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  int currentIndex = -1, previousIndex = 0;

  double getAnimationValue(int currentIndex, int widgetIndex, int previousIndex,
      {bool begin = true}) {
    if (widgetIndex == currentIndex) {
      return begin ? 0.9 : 1;
    } else {
      return begin ? 1 : 0.9;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          SizedBox(
            height: 200, // card height
            child: PageView.builder(
              itemCount: 10,
              controller: PageController(viewportFraction: 0.7),
              onPageChanged: (int index) {
                setState(() {
                  if (currentIndex != -1) {
                    previousIndex = currentIndex;
                  }
                  currentIndex = index;
                });
              },
              itemBuilder: (_, widgetIndex) {
                return (currentIndex != -1 &&
                        (previousIndex == widgetIndex ||
                            widgetIndex == currentIndex))
                    ? TweenAnimationBuilder(
                        duration: const Duration(milliseconds: 400),
                        tween: Tween<double>(
                          begin: getAnimationValue(
                            currentIndex,
                            widgetIndex,
                            previousIndex,
                          ),
                          end: getAnimationValue(
                            currentIndex,
                            widgetIndex,
                            previousIndex,
                            begin: false,
                          ),
                        ),
                        builder: (context, value, child) {
                          return Transform.scale(
                            scale: value,
                            child: Card(
                              elevation: 6,
                              shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(20)),
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  Text(
                                    "Card${widgetIndex + 1}",
                                    style: const TextStyle(fontSize: 30),
                                  ),
                                  Text(
                                    "$widgetIndex >> Widget Index << $widgetIndex",
                                    style: const TextStyle(fontSize: 22),
                                  ),
                                  Text(
                                    "$currentIndex >> Current Index << $currentIndex",
                                    style: const TextStyle(fontSize: 22),
                                  ),
                                  Text(
                                    "$previousIndex >> Previous Index << $previousIndex",
                                    style: const TextStyle(fontSize: 22),
                                  ),
                                ],
                              ),
                            ),
                          );
                        },
                      )
                    : Transform.scale(
                        // this is used when you want to disable animation when initialized the page
                        scale:
                            (widgetIndex == 0 && currentIndex == -1) ? 1 : 0.9,
                        child: Card(
                          elevation: 6,
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(20)),
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              Text(
                                "Card${widgetIndex + 1}",
                                style: const TextStyle(fontSize: 30),
                              ),
                              Text(
                                "$widgetIndex >> Widget Index << $widgetIndex",
                                style: const TextStyle(fontSize: 22),
                              ),
                              Text(
                                "$currentIndex >> Init Index << $currentIndex",
                                style: const TextStyle(fontSize: 22),
                              ),
                              Text(
                                "$previousIndex >> Previous Index << $previousIndex",
                                style: const TextStyle(fontSize: 22),
                              ),
                            ],
                          ),
                        ),
                      );
              },
            ),
          ),
        ],
      ),
    );
  }
}

나는 이 애니메이션에 트윈애니메이션빌더를 사용했고 위젯을 하드코딩했다. 필요할 때마다 위젯에 메소드를 사용하거나 쉬운 애니메이션을 위한 패키지를 사용할 수 있습니다.


반응형