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.最终效果

Flutter直播间弹幕效果实现_第1张图片

 

弹幕

你可能感兴趣的:(flutter,flutter)