Hi, I’m Alex who studies hard for 25 hours with only 24 hours a day.

The cause of

Touch fish is impossible to touch fish, this life will not touch fish.

Origin is guo guy to share a weibo: share. API. Weibo. Cn/share / 26900…

Then the thought crossed my mind: Nice stuff, but I have a MacBook and can’t use this app. But it looks like I could write one myself, right?

The preparatory work

The only thing you need when you’re young is action, and even though I’m working on my DevFest pitch right now, that doesn’t stop me from writing an App in 10 minutes. So I delivered a one-two punch:

  • flutter config --enable-macos-desktop
  • flutter create --platforms=macos touch_fish_on_macos

A Flutter project supporting macOS was created. (About a minute has passed.)

Start typing code

To find resources

First of all, we need a high-definition image of  without code, which you can search on the Internet. One thing to note is that the use of LOGO should pay attention to the copyright problems caused by the use of the scene. After you find the image, dump it into assets/apple-logo.png and add the resource reference in pubspec.yaml:

flutter:
  use-material-design: true
+ assets:
+ - assets/apple-logo.png
Copy the code

Think about the layout

Let’s take a look at the macOS splash screen and have a few takeaways:

  • LOGO in the middle of the screen, fixed size of about 100dp;
  • The interval between LOGO and progress bar is about 100 DP;
  • The height of the progress bar is about 5DP and the width is about 200DP. The rounded corners almost completely cover the height. The value part is white, and the background part is filled color + light gray border.

(Don’t ask me why these things can be observed, ask me to teach UI to change UI every day.)

After confirming the general layout pattern, we started to build the layout. (About 2 minutes have passed.)

To realize the layout

First center the LOGO, color it, set the width to 100, and set the spacing to 100:

return Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      const Spacer(),
      Padding(
        padding: const EdgeInsets.symmetric(vertical: 100),
        child: Image.asset(
          'assets/apple-logo.png',
          color: CupertinoColors.white, // Use Cupertino white color
          width: 100,),),const Spacer(),
    ],
  ),
);
Copy the code

Then place a progress bar on top of it:

return Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      const Spacer(),
      Padding(
        padding: const EdgeInsets.symmetric(vertical: 100),
        child: Image.asset(
          'assets/apple-logo.png',
          color: CupertinoColors.white, // Use Cupertino white
          width: 100,
        ),
      ),
      Expanded(
        child: Container(
          width: 200,
          alignment: Alignment.topCenter, // Align relative to upper middle
          child: DecoratedBox(
            border: Border.all(color: CupertinoColors.systemGrey), // Set the border
            borderRadius: BorderRadius.circular(10), // The ratio here is high
          ),
          child: ClipRRect(
            borderRadius: BorderRadius.circular(10), // It needs to be rounded
            child: LinearProgressIndicator(
              value: 0.3.// Current progress value
              backgroundColor: CupertinoColors.lightBackgroundGray.withOpacity(3.),
              color: CupertinoColors.white,
              minHeight: 5.// Set the progress bar height(() [[() [[() [() [()Copy the code

At this point you can just run, a static interface is already done. (About 4 minutes have passed.)

When you open the App, you’re ready to hang up, and your boss walks up to you, probably chatting with you about updates. However, the update interface does not move, can you call it update interface? When the boss walks by again and again at the same pace, you may have been marked off, or you may have been fired the next day for coming into the office and sitting in a chair.

So the next step is to figure out how to make it work.

Think about the animation

Let’s see what the startup animation looks like:

  • There is no progress bar at first;
  • Progress bars move at different levels, not necessarily at the same speed.

Based on the above two conditions, I designed an animation processing method:

  • Duration of tectonic segmentation (Duration), can be freely combined by multiple lengths;
  • The number of animation passes determines the final progress of each animation;
  • Each period controls the interval between the start value and the end value.

There are only three conditions, simple as take off, go! (About 5 minutes had passed.)

Realization of animation

Start with an AnimationController:

class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
  /// Skillfully use late initialization to save code
  late final AnimationController _controller = AnimationController(vsync: this);

  /// Duration of waiting after startup
  Duration get _waitingDuration => const Duration(seconds: 5);

  /// The animation length of segments
  List<Duration> get _periodDurations {
    return <Duration> [const Duration(seconds: 5),
      const Duration(seconds: 10),
      const Duration(seconds: 4)]; }/// Which segment is currently in progress
  final ValueNotifier<int> _currentPeriod = ValueNotifier<int> (1);
Copy the code

Next we implement the animation method, which uses recursive calls to reduce the control of the call chain:

@override
void initState() {
  super.initState();
  // Wait for the corresponding number of seconds to start the progress bar animation
  Future.delayed(_waitingDuration).then((_) => _callAnimation());
}

Future<void> _callAnimation() async {
  // take the current segment
  final Duration _currentDuration = _periodDurations[currentPeriod];
  // Prepare the next section
  currentPeriod++;
  // If you reach the last segment, empty
  final Duration? _nextDuration = currentPeriod < _periodDurations.length ? _periodDurations.last : null;
  // Calculates the end value of the current segment animation
  final double target = currentPeriod / _periodDurations.length;
  // Perform the animation
  await _controller.animateTo(target, duration: _currentDuration);
  // If the next segment is empty, the last segment is executed, reset the current segment, and the animation ends
  if (_nextDuration == null) {
    currentPeriod = 0;
    return;
  }
  // Otherwise call the animation of the next segment
  await _callAnimation();
}
Copy the code

The above just a few lines of code, the perfect implementation of the progress bar animation operation. (About 8 minutes had passed.)

In the last step, bind the animation and segmentation to the progress bar. The progress bar will not be displayed before the segmentation, and the corresponding progress will be displayed after the animation starts:

ValueListenableBuilder<int>(
  valueListenable: _currentPeriod,
  builder: (_, int period, __) {
    // segment 0 is not displayed
    if (period == 0) {
      return const SizedBox.shrink();
    }
    return DecoratedBox(
      decoration: BoxDecoration(
        border: Border.all(color: CupertinoColors.systemGrey),
        borderRadius: BorderRadius.circular(10),
      ),
      child: ClipRRect(
        borderRadius: BorderRadius.circular(10),
        child: AnimatedBuilder( // Use AnimatedBuilder to trigger updates while the animation is in progress
          animation: _controller,
          builder: (_, __) => LinearProgressIndicator(
            value: _controller.value, // Bind the value of controller to progress
            backgroundColor: CupertinoColors.lightBackgroundGray.withOpacity(3.),
            color: CupertinoColors.white,
            minHeight: 5(), (), (), (); },)Copy the code

And we’re done. Ten minutes. Let’s run and see what happens. (Figure 22.1 M)

Who is not confused by the degree of reduction? 🤩 from now on open the way to work…

Packaging releases

Releasing the official macOS app is complicated, but we can package it for our own use with a single command: Flutter Build macOS.

After the success, the product will output in the build/macos/build/Products/Release/touch_fish_on_macos app, double-click to use.

conclusion

Thus, a simple Flutter App that can run on macOS was developed. The full demo can be accessed at my repository: github.com/AlexV525/fl… . You can give it some requirements, I think such a software is quite interesting.

Perhaps most people don’t realize how easy it is to write a Flutter application to run on macOS. Of course, the seemingly short 10 minutes doesn’t include the time to install the environment, search for materials, and commit to Git, but it’s more than enough time to get things done.

Hopefully this article has inspired you to learn about Flutter, or to try Flutter on your desktop, or to challenge yourself with how fast you can program it (not to mention 10 minutes is impossible, I actually used less time).

Let me know if you have any questions about Flutter in the comments section, and join the most active Flutter group (FlutterCandies🍭) for direct discussion with most of the country’s Flutter writers (including me), ollie!

Inspired by the

  • Fishing/www.microsoft.com/zh-cn/p/…
  • fakeupdate.net