ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

小白带你践行Flutter拼图GAME

2020-06-24 11:05:07  阅读:270  来源: 互联网

标签:const 拼图 puzzle dart small GAME override import Flutter


小白带你践行Flutter拼图GAME

下载

https://github.com/kevmoo/slide_puzzle.git

运行效果

 

源代码分析

 

1) main.dart是入口代码

//main.dartimport 'src/core/puzzle_animator.dart';import 'src/flutter.dart';import 'src/puzzle_home_state.dart';void main() => runApp(PuzzleApp());class PuzzleApp extends StatelessWidget {  final int rows, columns;  PuzzleApp({int columns = 4, int rows = 4})      : columns = columns ?? 4,        rows = rows ?? 4;  @override  Widget build(BuildContext context) => MaterialApp(        title: '幻灯片拼图',        home: _PuzzleHome(rows, columns),      );}class _PuzzleHome extends StatefulWidget {  final int _rows, _columns;  const _PuzzleHome(this._rows, this._columns);  @override  PuzzleHomeState createState() =>      PuzzleHomeState(PuzzleAnimator(_columns, _rows));}

 

定义app_state.dart

import 'package:flutter/foundation.dart';import 'core/puzzle_proxy.dart';abstract class AppState {  PuzzleProxy get puzzle;  Listenable get animationNotifier;}

flutter.dart

export 'package:flutter/material.dart';export 'package:flutter/scheduler.dart' show Ticker;

定义控件类

PuzzleControls实现Listenable的监控

import 'package:flutter/foundation.dart';abstract class PuzzleControls implements Listenable {  void reset();  int get clickCount;  int get incorrectTiles;  bool get autoPlay;  void Function(bool newValue) get setAutoPlayFunction;}

继承流程委托

import 'core/puzzle_proxy.dart';import 'flutter.dart';class PuzzleFlowDelegate extends FlowDelegate {  final Size _tileSize;  final PuzzleProxy _puzzleProxy;  PuzzleFlowDelegate(this._tileSize, this._puzzleProxy, Listenable repaint)      : super(repaint: repaint);  @override  Size getSize(BoxConstraints constraints) => Size(      _tileSize.width * _puzzleProxy.width,      _tileSize.height * _puzzleProxy.height);  @override  BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) =>      BoxConstraints.tight(_tileSize);  @override  void paintChildren(FlowPaintingContext context) {    for (var i = 0; i < _puzzleProxy.length; i++) {      final tileLocation = _puzzleProxy.location(i);      context.paintChild(        i,        transform: Matrix4.translationValues(          tileLocation.x * _tileSize.width,          tileLocation.y * _tileSize.height,          i.toDouble(),        ),      );    }  }  @override  bool shouldRepaint(covariant PuzzleFlowDelegate oldDelegate) =>      _tileSize != oldDelegate._tileSize ||      !identical(oldDelegate._puzzleProxy, _puzzleProxy);}

 

实现PuzzleConrol类

import 'dart:async';import 'package:provider/provider.dart';import 'app_state.dart';import 'core/puzzle_animator.dart';import 'core/puzzle_proxy.dart';import 'flutter.dart';import 'puzzle_controls.dart';import 'puzzle_flow_delegate.dart';import 'shared_theme.dart';import 'themes.dart';import 'value_tab_controller.dart';class _PuzzleControls extends ChangeNotifier implements PuzzleControls {  final PuzzleHomeState _parent;  _PuzzleControls(this._parent);  @override  bool get autoPlay => _parent._autoPlay;  void _notify() => notifyListeners();  @override  void Function(bool newValue) get setAutoPlayFunction {    if (_parent.puzzle.solved) {      return null;    }    return _parent._setAutoPlay;  }  @override  int get clickCount => _parent.puzzle.clickCount;  @override  int get incorrectTiles => _parent.puzzle.incorrectTiles;  @override  void reset() => _parent.puzzle.reset();}class PuzzleHomeState extends State    with SingleTickerProviderStateMixin, AppState {  @override  final PuzzleAnimator puzzle;  @override  final _AnimationNotifier animationNotifier = _AnimationNotifier();  Duration _tickerTimeSinceLastEvent = Duration.zero;  Ticker _ticker;  Duration _lastElapsed;  StreamSubscription _puzzleEventSubscription;  bool _autoPlay = false;  _PuzzleControls _autoPlayListenable;  PuzzleHomeState(this.puzzle) {    _puzzleEventSubscription = puzzle.onEvent.listen(_onPuzzleEvent);  }  @override  void initState() {    super.initState();    _autoPlayListenable = _PuzzleControls(this);    _ticker ??= createTicker(_onTick);    _ensureTicking();  }  void _setAutoPlay(bool newValue) {    if (newValue != _autoPlay) {      setState(() {        // Only allow enabling autoPlay if the puzzle is not solved        _autoPlayListenable._notify();        _autoPlay = newValue && !puzzle.solved;        if (_autoPlay) {          _ensureTicking();        }      });    }  }  @override  Widget build(BuildContext context) => MultiProvider(        providers: [          Provider<AppState>.value(value: this),          ListenableProvider<PuzzleControls>.value(            listenable: _autoPlayListenable,          )        ],        child: Material(          child: Stack(            children: <Widget>[              const SizedBox.expand(                child: FittedBox(                  fit: BoxFit.cover,                  child: Image(                    image: AssetImage('asset/seattle.jpg'),                  ),                ),              ),              const LayoutBuilder(builder: _doBuild),            ],          ),        ),      );  @override  void dispose() {    animationNotifier.dispose();    _ticker?.dispose();    _autoPlayListenable?.dispose();    _puzzleEventSubscription.cancel();    super.dispose();  }  void _onPuzzleEvent(PuzzleEvent e) {    _autoPlayListenable._notify();    if (e != PuzzleEvent.random) {      _setAutoPlay(false);    }    _tickerTimeSinceLastEvent = Duration.zero;    _ensureTicking();    setState(() {      // noop    });  }  void _ensureTicking() {    if (!_ticker.isTicking) {      _ticker.start();    }  }  void _onTick(Duration elapsed) {    if (elapsed == Duration.zero) {      _lastElapsed = elapsed;    }    final delta = elapsed - _lastElapsed;    _lastElapsed = elapsed;    if (delta.inMilliseconds <= 0) {      // `_delta` may be negative or zero if `elapsed` is zero (first tick)      // or during a restart. Just ignore this case.      return;    }    _tickerTimeSinceLastEvent += delta;    puzzle.update(delta > _maxFrameDuration ? _maxFrameDuration : delta);    if (!puzzle.stable) {      animationNotifier.animate();    } else {      if (!_autoPlay) {        _ticker.stop();        _lastElapsed = null;      }    }    if (_autoPlay &&        _tickerTimeSinceLastEvent > const Duration(milliseconds: 200)) {      puzzle.playRandom();      if (puzzle.solved) {        _setAutoPlay(false);      }    }  }}class _AnimationNotifier extends ChangeNotifier {  void animate() {    notifyListeners();  }}const _maxFrameDuration = Duration(milliseconds: 34);Widget _updateConstraints(    BoxConstraints constraints, Widget Function(bool small) builder) {  const _smallWidth = 580;  final constraintWidth =      constraints.hasBoundedWidth ? constraints.maxWidth : 1000.0;  final constraintHeight =      constraints.hasBoundedHeight ? constraints.maxHeight : 1000.0;  return builder(constraintWidth < _smallWidth || constraintHeight < 690);}Widget _doBuild(BuildContext _, BoxConstraints constraints) =>    _updateConstraints(constraints, _doBuildCore);Widget _doBuildCore(bool small) => ValueTabController<SharedTheme>(      values: themes,      child: Consumer<SharedTheme>(        builder: (_, theme, __) => AnimatedContainer(          duration: puzzleAnimationDuration,          color: theme.puzzleThemeBackground,          child: Center(            child: theme.styledWrapper(              small,              SizedBox(                width: 580,                child: Consumer<AppState>(                  builder: (context, appState, _) => Column(                    mainAxisSize: MainAxisSize.min,                    crossAxisAlignment: CrossAxisAlignment.center,                    children: <Widget>[                      Container(                        decoration: const BoxDecoration(                          border: Border(                            bottom: BorderSide(                              color: Colors.black26,                              width: 1,                            ),                          ),                        ),                        margin: const EdgeInsets.symmetric(horizontal: 20),                        child: TabBar(                          controller: ValueTabController.of(context),                          labelPadding: const EdgeInsets.fromLTRB(0, 20, 0, 12),                          labelColor: theme.puzzleAccentColor,                          indicatorColor: theme.puzzleAccentColor,                          indicatorWeight: 1.5,                          unselectedLabelColor: Colors.black.withOpacity(0.6),                          tabs: themes                              .map((st) => Text(                                    st.name.toUpperCase(),                                    style: const TextStyle(                                      letterSpacing: 0.5,                                    ),                                  ))                              .toList(),                        ),                      ),                      Flexible(                        child: Container(                          padding: const EdgeInsets.all(10),                          child: Flow(                            delegate: PuzzleFlowDelegate(                              small ? const Size(90, 90) : const Size(140, 140),                              appState.puzzle,                              appState.animationNotifier,                            ),                            children: List<Widget>.generate(                              appState.puzzle.length,                              (i) => theme.tileButtonCore(                                  i, appState.puzzle, small),                            ),                          ),                        ),                      ),                      Container(                        decoration: const BoxDecoration(                          border: Border(                            top: BorderSide(color: Colors.black26, width: 1),                          ),                        ),                        padding: const EdgeInsets.only(                          left: 10,                          bottom: 6,                          top: 2,                          right: 10,                        ),                        child: Consumer<PuzzleControls>(                          builder: (_, controls, __) =>                              Row(children: theme.bottomControls(controls)),                        ),                      )                    ],                  ),                ),              ),            ),          ),        ),      ),    );

定义主题

import 'core/puzzle_proxy.dart';import 'flutter.dart';import 'puzzle_controls.dart';import 'widgets/material_interior_alt.dart';final puzzleAnimationDuration = kThemeAnimationDuration * 3;abstract class SharedTheme {  const SharedTheme();  String get name;  Color get puzzleThemeBackground;  RoundedRectangleBorder puzzleBorder(bool small);  Color get puzzleBackgroundColor;  Color get puzzleAccentColor;  EdgeInsetsGeometry tilePadding(PuzzleProxy puzzle) => const EdgeInsets.all(6);  Widget tileButton(int i, PuzzleProxy puzzle, bool small);  Ink createInk(    Widget child, {    DecorationImage image,    EdgeInsetsGeometry padding,  }) =>      Ink(        padding: padding,        decoration: BoxDecoration(          image: image,        ),        child: child,      );  Widget createButton(    PuzzleProxy puzzle,    bool small,    int tileValue,    Widget content, {    Color color,    RoundedRectangleBorder shape,  }) =>      AnimatedContainer(        duration: puzzleAnimationDuration,        padding: tilePadding(puzzle),        child: RaisedButton(          elevation: 4,          clipBehavior: Clip.hardEdge,          animationDuration: puzzleAnimationDuration,          onPressed: () => puzzle.clickOrShake(tileValue),          shape: shape ?? puzzleBorder(small),          padding: const EdgeInsets.symmetric(),          child: content,          color: color,        ),      );  // Thought about using AnimatedContainer here, but it causes some weird  // resizing behavior  Widget styledWrapper(bool small, Widget child) => MaterialInterior(        duration: puzzleAnimationDuration,        shape: puzzleBorder(small),        color: puzzleBackgroundColor,        child: child,      );  TextStyle get _infoStyle => TextStyle(        color: puzzleAccentColor,        fontWeight: FontWeight.bold,      );  List<Widget> bottomControls(PuzzleControls controls) => <Widget>[        IconButton(          onPressed: controls.reset,          icon: Icon(Icons.refresh, color: puzzleAccentColor),        ),        Checkbox(          value: controls.autoPlay,          onChanged: controls.setAutoPlayFunction,          activeColor: puzzleAccentColor,        ),        Expanded(          child: Container(),        ),        Text(          controls.clickCount.toString(),          textAlign: TextAlign.right,          style: _infoStyle,        ),        const Text(' Moves'),        SizedBox(          width: 28,          child: Text(            controls.incorrectTiles.toString(),            textAlign: TextAlign.right,            style: _infoStyle,          ),        ),        const Text(' Tiles left  ')      ];  Widget tileButtonCore(int i, PuzzleProxy puzzle, bool small) {    if (i == puzzle.tileCount && !puzzle.solved) {      return const Center();    }    return tileButton(i, puzzle, small);  }}

定义三种主题样式

import 'theme_plaster.dart';import 'theme_seattle.dart';import 'theme_simple.dart';const themes = [  ThemeSimple(),  ThemeSeattle(),  ThemePlaster(),];

3种样式

样式1:

import 'core/puzzle_proxy.dart';import 'flutter.dart';import 'shared_theme.dart';const _yellowIsh = Color.fromARGB(255, 248, 244, 233);const _chocolate = Color.fromARGB(255, 66, 66, 68);const _orangeIsh = Color.fromARGB(255, 224, 107, 83);class ThemePlaster extends SharedTheme {  @override  String get name => 'Plaster';  const ThemePlaster();  @override  Color get puzzleThemeBackground => _chocolate;  @override  Color get puzzleBackgroundColor => _yellowIsh;  @override  Color get puzzleAccentColor => _orangeIsh;  @override  RoundedRectangleBorder puzzleBorder(bool small) => RoundedRectangleBorder(        side: const BorderSide(          color: Color.fromARGB(255, 103, 103, 105),          width: 8,        ),        borderRadius: BorderRadius.all(          Radius.circular(small ? 10 : 18),        ),      );  @override  Widget tileButton(int i, PuzzleProxy puzzle, bool small) {    final correctColumn = i % puzzle.width;    final correctRow = i ~/ puzzle.width;    final primary = (correctColumn + correctRow).isEven;    if (i == puzzle.tileCount) {      assert(puzzle.solved);      return Center(        child: Icon(          Icons.thumb_up,          size: small ? 50 : 72,          color: _orangeIsh,        ),      );    }    final content = Text(      (i + 1).toString(),      style: TextStyle(        color: primary ? _yellowIsh : _chocolate,        fontFamily: 'Plaster',        fontSize: small ? 40 : 77,      ),    );    return createButton(      puzzle,      small,      i,      content,      color: primary ? _orangeIsh : _yellowIsh,      shape: RoundedRectangleBorder(        side: BorderSide(color: primary ? _chocolate : _orangeIsh, width: 5),        borderRadius: BorderRadius.circular(5),      ),    );  }}

 

样式2

import 'core/puzzle_proxy.dart';import 'flutter.dart';import 'shared_theme.dart';import 'widgets/decoration_image_plus.dart';class ThemeSeattle extends SharedTheme {  @override  String get name => 'Seattle';  const ThemeSeattle();  @override  Color get puzzleThemeBackground => const Color.fromARGB(153, 90, 135, 170);  @override  Color get puzzleBackgroundColor => Colors.white70;  @override  Color get puzzleAccentColor => const Color(0xff000579f);  @override  RoundedRectangleBorder puzzleBorder(bool small) =>      const RoundedRectangleBorder(        borderRadius: BorderRadius.all(          Radius.circular(1),        ),      );  @override  EdgeInsetsGeometry tilePadding(PuzzleProxy puzzle) =>      puzzle.solved ? const EdgeInsets.all(1) : const EdgeInsets.all(4);  @override  Widget tileButton(int i, PuzzleProxy puzzle, bool small) {    if (i == puzzle.tileCount && !puzzle.solved) {      assert(puzzle.solved);    }    final decorationImage = DecorationImagePlus(        puzzleWidth: puzzle.width,        puzzleHeight: puzzle.height,        pieceIndex: i,        fit: BoxFit.cover,        image: const AssetImage('asset/seattle.jpg'));    final correctPosition = puzzle.isCorrectPosition(i);    final content = createInk(      puzzle.solved          ? const Center()          : Container(              decoration: ShapeDecoration(                shape: const CircleBorder(),                color: correctPosition ? Colors.black38 : Colors.white54,              ),              alignment: Alignment.center,              child: Text(                (i + 1).toString(),                style: TextStyle(                  fontWeight: FontWeight.normal,                  color: correctPosition ? Colors.white : Colors.black,                  fontSize: small ? 25 : 42,                ),              ),            ),      image: decorationImage,      padding: EdgeInsets.all(small ? 20 : 32),    );    return createButton(puzzle, small, i, content);  }}

 

样式3

import 'core/puzzle_proxy.dart';import 'flutter.dart';import 'shared_theme.dart';const _accentBlue = Color(0xff000579e);class ThemeSimple extends SharedTheme {  @override  String get name => 'Simple';  const ThemeSimple();  @override  Color get puzzleThemeBackground => Colors.white;  @override  Color get puzzleBackgroundColor => Colors.white70;  @override  Color get puzzleAccentColor => _accentBlue;  @override  RoundedRectangleBorder puzzleBorder(bool small) =>      const RoundedRectangleBorder(        side: BorderSide(color: Colors.black26, width: 1),        borderRadius: BorderRadius.all(          Radius.circular(4),        ),      );  @override  Widget tileButton(int i, PuzzleProxy puzzle, bool small) {    if (i == puzzle.tileCount) {      assert(puzzle.solved);      return const Center(        child: Icon(          Icons.thumb_up,          size: 72,          color: _accentBlue,        ),      );    }    final correctPosition = puzzle.isCorrectPosition(i);    final content = createInk(      Center(        child: Text(          (i + 1).toString(),          style: TextStyle(            color: Colors.white,            fontWeight: correctPosition ? FontWeight.bold : FontWeight.normal,            fontSize: small ? 30 : 49,          ),        ),      ),    );    return createButton(      puzzle,      small,      i,      content,      color: const Color.fromARGB(255, 13, 87, 155),    );  }}

 

 

AS运行日志

Syncing files to device Android SDK built for x86...

D/EGL_emulation( 5021): eglMakeCurrent: 0xdf11a0c0: ver 3 0 (tinfo 0xdf10f070)

I/OpenGLRenderer( 5021): Davey! duration=2287ms; Flags=1, IntendedVsync=258843971182, Vsync=258843971182, OldestInputEvent=9223372036854775807, NewestInputEvent=0, HandleInputStart=258859266525, AnimationStart=258862226525, PerformTraversalsStart=258866966525, DrawStart=261012198525, SyncQueued=261017953525, SyncStart=261022699525, IssueDrawCommandsStart=261034468525, SwapBuffers=261089841525, FrameCompleted=261136182525, DequeueBufferDuration=29212000, QueueBufferDuration=1417000, 

I/Choreographer( 5021): Skipped 133 frames!  The application may be doing too much work on its main thread.

D/EGL_emulation( 5021): eglMakeCurrent: 0xf247fb20: ver 3 0 (tinfo 0xe7152e30)

 

运行效果:

 

标签:const,拼图,puzzle,dart,small,GAME,override,import,Flutter
来源: https://blog.csdn.net/keny88888/article/details/106933142

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有