Flutter了解之入門篇13-1(動畫)

  1. 內(nèi)置動畫組件
  2. 轉(zhuǎn)換動畫(通常命名為xxTransition)

內(nèi)置動畫組件

1. AnimatedPadding(縮放效果,改變padding)
  AnimatedPadding({ 
    Key? key,
    required this.padding,  // 不允許為負,否則異常
    this.child,
    Curve curve = Curves.linear,
    required Duration duration,
    VoidCallback? onEnd,
  });

2. AnimatedPositioned(配合Stack使用,改變定位)
  AnimatedPositioned({
    Key? key,
    required this.child,
    this.left,
    this.top,
    this.right,
    this.bottom,
    this.width,
    this.height,
    // 相比Positioned組件多了如下3個屬性
    Curve curve = Curves.linear,
    required Duration duration,
    VoidCallback? onEnd,
  }) 

3. AnimatedOpacity (圖片漸現(xiàn)過渡效果,改變透明度)
  AnimatedOpacity({
    Key? key,
    this.child,
    required this.opacity,  // 0-1
    Curve curve = Curves.linear,
    required Duration duration,  // 持續(xù)時間
    VoidCallback? onEnd,  // 動畫結(jié)束后的回調(diào)
    this.alwaysIncludeSemantics = false,  // 默認是 false。用于輔助訪問,如果是 true,則不管透明度是多少,都會顯示語義信息(可以輔助朗讀)。
  }) 

4. AnimatedAlign(改變alignment)

5. AnimatedContainer(改變container屬性)
  AnimatedContainer({
    Key? key,
    this.alignment,
    this.padding,
    Color? color,
    Decoration? decoration,
    this.foregroundDecoration,
    double? width,
    double? height,
    BoxConstraints? constraints,
    this.margin,
    this.transform,
    this.transformAlignment,
    this.child,
    this.clipBehavior = Clip.none,
    // 相比Container多了如下3個屬性
    Curve curve = Curves.linear,  // 動畫曲線,默認是線性
    required Duration duration,  // 持續(xù)時間
    VoidCallback? onEnd,  // 動畫結(jié)束后的回調(diào)
  });

6. AnimatedDefaultTextStyle(改變字體樣式)
  AnimatedDefaultTextStyle({
    Key? key,
    required this.child,
    required this.style,
    this.textAlign,
    this.softWrap = true,
    this.overflow = TextOverflow.clip,
    this.maxLines,
    this.textWidthBasis = TextWidthBasis.parent,
    this.textHeightBehavior,
    Curve curve = Curves.linear,
    required Duration duration,
    VoidCallback? onEnd,
  })

7. AnimatedDecoratedBox

示例(AnimatedOpacity圖片漸顯過渡,一張圖逐漸消失另一張圖逐漸顯示)

class _SwtichImageDemoState extends State<SwtichImageDemo> {
  var opacity1 = 1.0;
  var opacity2 = 0.0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('圖片切換'),
        brightness: Brightness.dark,
        backgroundColor: Colors.black,
      ),
      backgroundColor: Colors.black,
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            AnimatedOpacity(
              duration: Duration(milliseconds: 5000),
              opacity: opacity1,
              child: ClipOval(
                child: Image.asset(
                  'images/beauty.jpeg',
                  width: 300,
                  height: 300,
                ),
              ),
              curve: Curves.ease,
            ),
            AnimatedOpacity(
              duration: Duration(milliseconds: 5000),
              opacity: opacity2,
              child: ClipOval(
                child: Image.asset(
                  'images/beauty2.jpeg',
                  width: 300,
                  height: 300,
                ),
              ),
              curve: Curves.ease,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Text(
          '變',
          style: TextStyle(
            color: Colors.white,
          ),
          textAlign: TextAlign.center,
        ),
        onPressed: () {
          setState(() {
            opacity1 = 0.0;
            opacity2 = 1.0;
          });
        },
      ),
    );
  }
}

示例(AnimatedPositioned 火箭發(fā)射)

class RocketLaunch extends StatefulWidget {
  RocketLaunch({Key? key}) : super(key: key);
  @override
  _RocketLaunchState createState() => _RocketLaunchState();
}
class _RocketLaunchState extends State<RocketLaunch> {
  var rocketBottom = -80.0;
  var rocketWidth = 160.0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('火箭發(fā)射'),
        brightness: Brightness.dark,
        backgroundColor: Colors.black,
      ),
      backgroundColor: Colors.black,
      body: Center(
        child: Stack(
          alignment: Alignment.bottomCenter,
          children: [
            Image.asset(
              'images/earth.jpeg',
              height: double.infinity,
              fit: BoxFit.fill,
            ),
            AnimatedPositioned(
              child: Image.asset(
                'images/rocket.png',
                fit: BoxFit.fitWidth,
              ),
              bottom: rocketBottom,
              width: rocketWidth,
              duration: Duration(seconds: 5),
              curve: Curves.easeInCubic,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Text(
          '發(fā)射',
          style: TextStyle(
            color: Colors.white,
          ),
          textAlign: TextAlign.center,
        ),
        onPressed: () {
          setState(() {
            rocketBottom = MediaQuery.of(context).size.height;
            rocketWidth = 40.0;
          });
        },
      ),
    );
  }
}

示例(AnimatedPadding、AnimatedPositioned、AnimatedAlign、AnimatedContainer、AnimatedDefaultTextStyle、AnimatedDecoratedBox)

import 'package:flutter/material.dart';
class AnimatedWidgetsTest extends StatefulWidget {
  @override
  _AnimatedWidgetsTestState createState() => _AnimatedWidgetsTestState();
}
class _AnimatedWidgetsTestState extends State<AnimatedWidgetsTest> {
  double _padding = 10;
  var _align = Alignment.topRight;
  double _height = 100;
  double _left = 0;
  Color _color = Colors.red;
  TextStyle _style = TextStyle(color: Colors.black);
  Color _decorationColor = Colors.blue;
  @override
  Widget build(BuildContext context) {
    var duration = Duration(seconds: 5);
    return SingleChildScrollView(
      child: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () {
              setState(() {
                _padding = 20;
              });
            },
            child: AnimatedPadding(
              duration: duration,
              padding: EdgeInsets.all(_padding),
              child: Text("AnimatedPadding"),
            ),
          ),
          SizedBox(
            height: 50,
            child: Stack(
              children: <Widget>[
                AnimatedPositioned(
                  duration: duration,
                  left: _left,
                  child: RaisedButton(
                    onPressed: () {
                      setState(() {
                        _left = 100;
                      });
                    },
                    child: Text("AnimatedPositioned"),
                  ),
                )
              ],
            ),
          ),
          Container(
            height: 100,
            color: Colors.grey,
            child: AnimatedAlign(
              duration: duration,
              alignment: _align,
              child: RaisedButton(
                onPressed: () {
                  setState(() {
                    _align = Alignment.center;
                  });
                },
                child: Text("AnimatedAlign"),
              ),
            ),
          ),
          AnimatedContainer(
            duration: duration,
            height: _height,
            color: _color,
            child: FlatButton(
              onPressed: () {
                setState(() {
                  _height = 150;
                  _color = Colors.blue;
                });
              },
              child: Text(
                "AnimatedContainer",
                style: TextStyle(color: Colors.white),
              ),
            ),
          ),
          AnimatedDefaultTextStyle(
            child: GestureDetector(
              child: Text("hello world"),
              onTap: () {
                setState(() {
                  _style = TextStyle(
                    color: Colors.blue,
                    decorationStyle: TextDecorationStyle.solid,
                    decorationColor: Colors.blue,
                  );
                });
              },
            ),
            style: _style,
            duration: duration,
          ),
          AnimatedDecoratedBox(
            duration: duration,
            decoration: BoxDecoration(color: _decorationColor),
            child: FlatButton(
              onPressed: () {
                setState(() {
                  _decorationColor = Colors.red;
                });
              },
              child: Text(
                "AnimatedDecoratedBox",
                style: TextStyle(color: Colors.white),
              ),
            ),
          )
        ].map((e) {
          return Padding(
            padding: EdgeInsets.symmetric(vertical: 16),
            child: e,
          );
        }).toList(),
      ),
    );
  }
}

轉(zhuǎn)換動畫(通常命名為xxTransition)

蘋果風(fēng)格的全屏轉(zhuǎn)換動效
CupertinoFullscreenDialogTransition({
  Key? key,
  required Animation<double> primaryRouteAnimation,
  required Animation<double> secondaryRouteAnimation,
  required this.child,
  required bool linearTransition,
}) 
/*
看一下CupertinoFullscreenDialogTransition的build方法:
// 使用了兩個SlideTransition實現(xiàn)該動效。
Widget build(BuildContext context) {
  assert(debugCheckHasDirectionality(context));
  final TextDirection textDirection = Directionality.of(context);
  return SlideTransition(
    position: _secondaryPositionAnimation,
    textDirection: textDirection,
    transformHitTests: false,
    child: SlideTransition(
      position: _positionAnimation,
      child: child,
    ),
  );
}
*/

橫向轉(zhuǎn)換(可實現(xiàn)抽屜效果)
CupertinoPageTransition({
  Key? key,
  required Animation<double> primaryRouteAnimation,
  required Animation<double> secondaryRouteAnimation,
  required this.child,
  required bool linearTransition,
})

更改 子組件的外框的特性來實現(xiàn)動效
DecoratedBoxTransition

滑動轉(zhuǎn)換
SlideTransition({  // AnimatedWidget的子類
  Key? key,
  // 使用AnimationController控制,是一個比例偏移。
  // new_x = width * dx; new_y = height * dy;
  // 如果想讓組件從左邊滑入,可以設(shè)置dx為負值。
  required Animation<Offset> position, 
  this.transformHitTests = true,
  this.textDirection,
  this.child,
})

旋轉(zhuǎn)轉(zhuǎn)換
RotationTransition({
  Key? key, 
  required Animation<double> turns, 
  Alignment alignment, 
  FilterQuality? filterQuality, 
  Widget? child
})

尺寸轉(zhuǎn)換
SizeTransition({
  Key? key,
  this.axis = Axis.vertical,  // vertical則更改高度;horizontal則更改寬度。
  required Animation<double> sizeFactor, // 控制組件尺寸變化的 Animation 對象,乘數(shù)。
  this.axisAlignment = 0.0, // 子組件的對齊位置,默認為0.0從中間開始更改尺寸(橫向時可實現(xiàn)卷軸從中間向兩邊打開效果)。當axis為vertical時,-1.0代表頂部對齊開始動畫(即尺寸從上到下開始變大);當 axis 為horizontal 時,開始的方向和文本的反向有關(guān)(TextDirection.ltr 還是 TextDirection.rtl),當文本為從左到右時(TextDirection.ltr,默認),-1.0表示從左側(cè)開始動畫(即尺寸從左到右開始變大)。
  this.child,
}) 

縮放轉(zhuǎn)換
ScaleTransition({
  Key? key,
  required Animation<double> scale,
  this.alignment = Alignment.center,
  this.child,
})

更改組件在Stack中的位置
PositionedTransition

漸顯轉(zhuǎn)換
FadeTransition

示例(FadeTransition 漸顯轉(zhuǎn)換)

Widget build(BuildContext context) {
  return Container(
    color: Colors.white,
    child: FadeTransition(
      opacity: _animation,
      child: const Padding(padding: EdgeInsets.all(8), child: FlutterLogo()),
    ),
  );
}

示例(PositionedTransition 更改組件在Stack中的位置)

@override
Widget build(BuildContext context) {
  const double smallLogo = 100;
  const double bigLogo = 200;
  return LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
      final Size biggest = constraints.biggest;
      return Stack(
        children: <Widget>[
          PositionedTransition(
            rect: RelativeRectTween(
              begin: RelativeRect.fromSize(
                  const Rect.fromLTWH(0, 0, smallLogo, smallLogo), biggest),
              end: RelativeRect.fromSize(
                  Rect.fromLTWH(biggest.width - bigLogo,
                      biggest.height - bigLogo, bigLogo, bigLogo),
                  biggest),
            ).animate(CurvedAnimation(
              parent: _controller,
              curve: Curves.elasticInOut,
            )),
            child: const Padding(
                padding: EdgeInsets.all(8), child: FlutterLogo()),
          ),
        ],
      );
    },
  );
}

示例(DecoratedBoxTransition 更改外框)

class _MyStatefulWidgetState extends State<MyStatefulWidget>
    with TickerProviderStateMixin {
  final DecorationTween decorationTween = DecorationTween(
    begin: BoxDecoration(
      color: const Color(0xFFFFFFFF),
      border: Border.all(style: BorderStyle.none),
      borderRadius: BorderRadius.circular(60.0),
      shape: BoxShape.rectangle,
      boxShadow: const <BoxShadow>[
        BoxShadow(
          color: Color(0x66666666),
          blurRadius: 10.0,
          spreadRadius: 3.0,
          offset: Offset(0, 6.0),
        )
      ],
    ),
    end: BoxDecoration(
      color: const Color(0xFFFFFFFF),
      border: Border.all(
        style: BorderStyle.none,
      ),
      borderRadius: BorderRadius.zero,
      // No shadow.
    ),
  );
  late final AnimationController _controller = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 3),
  )..repeat(reverse: true);
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Center(
        child: DecoratedBoxTransition(
          position: DecorationPosition.background,
          decoration: decorationTween.animate(_controller),
          child: Container(
            width: 200,
            height: 200,
            padding: const EdgeInsets.all(10),
            child: const FlutterLogo(),
          ),
        ),
      ),
    );
  }
}

示例(SlideTransition 滑動)

// 一張圖片左側(cè)滑入,一張圖片右側(cè)劃出。
class SlideTransitionDemo extends StatefulWidget {
  SlideTransitionDemo({Key? key}) : super(key: key);
  @override
  _SlideTransitionDemoState createState() => _SlideTransitionDemoState();
}
class _SlideTransitionDemoState extends State<SlideTransitionDemo>
    with SingleTickerProviderStateMixin {
  bool _forward = true;
  final begin = Offset.zero;
  // 第一張圖片結(jié)束位置移出右側(cè)屏幕
  final end1 = Offset(1.1, 0.0);
  // 第二張圖片的初始位置在左側(cè)屏幕
  final begin2 = Offset(-1.1, 0.0);
  late Tween<Offset> tween1 = Tween(begin: begin, end: end1);
  late Tween<Offset> tween2 = Tween(begin: begin2, end: begin);
  late AnimationController _controller =
      AnimationController(duration: const Duration(seconds: 1), vsync: this);
  // 使用自定義曲線動畫過渡效果
  late Animation<Offset> _animation1 = tween1.animate(
    CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ),
  );
  late Animation<Offset> _animation2 = tween2.animate(CurvedAnimation(
    parent: _controller,
    curve: Curves.easeInOut,
  ));
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SlideTransition'),
        brightness: Brightness.dark,
        backgroundColor: Colors.black,
      ),
      backgroundColor: Colors.black,
      body: Center(
        child: Container(
          padding: EdgeInsets.all(10.0),
          child: Stack(
            children: [
              SlideTransition(
                child: ClipOval(
                  child: Image.asset('images/beauty.jpeg'),
                ),
                position: _animation1,
              ),
              SlideTransition(
                child: ClipOval(
                  child: Image.asset('images/beauty2.jpeg'),
                ),
                position: _animation2,
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.swap_horizontal_circle_sharp),
        onPressed: () {
          setState(() {
            if (_forward) {
              _controller.forward();
            } else {
              _controller.reverse();
            }
            _forward = !_forward;
          });
        },
      ),
    );
  }
}

示例(SizeTransition)

// 圖片從左向右飛入
class SizeTransitionDemo extends StatefulWidget {
  SizeTransitionDemo({Key? key}) : super(key: key);
  @override
  _SizeTransitionDemoState createState() => _SizeTransitionDemoState();
}
class _SizeTransitionDemoState extends State<SizeTransitionDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller =
      AnimationController(duration: const Duration(seconds: 3), vsync: this)
        ..repeat();
  late Animation<double> _animation = CurvedAnimation(
      parent: _controller, curve: Curves.fastLinearToSlowEaseIn);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SizeTransition'),
        brightness: Brightness.dark,
        backgroundColor: Colors.blue,
      ),
      body: SizeTransition(
        child: Center(
          child: Image.asset(
            'images/superman.png',
            width: 300.0,
            height: 300.0,
          ),
        ),
        sizeFactor: _animation,
        axis: Axis.horizontal,
        axisAlignment: 1.0,
      ),
    );
  }
  @override
  void dispose() {
    _controller.stop();
    _controller.dispose();
    super.dispose();
  }
}

示例(ScaleTransition)

class ScaleTransitionDemo extends StatefulWidget {
  ScaleTransitionDemo({Key? key}) : super(key: key);
  @override
  _ScaleTransitionDemoState createState() => _ScaleTransitionDemoState();
}
class _ScaleTransitionDemoState extends State<ScaleTransitionDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller =
      AnimationController(duration: const Duration(seconds: 10), vsync: this)
        ..repeat();
  late Animation<double> _animation =
      CurvedAnimation(parent: _controller, curve: Curves.easeOut);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ScaleTransition'),
        brightness: Brightness.dark,
        backgroundColor: Colors.blue,
      ),
      body: Center(
        child: balloon(),
      ),
    );
  }
  @override
  void dispose() {
    _controller.stop();
    _controller.dispose();
    super.dispose();
  }
  Widget balloon() {
    return ScaleTransition(
      alignment: Alignment.bottomCenter,
      child: Image.asset(
        'images/balloon.png',
      ),
      scale: _animation,
    );
  }
}

其他

Transform 組件

對子組件進行轉(zhuǎn)換操作

定義如下:
Transform({
    Key? key,
    required this.transform,  // 一個Matrix4 對象,用于定義三維空間的變換操作。
    this.origin,  // 一個坐標偏移量,實際會加入到 Matrix4 的 translation(平移)中。
    this.alignment,  // 轉(zhuǎn)變進行的參考方位
    this.transformHitTests = true,
    Widget? child,  // 
  }) : assert(transform != null),
       super(key: key, child: child);

TweenAnimationBuilder組件(由用戶觸發(fā)動畫)

TweenAnimationBuilder({
  Key? key,
  required this.tween,  // Twee<T>類型,動畫過程中會把 Tween 的中間插值傳給 builder 來構(gòu)建子組件,從而可以實現(xiàn)過渡動畫效果。
  required Duration duration,
  Curve curve = Curves.linear,
  // 構(gòu)建組件。value參數(shù)為tween動畫過程中的中間插值,動畫期間會不斷調(diào)用builder重新繪制子組件。從源碼中可看出初始化時tween起始值和結(jié)束值不一致就會啟動動畫。
  // typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, Widget? child);
  required this.builder,  
  VoidCallback? onEnd,
  this.child,
}) 

示例(濾鏡)

class TweenAnimationDemo extends StatefulWidget {
  TweenAnimationDemo({Key? key}) : super(key: key);
  @override
  _TweenAnimationDemoState createState() => _TweenAnimationDemoState();
}
class _TweenAnimationDemoState extends State<TweenAnimationDemo> {
  var _sliderValue = 0.0;
  Color _newColor = Colors.orange;
  @override
  void initState() {
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TweenAnimationBuilder'),
        brightness: Brightness.dark,
        backgroundColor: Colors.black,
      ),
      backgroundColor: Colors.black,
      body: Center(
        child: Column(
          children: [
            TweenAnimationBuilder(
              tween: ColorTween(
                begin: Colors.white,
                end: _newColor,
              ),
              duration: Duration(seconds: 1),
              builder: (_, color, child) {
                // 對子組件的每一個像素進行顏色過濾。實際上是插入了一個顏色層,從而看起來有濾鏡效果。
                return ColorFiltered(  
                  colorFilter:
                      ColorFilter.mode(color as Color, BlendMode.modulate),
                  child: ClipOval(
                    child: ClipOval(
                      child: Image.asset(
                        'images/beauty.jpeg',
                        width: 300,
                      ),
                    ),
                  ),
                );
              },
            ),
            Slider.adaptive(
              value: _sliderValue,
              onChanged: (value) {
                setState(() {
                  _sliderValue = value;
                });
              },
              onChangeEnd: (value) {
                setState(() {
                  _newColor = _newColor.withRed((value * 255).toInt());
                });
              },
            )
          ],
        ),
      ),
    );
  }
}

AnimatedModelBarrier

ModalBarrier 的替換,可以擋住它下層的組件使得這些組件無法與用戶交互,并且在組件上加一層顏色動畫過渡遮罩。

AnimatedModalBarrier({  
  Key? key,
  required Animation<Color?> color,
  bool dismissible,  // 為true時點擊遮罩會退出當前頁返回到上一頁
  String? semanticsLabel,
  bool? barrierSemanticsDismissible
})

AnimatedPhysicalModel

控制組件的陰影、顏色、邊框圓弧等物理模型,但組件自身的形狀不發(fā)生改變

AnimatedPhysicalModel({
  Key? key,
  required Widget child,
  required BoxShape shape,
  Clip clipBehavior,
  BorderRadius borderRadius,
  required double elevation,
  required Color color,
  bool animateColor,
  required Color shadowColor,
  bool animateShadowColor,
  Curve curve = Curves.linear,
  required Duration duration,
  VoidCallback? onEnd
})

示例

更改elevation 屬性實現(xiàn)Z 軸陰影的變化

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('AnimatedPhysicalModel 動畫'),
    ),
    body: Center(
      child: AnimatedPhysicalModel(
        child: Container(
          width: 300,
          height: 300,
        ),
        duration: Duration(seconds: 1),
        color: _elevation == 0.0 ? Colors.blue : Colors.green,
        animateColor: true,
        animateShadowColor: true,
        elevation: _elevation,
        shape: BoxShape.circle,
        shadowColor: Colors.blue[900]!,
        curve: Curves.easeInOutCubic,
      ),
    ),
    floatingActionButton: FloatingActionButton(
      child: Text(
        'Play',
        style: TextStyle(
          color: Colors.white,
        ),
        textAlign: TextAlign.center,
      ),
      onPressed: () {
        setState(() {
          _elevation = _elevation == 0 ? 10.0 : 0.0;
        });
      },
    ),
  );
}

AnimatedSize

Widget build(BuildContext context) {
  return GestureDetector(
    onTap: () => _updateSize(),
    child: Container(
      color: Colors.amberAccent,
      child: AnimatedSize(
        curve: Curves.easeIn,
        duration: const Duration(seconds: 1),
        child: FlutterLogo(size: _size),
      ),
    ),
  );
}

三方庫

  1. animations 三方庫
1. Container Transform
  轉(zhuǎn)場時將兩個頁面的元素聯(lián)系使得轉(zhuǎn)場更為平滑。
  類似Hero動畫。
2. Shared Axis
  共享軸,適用于UI元素之間有空間或引導(dǎo)聯(lián)系的場景,通過在x,y或z軸的轉(zhuǎn)換,實現(xiàn)界面之間的聯(lián)系來進行動畫過渡。
3. Fade Through
  通過快速漸現(xiàn)和消失來實現(xiàn)沒有關(guān)聯(lián)UI界面的切換,避免突兀。
4. Fade
  彈窗動效。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容