本系列,将经过 Flutter 完成一个全平台的像素修改器应用。源码见开源项目 【pix_editor】
上一篇咱们完成了修改装备,能够设置网格数、背景色、画笔色彩。本篇将引入 图层 的概念,支撑新建图层进行制作,各图层间的像素内容互不干涉,能够点击切换激活的图层进行修改,效果如下:
1. 需求分析与数据规划
在当时功用中,展现数据由单个
变为了列表
。此刻期望每个图层都能够独立装备网格数量,能够将队伍数视为视图中的数据,每层独立保护。另外,有一个很重要的优化点:
如下所示,需要在图层中展现缩小版的当时内容。假如从头画一遍,那么每次视图变化就会制作 两次相同内容,包括遍历像素点数据,这是色彩、制作矩形。有种办法能够优化这种制作场景,那就是 canvas.drawPicture :
将两处的制作运用同一个 Picture 图形,经过缩放的办法完成大小的不同。如下所示,界说 PaintLayer
作为图层的顶层笼统,其间持有 Picture 数据,经过 update
办法创立或更新图形数据。这儿制作视口一致运用 1024*1024
; 并笼统出 paint
办法,处理制作逻辑:
abstract class PaintLayer {
static Size kPaintViewPort = const Size(1024, 1024);
String name;
final String id;
late Picture picture;
PaintLayer({
required this.id,
this.name = '新建图层',
});
void update() {
PictureRecorder recorder = PictureRecorder();
Canvas canvas = Canvas(recorder);
paint(canvas, kPaintViewPort);
picture = recorder.endRecording();
}
void paint(Canvas canvas, Size size);
}
然后派生出 PixLayer
负责制作像素图层,其间持有队伍格数和像素数据列表 pixCells
。然后完成 paint 办法,在 1024*1024
的画板上制作内容:
class PixLayer extends PaintLayer {
int row;
int column;
final List<PixCell> pixCells;
PixLayer({
required this.row,
required this.column,
required this.pixCells,
super.name,
required super.id,
});
@override
void paint(Canvas canvas, Size size) {
Paint cellPaint = Paint();
double side = min(size.width, size.height);
double stepH = side / column;
double stepW = side / row;
for (int i = 0; i < pixCells.length; i++) {
PixCell cell = pixCells[i];
double top = cell.position.$1 * stepW;
double left = cell.position.$2 * stepH;
Rect rect = Rect.fromLTWH(top, left, stepW, stepH);
canvas.drawRect(rect.deflate(-0.2), cellPaint..color = cell.color);
}
}
}
2.事务逻辑处理
此刻处理制作逻辑的 PixPaintLogic 类,需要保护 PaintLayer
列表数据,由于需要切换激活的图层,运用保护 activeLayerId
作为激活索引。另外,基于激活图层和图层列表,能够供给一些 get 办法便于拜访数据:
class PixPaintLogic with ChangeNotifier {
String activeLayerId = '';
final List<PaintLayer> _layers = [];
PixLayer get activePixLayer => _layers.whereType<PixLayer>().singleWhere((e) => e.id == activeLayerId);
List<PixCell> get pixCells => activePixLayer.pixCells;
int get row => activePixLayer.row;
int get column => activePixLayer.column;
- 增加图层:
addPixLayer
处理逻辑
运用 Uuid
作为仅有标识,创立 PixLayer
目标,并参加 _layers
列表中,假如有激活的索引,插入在它上方:并触发 changeActiveLayer
办法,更新激活索引
PixPaintLogic() {
addPixLayer();
}
void addPixLayer() {
int activeIndex = 0;
if (activeLayerId.isNotEmpty) {
activeIndex = _layers.indexWhere((e) => e.id == activeLayerId);
}
String id = const Uuid().v4();
PixLayer pixLayer =
PixLayer(name: "像素图层", row: 32, column: 32, pixCells: [], id: id)
..update();
_layers.insert(activeIndex, pixLayer);
changeActiveLayer(id);
}
- 激活图层:
changeActiveLayer
处理逻辑
激活图层十分简单,需要更新 activeLayerId
,并经过 activePixLayer.update
更新图层中的 picture 数据即可。这样在 notifyListeners
之后,两处的制作逻辑中拜访的就是新版的 picture 目标。
void changeActiveLayer(String layerId) {
activeLayerId = layerId;
activePixLayer.update();
notifyListeners();
}
- 删去激活图层:
removeActiveLayer
处理逻辑
删去图层时,当只有一个时制止删去。假如当时激活图层不是最终一个,删去后会激活下一个图层。假如激活图层是最终一个,则激活前一个:
void removeActiveLayer() {
if (_layers.length == 1) {
return;
}
int currentIndex = 0;
int activeIndex = 0;
if (activeLayerId.isNotEmpty) {
currentIndex = _layers.indexWhere((e) => e.id == activeLayerId);
}
if (currentIndex == _layers.length - 1) {
activeIndex = currentIndex - 1;
} else {
activeIndex = currentIndex + 1;
}
activeLayerId = _layers[activeIndex].id;
_layers.removeAt(currentIndex);
notifyListeners();
}
3. 视图层处理
首先中心区域的 PixEditorPainter 制作逻辑中,经过缩放的办法,对激活图层中的 picture 进行制作:
@override
void paint(Canvas canvas, Size size) {
Paint bgPaint = Paint()..color = config.backgroundColor;
canvas.drawRect(Offset.zero & size, bgPaint);
/// 制作激活图层的 picture
canvas.save();
double rate = size.height / PaintLayer.kPaintViewPort.height;
canvas.scale(rate);
canvas.drawPicture(pixPaintLogic.activePixLayer.picture);
canvas.restore();
if (config.showGrid) {
drawGrid(canvas, size);
}
}
缩略图封装为 LayerPreview
组件,运用 LayerPreviewPainter
进行制作。其间制作的内容也是激活图层中的 picture 目标,以此完成了两个制作区域,运用同一份制作资源:
class LayerPreview extends StatelessWidget {
final Picture picture;
const LayerPreview({super.key, required this.picture});
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: LayerPreviewPainter(picture),
);
}
}
class LayerPreviewPainter extends CustomPainter {
final Picture picture;
LayerPreviewPainter(this.picture);
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Offset.zero & size, Paint()..color = Colors.white);
canvas.save();
canvas.translate((size.width - size.height) / 2, 0);
double rate = size.height / PaintLayer.kPaintViewPort.height;
canvas.scale(rate);
canvas.drawPicture(picture);
canvas.restore();
canvas.drawRect(Offset.zero & size, Paint()..style = PaintingStyle.stroke);
}
@override
bool shouldRepaint(covariant LayerPreviewPainter oldDelegate) {
return picture != oldDelegate.picture;
}
}
图层的操作面板,运用 ListView
展现 PaintLayer
列表数据。条目构造的细节没什么难度,就不多说了,能够详见源码。
ListView.separated(
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (_, index) => LayerItem(
onSelectLayer: (layer) {
logic.changeActiveLayer(layer.id);
},
active: layers[index].id == logic.activeLayerId,
layer: layers[index],
),
itemCount: layers.length,
),
目前为止,已经引入了图层的概念,并且支撑新建、删去、切换图层。至于图层更多的功用、比方确定、合并、复制、修改等,将在后续逐步完善。那本文就到这儿,谢谢观看~