Flutter Pageview.builder Advanced Vertical Animation | Height Scaling and Transition

Flutter Pageview.builder in default, does not do horizontal or vertical scalling. If we want to do any kind of scaling within PageView.builder, We need to do this scaling based on scaling factor. Scaling factor could come from current value of the page. 

If we get the page value, we would solve the height problem and height scalling.

Download the starter code here

Pageview.builder starter code

 

Below is the function and code for getting page's value.

 PageController pageController = PageController(viewportFraction: 0.85);
  var _currPageValue = 0.0;
  @override
  void initState(){
    super.initState();
    pageController.addListener(() {
      setState(() {
        _currPageValue=  pageController.page!;
        print(_currPageValue);
      });
    });
  }

To get page value we need to attach a listener to our page controller. That's what we did in the initState() function.

And we want to update the page value immediately, we need to save it inside the setState() function.

We need to get that value from the PageController(). Some must know important keys about Pageview.builder

Current index value is same as page value

Index value suddenly changes, but page value changes slowly

Index value could be integer only, but page value have decimals

Moving to the left, index and page value increases

Moving to the right, index and page value decrease. Index value decreases imediately by one.

At the end, we need to dispose the controller.

@override
  void dispose() {
    super.dispose();
    pageController.dispose();
  }

 

Entry of the Pageview.builder

PageView.builder(

                    controller: pageController,
                    physics: BouncingScrollPhysics(),
                    itemCount: 5,
                    itemBuilder: (context, position) {
                      return _buildPageItem(position);
                    },
                  )

In the PageView.builder() contstructor we need to use property for mentioning pageController.

 

The algorithm to realise a smooth scaling for height. We also did the height transition here.

  //scale factor
  double _scaleFactor = .8;
  //view page height
  double _height = 230.0;
  _buildPageItem(int index) {
    Matrix4 matrix = new Matrix4.identity();
    
    if(index==_currPageValue.floor()){
      var currScale = 1-(_currPageValue-index)*(1- _scaleFactor );
      var currTrans = _height*(1-currScale)/2;
      matrix = Matrix4.diagonal3Values(1.0, currScale, 1.0)
      ..setTranslationRaw(0, currTrans, 0);
    }else if(index ==_currPageValue.floor()+1){
      var currScale = _scaleFactor+(_currPageValue-index+1)*(1- _scaleFactor );
      var currTrans = _height*(1-currScale)/2;
      matrix = Matrix4.diagonal3Values(1.0, currScale, 1.0)
        ..setTranslationRaw(0, currTrans, 0);
    }else if(index ==_currPageValue.floor()-1){
      var currScale = 1-(_currPageValue-index)*(1- _scaleFactor );
      var currTrans = _height*(1-currScale)/2;
      matrix = Matrix4.diagonal3Values(1.0, currScale, 1.0)
        ..setTranslationRaw(0, currTrans, 0);
    }else{
      var currScale = 0.8;
      matrix = Matrix4.diagonal3Values(1.0, currScale, 1.0)
      ..setTranslationRaw(0, _height*(1-_scaleFactor)/2, 0);
    }

    return Transform(
      transform: matrix,
      child: Padding(
        padding: EdgeInsets.symmetric(horizontal: 5),
        child:   GestureDetector(
          child: Align(
            alignment: Alignment.topCenter,
            child: Stack(
              children: [
                Container(
                  padding: const EdgeInsets.only(left: 20, top: 20),
                  height: 220,
                  width: MediaQuery.of(context).size.width-20,
                  margin: const EdgeInsets.only(right: 5, left: 5),
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(30),
                      color:index.isEven?Color(0xFF69c5df):Color(0xFF9294cc),
                      image: DecorationImage(
                          fit: BoxFit.cover,
                          image: AssetImage(
                              "img/food0.png"
                          )
                      )
                  ),

                ),
                Align(
                  alignment: Alignment.bottomCenter,
                  child: Container(
                    margin: const EdgeInsets.only(bottom: 40, left: 35, right: 35),
                    width: double.maxFinite,
                    height: 120,

                    decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(20),
                        color:Colors.white,
                        boxShadow: [
                          BoxShadow(
                              color: Color(0xFFe8e8e8),
                              blurRadius: 5.0,
                              offset: Offset(0, 5)
                          ),
                          BoxShadow(
                              color: Colors.white,
                              offset: Offset(-5, 0)
                          ),
                          BoxShadow(
                              color: Colors.white,
                              offset: Offset(5, 0)
                          )

                        ]
                    ),
                    child: Container(
                      padding: const EdgeInsets.only(top: 20, left: 15, right: 15),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text("Fruit nutrition meal"),
                          SizedBox(height: 10,),
                          Row(
                            children: [
                              Wrap(
                                children: List.generate(5, (index) => Icon(Icons.star, color:Colors.cyan, size:15)),

                              ),
                              SizedBox(width: 10,),
                              TextWidget(text: "4.5", color: Color(0xFFccc7c5)),
                              SizedBox(width: 10,),
                              TextWidget(text: "1287 comments", color: Color(0xFFccc7c5))
                            ],
                          ),
                          SizedBox(height: 20,),
              
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

Scaling can not be done without Matrix4 API in flutter. That's why we use this API, and pass the scaled value to the function called Matrix4.diagonal3Value() and Matrix4.setTransitionRaw()

Matrix4.diagonal3Value() is for setting the scaling factor in x, y and z.

Matrix4.setTransitionRaw() for changing the height position.