質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Q&A

解決済

1回答

1445閲覧

Flutter: Animation<double>型の変数にListenableを代入するとエラーになる

massanmesu

総合スコア36

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

0グッド

0クリップ

投稿2021/07/30 09:48

Codelabのサンプルアプリ作成サイトでECアプリを作成中です。ホーム画面のAppBarのTitleを変更する作業をしています。

変更前変更後
イメージ説明イメージ説明

titleプロパティにじAnimatedWidgetを拡張させた_BackdropTitleを定義し引数listenableにAnimationControllerを渡しています。

import 'package:Shrine/login.dart'; import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; import 'model/product.dart'; import 'login.dart'; const double _kFlingVelocity = 2.0; class Backdrop extends StatefulWidget { final Category currentCategory; final Widget frontLayer; final Widget backLayer; final Widget frontTitle; final Widget backTitle; const Backdrop({ Key? key, required this.currentCategory, required this.frontLayer, required this.backLayer, required this.frontTitle, required this.backTitle, }) : super(key: key); @override _BackdropState createState() => _BackdropState(); } class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin { final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop'); late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: Duration(milliseconds: 300), value: 1.0, ); } @override void dispose() { _controller.dispose(); super.dispose(); } bool get _frontLayerVisible { final AnimationStatus status = _controller.status; return status == AnimationStatus.completed || status == AnimationStatus.forward; } void _toggleBackdropLayerVisibility() { _controller.fling( velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity); } Widget _buildStack(BuildContext context, BoxConstraints constraints) { const double layerTitleHeight = 48.0; final Size layerSize = constraints.biggest; final double layerTop = layerSize.height - layerTitleHeight; Animation<RelativeRect> layerAnimation = RelativeRectTween( begin: RelativeRect.fromLTRB( 0.0, layerTop, 0.0, layerTop - layerSize.height), end: RelativeRect.fromLTRB(0, 0, 0, 0), ).animate(_controller.view); return Stack( key: _backdropKey, children: [ ExcludeSemantics( child: widget.backLayer, excluding: _frontLayerVisible, ), PositionedTransition( rect: layerAnimation, child: _FrontLayer( onTap: _toggleBackdropLayerVisibility, child: widget.frontLayer), ), ], ); } @override void didUpdateWidget(Backdrop old) { super.didUpdateWidget(old); if (widget.currentCategory != old.currentCategory) { _toggleBackdropLayerVisibility(); } else if (!_frontLayerVisible) { _controller.fling(velocity: _kFlingVelocity); } } @override Widget build(BuildContext context) { var appBar = AppBar( brightness: Brightness.light, elevation: 0.0, titleSpacing: 0.0, title: _BackdropTitle( // AppBarのtitleプロパティにセット listenable: _controller.view, onPress: _toggleBackdropLayerVisibility, frontTitle: widget.frontTitle, backTitle: widget.backTitle, ), actions: [ IconButton( icon: Icon( Icons.search, semanticLabel: 'login', ), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => LoginPage()), ); }), IconButton( icon: Icon( Icons.tune, semanticLabel: 'login', ), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => LoginPage()), ); }), ], ); return Scaffold( appBar: appBar, body: LayoutBuilder( builder: _buildStack, ), ); } } class _FrontLayer extends StatelessWidget { const _FrontLayer({Key? key, required this.child, required this.onTap}) : super(key: key); final Widget child; final VoidCallback onTap; @override Widget build(BuildContext context) { return Material( elevation: 16.0, shape: BeveledRectangleBorder( borderRadius: BorderRadius.only(topLeft: Radius.circular(46.0)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: onTap, child: Container( height: 40.0, alignment: AlignmentDirectional.centerStart, ), ), Expanded(child: child), ], ), ); } }

そして、_BackdropTitleのビルド内でAnimation<double>の変数animationに渡したコントローラーを代入するとエラーが発生します。

class _BackdropTitle extends AnimatedWidget { final Function onPress; final Widget frontTitle; final Widget backTitle; const _BackdropTitle({ Key? key, required Listenable listenable, required this.onPress, required this.frontTitle, required this.backTitle, }) : assert(frontTitle != null), assert(backTitle != null), super(key: key, listenable: listenable); @override Widget build(BuildContext context) { final Animation<double> animation = this.listenable; // ここでエラー発生 return DefaultTextStyle( style: Theme.of(context).primaryTextTheme.headline6!, softWrap: false, overflow: TextOverflow.ellipsis, child: Row(children: <Widget>[ // branded icon SizedBox( width: 72.0, child: IconButton( padding: EdgeInsets.only(right: 8.0), onPressed: () => this.onPress, icon: Stack(children: <Widget>[ Opacity( opacity: animation.value, child: ImageIcon(AssetImage('assets/slanted_menu.png')), ), FractionalTranslation( translation: Tween<Offset>( begin: Offset.zero, end: Offset(1.0, 0.0), ).evaluate(animation), child: ImageIcon(AssetImage('assets/diamond.png')), ) ]), ), ), // Here, we do a custom cross fade between backTitle and frontTitle. // This makes a smooth animation between the two texts. Stack( children: <Widget>[ Opacity( opacity: CurvedAnimation( parent: ReverseAnimation(animation), curve: Interval(0.5, 1.0), ).value, child: FractionalTranslation( translation: Tween<Offset>( begin: Offset.zero, end: Offset(0.5, 0.0), ).evaluate(animation), child: backTitle, ), ), Opacity( opacity: CurvedAnimation( parent: animation, curve: Interval(0.5, 1.0), ).value, child: FractionalTranslation( translation: Tween<Offset>( begin: Offset(-0.25, 0.0), end: Offset.zero, ).evaluate(animation), child: frontTitle, ), ), ], ) ]), ); } }
error: A value of type 'Listenable' can't be assigned to a variable of type 'Animation<double>'. (invalid_assignment at [Shrine] lib/backdrop.dart:209)

型の不一致みたいですが、サンプルのコード通りに記述しています。
サイトにもそのようなことは書かれていませんでした。
調べても全くでここない症状のため質問いたしました。
ご協力いただけると助かります。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

invalid assignment (dart docs)

Common fixes:
If the value being assigned is always assignable at runtime,
even though the static types don’t reflect that, then add an explicit cast.

下記のように、asを使って、Animation<double>型にキャストしてやります。

diff

1class _BackdropTitle extends AnimatedWidget { 2 final Function onPress; 3 final Widget frontTitle; 4 final Widget backTitle; 5 6 const _BackdropTitle({ 7 Key? key, 8 required Listenable listenable, 9 required this.onPress, 10 required this.frontTitle, 11 required this.backTitle, 12 }) : assert(frontTitle != null), 13 assert(backTitle != null), 14 super(key: key, listenable: listenable); 15 16 @override 17 Widget build(BuildContext context) { 18- final Animation<double> animation = this.listenable; 19+ final Animation<double> animation = this.listenable as Animation<double>;

補足:
「サイトの記載どおりに書いたのに動かない」という件について、そのサイトのサンプルコードの内容が更新されておらず、最新のFlutter環境に合致していない可能性があります。

同じサイトの3. Codelab のスターター アプリをダウンロードする
の冒頭 「GitHub からクローンを作成する」の記載に従って
git経由でソースコードをダウンロードし、104_completeブランチにチェックアウトすることで、最新の完成版コードを実行することができます。
(サイト記載のサンプルコードとは一部変わっているようです)

投稿2021/07/30 18:06

編集2021/07/30 18:19
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

massanmesu

2021/07/31 05:08

動きました。分かりやすいご説明ありがとうございました。 完成版の存在は知ってはいたのですが、開始時にサイトのボタンからダウンロードしたため、そこからandroid studioでブランチを切る方法がわからなかったことと、自分の手で実装した方が身につくかなぁということもあってあえて見ていませんでした。 兎にも角にも解決できて良かったです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問