阅读 1267

《flutter整活系列》开发个能看电影电视剧的app

最近有空关注了下flutter空安全升级,我也跟着升级了下,重构一下自己写的dart-cms-flutter项目。主要使用flutter + getx, 《getx yyds》

我不会放出采集那些数据,自己处理就好。这里只是说下如何做的

项目地址:Dart-Cms-Flutter

项目截图

未标题-1.jpg

Screenshot_20210909-075322.png

从首页开始

由于使用了getx,所以,首页的部分需要改一些东西,这里需要多提一嘴的是,getx提供了全局注入控制器,相当于全局状态,非常好用,使用GetxService,下面是例子

这是一个收藏记录的例子,

import 'package:get/get.dart';
import 'package:dart_cms_flutter/utils/storage.dart';
import 'package:dart_cms_flutter/interface/videoDetaill.dart';

// 全局响应数据
class StoreService extends GetxService {
  // 历史记录
  RxList<dynamic> storeList = [].obs;

  Future<StoreService> init() async {
    List<Map<String, dynamic>> storeData =
        List.from(StorageUtil().getJSON("store") ?? []);
    storeList.addAll(storeData);
    return this;
  }

  Future<bool> add<T extends VideoDetaillInterFace>(
    T obj,
  ) async {
    // 插入一条新的
    String newKey = obj.Id!;
    // 检查是否存在
    bool isExist = storeList.any((el) => el["Id"] == newKey);
    // 是否超出限制50个存储配额, 并且当前历史记录中没有这个视频
    if (storeList.length >= 50 && !isExist) {
      storeList.removeLast();
    }
    // 当前的id是否已经存在
    if (isExist) {
      // 存在就删除,重新插入,变化位置,插入到最前
      storeList.removeWhere((el) => el["Id"] == newKey);
    }
    // 当前视频的数据 formant
    Map<String, dynamic> curVideoMap = _formantVideoDetaill(
      obj,
    );
    // 插入新的
    storeList.insert(0, curVideoMap);
    // 存入
    // ignore: invalid_use_of_protected_member
    return StorageUtil().setJSON('store', storeList.value);
  }

  Future<bool> removeKey(String keyName) async {
    storeList.removeWhere((element) => true);
    return StorageUtil().remove(keyName);
  }

  Map<String, dynamic> _formantVideoDetaill<T extends VideoDetaillInterFace>(
    T obj,
  ) {
    return {
      "Id": obj.Id,
      "videoTitle": obj.videoTitle,
      "director": obj.director,
      "poster": obj.performer,
      "videoImage": obj.videoImage,
      "video_type": obj.videoType!.name,
      "video_rate": obj.videoRate,
      "update_time": obj.updateTime,
      "language": obj.language,
      "sub_region": obj.subRegion,
      "rel_time": obj.relTime,
      "introduce": obj.introduce,
      "remind_tip": obj.remindTip,
      "popular": obj.popular,
      "allow_reply": obj.allowReply,
      "display": obj.display,
      "scource_sort": obj.scourceSort,
    };
  }
}
复制代码

全局注入,使得控制器不会被回收,下面是注入时的例子

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // debugPaintSizeEnabled = true;
  await initStore();
  runApp(MyApp());
}

Future<void> initStore() async {
  // 初始化http请求
  HttpUtils().init(baseUrl: hostUrl);
  // 初始化单例模式
  await StorageUtil().init();
  // 这里注入历史记录模块
  await Get.putAsync(() => HistoryService().init());
  // 这里注入收藏记录模块
  await Get.putAsync(() => StoreService().init());
  print("全局注入");
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      // designSize: Size(375, 812),
      builder: () => GetMaterialApp(
        debugShowCheckedModeBanner: false,
        initialRoute: PageName.HOME,
        getPages: PageRoutes.routes,
      ),
    );
  }
}
复制代码

在runapp之前注入这些需要的依赖,以及单例对象,

首页,文章页,分类页,页面部分

页面主要还是statefullwdiget, 但是getx给我们提供了一个简便操作,那就是getview,使用getview我们一些一次性请求的页面(指的是数据出现之后不会有下拉刷新这种情况下)使用getview就很方便,比如我上面的文章页面,下面是例子:

// view部分
class AppBarIndexView extends GetView<AppbarIndexViewStore>{

    @override
    Widget build(BuildContext context) {
        // 这里注入控制器
        AppbarIndexViewStore controller = Get.put(AppbarIndexViewStore());
        return controller.obx(
            // 这里是成功时候展现的内容
            (state) => Widget,
            // 这里是loading时候的内容
            onLoading: widget,
            // 这里是为空数据时候的内容
            onEmpty: widget,
            // 这里是失败时候的内
            onError: widget,
        )
    }
}

// 控制器部分
class AppbarIndexViewStore extends GetxController with SingleGetTickerProviderMixin,StateMixin {

    initEvent(){
        // 改变状态,调用change方法
        change("失败", status: RxStatus.error());
        change("成功", status: RxStatus.success());
        change("加载中", status: RxStatus.loading());
    }

    @override
    void onInit() {
        super.onInit();
        // 初始化控制要运行啥
        initEvent();
    }
}
复制代码

关键部分,视频播放器

flutter的视频播放器,我发过一篇水贴,各位可以看下,flutter这边目前你能够找到的播放器,比如chewie,betterplayer,这些都没有滑动快进上下滑动改变音量和屏幕亮度。所以对于你想开箱即用,还能满足一下下自己,很难。据我观察许久。如果你和我一样是一个垃圾小前端(大废物)。那么你最好是基于fijkplayer。

说到fijkplayer,必须得提一嘴,我基于fijkplayer开发的皮肤,在fijkplayer作者皮肤的基础之上开发的,加入了手势上下滑动改变音量和屏幕亮度,左右滑动快进快退。项目地址:fijkplayer_skin

效果就是上面截图的效果

这里着重说下给播放皮肤加功能,

快进快退实现

快进快退,说白了就是检测滑动的时候判断当前点和屏幕按下时候的点的距离,最简单的方法就是>大于就+1,小于就-1,以下是我的实现。这里只是函数部分,ui部分,你需要自己写布局,不想写可以抄袭下我的皮肤

_onHorizontalDragStart(detills) {
    setState(() {
      // 在按下的时候现存一下当前的点的位置
      updatePrevDx = detills.globalPosition.dx;
      updatePosX = _currentPos.inSeconds;
    });
  }

  _onHorizontalDragUpdate(detills) {
    double curDragDx = detills.globalPosition.dx;
    // 确定当前是前进或者后退
    int cdx = curDragDx.toInt();
    int pdx = updatePrevDx!.toInt();
    bool isBefore = cdx > pdx;
    // + -, 不满足, 左右滑动合法滑动值,> 1
    if (isBefore && cdx - pdx < 1 || !isBefore && pdx - cdx < 1) return null;

    int dragRange = isBefore ? updatePosX! + 1 : updatePosX! - 1;

    // 是否溢出 最大
    int lastSecond = _duration.inSeconds;
    if (dragRange >= _duration.inSeconds) {
      dragRange = lastSecond;
    }
    // 是否溢出 最小
    if (dragRange <= 0) {
      dragRange = 0;
    }
    //
    this.setState(() {
      _isHorizontalMove = true;
      _hideStuff = false;
      _isTouch = true;
      // 更新下上一次存的滑动位置
      updatePrevDx = curDragDx;
      // 更新时间
      updatePosX = dragRange.toInt();
      _dargPos = Duration(seconds: updatePosX!.toInt());
    });
  }
  
  // 在松手的时候,说明就不打架了,所以就各回各家,各找各妈
  _onHorizontalDragEnd(detills) {
    // 这里就给播放器设置更新后的位置,就是时间
    player.seekTo(_dargPos.inMilliseconds);
    this.setState(() {
      _isHorizontalMove = false;
      _isTouch = false;
      _hideStuff = true;
      _currentPos = _dargPos;
    });
  }
  
复制代码

上下滑动也是同理,稍作修改便可以完成。至此一些主要的点结束。

文章分类
Android
文章标签