Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

parallax effect | scrollable background image in flutter

I'm trying to implement a scrollable background image (parallax). Like in a home screen launcher.

An example: In Evie launcher: this video

I've tried using AnimatedBuilder mentioned here in the docs like this.

I'm using a ValueNotifier<double> as the listener for the animation of the AnimatedBuilder Widget.

The complete code is this

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PageView Scrolling',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>{
  ValueNotifier<double> _notifier;
  double _prevnotifier;

  double getOffset(){
    if (_notifier.value == 0 && _prevnotifier != null){
      return _prevnotifier;
    }
    return _notifier.value;
  }

  @override
  void dispose() {
    _notifier?.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    _notifier = ValueNotifier<double>(0);
    _prevnotifier = _notifier.value;

    _notifier.addListener(
       (){
         print('object ${_notifier.value}');
           if (_notifier.value != 0)
             _prevnotifier = _notifier.value;
       }
    );
  }

  @override
  Widget build(BuildContext context) {
    print("Size is ${MediaQuery.of(context).size}");
    return Scaffold(
      body: Stack(
        children: <Widget>[
           AnimatedBuilder(
             animation: _notifier,
             builder: (context, _) {
               return Transform.translate(
                 offset: Offset(-getOffset() * 60, 0),
                 child: Image.network(
                   "https://w.wallhaven.cc/full/r2/wallhaven-r276qj.png",
                   height: MediaQuery.of(context).size.height,
                   fit: BoxFit.fitHeight
                 ),
               );
            },
          ),
          NotifyingPageView(
            notifier: _notifier,
          ),
        ],
      ),
    );
  }
}

class NotifyingPageView extends StatefulWidget {
  final ValueNotifier<double> notifier;

  const NotifyingPageView({Key key, this.notifier}) : super(key: key);

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

class _NotifyingPageViewState extends State<NotifyingPageView> {
  int _previousPage;
  PageController _pageController;

  void _onScroll() {
    // Consider the page changed when the end of the scroll is reached
    // Using onPageChanged callback from PageView causes the page to change when
    // the half of the next card hits the center of the viewport, which is not
    // what I want

    if (_pageController.page.toInt() == _pageController.page) {
      _previousPage = _pageController.page.toInt();
    }
    widget.notifier?.value = _pageController.page - _previousPage;
  }

  @override
  void initState() {
    _pageController = PageController(
       initialPage: 0,
       viewportFraction: 0.9,
     )..addListener(_onScroll);

     _previousPage = _pageController.initialPage;
     super.initState();
  }

  List<Widget> _pages = List.generate(
    10,
    (index) {
      return Container(
        height: 10,
        alignment: Alignment.center,
          color: Colors.transparent,
          child: Text(
            "Card number $index",
            style: TextStyle(
              color: Colors.teal,
              fontWeight: FontWeight.bold,
              fontSize: 25,
            ),
          ),
        );
      },
  );

  @override
  Widget build(BuildContext context) {
    return PageView(
      children: _pages,
      controller: _pageController,
    );
  }
}

The image can be found here

Now I have two issues:

  • The image when using fit: BoxFit.fitHeight is not overflowing fully. It's currently like this
  • Because the value will become zero when the animation is done it's snapping like this: this video

I tried storing the value just before the _notifier.value becomes zero and use it when it returns zero but it resulted in that weird transition that I've shown you in that above video.

What do you suggest can be done to make something like a scrollable wallpaper in flutter?

Something like this

Design

like image 216
Phani Rithvij Avatar asked Mar 16 '26 03:03

Phani Rithvij


1 Answers

This is not as trivial as I thought it would be.

TLDR; Github read the comments.

I used a ValueNotifier<double> like I mentioned to control the scroll.

Then instead of Transform.translate I used an OverflowBox with its alignment property. Which is computed based on the notifier.value before rendering.

And to display the image in fullscreen mode:

I used AspectRatio with a child DecoratedBox whose decoration is a BoxDecoration with its image as an ImageProvider.

All the code can be found here on github. (Read the comments)

And this issue on github has slightly detailed info and a less complicated alternate implementation by Antonello Galipò

like image 102
Phani Rithvij Avatar answered Mar 17 '26 16:03

Phani Rithvij