Flutter光标轨迹追踪插件cursor_trail的使用
Flutter光标轨迹追踪插件cursor_trail的使用
cursor_trail
是一个用于在移动鼠标时显示一系列小部件或图像轨迹的小部件。灵感来源于 bridget.pictures 网站。
演示:Cursor Trail
特性
- 显示任何小部件作为轨迹,适合显示图像。
- 类似于
ListView.builder
的易于使用的API。 - 可自定义光标移动的阈值。
- 控制在轨迹中可见的小部件数量。
- 在全屏模式下显示小部件/图像。
- 全屏模式下自定义文本光标。
- 切换全屏模式时有动画效果。
开始使用
首先,在你的 pubspec.yaml
文件中添加以下依赖:
dependencies:
cursor_trail: <latest_version>
然后,你可以使用 CursorTrail
小部件来添加光标轨迹到你的应用中:
CursorTrail(
itemCount: images.length,
itemBuilder: (context, index, maxSize) {
// 构建你的小部件/图像
return CachedNetworkImage(
imageUrl: images[index],
fit: BoxFit.contain,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
);
},
onImageChanged: (index) {
// 当图像改变时执行某些操作
},
);
查看示例应用以获得更多详细信息。
查看 API 文档 以了解可用选项。
贡献
欢迎您为本项目贡献代码!
在贡献之前,请查阅 贡献指南。
喜欢你所看到的吗?
通过给仓库加星来表达你的支持。
或者你可以
许可证
BSD 3-Clause License
Copyright (c) 2023, Birju Vachhani
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
完整示例代码
以下是完整的示例代码,展示了如何在 Flutter 应用中使用 cursor_trail
插件:
import 'dart:developer';
import 'dart:ui';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cursor_trail/cursor_trail.dart';
import 'package:example/utils/universal/universal.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:url_launcher/url_launcher_string.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return AdaptiveTheme(
light: ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorSchemeSeed: Colors.blue,
),
dark: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: Colors.blue,
scaffoldBackgroundColor: Colors.black,
),
initial: AdaptiveThemeMode.dark,
builder: (light, dark) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: light,
darkTheme: dark,
home: const MyHomePage(),
);
});
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
[@override](/user/override)
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> images = [];
bool isLoading = true;
double threshold = 80;
int currentIndex = -1;
[@override](/user/override)
void initState() {
super.initState();
loadImages();
}
Future<void> loadImages() async {
try {
isLoading = true;
int index = 0;
while (index < 30) {
await Future.delayed(const Duration(milliseconds: 100));
getRedirectionUrl('https://source.unsplash.com/random?sig=$index')
.then((image) {
if (image != null) {
images.add(image);
precacheImage(CachedNetworkImageProvider(image), context);
}
});
index++;
}
setState(() => isLoading = false);
} catch (error, stacktrace) {
setState(() => isLoading = false);
log(error.toString());
log(stacktrace.toString());
}
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (isLoading)
const Expanded(
child: Center(
child: CupertinoActivityIndicator(
radius: 20,
),
),
),
if (!isLoading)
Expanded(
child: Stack(
fit: StackFit.expand,
children: [
Positioned.fill(
child: CursorTrail(
threshold: threshold,
itemCount: images.length,
itemBuilder: (context, index, maxSize) {
return CachedNetworkImage(
imageUrl: images[index],
fit: BoxFit.contain,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
);
},
onItemChanged: (index) {
currentIndex = index;
setState(() {});
},
),
),
const Positioned(right: 12, top: 12, child: TopBar()),
],
),
),
BottomBar(
threshold: threshold,
currentIndex: currentIndex,
totalImages: images.length,
onThresholdChanged: (value) {
setState(() => threshold = value);
},
),
],
),
);
}
}
class TopBar extends StatelessWidget {
const TopBar({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
final adaptiveTheme = AdaptiveTheme.of(context);
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ButtonBar(
children: [
IconButton(
onPressed: () {
launchUrlString(
'https://github.com/birjuvachhani/cursor_trail');
},
icon: const Icon(FontAwesomeIcons.github),
),
IconButton(
onPressed: () {
adaptiveTheme.toggleThemeMode();
},
icon: Builder(
builder: (context) {
switch (adaptiveTheme.mode) {
case AdaptiveThemeMode.light:
return const Icon(Icons.wb_sunny);
case AdaptiveThemeMode.dark:
return const Icon(Icons.nightlight_round);
case AdaptiveThemeMode.system:
return const Icon(Icons.brightness_auto);
}
},
),
),
],
),
],
),
);
}
}
class BottomBar extends StatelessWidget {
final double threshold;
final ValueChanged<double> onThresholdChanged;
final int currentIndex;
final int totalImages;
const BottomBar({
super.key,
required this.threshold,
required this.onThresholdChanged,
required this.currentIndex,
required this.totalImages,
});
[@override](/user/override)
Widget build(BuildContext context) {
return Container(
height: 40,
color: Theme.of(context).colorScheme.surfaceVariant,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: LayoutBuilder(builder: (context, constraints) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Cursor Trail'),
if (constraints.maxWidth >= 370) ...[
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {
if (threshold == 0) return;
onThresholdChanged(threshold - 40);
},
splashRadius: 8,
constraints: const BoxConstraints(minWidth: 0, minHeight: 0),
padding: const EdgeInsets.all(4),
iconSize: 20,
icon: const Icon(Icons.remove),
),
const SizedBox(width: 4),
Text(
'threshold: ${threshold.toInt().toString().padLeft(4, '0')}',
style: const TextStyle(
fontFeatures: [FontFeature.tabularFigures()],
),
),
IconButton(
onPressed: () {
onThresholdChanged((threshold + 40).clamp(0, 200));
},
splashRadius: 10,
constraints: const BoxConstraints(minWidth: 0, minHeight: 0),
padding: const EdgeInsets.all(4),
iconSize: 20,
icon: const Icon(Icons.add),
),
],
),
Text(
'${(currentIndex + 1).toString().padLeft(4, '0')} / ${totalImages.toString().padLeft(4, '0')}',
style: const TextStyle(
fontFeatures: [FontFeature.tabularFigures()],
),
),
],
],
);
}),
),
);
}
}
更多关于Flutter光标轨迹追踪插件cursor_trail的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter光标轨迹追踪插件cursor_trail的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter中使用cursor_trail
插件来实现光标轨迹追踪的示例代码。cursor_trail
插件通常用于追踪文本输入光标的位置和轨迹,为开发者提供自定义光标路径和动画的能力。
首先,确保你已经在pubspec.yaml
文件中添加了cursor_trail
依赖:
dependencies:
flutter:
sdk: flutter
cursor_trail: ^最新版本号 # 请替换为实际的最新版本号
然后,运行flutter pub get
来安装依赖。
接下来是一个简单的示例,展示如何在Flutter应用中使用cursor_trail
插件:
import 'package:flutter/material.dart';
import 'package:cursor_trail/cursor_trail.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cursor Trail Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CursorTrailExample(),
);
}
}
class CursorTrailExample extends StatefulWidget {
@override
_CursorTrailExampleState createState() => _CursorTrailExampleState();
}
class _CursorTrailExampleState extends State<CursorTrailExample> {
final TextEditingController _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Cursor Trail Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CursorTrailTextField(
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Type something...',
),
cursorTrailColor: Colors.red.withOpacity(0.5),
cursorTrailWidth: 2.0,
cursorTrailDuration: Duration(milliseconds: 300),
cursorTrailBuilder: (context, positions) {
return CustomPaint(
painter: CursorTrailPainter(
positions: positions,
color: Colors.red.withOpacity(0.5),
width: 2.0,
),
child: Container(),
);
},
),
SizedBox(height: 16.0),
Text(
'Cursor positions: $_controller.text',
style: TextStyle(fontSize: 14.0),
),
],
),
),
);
}
}
class CursorTrailPainter extends CustomPainter {
final List<Offset> positions;
final Color color;
final double width;
CursorTrailPainter({required this.positions, required this.color, required this.width});
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..color = color
..strokeWidth = width
..style = PaintingStyle.stroke;
for (int i = 1; i < positions.length; i++) {
final Offset start = positions[i - 1];
final Offset end = positions[i];
canvas.drawLine(start, end, paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true; // For simplicity, always repaint
}
}
在这个示例中,我们做了以下几件事:
- 导入必要的包:导入
flutter/material.dart
和cursor_trail/cursor_trail.dart
。 - 创建主应用:
MyApp
是一个简单的Flutter应用,包含一个主页面CursorTrailExample
。 - 定义
CursorTrailExample
:这是一个有状态的Widget,包含一个TextEditingController
用于管理文本输入。 - 使用
CursorTrailTextField
:这是cursor_trail
插件提供的一个自定义TextField,它允许我们设置光标轨迹的颜色、宽度和动画持续时间。 - 自定义光标轨迹绘制:通过
cursorTrailBuilder
参数,我们提供了一个自定义的CustomPaint
来绘制光标轨迹。在这个例子中,我们使用CursorTrailPainter
类来绘制轨迹。
请注意,cursor_trail
插件的具体API可能会随着版本更新而变化,因此请参考最新的文档和示例代码以获取最佳实践。如果你发现某些功能或参数在当前版本中不可用,请查阅最新的插件文档。