introduce

This function is part of the imitation of netease Cloud Music App:Copy the code

Imitation netease cloud music App

preview

We can see the card switching effect at the bottom of the page

Analysis of the

First, the animation is divided into two parts along the X-axis: text on the left and picture on the right

The picture on the right is divided into upper and lower parts along the Z-axis

If you look closely, you can see that its animation flow is roughly as follows:

The upper layer of the picture on the right fades out at the same time as the left text. 3. Then the lower layer moves up to the position of the upper layer picture. 4Copy the code

implementation

First we define a class

class MusicCalendar extends WidgetState with SingleTickerProviderStateMixin{}
Copy the code
Because this animation is quite complicated, I used provider in the actual development, and you may see musicCalendarVM in the code, which is mainly used to hold and control the state and some data. I will try to remove the code insideCopy the code

MusicCalendar

When the page is initialized, we call init() :

/// This method will be seen later, and its execution will start the animation, i.e., fade-move-fade /// in this case, to give you an idea of the process, Easy to understand the code behind the init () {if (streamSubscription. IsPaused) {streamSubscription. Resume (); }}Copy the code

Start by creating some variables

// Fade out/fade in AnimationController fadeController; Animation fadeAnim; Double aboveRightMax; double aboveRightMax; double aboveRightMax; double aboveRightMax; double aboveBottomMax;Copy the code

Let’s look at the layout again, there’s a lot of code, so I’ll put the instructions in the comments

//root layout Container(child: Stack(children: <Widget>) [/// /date this does not care, it does not have to do one of us. GetWidthPx (10), child: Text(' day after ',style: TextStyle(fontSize: getSp(28),color: color.black,fontWeight: Fontgravit.bold),),), /// this is the left side of the text, and the animation it will do is fade into and fade out of ___ (top: getWidthPx(60), child: Fade components FadeTransition (/ / use flutter opacity: musicCalendarVM. FadeAnim, / / and our animation binding child: Container (width: getWidthPx(430), child: Text('${creatives[musicCalendarVM.currentIndex].uiElement.mainTitle.title}', style: TextStyle(color: color.grey,fontSize: getSp(32)),maxLines: 2,),),), /// here is the right-hand corner of the image of space. getWidthPx(30), right: 0, child: imageSwitcher(), ), ], ), );Copy the code

Let’s look at this method: imageSwitcher()

Widget imageSwitcher(){return Container(){width: getWidthPx(150),height: GetWidthPx (150), child: Stack(children: <Widget>[/// /below /// this is the picture below, initially provides the lower right corner of the toy (right: 0, bottom: 0, // Function: controls the decaying of any picture. ShowImageUtil.showImageWithDefaultError(creatives[musicCalendarVM.currentIndex<=creatives.length-2 ?musicCalendarVM.currentIndex+1 : 0].uiElement.image.imageUrl , getWidthPx(130), getWidthPx(130),borderRadius: GetHeightPx (10)),),), /// /fake /// here I put an extra fake picture, and the following says small tracts (right: musicCalendarVM. Right, bottom: musicCalendarVM.bottom, child: Visibility( visible:musicCalendarVM.showFake , child: ShowImageUtil.showImageWithDefaultError(creatives[musicCalendarVM.fakeIndex].uiElement.image.imageUrl , GetWidthPx (130), getWidthPx(130),borderRadius: getHeightPx(10)),),), /// /// the upper picture starts as the upper corner of the uterus (left: 0, top: 0, // right: 0, // top: 0, // top: 0, // top: 0, // right: 0, /// MusicCalendarVM fadeAnim, / / and animation binding, like the previous text animation child: ShowImageUtil.showImageWithDefaultError(creatives[musicCalendarVM.currentIndex].uiElement.image.imageUrl , getWidthPx(130), getWidthPx(130),borderRadius: getHeightPx(10)), ), ), ], ), ); }Copy the code

First, we can see that there are three widgets in the stack:

The image below is "below". The image above is "Above". The fake image is "fake"Copy the code

below & above widget

Let’s look at ‘below’ and ‘above’. They both fade in and out, but they use different components.

"Below" uses Opacity and "above" uses FadeTransition, the same as the textCopy the code

The difference here is that they do not share the same animation because the actual execution and animation direction are different. Above, like the text, is controlled by the animation, so we don’t have to worry about it. Let’s look at below.

Its opacity property is bound to musicCalendarVM. Opacity, and the refresh of this opacity property mainly involves two methods

Just to review what it does, fade in (the actual move is done by fake, we'll get to that later)Copy the code
Timer = Timer. Milliseconds (Duration(milliseconds:); 1), (timer){if(opacity >=1.0){/// When opacity >=1.0, we end timer timer.cancel(); // This means "below" fades into complete, // Fade above and title fadecontroller.reverse (). WhenComplete ((){// when above fades in, Hide fake showFake = false; notifyListeners(); /// "fake" actually moves before "below" fades in to the top left corner. So "above" fades in, we reset fake to say "fake" right = 0; bottom = 0; FakeIndex = currentIndex <= creatives. Length-2? currentIndex+1:0; showFake = true; Opacity = 0; opacity = 0; notifyListeners(); }); return; } // Opacity +0.1 per 20 seconds =(opacity+0.1).clamp(0.0, 1.0); notifyListeners(); }); }Copy the code

Let’s see where showBelow() is called

If you're not familiar with the Provider or Bedrock framework, this simply means that when the page is first tested, clock listens and animations are performed. / / / at the beginning of that method init () {if (streamSubscription. IsPaused) {streamSubscription. Resume (); } } final Duration interval = Duration(seconds: 5); /// Switch MusicCalendarVM(this.block3, this.creatives,){clock = stream.periodic (interval,(index){}); /// Switch MusicCalendarVM(this.block3, this.creatives,){clock = stream.periodic (interval,(index){}); StreamSubscription = clock.listen((I) async{if(destroy)return; if(fadeController.status == AnimationStatus.completed|| fadeController.status == AnimationStatus.dismissed){ Fadecontroller.forward (). WhenComplete (){// Where right and bottom bind to fake, We'll talk about that later on. // In fact, right and bottom here are theoretically equal to the value on the right, but in fact, there is still a deviation. Here we calibrate right = aboveRightMax; bottom = aboveBottomMax; notifyListeners(); // update index = "dataList index incrementIndex(); /// insert a new below /// call our previous method showBelow(); //fadeController.reverse(); }); }}); streamSubscription.pause(); }Copy the code

fake widget

So above and below, let’s talk about fake,

		///fake
          Positioned(
            right: musicCalendarVM.right,
            bottom: musicCalendarVM.bottom,
            child: Visibility(
              visible:musicCalendarVM.showFake ,
              child: ShowImageUtil.showImageWithDefaultError(creatives[musicCalendarVM.fakeIndex].uiElement.image.imageUrl
                  , getWidthPx(130), getWidthPx(130),borderRadius: getHeightPx(10)),
            ),
          ),
Copy the code

How fake works:

When the page starts: above is the first image, fake and below are the second above in the top right corner, below and fake in the bottom right corner, and fake blocks "below" when the animation starts: Above fades out and fake slides to the top left corner. When the fade is over, we add index+1 to above and below, where below is still invisible. We then fade "below" (it's always in the bottom right corner) and "above" (you can't see it fading because it's so fast and overlaps with fake) : We hide fake (top left) and adjust its right and bottom to 0, so that it moves to the bottom right, which then hides below, and the cycle is overCopy the code

Let’s take a look at the main attributes of fake:

Double right = 0; double bottom = 0; ShowFake // Controls the concealment of fake, as seen in the above methodCopy the code

The location to update these two properties is:

  void updatePosition(){
    right = aboveRightMax * (1-fadeAnim.value);
    bottom = aboveBottomMax * (1-fadeAnim.value);
    notifyListeners();
  }
Copy the code

The updatePosition() method is called in the animationListener

	
  musicCalendarVM.fadeController.addListener(musicCalendarVM.animationListener);
	
  animationListener(){
    if(fadeController.status == AnimationStatus.forward){
      if(!showFake) showFake = true;
      updatePosition();
    }

  }
Copy the code

At this point, the entire animation effect is realized, if a bit messy, you can compare the source code and the real machine effect in the demo to understand.

Thank you for reading and feel free to point out underspending 🙂

Demo

Internal search

Imitation netease Cloud music

This function is part of the imitation of netease Cloud Music App:Copy the code

Imitation netease cloud music App