Flutter直播间弹幕效果实现

主要实现直播间弹幕的布局,弹幕滚动,弹幕淡出效果,去除ListView的水波纹
1.danmu.dart布局的实现

(1)使用Text.rich组件完成弹幕文字部分的布局,TextSpan的children可以放入WidgetSpan和TextSpan实现文字超出部分自动换行。

(2)使用ShaderMask可以实现弹幕的淡出效果

(3)去水波纹:重写ScrollBehavior类,将showLeading和showTrailing属性设为false,然后使用ScrollConfiguration重构滚动组件(代码在下方)

(4)弹幕滚动效果:给listView绑定一个controller,这里名为listController,在更新完数据之后通过listController的跳转动画跳转到指定位置

listController.animateTo(listController.offset+100, duration: const Duration(milliseconds: 500), curve: Curves.easeInOutSine);
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'package:get/get.dart';
import 'package:getxapp/danmu/controller.dart';

class Danmu extends StatelessWidget {
  const Danmu({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(DanmuController(), tag: "danmuPageController");
    Widget viewItem(index) {
      return Container(
        padding: const EdgeInsets.only(left: 10, right: 10, bottom: 5),
        child: Text.rich(TextSpan(children: [
          WidgetSpan(
              alignment: ui.PlaceholderAlignment.middle,
              child: Container(
                margin: const EdgeInsets.only(bottom: 3),
                padding: const EdgeInsets.all(3),
                decoration: BoxDecoration(
                    color: Colors.blue[900],
                    borderRadius: BorderRadius.circular(10)),
                child: Text(
                  "LV$index",
                  style: const TextStyle(color: Colors.white, fontSize: 8),
                ),
              )),
          TextSpan(
              text: '  用户名$index',
              style: const TextStyle(color: Colors.cyan, fontSize: 12)),
          const TextSpan(
              text: '  文本内容文本内容文本内容文本内容文本内容文本内容文本内容文本内容文本内容文本内容文本内容',
              style: TextStyle(color: Colors.white, fontSize: 12)),
        ])),
      );
    }

    return Scaffold(
        body: Stack(
      children: [
        Container(
          decoration: const BoxDecoration(
              gradient: LinearGradient(
                  begin: Alignment.topCenter,
                  end: Alignment.bottomCenter,
                  colors: [
                Colors.black54,
                Colors.black54,
                Colors.black87,
                Colors.black87,
                Colors.black87,
                Colors.black54
              ])),
        ),
        Positioned(
            bottom: 0,
            child: SizedBox(
              height: MediaQuery.of(context).size.height,
              child: Column(
                children: [
                  Expanded(
                      child: Container(
                    padding: const EdgeInsets.only(bottom: 10),
                    child: Image.network(
                      'http://pages.ctrip.com/commerce/promote/20180718/yxzy/img/640sygd.jpg',
                      key: const Key("img"),
                      loadingBuilder: (context, child, loadingProgress) {
                        return Container(
                          color: Colors.blue,
                        );
                      },
                      errorBuilder: (context, obj, stackTrace) {
                        return Container(
                          decoration: const BoxDecoration(
                              gradient: LinearGradient(
                                  begin: Alignment.topLeft,
                                  end: Alignment.bottomRight,
                                  colors: [
                                Colors.grey,
                                Colors.grey,
                                Colors.grey,
                                Colors.blueGrey,
                                Colors.grey,
                                Colors.grey
                              ])),
                        );
                      },
                    ),
                    width: MediaQuery.of(context).size.width,
                  )),
                  ShaderMask(
                      shaderCallback: (rect) {
                        // add transparent gradient to lyric top and bottom.
                        return ui.Gradient.linear(
                          Offset(rect.width / 2, 0),
                          Offset(
                            rect.width / 2,
                            MediaQuery.of(context).size.height / 3,
                          ),
                          [
                            Colors.white.withOpacity(0),
                            Colors.white,
                            Colors.white,
                            Colors.white.withOpacity(0.5),
                          ],
                          const [0, 0.15, 0.85, 1],
                        );
                      },
                      child: SizedBox(
                          width: MediaQuery.of(context).size.width,
                          height: MediaQuery.of(context).size.height / 3,
                          child: ScrollConfiguration(
                              behavior: EUMNoScrollBehavior(),
                              child: Obx(() => ListView.builder(
                                  controller: controller.listController,
                                  // physics: const BouncingScrollPhysics(),
                                  physics: const AlwaysScrollableScrollPhysics(),
                                  // shrinkWrap: true,
                                  itemCount: controller.dataList.length,
                                  itemBuilder: (context, index) {
                                    return viewItem(controller.dataList[index]);
                                  }))))),
                  const SizedBox(
                    height: 10,
                  ),
                  SizedBox(
                    width: MediaQuery.of(context).size.width,
                    child: Row(
                      children: [
                        const SizedBox(
                          width: 10,
                        ),
                        Expanded(
                          flex: 2,
                          child: Container(
                              padding: const EdgeInsets.only(
                                  left: 5, right: 5, top: 5, bottom: 5),
                              decoration: BoxDecoration(
                                  color: Colors.black26,
                                  borderRadius: BorderRadius.circular(20)),
                              child: const Text(
                                "说点什么",
                                style: TextStyle(color: Colors.white),
                              )),
                        ),
                        const SizedBox(
                          width: 10,
                        ),
                        Expanded(
                          flex: 1,
                          child: InkWell(
                              onTap: () {
                                debugPrint("发送");
                                controller.addData();
                              },
                              child: Container(
                                  padding: const EdgeInsets.all(5),
                                  decoration: BoxDecoration(
                                      borderRadius: BorderRadius.circular(20),
                                      color: Colors.black),
                                  child: const Text("发送",
                                      textAlign: TextAlign.center,
                                      style: TextStyle(
                                        color: Colors.white,
                                      )))),
                        ),
                        const SizedBox(
                          width: 10,
                        ),
                      ],
                    ),
                  ),
                  const SizedBox(
                    height: 10,
                  ),
                ],
              ),
            )),
        Positioned(
            top: 30,
            left: 5,
            child: IconButton(
              iconSize: 30,
              icon: const Icon(Icons.arrow_back),
              onPressed: () {
                debugPrint('返回s');
                Get.back();
              },
            )),
      ],
    ));
  }
}

class EUMNoScrollBehavior extends ScrollBehavior {
  @override
  Widget buildViewportChrome(
      BuildContext context, Widget child, AxisDirection axisDirection) {
    switch (getPlatform(context)) {
      case TargetPlatform.iOS:
        return child;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return GlowingOverscrollIndicator(
          child: child,
          // 不显示头部水波纹
          showLeading: false,
          // 不显示尾部水波纹
          showTrailing: false,
          axisDirection: axisDirection,
          color: Theme.of(context).accentColor,
        );
      case TargetPlatform.linux:
        break;
      case TargetPlatform.macOS:
        break;
      case TargetPlatform.windows:
        break;
    }
    return child;
  }
}

 2.controller.dart   getx控制器

import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';

class DanmuController extends GetxController{
  List dataList = [
    "文字内容1",
    "文字内容2"
  ].obs;
  ScrollController listController = ScrollController();

  @override
  void onInit() {
    // TODO: implement onInit
    super.onInit();
  }
  addData(){
    print("更新");
    dataList.add("文字内容");
    listController.animateTo(listController.offset+100, duration: const Duration(milliseconds: 500), curve: Curves.easeInOutSine);
    // update();
  }

  @override
  void onClose() {
    // TODO: implement onClose
    super.onClose();
  }
}

3.最终效果

 

弹幕

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Flutter 底部弹出动画可以通过使用 BottomSheet widget 和 AnimatedContainer widget 来实现。 首先,创建一个 StatefulWidget,包含一个 bool 变量用于控制 BottomSheet 的显示和隐藏。 ``` class BottomSheetDemo extends StatefulWidget { @override _BottomSheetDemoState createState() => _BottomSheetDemoState(); } class _BottomSheetDemoState extends State<BottomSheetDemo> { bool _isVisible = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Bottom Sheet Demo'), ), body: Center( child: RaisedButton( child: Text('Show Bottom Sheet'), onPressed: () { setState(() { _isVisible = true; }); }, ), ), bottomSheet: _isVisible ? Container( decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: 5.0, spreadRadius: 1.0, offset: Offset(0.0, -1.0), ), ], ), child: SafeArea( child: AnimatedContainer( duration: Duration(milliseconds: 300), height: _isVisible ? 200.0 : 0.0, child: Center( child: Text('This is a Bottom Sheet'), ), ), ), ) : null, ); } } ``` 在上面的代码中,我们使用了一个 RaisedButton 来触发 Bottom Sheet 的显示,当用户点击按钮后,我们将 _isVisible 变量设置为 true,Bottom Sheet 就会显示出来。 Bottom Sheet 的内容是一个 AnimatedContainer,它的高度可以通过修改 _isVisible 变量来控制。在 AnimatedContainer 中,我们设置了一个动画时长为 300 毫秒,当 _isVisible 变量变化时,高度会从 0.0 到 200.0 进行动画过渡。 在 Bottom Sheet 的外部,我们使用了一个 Container 来包装它,并设置了一些阴影效果和背景颜色。我们还使用了 SafeArea 来确保 Bottom Sheet 不会被设备的导航栏遮挡。 通过这种方式,我们可以很容易地实现一个底部弹出动画效果
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值