介紹
預(yù)覽圖
image
我們可以看到頁面下方切換的卡片效果
分析
首先動畫以 x軸分為兩部分 : 左側(cè)文字 和 右側(cè)圖片
右側(cè)圖片以 z軸 分為 : 上踊淳、下
仔細(xì)觀察假瞬,可以看到它的動畫流程大致如下:
上層顯示的是當(dāng)前圖片,下層顯示的時下一張
1迂尝、左側(cè)文字淡入淡出切換
2脱茉、右側(cè)圖片的上層,與左側(cè)文字同時淡出
3垄开、之后下層圖片上移到 上層圖片的位置
4琴许、移動完成后,淡入一張下層圖片溉躲,
5榜田、于此同時新的文字淡入
實(shí)現(xiàn)
首先我們定義一個類
class MusicCalendar extends WidgetState with SingleTickerProviderStateMixin{}
因?yàn)檫@個動畫比較復(fù)雜寸认,實(shí)際開發(fā)時用了provider,
代碼中可能會看到musicCalendarVM串慰, 它主要是用來持有和控制狀態(tài)及一些數(shù)據(jù)
我盡量把里面的代碼移出來
MusicCalendar
當(dāng)頁面初始完成后偏塞,我們會執(zhí)行init()這個方法:
///這個方法后面還會見到,它的執(zhí)行會使動畫開始邦鲫,即淡出-移動-淡入
///在這里講灸叼,是讓你對流程大概有了解,方便理解后面的代碼
init(){
if(streamSubscription.isPaused){
streamSubscription.resume();
}
}
首先創(chuàng)建一些變量
//淡出/淡入動畫
AnimationController fadeController;
Animation fadeAnim;
//圖片外層是 stack庆捺,所以下面兩個變量用于定位
//我們根據(jù)動畫的進(jìn)度配合下面的兩個變量古今,就可以達(dá)到移動圖片的效果
//具體可以看下面的實(shí)現(xiàn)
double aboveRightMax;
double aboveBottomMax;
我們再看一下布局,代碼較多,我把說明寫在注釋里
去掉一些不必要的代碼
//root layout
Container(
child: Stack(
children: <Widget>[
///date 這個不用管滔以,跟咱們做的沒關(guān)系
Positioned(
top: getWidthPx(10),
child: Text('后天',style: TextStyle(fontSize: getSp(28),color: Colors.black,fontWeight: FontWeight.bold),),
),
///這里是左側(cè) 文字部分捉腥,它要做的動畫 就是淡入和淡出
Positioned(
top: getWidthPx(60),
child: FadeTransition( //使用flutter 提供的fade組件
opacity: musicCalendarVM.fadeAnim,//這里與我們的 animation 綁定
child: Container(
width: getWidthPx(430),
child: Text('${creatives[musicCalendarVM.currentIndex].uiElement.mainTitle.title}',
style: TextStyle(color: Colors.grey,fontSize: getSp(32)),maxLines: 2,),
),
),
),
///這里是右側(cè)圖片區(qū)域
Positioned(
top: getWidthPx(30),
right: 0,
child: imageSwitcher(),
),
],
),
);
我們來看一下 這個方法:imageSwitcher()
Widget imageSwitcher(){
return Container(
//這里我們限定一下右側(cè)圖片區(qū)域的整體大小
//注意,圖片要小于這個值
width: getWidthPx(150),height: getWidthPx(150),
child: Stack(
children: <Widget>[
///below
///這是下面那張圖片你画,初始為右下角
Positioned(
right: 0,
bottom: 0,
//Opacity用于控制圖片的淡入淡出
child: Opacity(
opacity: musicCalendarVM.opacity,
child: ShowImageUtil.showImageWithDefaultError(creatives[musicCalendarVM.currentIndex<=creatives.length-2
?musicCalendarVM.currentIndex+1 : 0].uiElement.image.imageUrl
, getWidthPx(130), getWidthPx(130),borderRadius: getHeightPx(10)),
),
),
///fake
///這里我額外放置了一張假的圖片抵碟,下面細(xì)說
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)),
),
),
///above
///上面那張圖片初始為左上角
Positioned(
left: 0,
top: 0,
// right: 0,
// bottom: 0,
///這里用FadeTransition 控制淡入/淡出
child: FadeTransition(
opacity: musicCalendarVM.fadeAnim,//與 animation綁定,與之前的 文字動畫一樣
child: ShowImageUtil.showImageWithDefaultError(creatives[musicCalendarVM.currentIndex].uiElement.image.imageUrl
, getWidthPx(130), getWidthPx(130),borderRadius: getHeightPx(10)),
),
),
],
),
);
}
首先我們可以看到坏匪,這個stack中有3個widget:
下方圖片 代號below
上方圖片 代號above
假圖片 代號fake
below & above widget
我們先來看一下 below 和 above,他們雖然都是做淡入和淡出效果拟逮,但是用的組件不一樣。
below 使用的是Opacity
above 使用的是FadeTransition 與文字一樣
這里之所以不同适滓,是因?yàn)閮烧邎?zhí)行的實(shí)際和動畫方向不同敦迄,所以未共用一個動畫。above和文字一樣凭迹,由animation控制罚屋,我們不用管它,來看一下below吧嗅绸。
它的 opacity屬性與musicCalendarVM.opacity綁定脾猛,而這個opacity屬性的刷新主要涉及兩個方法
回顧一下它要做的效果,淡入(實(shí)際移動由fake來做朽砰,我們后面講)
void showBelow(){
///這里我們每20毫秒更新一下不透明度
Timer timer = Timer.periodic(Duration(milliseconds: 20), (timer){
if(opacity >= 1.0){
///當(dāng)不透明度>=1.0時尖滚,我們結(jié)束timer
timer.cancel();
//這個時候也就意味著,below淡入完成瞧柔,
//我們可以將上層圖片也淡入(這個淡入的是 above)
///漸顯above和title
fadeController.reverse().whenComplete((){
///當(dāng)above淡入后漆弄,隱藏fake
showFake = false; notifyListeners();
///實(shí)際上在below淡入前,fake做了移動造锅,移動到左上角了(冒充 above)
///因此撼唾,above完成淡入后,我們要重置fake位置,顯示fake(冒充 below)
/// 嘿嘿
right = 0; bottom = 0;
///當(dāng)然 fake顯示的圖片也應(yīng)該是上層圖片的下一張
fakeIndex = currentIndex <= creatives.length-2 ? currentIndex+1:0;
showFake = true;
///真正的 below又變成全透明了
opacity = 0;
notifyListeners();
});
return;
}
//每20秒 不透明度+0.1
opacity =(opacity+0.1).clamp(0.0, 1.0);
notifyListeners();
});
}
我們來看一下哪里調(diào)用了showBelow()
如果你不太熟悉Provider或者bedrock框架的話哥蔚,這里簡單來講倒谷,
就是頁面初試完后蛛蒙,開始執(zhí)行clock監(jiān)聽并執(zhí)行相應(yīng)的動畫操作。
///開頭的那個方法
init(){
if(streamSubscription.isPaused){
streamSubscription.resume();
}
}
final Duration interval = Duration(seconds: 5);///每5秒切換一次卡片
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){
///當(dāng)上層動畫淡出完成后
///title和 above 漸隱渤愁,同時fake上移
fadeController.forward().whenComplete((){
//這里的right和bottom與fake綁定牵祟,我們稍后介紹
//實(shí)際上這里的right和bottom理論上已經(jīng)等于右邊的值
//但實(shí)際上還是會出現(xiàn)偏差,這里進(jìn)行校準(zhǔn)
right = aboveRightMax;
bottom = aboveBottomMax;
notifyListeners();
///更新index =》 dataList的index
incrementIndex();
///插入新的below
///調(diào)用了我們上的方法
showBelow();
//fadeController.reverse();
});
}
});
streamSubscription.pause();
}
fake widget
至此 above和below就介紹完了抖格,我們來說一下 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)),
),
),
fake的工作流程介紹:
當(dāng)頁面初始時:
above圖片為第一張,fake和below為第二張
above在右上角雹拄,below和fake在右下角收奔,且fake遮擋below(實(shí)際below為不可見)
當(dāng)動畫開始時:
above淡出,于此同時fake滑動到左上角滓玖。
當(dāng)?shù)鰟赢嫿Y(jié)束后坪哄,我們將 above、below顯示的圖片index+1势篡, 這里below還是不可見的翩肌。
然后,我們將below淡入(它始終在右下角)殊霞,同時above也是淡入
(因?yàn)樗俣葮O快摧阅,且與fake重合,你是看不出它的淡入的)
操作完成后:
我們將fake(在左上)隱藏绷蹲,并調(diào)整它的right和bottom為0,這樣又到了右下顾孽,
隨后再顯示它祝钢,這樣又遮擋了below,
至此一個輪回就結(jié)束了
我們大致看一下fake的主要屬性:
//控制它的位置
double right = 0; double bottom = 0;
showFake//控制fake的隱藏若厚,在上面的方法中有出現(xiàn)過
更新這兩個屬性的位置在:
void updatePosition(){
right = aboveRightMax * (1-fadeAnim.value);
bottom = aboveBottomMax * (1-fadeAnim.value);
notifyListeners();
}
updatePosition()方法則在 animationListener中調(diào)用
musicCalendarVM.fadeController.addListener(musicCalendarVM.animationListener);
animationListener(){
if(fadeController.status == AnimationStatus.forward){
if(!showFake) showFake = true;
updatePosition();
}
}
到了這里拦英,整個動畫效果就實(shí)現(xiàn)了,如果有點(diǎn)亂测秸,可以在demo中對照源碼和真機(jī)效果來理解疤估。
謝謝大家的閱讀,歡迎指出不足支出 :)
Demo
內(nèi)部搜索即可