04_Flutter自定义Slider滑块

news2024/11/26 21:19:38

04_Flutter自定义Slider滑块

一.Slider控件基本用法
Column(
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    Text(
      "sliderValue: ${_sliderValue.toInt()}"
    ),
    Slider(
      value: _sliderValue,
      min: 0,
      max: 100,
      divisions: 10,
      thumbColor: Colors.red,
      activeColor: Colors.red,
      onChanged: (value) {
        setState(() {
          _sliderValue = value;
        });
      }
    )
  ],
)

在这里插入图片描述

const Slider({
    super.key,
    required this.value,
    this.secondaryTrackValue,
    required this.onChanged,
    this.onChangeStart,
    this.onChangeEnd,
    this.min = 0.0,
    this.max = 1.0,
    this.divisions,
    this.label,
    this.activeColor,
    this.inactiveColor,
    this.secondaryActiveColor,
    this.thumbColor,
    this.overlayColor,
    this.mouseCursor,
    this.semanticFormatterCallback,
    this.focusNode,
    this.autofocus = false,
  })

几个比较重要的属性:

  • value:slider控件显示的值
  • min:slider控件滑动到最左边对应的值,即最小值
  • max: slider控件滑动到最右边对应的值,即最大值
  • divisions: 最小值到最大值之间被几等分
  • activeColor: 滑块划过部分的颜色值,即选中的颜色值
  • inactiveColor:滑块未划过部分的颜色值,即为选中的颜色值
  • thumbColor:滑块的颜色值
二.如何修改滑块的大小以及滑块轨迹的高度

从上面的示例可以看到,通过Slider控件为我们提供的属性,只支持改变滑块的颜色,以及滑块轨迹的颜色,那么我们想要改变滑块的大小以及滑块轨迹的高度,是不是只能重新自定义呢?

NO! NO! NO!,细心的您在使用Flutter的AppBar时,可能会发现,为AppBar控件指定样式时,除了使用AppBar控件提供的属性外,也可以使用AppBarTheme来为AppBar设置某些特定的样式,既然如此,不妨查看下Flutter sdk的源码与Slider对应的是否有一个叫SliderTheme的控件呢? 嘿嘿,还真有。

final SliderThemeData data;
const SliderTheme({
  super.key,
  required this.data,
  required super.child,
});

const SliderThemeData({
  this.trackHeight,
  this.thumbShape,
  ...
});

仔细找SliderThemeData的trackHeight以及thumbShape的属性注释:

/// The height of the [Slider] track.
final double? trackHeight;

/// The shape that will be used to draw the [Slider]'s thumb.
/// The default value is [RoundSliderThumbShape].
final SliderComponentShape? thumbShape;

此处省略…翻译软件的时间:

  • trackHeight:[滑块]轨迹的高度
  • thumbShape:默认值是一个RoundSliderThumbShape对象

看下RoundSliderThumbShape的源码怎么写的:

const RoundSliderThumbShape({
  this.enabledThumbRadius = 10.0,
  this.disabledThumbRadius,
  this.elevation = 1.0,
  this.pressedElevation = 6.0,
});

看到这里就不用做过多的解释了吧😂,因此要修改滑块的大小,可以重新指定thumbShape为RoundSliderThumbShape对象,并设置enabledThumbRadius的值。

在这里插入图片描述

Column(
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    Text(
      "sliderValue: ${_sliderValue.toInt()}"
    ),
    SliderTheme(
      data: const SliderThemeData(
        trackHeight: 20,
        thumbShape: RoundSliderThumbShape(
          enabledThumbRadius: 20
        )
      ),
      child: Slider(
        value: _sliderValue,
        min: 0,
        max: 100,
        divisions: 10,
        thumbColor: Colors.red,
        activeColor: Colors.red,
        onChanged: (value) {
          setState(() {
            _sliderValue = value;
          });
        }
      )
    )
  ],
)
三.使用本地资源图片作为自定义滑块

既然要自定义滑块,毫无疑问需要从SliderThemeData的thumbShape入手。

final SliderComponentShape? thumbShape;

thumbShape的类型为SliderComponentShape,继续查看SliderComponentShape源码:

abstract class SliderComponentShape {

  const SliderComponentShape();

  Size getPreferredSize(bool isEnabled, bool isDiscrete);

  void paint(
    PaintingContext context,
    Offset center, {
    required Animation<double> activationAnimation,
    required Animation<double> enableAnimation,
    required bool isDiscrete,
    required TextPainter labelPainter,
    required RenderBox parentBox,
    required SliderThemeData sliderTheme,
    required TextDirection textDirection,
    required double value,
    required double textScaleFactor,
    required Size sizeWithOverflow,
  });
}

因此我们可以定义一个类继承SliderComponentShape,并实现getPreferredSize和paint方法,getPreferredSize控制滑块大小,paint负责把滑块绘制到屏幕上。

  • 首先第一步我们需要将本地图片为一个ImageInfo,例如传入一个"lib/assets/images/ic_slider_thumb.png",最后得到一个ImageInfo,这里就直接奉上源码了,其实现也是参考了Image.asset的源码:
typedef AssertsWidgetBuilder = Widget Function(BuildContext context, ImageInfo? imageInfo);

class AssertsImageBuilder extends StatefulWidget {

  final String assertsName;
  final AssertsWidgetBuilder builder;

  const AssertsImageBuilder(
    this.assertsName,
    {
      super.key,
      required this.builder,
    }
  );

  
  State<StatefulWidget> createState() => _AssertsImageBuilderState();

}

class _AssertsImageBuilderState extends State<AssertsImageBuilder> {

  ImageInfo? _imageInfo;

  
  void initState() {
    super.initState();
    _loadAssertsImage().then((value) {
      setState(() {
        _imageInfo = value;
      });
    });

  }

  
  void didUpdateWidget(covariant AssertsImageBuilder oldWidget) {
    super.didUpdateWidget(oldWidget);
    if(oldWidget.assertsName != widget.assertsName) {
      _loadAssertsImage().then((value) {
        setState(() {
          _imageInfo = value;
        });
      });
    }
  }

  
  Widget build(BuildContext context) {
    return widget.builder!.call(context, _imageInfo);
  }

  Future<ImageInfo?> _loadAssertsImage() {
    final Completer<ImageInfo?> completer = Completer<ImageInfo?>();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      final ImageProvider imageProvider = AssetImage(widget.assertsName);
      final ImageConfiguration config = createLocalImageConfiguration(context);
      final ImageStream stream = imageProvider.resolve(config);
      ImageStreamListener? listener;
      listener = ImageStreamListener(
            (ImageInfo? image, bool sync) {
          if (!completer.isCompleted) {
            completer.complete(image);
          }

          SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
            stream.removeListener(listener!);
          });
        },
        onError: (Object exception, StackTrace? stackTrace) {
          stream.removeListener(listener!);
          completer.completeError(exception, stackTrace);
        },
      );
      stream.addListener(listener);
    });

    return completer.future;
  }

}
  • 自定义SliderComponentShape
import 'package:flutter/material.dart';
import 'dart:ui' as ui;

class ImageSliderThumb extends SliderComponentShape {

  final Size size;
  final ui.Image? image;

  const ImageSliderThumb({
    required this.image,
    Size? size
  }): size = size ?? const Size(20, 20);

  
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return size;
  }

  
  void paint(PaintingContext context, Offset center, {required Animation<double> activationAnimation, required Animation<double> enableAnimation, required bool isDiscrete, required TextPainter labelPainter, required RenderBox parentBox, required SliderThemeData sliderTheme, required TextDirection textDirection, required double value, required double textScaleFactor, required Size sizeWithOverflow}) {
    
  }

}
  • 绘制图片滑块

void paint(PaintingContext context, Offset center, {required Animation<double> activationAnimation, required Animation<double> enableAnimation, required bool isDiscrete, required TextPainter labelPainter, required RenderBox parentBox, required SliderThemeData sliderTheme, required TextDirection textDirection, required double value, required double textScaleFactor, required Size sizeWithOverflow}) {
  //图片中心点
  double dx = size.width/2;
  double dy = size.height/2;

  if(image != null) {
    final Rect sourceRect = Rect.fromLTWH(0, 0, image!.width.toDouble(), image!.width.toDouble());
    //center会随着滑块的移动而改变,所以这里需要根据center计算图片绘制的位置
    double left = center.dx - dx;
    double top = center.dy - dy;
    double right = center.dx + dx;
    double bottom = center.dy + dy;
    Rect destinationRect = Rect.fromLTRB(left, top, right, bottom);

    final Canvas canvas = context.canvas;
    final Paint paint = new Paint();
    paint.isAntiAlias = true;
    //绘制滑块
    canvas.drawImageRect(image!, sourceRect, destinationRect, paint);
  }
}
四.怎么使用?
Column(
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    Text(
      "sliderValue: ${_sliderValue.toInt()}"
    ),
    AssertsImageBuilder(
      "lib/assets/images/ic_slider_thumb.png",
      builder: (context, imageInfo) {
        return SliderTheme(
          data: SliderThemeData(
            trackHeight: 10,
            thumbShape: ImageSliderThumb(
              image: imageInfo?.image,
              size: const Size(30, 30)
            )
          ),
          child: Slider(
            value: _sliderValue,
            min: 0,
            max: 100,
            divisions: 10,
            thumbColor: Colors.red,
            activeColor: Colors.red,
            onChanged: (value) {
              setState(() {
                _sliderValue = value;
              });
            }
          )
        );
      }
    ),
  ],
)

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1264753.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

二叉树的最近公共祖先(C++实现)

二叉树的最近公共祖先 题目思路代码&#xff08;详细注释&#xff09; 题目 二叉树的最近公共祖先 思路 我们可以通过两个栈来实现 实现一个FindPath函数&#xff0c;用来查找从根节点到目标节点的路径&#xff08;路径可以用栈来保存&#xff09; 路径保存好后&#xff0c;…

黄金比例设计软件Goldie App mac中文版介绍

Goldie App mac是一款测量可视化黄金比例的工具。专门为设计师打造&#xff0c;可以帮助他们在Mac上测量和可视化黄金比例&#xff0c;从而轻松创建出完美、平衡的设计。 Goldie App mac体积小巧&#xff0c;可以驻留在系统的菜单栏之上&#xff0c;随时提供给用户调用。 拥有独…

uniapp设置手机通知权限

提醒用户开启通知权限&#xff0c;与unipush功能联用 效果图&#xff1a; 方法&#xff1a; 直接使用即可&#xff0c;在真机或模拟器运行 setPermissions() {// #ifdef APP-PLUS if (plus.os.name Android) { // 判断是Androidvar main plus.android.runtimeMainActivity…

【LeetCode刷题-链表】--86.分隔链表

86.分隔链表 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }* }*/ class…

文件服务器迁移

文件服务器迁移还是比较简单的 win server加域 导出配额文件 选中所有项&#xff0c;点击导出 导出共享文件夹权限列表 导出文件夹的权限表&#xff0c;留作备用。需要用到“icacls” icacls c:\windows\* /save aclfile /t # C:\Windows 目录及其子目录中所有文件的 DAC…

等保——密评技术要求

密评简介 密评定义&#xff1a;全称商用密码应用安全评估, 是指对采用商用密码技术、产品和服务集成建设的网络和信息系统密码应用的合规性、正确性、有效性进行评估。密评对象&#xff1a;重要信息系统、关键信息基础设施、网络安全等保三级及以上的系统。评测依据&#xff1…

OpenCV数字图像处理——检测出图像中的几何形状并测量出边长、直径、内角

一、简介 在传统的自动化生产尺寸测量中&#xff0c;常用的方法是利用卡尺或千分尺对被测工件的某个参数进行多次测量&#xff0c;并取这些测量值的平均值。然而&#xff0c;这些传统的检测设备或手动测量方法存在着一些问题&#xff1a;测量精度不高、测量速度缓慢&#xff0…

使用std::mutext与std::condition_variables实现信号量

1. 信号量的定义 2. 使用std::mutext与std::condition_variables实现信号量 代码来自&#xff1a;https://zhuanlan.zhihu.com/p/462668211 #ifndef _SEMAPHORE_H #define _SEMAPHORE_H #include <mutex> #include <condition_variable> using namespace std;cla…

JSP forEach标签varStatus使用讲解(了解即可 基本用不到)

上文 JSP迭代标签之 forEach循环标签 基本使用讲解 我们讲了一下forEach标签 大多数时候会用的语法 但是varStatus 没有讲到 因为我觉得这个东西 做个了解就好了 如果你不感兴趣都可以不看 因为感觉开发中基本是用不到的 但是 官方有提供 我还是说一下 当前遍历的基本信息 包括…

postman接口测试教程与实例分享

postman 的界面图 各个功能区的使用如下&#xff1a; 快捷区&#xff1a; 快捷区提供常用的操作入口&#xff0c;包括运行收藏夹的一组测试数据&#xff0c;导入别人共享的收藏夹测试数据&#xff08;Import from file, Import from folder, Import from link等&#xff09;&…

【Python】获取ip

要使用Python获取IP地址&#xff0c;可以使用socket库中的gethostname()函数和gethostbyname()函数。 import socketdef get_ip_address():hostname socket.gethostname()ip_address socket.gethostbyname(hostname)return ip_addressip get_ip_address() print("IP地…

成功的蓝图:实现长期成长与卓越表现的 6 项策略

能在收入和利润上持续领先同行的公司寥寥无几&#xff0c;不到四分之一。McKinsey的最新研究揭示了这些增长标杆公司与众不同的六大心态和策略。过度谨慎的公司&#xff0c;尤其在动荡时期&#xff0c;也许能捱过当下&#xff0c;但往往无法发掘全部潜力。考虑到近五年历经前所…

事务的自动提交机制和隐式提交机制

自动提交机制就是一个sql语句完成默认提交一次&#xff0c;也就是说一个sql语句是原子性的。想关闭这种功能&#xff0c;两种方式一种写START TRANSACTION&#xff0c;另一种SET autocommit OFF 隐式提交机制&#xff0c;在START TRANSACTION后&#xff0c;会有一些情况导致语…

在centos7上源码安装nginx

1. 安装必要的编译工具和依赖项 在编译Nginx之前&#xff0c;你需要安装一些编译工具和依赖项。可以通过以下命令安装&#xff1a; yum install gcc-c pcre-devel zlib-devel make 2. 下载Nginx源代码 从Nginx官网下载最新的源代码。你可以使用wget命令来下载&#xff1a; …

LLM、ChatGPT与多模态必读论文150篇

为了写本 ChatGPT 笔记&#xff0c;我和10来位博士、业界大佬&#xff0c;在过去半年翻了大量中英文资料/paper&#xff0c;读完 ChatGPT 相关技术的150篇论文&#xff0c;当然还在不断深入。 由此而感慨&#xff1a; 读的论文越多&#xff0c;你会发现大部分人对ChatGPT的技…

解决electron-build打包后运行app报错:cannot find module xxx

现象&#xff1a; 关于这个问题查了很多资料&#xff0c;也问了chatgpt都没有找到答案。 最后只能靠自己了。 于是冷静下来回想一下细节。突然发现了一个特别点。 eletron-builder打包时&#xff0c;强制要求eletron-builder和eletron必须都放在devDependencies 否则&#…

001-调用函数访问结构体数组成员,并修改其数值

1 代码 /*调用函数访问结构体数组成员&#xff0c;并修改其数值 */ #include <stdio.h> /* for printf */ #include <stdlib.h> /* for exit */struct mytest{char a ;char b ;char c ; };void p_find_test(struct mytest *aaa) {struct mytest *test aaa…

这个变量要不要用volatile修饰呢?

正文 大家好&#xff0c;又见面了&#xff0c;我是bug菌~ 在嵌入式软件开发过程中&#xff0c;如果对volatile不熟&#xff0c;那可以你应该是个"假嵌入式程序员"&#xff0c;因为一个变量需不需要使用volatile考虑的场景挺多的&#xff0c;如果在某些场景下乱用&…

OpenGL笔记:纹理的初次使用

说明 纹理的代码写完后&#xff0c;一直出不来结果&#xff0c;原因是没有设置GL_TEXTURE_MIN_FILTER&#xff0c; 它的默认值为GL_NEAREST_MIPMAP_LINEAR&#xff0c; 因为这里我还没用到Mipmap&#xff0c;所以使用这个默认值&#xff0c;结果是错误的&#xff0c;关于mipma…

pycharm全网最新安装教程(附加activation code),支持2018-2023版本

官网地址Download PyCharm: Python IDE for Professional Developers by JetBrains 下载的话无脑下载安装即可&#xff01; 2018.2~2023版本用这个&#xff0c;最新的activation code码 2018.1以下版本用这个 老是审核不通过只能贴图片了&#xff08;T-T&#xff09;