Flutter Flare, Rive,它可以用作背景吗?

Posted

技术标签:

【中文标题】Flutter Flare, Rive,它可以用作背景吗?【英文标题】:Flutter Flare, Rive, is it able to use for background? 【发布时间】:2020-09-20 11:09:14 【问题描述】:

我刚刚为我的颤振小部件成功了颤振

但我找不到将其用作背景(容器)的示例

这可能吗?

【问题讨论】:

【参考方案1】:

使用官方示例代码https://github.com/2d-inc/Flare-Flutter/tree/stable/example/teddy/lib 稍作修改,您可以使用StackPositioned 并更改属性值,如top

代码sn-p

return Scaffold(
      backgroundColor: Color.fromRGBO(93, 142, 155, 1.0),
      body: Container(
          child: Stack(
        children: <Widget>[
          Positioned(
              top: 50,
              left:0,
              right: 0,
              child: Container(
                  height: 200,
                  padding:
                  const EdgeInsets.only(left: 30.0, right: 30.0),
                  child: FlareActor(
                    "assets/Teddy.flr",
                    shouldClip: false,
                    alignment: Alignment.bottomCenter,
                    fit: BoxFit.contain,
                    controller: _teddyController,
                  )),),
          Positioned(
            child: SingleChildScrollView(

工作演示

完整代码

import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart';
import 'package:flutter/rendering.dart';
import 'dart:async';
import 'dart:math';
import 'dart:ui';

import 'package:flare_flutter/flare.dart';
import 'package:flare_dart/math/mat2d.dart';
import 'package:flare_dart/math/vec2d.dart';
import 'package:flare_flutter/flare_controls.dart';

// Adapted these helpful functions from:
// https://github.com/flutter/flutter/blob/master/packages/flutter/test/material/text_field_test.dart

// Returns first render editable
RenderEditable findRenderEditable(RenderObject root) 
  RenderEditable renderEditable;
  void recursiveFinder(RenderObject child) 
    if (child is RenderEditable) 
      renderEditable = child;
      return;
    
    child.visitChildren(recursiveFinder);
  

  root.visitChildren(recursiveFinder);
  return renderEditable;


List<TextSelectionPoint> globalize(
    Iterable<TextSelectionPoint> points, RenderBox box) 
  return points.map<TextSelectionPoint>((TextSelectionPoint point) 
    return TextSelectionPoint(
      box.localToGlobal(point.point),
      point.direction,
    );
  ).toList();


Offset getCaretPosition(RenderBox box) 
  final RenderEditable renderEditable = findRenderEditable(box);
  if (!renderEditable.hasFocus) 
    return null;
  
  final List<TextSelectionPoint> endpoints = globalize(
    renderEditable.getEndpointsForSelection(renderEditable.selection),
    renderEditable,
  );
  return endpoints[0].point + const Offset(0.0, -2.0);


class SigninButton extends StatelessWidget 
  final Widget child;
  final Gradient gradient;
  final double width;
  final double height;
  final Function onPressed;

  const SigninButton(
    Key key,
    @required this.child,
    this.gradient,
    this.width = double.infinity,
    this.height = 50.0,
    this.onPressed,
  ) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return Container(
      width: width,
      height: 50.0,
      decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(25.0),
          gradient: LinearGradient(
            colors: <Color>[
              Color.fromRGBO(160, 92, 147, 1.0),
              Color.fromRGBO(115, 82, 135, 1.0)
            ],
          )),
      child: Material(
        color: Colors.transparent,
        child: InkWell(
            onTap: onPressed,
            child: Center(
              child: child,
            )),
      ),
    );
  




class TeddyController extends FlareControls 
  // Store a reference to our face control node (the "ctrl_look" node in Flare)
  ActorNode _faceControl;

  // Storage for our matrix to get global Flutter coordinates into Flare world coordinates.
  Mat2D _globalToFlareWorld = Mat2D();

  // Caret in Flutter global coordinates.
  Vec2D _caretGlobal = Vec2D();

  // Caret in Flare world coordinates.
  Vec2D _caretWorld = Vec2D();

  // Store the origin in both world and local transform spaces.
  Vec2D _faceOrigin = Vec2D();
  Vec2D _faceOriginLocal = Vec2D();

  bool _hasFocus = false;

  // Project gaze forward by this many pixels.
  static const double _projectGaze = 60.0;

  String _password;

  @override
  bool advance(FlutterActorArtboard artboard, double elapsed) 
    super.advance(artboard, elapsed);
    Vec2D targetTranslation;
    if (_hasFocus) 
      // Get caret in Flare world space.
      Vec2D.transformMat2D(_caretWorld, _caretGlobal, _globalToFlareWorld);

      // To make it more interesting, we'll also add a sinusoidal vertical offset.
      _caretWorld[1] +=
          sin(new DateTime.now().millisecondsSinceEpoch / 300.0) * 70.0;

      // Compute direction vector.
      Vec2D toCaret = Vec2D.subtract(Vec2D(), _caretWorld, _faceOrigin);
      Vec2D.normalize(toCaret, toCaret);
      Vec2D.scale(toCaret, toCaret, _projectGaze);

      // Compute the transform that gets us in face "ctrl_face" space.
      Mat2D toFaceTransform = Mat2D();
      if (Mat2D.invert(toFaceTransform, _faceControl.parent.worldTransform)) 
        // Put toCaret in local space, note we're using a direction vector
        // not a translation so transform without translation
        Vec2D.transformMat2(toCaret, toCaret, toFaceTransform);
        // Our final "ctrl_face" position is the original face translation plus this direction vector
        targetTranslation = Vec2D.add(Vec2D(), toCaret, _faceOriginLocal);
      
     else 
      targetTranslation = Vec2D.clone(_faceOriginLocal);
    

    // We could just set _faceControl.translation to targetTranslation, but we want to animate it smoothly to this target
    // so we interpolate towards it by a factor of elapsed time in order to maintain speed regardless of frame rate.
    Vec2D diff =
    Vec2D.subtract(Vec2D(), targetTranslation, _faceControl.translation);
    Vec2D frameTranslation = Vec2D.add(Vec2D(), _faceControl.translation,
        Vec2D.scale(diff, diff, min(1.0, elapsed * 5.0)));

    _faceControl.translation = frameTranslation;

    return true;
  

  // Fetch references for the `ctrl_face` node and store a copy of its original translation.
  @override
  void initialize(FlutterActorArtboard artboard) 
    super.initialize(artboard);
    _faceControl = artboard.getNode("ctrl_face");
    if (_faceControl != null) 
      _faceControl.getWorldTranslation(_faceOrigin);
      Vec2D.copy(_faceOriginLocal, _faceControl.translation);
    
    play("idle");
  

  onCompleted(String name) 
    play("idle");
  

  // Called by [FlareActor] when the view transform changes.
  // Updates the matrix that transforms Global-Flutter-coordinates into Flare-World-coordinates.
  @override
  void setViewTransform(Mat2D viewTransform) 
    Mat2D.invert(_globalToFlareWorld, viewTransform);
  

  // Transform the [Offset] into a [Vec2D].
  // If no caret is provided, lower the [_hasFocus] flag.
  void lookAt(Offset caret) 
    if (caret == null) 
      _hasFocus = false;
      return;
    
    _caretGlobal[0] = caret.dx;
    _caretGlobal[1] = caret.dy;
    _hasFocus = true;
  

  void setPassword(String value) 
    _password = value;
  

  bool _isCoveringEyes = false;
  coverEyes(cover) 
    if (_isCoveringEyes == cover) 
      return;
    
    _isCoveringEyes = cover;
    if (cover) 
      play("hands_up");
     else 
      play("hands_down");
    
  

  void submitPassword() 
    if (_password == "bears") 
      play("success");
     else 
      play("fail");
    
  




typedef void CaretMoved(Offset globalCaretPosition);
typedef void TextChanged(String text);

// Helper widget to track caret position.
class TrackingTextInput extends StatefulWidget 
  TrackingTextInput(
      Key key, this.onCaretMoved, this.onTextChanged, this.hint, this.label, this.isObscured = false)
      : super(key: key);
  final CaretMoved onCaretMoved;
  final TextChanged onTextChanged;
  final String hint;
  final String label;
  final bool isObscured;
  @override
  _TrackingTextInputState createState() => _TrackingTextInputState();


class _TrackingTextInputState extends State<TrackingTextInput> 
  final GlobalKey _fieldKey = GlobalKey();
  final TextEditingController _textController = TextEditingController();
  Timer _debounceTimer;
  @override
  initState() 
    _textController.addListener(() 
      // We debounce the listener as sometimes the caret position is updated after the listener
      // this assures us we get an accurate caret position.
      if (_debounceTimer?.isActive ?? false) _debounceTimer.cancel();
      _debounceTimer = Timer(const Duration(milliseconds: 100), () 
        if (_fieldKey.currentContext != null) 
          // Find the render editable in the field.
          final RenderObject fieldBox =
          _fieldKey.currentContext.findRenderObject();
          Offset caretPosition = getCaretPosition(fieldBox);

          if (widget.onCaretMoved != null) 
            widget.onCaretMoved(caretPosition);
          
        
      );
      if (widget.onTextChanged != null) 
        widget.onTextChanged(_textController.text);
      
    );
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    return Padding(
      padding: const EdgeInsets.only(bottom: 20.0),
      child: TextFormField(
          decoration: InputDecoration(
            hintText: widget.hint,
            labelText: widget.label,
          ),
          key: _fieldKey,
          controller: _textController,
          obscureText: widget.isObscured,
          validator: (value) ),
    );
  


void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  


class MyHomePage extends StatefulWidget 
  MyHomePage(Key key, this.title) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  TeddyController _teddyController;
  @override
  initState() 
    _teddyController = TeddyController();
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    EdgeInsets devicePadding = MediaQuery.of(context).padding;

    return Scaffold(
      backgroundColor: Color.fromRGBO(93, 142, 155, 1.0),
      body: Container(
          child: Stack(
        children: <Widget>[
          Positioned(
              top: 50,
              left:0,
              right: 0,
              child: Container(
                  height: 200,
                  padding:
                  const EdgeInsets.only(left: 30.0, right: 30.0),
                  child: FlareActor(
                    "assets/Teddy.flr",
                    shouldClip: false,
                    alignment: Alignment.bottomCenter,
                    fit: BoxFit.contain,
                    controller: _teddyController,
                  )),),
          Positioned(
            child: SingleChildScrollView(
                padding: EdgeInsets.only(
                    left: 20.0, right: 20.0, top: devicePadding.top + 150.0),
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      Container(
                          decoration: BoxDecoration(
                              color: Colors.white,
                              borderRadius:
                                  BorderRadius.all(Radius.circular(25.0))),
                          child: Padding(
                            padding: const EdgeInsets.all(30.0),
                            child: Form(
                                child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: <Widget>[
                                TrackingTextInput(
                                    label: "Email",
                                    hint: "What's your email address?",
                                    onCaretMoved: (Offset caret) 
                                      _teddyController.lookAt(caret);
                                    ),
                                TrackingTextInput(
                                  label: "Password",
                                  hint: "Try 'bears'...",
                                  isObscured: true,
                                  onCaretMoved: (Offset caret) 
                                    _teddyController.coverEyes(caret != null);
                                    _teddyController.lookAt(null);
                                  ,
                                  onTextChanged: (String value) 
                                    _teddyController.setPassword(value);
                                  ,
                                ),
                                SigninButton(
                                    child: Text("Sign In",
                                        style: TextStyle(
                                            fontFamily: "RobotoMedium",
                                            fontSize: 16,
                                            color: Colors.white)),
                                    onPressed: () 
                                      _teddyController.submitPassword();
                                    )
                              ],
                            )),
                          )),
                    ])),
          ),
        ],
      )),
    );
  

【讨论】:

以上是关于Flutter Flare, Rive,它可以用作背景吗?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 动画,Flare VS GIF

如何在flutter中使用rive的状态机?

在 Flutter 中更改 Rive 2 动画

Flare Flutter 动画问题

如何在颤动中播放 Rive 动画

Flare Flutter 动画