原本是要作一個仿網易雲音樂的flutter項目,可是由於最近事情比較多,項目週期跨度會比較長,所以分幾個步驟來完成。這是仿網易雲音樂項目系列文章的第一篇。沒有徹底照搬網易雲音樂的UI,借鑑了其中的黑膠唱機動畫。git
先貼上項目地址 github.com/KinsomyJS/f…github
這個界面實現起來實際上是比較簡單的,大體分爲以下幾個部分:markdown
咱們一個個來講實現過程。網絡
整個界面是一個堆疊視圖,最下面是一個背景圖片,上面覆蓋一層高斯模糊半透明遮罩,再上層是title,黑膠唱機和控制器。ide
首先使用stack組件用來包裹堆疊視圖,在裏面有兩個container,第一個是背景網絡圖片,第二個就是一個BackdropFilter
。動畫
Stack(
children: <Widget>[
new Container(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new NetworkImage(coverArt),
fit: BoxFit.cover,
colorFilter: new ColorFilter.mode(
Colors.black54,
BlendMode.overlay,
),
),
),
),
new Container(
child: new BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Opacity(
opacity: 0.6,
child: new Container(
decoration: new BoxDecoration(
color: Colors.grey.shade900,
),
),
),
)),
...
]
複製代碼
這裏的高斯模糊sigmaX和sigmaY的值選擇了10,而後透明度爲0.6,顏色爲grey.shade900。ui
關於動畫的知識這裏就不作詳細介紹了,能夠參考官方文檔傳送門this
自定義動畫組件在needle_anim.dart
文件裏。 這裏將動畫和組件解耦,分別定義了動畫過程類PivotTransition
,顧名思義圍繞一個支點旋轉,繼承自AnimatedWidget
。spa
支點定在child組件的topcenter位置。 注意turns不能爲空,須要根據turns的值計算旋轉繞過的周長,圍繞Z軸旋轉。code
class PivotTransition extends AnimatedWidget {
/// 建立旋轉變換
/// turns不能爲空.
PivotTransition({
Key key,
this.alignment: FractionalOffset.topCenter,
@required Animation<double> turns,
this.child,
}) : super(key: key, listenable: turns);
/// The animation that controls the rotation of the child.
/// If the current value of the turns animation is v, the child will be
/// rotated v * 2 * pi radians before being painted.
Animation<double> get turns => listenable;
/// The pivot point to rotate around.
final FractionalOffset alignment;
/// The widget below this widget in the tree.
final Widget child;
@override
Widget build(BuildContext context) {
final double turnsValue = turns.value;
final Matrix4 transform = new Matrix4.rotationZ(turnsValue * pi * 2.0);
return new Transform(
transform: transform,
alignment: alignment,
child: child,
);
}
}
複製代碼
接下來就是自定義黑膠唱頭組件。
final _rotateTween = new Tween<double>(begin: -0.15, end: 0.0);
new Container(
child: new PivotTransition(
turns: _rotateTween.animate(controller_needle),
alignment: FractionalOffset.topLeft,
child: new Container(
width: 100.0,
child: new Image.asset("images/play_needle.png"),
),
),
),
複製代碼
將png圖片包裹在container內做爲child參數傳遞給PivotTransition
。
外部使用的時候傳入一個Tween,起始位置爲-0.15 ~ 0.0。
這部分代碼在record_anim.dart
文件內。使用了package:flutter/animation.dart
提供的RotationTransition
作旋轉,很簡單。
class RotateRecord extends AnimatedWidget {
RotateRecord({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: 250.0,
width: 250.0,
child: new RotationTransition(
turns: animation,
child: new Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(
"https://images-na.ssl-images-amazon.com/images/I/51inO4DBH0L._SS500.jpg"),
),
),
)),
);
}
}
複製代碼
接着自定義旋轉動畫的控制邏輯。旋轉一圈用時十五秒鐘,速度爲線性勻速,同時會重複旋轉動畫。
controller_record = new AnimationController(
duration: const Duration(milliseconds: 15000), vsync: this);
animation_record =
new CurvedAnimation(parent: controller_record, curve: Curves.linear);
animation_record.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller_record.repeat();
} else if (status == AnimationStatus.dismissed) {
controller_record.forward();
}
});
複製代碼
播放流媒體音頻使用了三方組件audioplayers
,具體代碼在player_page.dart
文件內,封裝了一個player組件,接受了一系列參數包括音頻路徑,播放操做回調等。該組件支持本地資源和網絡資源,這裏用網絡音頻資源作demo。
const Player(
{@required this.audioUrl,
@required this.onCompleted,
@required this.onError,
@required this.onNext,
@required this.onPrevious,
this.key,
this.volume: 1.0,
this.onPlaying,
this.color: Colors.white,
this.isLocal: false});
複製代碼
在initState方法裏初始化AudioPlayer
對象。".."是dart的級聯操做符。
audioPlayer = new AudioPlayer();
audioPlayer
..completionHandler = widget.onCompleted
..errorHandler = widget.onError
..durationHandler = ((duration) {
setState(() {
this.duration = duration;
if (position != null) {
this.sliderValue = (position.inSeconds / duration.inSeconds);
}
});
})
..positionHandler = ((position) {
setState(() {
this.position = position;
if (duration != null) {
this.sliderValue = (position.inSeconds / duration.inSeconds);
}
});
});
複製代碼
開始播放代碼
audioPlayer.play(
widget.audioUrl,
isLocal: widget.isLocal,
volume: widget.volume,
);
複製代碼
開始播放後,durationHandler
會回調音頻總時長,positionHandler
會回調播放進度,兩個回調都返回一個Duration
對象。根據這兩個duration對象能夠計算機播放進度的百分比,這裏使用Slider
組件作進度條。
new Slider(
onChanged: (newValue) {
if (duration != null) {
int seconds = (duration.inSeconds * newValue).round();
print("audioPlayer.seek: $seconds");
audioPlayer.seek(new Duration(seconds: seconds));
}
},
value: sliderValue ?? 0.0,
activeColor: widget.color,
),
複製代碼
總體實現是很是簡單的,只要對flutter的組件有所瞭解就能很快寫出來,後面還會加入歌詞滾動功能來豐富界面。
具體項目能夠到 github.com/KinsomyJS/f… 查看,也歡迎star持續關注。