Written on the front

Originally, WE planned to make a flutter project imitating netease Cloud music. However, due to a lot of recent events, the project cycle would be long, so we should complete the project in several steps. This is the first in a series of articles about the netease cloud music project. Did not completely copy netease cloud music UI, draw lessons from the black record player animation.

First paste the project address github.com/KinsomyJS/f…

Preliminary renderings

Train of thought

This interface is actually relatively simple to achieve, roughly divided into the following parts:

  • 1. Gaussian blur effect of background
  • 2. Rotation animation of vinyl head
  • 3. Spinning animation of vinyl records
  • 4. Lower controller and progress bar

Let’s go through the implementation process one by one.

practice

The whole interface is a stacked view, with a background image at the bottom covered with a Gaussian blur translucent mask, title, vinyl record player and controller at the top.

1. Background Gaussian blur

The stack component is used to wrap the stacked view first. There are two Containers inside, the first is the background network image, and the second is a 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,
              ),
            ),
          ),
        )),
        
        ...
    ]
Copy the code

Here the Gaussian blur sigmaX and sigmaY values are 10, then the opacity is 0.6 and the color is grey.shade900.

2. Rotation animation of vinyl head

Animation knowledge is not detailed here, you can refer to the official document portal

The custom animation component is in needle_im.dart. Here, the animation and components are decoupled and the animation process class PivotTransition is defined, which rotates around a pivot as the name implies and inherits from the AnimatedWidget.

The fulcrum is positioned at the Topcenter location of the Child component. Note that turns cannot be empty. You need to calculate the circumference of the rotation around the Z-axis according to the value of Turns.

class PivotTransition extends AnimatedWidget {
  /// create a rotation transform
  Turns cannot be empty.
  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 newTransform( transform: transform, alignment: alignment, child: child, ); }}Copy the code

Next comes the custom vinyl head component.

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"),),),),Copy the code

The PNG image is wrapped in the Container and passed to PivotTransition as a child parameter.

For external use, pass a Tween, starting at -0.15 to 0.0.

3. Spinning animation of vinyl records

This code is in the record_im.dart file. Using package: flutter/animation. The dart RotationTransition do rotation, very simple.

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"),),),))); }}Copy the code

Then customize the control logic of the rotation animation. The rotation takes 15 seconds, and the speed is linear and uniform, and the rotation animation is repeated.

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(); }});Copy the code

4. Lower controller and progress bar

Dart file, encapsulates a Player component that accepts a series of parameters including audio path, playback callback, etc. This component supports both local and network resources. Network audio resources are used in this 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});
Copy the code

Initialize the AudioPlayer object in the initState method. ..” Is the dart cascade operator.

 audioPlayer = newAudioPlayer(); 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); }}); });Copy the code

Start playing the code

audioPlayer.play(
    widget.audioUrl,
    isLocal: widget.isLocal,
    volume: widget.volume,
  );
Copy the code

When playback starts, durationHandler calls back the total Duration of the audio, positionHandler calls back the playback progress, and both callbacks return a Duration object. The Slider component is used as a progress bar based on the percentage of progress that can be played by the computer by these two Duration objects.

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,
  ),
Copy the code

conclusion

The overall implementation of Flutter is very simple and can be written quickly with some knowledge of the flutter components. Lyrics scrolling will be added later to enrich the interface.

Specific projects can be found at github.com/KinsomyJS/f… Check it out and stay tuned to star.

The resources

  1. The official documentation
  2. pub: audioplayers