308 lines
8.6 KiB
Markdown
308 lines
8.6 KiB
Markdown
|
||
|
||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||
|
||
|
||
21 白板画笔的参数设置
|
||
1. 线粗选择器
|
||
|
||
如下所示,左下角可以选择线的粗细,激活状态通过蓝色线框表示。在选择之后,激活状态发生变化,绘制时线的宽度也会变化。这个选择器的构建逻辑相对独立,以后也有复用的可能,可以单独抽离为一个组件维护,这里创建 StorkWidthSelector 组件,并将其通过 Stack 组件叠放在画板之上:
|
||
|
||
|
||
|
||
|
||
线粗 = 1
|
||
线粗 = 8
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
通过选择器在界面上的展示效果,不难看出组件需要依赖的数据有:
|
||
|
||
|
||
支持的线宽列表 List<double>
|
||
激活索引 int
|
||
条目的颜色 Color
|
||
点击条目时的回调 ValueChanged<int>
|
||
|
||
|
||
类定义如下:
|
||
|
||
class StorkWidthSelector extends StatelessWidget {
|
||
final List<double> supportStorkWidths;
|
||
final int activeIndex;
|
||
final Color color;
|
||
final ValueChanged<int> onSelect;
|
||
|
||
|
||
const StorkWidthSelector({
|
||
Key? key,
|
||
required this.supportStorkWidths,
|
||
required this.activeIndex,
|
||
required this.onSelect,
|
||
required this.color,
|
||
}) : super(key: key);
|
||
|
||
|
||
|
||
|
||
在组件构建逻辑中,主要是便历 supportStorkWidths 列表,通过 _buildByIndex 方法根据索引值构建条目。其中可以通过 index == activeIndex 来确定当前条目是否被激活;再通过激活状态确定是否添加边线:
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.end,
|
||
children: List.generate(
|
||
supportStorkWidths.length,
|
||
_buildByIndex,
|
||
)),
|
||
);
|
||
}
|
||
|
||
Widget _buildByIndex(int index) {
|
||
bool select = index == activeIndex;
|
||
return GestureDetector(
|
||
onTap: () => onSelect(index),
|
||
child: Container(
|
||
margin: const EdgeInsets.symmetric(horizontal: 2),
|
||
width: 70,
|
||
height: 18,
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(8),
|
||
border: select ? Border.all(color: Colors.blue) : null),
|
||
child: Container(
|
||
width: 50,
|
||
color: color,
|
||
height: supportStorkWidths[index],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
|
||
|
||
|
||
最后通过 Stack + Positioned 组件,将 StorkWidthSelector 组件叠放在界面的右下角;并通过 _onSelectStorkWidth 作为选择的回调处理界面状态数据的变化逻辑:
|
||
|
||
|
||
|
||
处理逻辑很简单,只要更新 _activeStorkWidthIndex 激活索引即可;另外在 _onPanStart 创建 Line 对象时,设置激活线宽即可:
|
||
|
||
void _onSelectStorkWidth(int index) {
|
||
if (index != _activeStorkWidthIndex) {
|
||
setState(() {
|
||
_activeStorkWidthIndex = index;
|
||
});
|
||
}
|
||
}
|
||
|
||
void _onPanStart(DragStartDetails details) {
|
||
_lines.add(Line(
|
||
points: [details.localPosition],
|
||
// 使用激活线宽
|
||
strokeWidth: supportStorkWidths[_activeStorkWidthIndex],
|
||
));
|
||
}
|
||
|
||
|
||
到这里,就完成了线条宽度的选择功能,当前代码位置 paper 。
|
||
|
||
|
||
|
||
2. 颜色选择器
|
||
|
||
颜色选择器的原理也是一样,选择激活,在创建 Line 对象时设置颜色。只不过是条目的界面表现不同罢了,条目的构建逻辑也是通过 _buildByIndex 实现的。这里通过圆圈也表示颜色,点击时激活条目,激活状态由外圈的圆形边线进行表示:
|
||
|
||
|
||
|
||
|
||
标题
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class ColorSelector extends StatelessWidget {
|
||
final List<Color> supportColors;
|
||
final ValueChanged<int> onSelect;
|
||
final int activeIndex;
|
||
|
||
const ColorSelector({
|
||
Key? key,
|
||
required this.supportColors,
|
||
required this.activeIndex,
|
||
required this.onSelect,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8),
|
||
child: Wrap(
|
||
// crossAxisAlignment: CrossAxisAlignment.end,
|
||
children: List.generate(
|
||
supportColors.length,
|
||
_buildByIndex,
|
||
)),
|
||
);
|
||
}
|
||
|
||
Widget _buildByIndex(int index) {
|
||
bool select = index == activeIndex;
|
||
return GestureDetector(
|
||
onTap: () => onSelect(index),
|
||
child: Container(
|
||
margin: const EdgeInsets.symmetric(horizontal: 2),
|
||
padding: const EdgeInsets.all(2),
|
||
width: 24,
|
||
height: 24,
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
border: select ? Border.all(color: Colors.blue) : null
|
||
),
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
color: supportColors[index],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
同理,在 _PaperState 中,颜色选择器也通过 Stack + Positioned 叠放在画板的左下角;点击回调时处理激活颜色索引数据的更新;以及创建 Line 对象时设置激活颜色:
|
||
|
||
|
||
|
||
void _onSelectColor(int index) {
|
||
if (index != _activeColorIndex) {
|
||
setState(() {
|
||
_activeColorIndex = index;
|
||
});
|
||
}
|
||
}
|
||
|
||
void _onPanStart(DragStartDetails details) {
|
||
_lines.add(Line(
|
||
points: [details.localPosition],
|
||
strokeWidth: supportStorkWidths[_activeStorkWidthIndex],
|
||
color: supportColors[_activeColorIndex],
|
||
));
|
||
}
|
||
|
||
|
||
到这里,颜色选择和线宽选择功能就已经实现了,当前代码位置 paper 。但这里在布局上还有些问题,下面来分析处理一下。
|
||
|
||
|
||
|
||
3. 布局分析
|
||
|
||
这里通过 Positioned 将两块分别叠放在 Stack 的两侧,上面颜色少时没有什么问题。但如果颜色过多,可以发现这种方式的叠放会让后者把前者覆盖住
|
||
|
||
|
||
|
||
|
||
多颜色时
|
||
布局边界
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
在布局树中可以发现,默认情况下 Positioned 之下的约束为无限约束,也就是子组件想要多大都可以。所以子组件在竖直方向上没有约束,颜色太多时就会溢出的原因。
|
||
|
||
|
||
|
||
|
||
|
||
解决方案方案有很多,其中最简单的是指定 Positioned 组件的 width 参数,在水平方向施加紧约束。如下所示,可以限制 ColorSelector 的宽度等于 240。 ColorSelector 内部通过 Wrap 进行构建,在区域之内会自动换行:
|
||
|
||
|
||
|
||
Positioned(
|
||
bottom: 40,
|
||
width: 240,
|
||
child: ColorSelector(
|
||
supportColors: supportColors,
|
||
activeIndex: _activeColorIndex,
|
||
onSelect: _onSelectColor,
|
||
),
|
||
),
|
||
|
||
|
||
通过布局查看器可以看出,此时 ColorSelector 受到的约束宽度就固定在 240。
|
||
|
||
|
||
|
||
|
||
|
||
另外,我们还可以将 Positioned 提供的约束尺寸设为屏幕宽度,通过 Row 来水平排列,其中 ColorSelector 的宽度通过 Expanded 延展成剩余宽度。当前代码位置 paper
|
||
|
||
|
||
|
||
Positioned(
|
||
bottom: 0,
|
||
width: MediaQuery.of(context).size.width,
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: ColorSelector(
|
||
supportColors: supportColors,
|
||
activeIndex: _activeColorIndex,
|
||
onSelect: _onSelectColor,
|
||
),
|
||
),
|
||
StorkWidthSelector(
|
||
supportStorkWidths: supportStorkWidths,
|
||
color: supportColors[_activeColorIndex],
|
||
activeIndex: _activeStorkWidthIndex,
|
||
onSelect: _onSelectStorkWidth,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
|
||
|
||
如果这里不提供 width ,而使用 Row + Expanded 组件的话,就会报错;根本原因是 Positioned 施加了无限约束,而 Row 使用了 Expanded 组件,延展无限的宽度区域是不被允许的:
|
||
|
||
|
||
|
||
通过这个小问题,带大家简单认识一下布局中约束的分析。很多布局上的问题,都可以从约束的角度解决。这里点到为止,如果对约束感兴趣,或有很多布局的困扰,可以研读一下我的布局小册: Flutter 布局探索 - 薪火相传
|
||
|
||
|
||
|
||
4. 本章小结
|
||
|
||
本章主要任务是完成白板画笔的参数设置,为用户提供修改颜色和线宽的操作,以便于绘制更复杂多彩的图案。从中可以体会出:新增加一个需求,往往会引入相关的数据来实现功能。比如对于修改颜色的需求,需要引入支持的颜色列表和激活的颜色索引两个数据。所以对于任何功能需求而言,不要只看其表面的界面呈现,更重要的是分析其背后的用户交互过程中的数据变化情况。
|
||
|
||
下一章,将继续对当前的画板项目进行一些小优化,比如支持回退和撤销回退的功能;以及优化一下点集的收集策略,来尽可能地避免收录过多无用点,减小绘制的压力。
|
||
|
||
|
||
|
||
|