Last active
September 5, 2025 15:52
-
-
Save hectorAguero/bea2c87897f3554e0fc4304d9d5a90c8 to your computer and use it in GitHub Desktop.
PageView.builder(Carousel) with Scrollable childs wrapped in ClipRRect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: PageViewScreen(), | |
| ); | |
| } | |
| } | |
| class PageViewScreen extends StatefulWidget { | |
| const PageViewScreen({super.key}); | |
| @override | |
| State<PageViewScreen> createState() => _PageViewScreenState(); | |
| } | |
| class _PageViewScreenState extends State<PageViewScreen> { | |
| late final PageController _pageController; | |
| final List<Map<String, dynamic>> _pageData = [ | |
| { | |
| 'color': Colors.red.shade400, | |
| 'title': 'Explore New Horizons', | |
| 'description': | |
| 'Welcome to the first page! This interface demonstrates how to create a dynamic and interactive user experience with custom UI elements.', | |
| }, | |
| { | |
| 'color': Colors.blue.shade400, | |
| 'title': 'Seamless Navigation', | |
| 'description': | |
| 'Effortlessly swipe between pages. The adjacent cards are partially visible, providing a smooth and intuitive browsing experience.', | |
| }, | |
| { | |
| 'color': Colors.green.shade400, | |
| 'title': 'Responsive Content', | |
| 'description': | |
| 'Each card is designed to hold rich content. If the content exceeds the card\'s fixed height, it automatically becomes scrollable.', | |
| }, | |
| { | |
| 'color': Colors.purple.shade400, | |
| 'title': 'Elegant Design', | |
| 'description': | |
| 'Enjoy the clean aesthetic with rounded corners and a clear information hierarchy. Guiding you towards your desired outcome with ease.', | |
| }, | |
| { | |
| 'color': Colors.orange.shade400, | |
| 'title': 'Fully Interactive', | |
| 'description': | |
| 'This is the fifth page. All UI components are functional and update the data model in real-time. No "submit" buttons needed.', | |
| }, | |
| ]; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _pageController = PageController(viewportFraction: 0.8); | |
| } | |
| @override | |
| void dispose() { | |
| _pageController.dispose(); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar( | |
| title: const Text('Custom Card Carousel'), | |
| backgroundColor: Colors.deepPurple, | |
| foregroundColor: Colors.white, | |
| ), | |
| body: Center( | |
| child: SizedBox( | |
| // Constrain the overall height of the PageView to allow it to be centered | |
| // and provide a specific vertical space for the cards. | |
| height: | |
| MediaQuery.of(context).size.height * 0.75, // 75% of screen height | |
| child: PageView.builder( | |
| controller: _pageController, | |
| itemCount: _pageData.length, | |
| itemBuilder: (BuildContext context, int index) { | |
| final page = _pageData[index]; | |
| return Padding( | |
| padding: const EdgeInsets.symmetric( | |
| horizontal: 10.0, | |
| vertical: 20.0, | |
| ), | |
| child: ClipRRect( | |
| borderRadius: BorderRadius.circular( | |
| 20.0, | |
| ), // Rounded corners for the card | |
| child: SizedBox( | |
| height: double | |
| .infinity, // Take the full height from the parent SizedBox | |
| child: SingleChildScrollView( | |
| child: Card( | |
| elevation: 8.0, | |
| color: page['color'] as Color, | |
| margin: EdgeInsets | |
| .zero, // Card margin handled by Padding in PageView.builder | |
| shape: RoundedRectangleBorder( | |
| borderRadius: BorderRadius.circular( | |
| 20.0, | |
| ), // Match ClipRRect | |
| ), | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, // Wrap content height | |
| children: <Widget>[ | |
| Image.network( | |
| 'https://picsum.photos/200', | |
| width: double.infinity, | |
| height: 150, | |
| fit: BoxFit.cover, | |
| loadingBuilder: | |
| (context, child, loadingProgress) { | |
| if (loadingProgress == null) return child; | |
| return Placeholder( | |
| fallbackHeight: 150, | |
| fallbackWidth: double.infinity, | |
| ); | |
| }, | |
| ), | |
| const SizedBox(height: 15), | |
| Text( | |
| page['title'] as String, | |
| style: const TextStyle( | |
| fontSize: 26, | |
| color: Colors.white, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| textAlign: TextAlign.center, | |
| ), | |
| const SizedBox(height: 15), | |
| Text( | |
| page['description'] as String, | |
| style: const TextStyle( | |
| fontSize: 16, | |
| color: Colors.white70, | |
| ), | |
| textAlign: TextAlign.center, | |
| ), | |
| const SizedBox(height: 25), | |
| // Simulate more content to demonstrate scrolling within the fixed height | |
| for (int i = 0; i < 10; i++) | |
| Padding( | |
| padding: const EdgeInsets.only( | |
| bottom: 8.0, | |
| right: 24.0, | |
| left: 24.0, | |
| ), | |
| child: Text( | |
| 'Detail point ${i + 1}: This content is designed to potentially overflow the card\'s view, making the SingleChildScrollView active and functional.', | |
| style: const TextStyle( | |
| fontSize: 14, | |
| color: Colors.white54, | |
| ), | |
| textAlign: TextAlign.left, | |
| ), | |
| ), | |
| const SizedBox(height: 10), | |
| ElevatedButton( | |
| onPressed: () { | |
| // In a real app, this would trigger an action for the current page | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| SnackBar( | |
| content: Text( | |
| 'Action button tapped for "${page['title']}"', | |
| ), | |
| duration: const Duration(seconds: 1), | |
| ), | |
| ); | |
| }, | |
| style: ElevatedButton.styleFrom( | |
| backgroundColor: Colors.white, | |
| foregroundColor: page['color'] as Color, | |
| shape: RoundedRectangleBorder( | |
| borderRadius: BorderRadius.circular(10.0), | |
| ), | |
| padding: const EdgeInsets.symmetric( | |
| horizontal: 30, | |
| vertical: 12, | |
| ), | |
| ), | |
| child: const Text( | |
| 'Learn More', | |
| style: TextStyle( | |
| fontSize: 16, | |
| fontWeight: FontWeight.bold, | |
| ), | |
| ), | |
| ), | |
| SizedBox(height:24) | |
| ], | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| }, | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment