如何获得设备的最大刷新率在Flutter?

k2arahey  于 2023-05-30  发布在  Flutter
关注(0)|答案(1)|浏览(339)

我的小部件使用TickerProviderStateMixnAnimationController来制作CustomPaint的动画。每当AnimationController更新CustomPaint时,它也通过调用该数据处理程序中的update()方法来更新数据处理程序类。
这个小部件包含播放器的东西,每当update()被调用时,我让播放器栏移动特定数量的x位置。玩家还必须显示时间值,基本上,玩家栏的位置指示时间如何流逝。所以时间值是沿着播放器栏的位置显示的。我以为flutter只支持60 fps,这意味着update()每秒被调用60次,我让播放器栏在update()被调用时移动X/60X是任意值,如果播放条到达100位置时时间变为1秒,则X将为100。
但最近,我把我的手机换成了新的,这个设备可以支持120 fps,似乎AnimationController试图用这个最大的刷新率来动画小部件。所以update()现在每秒被调用120次,所有事情都快了两倍。我想使数据处理程序能够获得最大的刷新率,所以它可以同步播放器栏移动与设备的屏幕。如果设备的最大刷新率为60 fps,则处理程序将使bar每次调用移动X/60,如果设备可以支持120 fps,则它将使bar每次调用移动X/120,依此类推。
为了实现这一点,我需要通过设备的显示器的最大刷新率的数据处理程序。我试着用谷歌搜索,但我找不到任何有用的帖子或代码,告诉如何获得此信息。
有没有什么方法可以在flutter中获得最大刷新率?或者有任何插件可以提供这样的信息?
编辑:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:test_app/util/core/TestPainter.dart';
import 'package:test_app/util/core/cubit/TestData.dart';
import 'package:test_app/widget/EditPanel.dart';
import 'package:test_app/widget/Editor.dart';

class EditorPage extends StatefulWidget {
  EditorPage({required this.sh, required this.sw});

  final double sh;
  final double sw;

  @override
  EditorStatus createState() => EditorStatus();
}

class EditorStatus extends State<EditorPage> with TickerProviderStateMixin {
  late AnimationController _controller;
  late TestPainter painter;
  late TestData data;

  @override
  void initState() {
    _controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
    _controller.repeat();

    data = TestData(widget.sh * 0.85, widget.sw, _controller);

    painter = TestPainter(data: data, animated: _controller);

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider<TestData>.value(
      value: data,
      child: Scaffold(
          body: FractionallySizedBox(
            widthFactor: 1,
            heightFactor: 1,
            child: SafeArea(
              child: Stack(
                children: [
                  Positioned(
                    top: widget.sh * 0.15,
                    bottom: 0,
                    left: 0,
                    right: 0,
                    child: Editor(
                      sh: widget.sh,
                      sw: widget.sw,
                      painter: painter,
                    ),
                  ),
                  Positioned(
                    top: widget.sh * 0.15 / 3,
                    left: 0,
                    right: 0,
                    height: widget.sh * 0.1,
                    child: EditPanel(widget.sw, widget.sh * 0.1),
                  ),
                ],
              ),
            ),
          )),
    );
  }
}

基本上,名为EditorEditPanel的小部件需要名为TestData的数据处理程序和名为TestPainter的绘图程序。TestData需要_controller通过调用repeat()stop()来操纵TestPainter的动画状态。通过小部件的状态(用户是否正在触摸/缩放/长时间触摸/等小部件,或者是否点击了EditPanel中的按钮),_controller动画小部件或停止动画。TestPainter需要TestData作为变量(我现在知道TestPainter可以访问_controller,但只要我让TestPainter不接触_controller,应该就可以了吧?),并且每当调用paint方法时它都会更新数据。即使调用了数据更新代码,数据的结果也与小部件的 * 状态 * 不同
比如说

return IconButton(
  splashRadius: sh * 0.75 / 3,
  icon: Icon(Icons.play_arrow, color: ThemeManager.getRGBfromHex(0x77d478),),
  onPressed: () {
    if (!data.playing) {
      data.setStatus(Status.playing);
    }
  },
);

EditPanel中,此播放按钮称为data,即TestData,而在TestData中,

void setStatus(Status st) {
  if(st == Status.idle) {
    pause();
    stopVelocity();
    current = st;
    if(!blockAnimating) {
      controller.stop();
    }
  } else if(st == Status.playing) {
    play();
    controller.repeat();
    current = st;
  } else if(st == Status.sliding) {
    current = st;
    controller.repeat();
  } else if(st == Status.reversing) {
    reverse();
    controller.repeat();
    current = st;
  } else if(st == Status.selecting) {
    current = st;
    controller.repeat();
  }

  if(blockAnimating) {
    controller.repeat();
  }
}

如您所见,如果stplaying,则它调用controller.repeat(),这是存储在TestData中的AnimationController
因此,如果播放器状态为playing,则_controller将重复其自身,从而导致TestPainter中的paint方法的调用。然后TestPainer通过调用paint中的updateData(Size s)方法来调用_handlePlaying(),这样就可以用AnimationController来完成带有数据更新的动画。每当调用paint时,数据都会更新,因此每个paint都会导致不同的外观(或者可能不取决于玩家的状态)。如果玩家处于空闲状态,那么它将绘制相同。实际上,它根本不会绘制,因为controller.stop()将被调用)。

void updateData(Size s) {
  sh = s.height;
  sw = s.width;

  if(current == Status.playing) {
    _handlePlaying();
  } else if(current == Status.selecting) {
    updateSelectMode();
  } else if(current == Status.sliding) {
    performVelocity();
  } else if(current == Status.reversing) {
    _handleReverse();
  }

  _handleBlockAnimation();
}

void _handlePlaying() {
  if(playPos == ((_lastBlock?.row ?? 0) + 48) * blockSize) {
    setStatus(Status.idle);
    return;
  }

  double upf = tick * blockSize / frames; //Here

  playPos += upf;

  if(playPos - posX >= sw - sh * 0.075) {
    posX += upf;
  }

  updatePlayer();

  if(posX > endPosX) {
    posX = endPosX;
  }

  if(playPos > (( _lastBlock?.row ?? 0) + 48) * blockSize) {
    playPos = ((_lastBlock?.row ?? 0) + 48) * blockSize;
  }
}

playPos是播放器条的位置,upf(每帧单位)是条每帧必须移动的x-pos的量。所以在这段代码中,X将是tick * blockSize,而frames最初是60。通过调整这个frames变量,我可以让玩家在每帧中移动得更慢或更快。如果我在显示器可以支持120 fps的设备上将frames设置为120,则播放器与实时同步,但在60 fps显示器上,播放器会慢两倍。相反,如果我将frames设置为60,那么播放器将与实时同步,并且在120 fps设备上,播放器将获得两倍的速度。
我知道如果fps低于60,这种同步将被打破。我知道这一点。我检查了我的小部件可以在慢速设备的调试模式下以60 fps的速度运行,所以它应该在未来的发布模式下运行得更快。我们跳过这个。

@override
void paint(Canvas cv, Size s) {
  //Somewhere of codes in TestPainter

  data.updateData(s);

  //Keep going
}
zujrkrfu

zujrkrfu1#

您可以使用flutter_displaymode插件来获取设备的显示刷新率。该插件还允许设置设备的首选显示模式,但它有限制,具体取决于设备。
您可以使用FlutterDisplayMode.supported获取支持的显示模式,以确定最大刷新率。

import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'dart:math';

try {
  List<DisplayMode> modes = await FlutterDisplayMode.supported;
  List<double> refreshRateList = [];
  
  // fetch list of display modes available in the device
  modes.forEach((DisplayMode mode){
    refreshRateList.add(mode.refreshRate);
  });
  
  // if refreshRateList is not empty, fetch max refresh rate
  if(!refreshRateList.isEmpty) {
    // print max refresh rate
    debugPrint('${refreshRateList.reduce(max)}');
  }
} on PlatformException catch (e) {
  /// e.code =>
  /// noAPI - No API support. Only Marshmallow and above.
  /// noActivity - Activity is not available. Probably app is in background
}

相关问题