<七>Drawables的使用(一)

本文深入介绍了Drawable中的着色、颜色提取与矢量图片技术,包括Tint的概念与实现,从图像中提取颜色的方法,以及矢量图片的应用。通过实例解析,展示了如何利用这些技术优化客户端资源,实现Material Design效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下从三个方面进行介绍Drawables:

* Drawable tinting (着色)
* Prominent color extraction(颜色提取)
* Vector drawables(矢量图片)

Tint Drawable Resources

以下部分介绍转自:http://segmentfault.com/a/1190000003038675?utm_source=tuicool&utm_medium=referral

什么是Tint
当我开始接触Tint这个词的时候,其实是蛮不理解它的意思的,以及并不清楚Google发明它的目的,它一般搭配Background配合使用,但是现在已经有了Background,为什么还需要Tint呢?
Tint 翻译为着色。
着色,着什么色呢?和背景有关,当然是着背景的色。当我开发客户端,使用了appcompat-v7包的时候,为了实现Material
Design的效果,我们会去设置主题里的几个颜色,重要的比如primaryColor,colorControlNormal,colorControlActived
等等,而我们使用的一些组件,比如EditText就会自动变成我们想要的背景颜色,在背景图只有一张的情况下,这样的做法极大的减少了我们apk包的大小。
实现的方式就是用一个颜色为我们的背景图片设置tint(着色)。 例子:

看看即将发布的SegmentFault for Android
2.7中,发布问题功能,这个EditText的颜色和我们的主要颜色相同。它利用了TintManager这个类,为自己的背景进行着色(绿色)。 那么这个原始图是什么样子呢?我们从appcompat-v7包中找到了这个图,是一个.9图,样子如下:

其实它只是一个黑色的条,通过绿色的着色,变成了一个绿色的条。 就是这样的设计方式,使得我们在Material
Design中省了多少资源文件呀! 好了,既然理解了tint的含义,我们赶紧看下这一切是如何实现的吧。
其实底层特别简单,了解过渲染的同学应该知道PorterDuffColorFilter这个东西,我们使用SRC_IN的方式,对这个Drawable进行颜色方面的渲染,就是在这个Drawable中有像素点的地方,再用我们的过滤器着色一次。
实际上如果要我们自己实现,只用获取View的backgroundDrawable之后,设置下colorFilter即可。
看下最核心的代码就这么几行
if (filter == null) {
// Cache miss, so create a color filter and add it to the cache
filter = new PorterDuffColorFilter(color, mode);
}

 d.setColorFilter(filter); d.setColorFilter(filter); 由于API Level 21以前不支持background

tint在xml中设置(android:tint和android:tintMod),于是提供了ViewCompat.setBackgroundTintList方法和ViewCompat.setBackgroundTintMode用来手动更改需要着色的颜色,但要求相关的View继承TintableBackgroundView接口。

TintMode的类型有以下16种:
这里写图片描述
1.PorterDuff.Mode.CLEAR 所绘制不会提交到画布上。
2.PorterDuff.Mode.SRC 显示上层绘制图片
3.PorterDuff.Mode.DST 显示下层绘制图片
4.PorterDuff.Mode.SRC_OVER 正常绘制显示,上下层绘制叠盖。
5.PorterDuff.Mode.DST_OVER 上下层都显示。下层居上显示。
6.PorterDuff.Mode.SRC_IN 取两层绘制交集。显示上层。
7.PorterDuff.Mode.DST_IN 取两层绘制交集。显示下层。
8.PorterDuff.Mode.SRC_OUT 取上层绘制非交集部分。
9.PorterDuff.Mode.DST_OUT 取下层绘制非交集部分。
10.PorterDuff.Mode.SRC_ATOP 取下层非交集部分与上层交集部分
11.PorterDuff.Mode.DST_ATOP 取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR 异或:去除两图层交集部分
13.PorterDuff.Mode.DARKEN 取两图层全部区域,交集部分颜色加深
14.PorterDuff.Mode.LIGHTEN 取两图层全部,点亮交集部分颜色
15.PorterDuff.Mode.MULTIPLY 取两图层交集部分叠加后颜色
16.PorterDuff.Mode.SCREEN 取两图层全部区域,交集部分变为透明色

从图像中提取颜色(Palette)

Android Support Library r21 r21 及更高版本提供了Palette 类,以便我们从图像提取颜色,主要的颜色类型有以下六种:

* vibrant(鲜艳色)
* dark vibrant(鲜艳色中的暗色)
* light vibrant(鲜艳色中的亮色)
* muted(柔和色)
* dark muted(柔和色中的暗色)
* light muted(柔和色中的亮色)    

###使用:

1.首先需要添加依赖:

dependencies {
    ...
    compile 'com.android.support:palette-v7:21.0.0'
}

2.创建示例

  • 同步方法(尽量避免在主线程中使用):
Palette p = Palette.from(bitmap).generate();
  • 异步方法(在异步线程中执行完之后通过监听器回调)
 // Asynchronous
 Palette.from(bitmap).generate(new PaletteAsyncListener() {
     public void onGenerated(Palette p) {
         // Use generated instance
     }
 });

3.获取颜色

  • 通过提供的getter函数直接获取以上提及的六种类型颜色:
  Palette palette = Palette.from(bitmap).generate();
      int vibrant = palette.getVibrantColor(0x000000);
      int vibrantLight = palette.getLightVibrantColor(0x000000);
      //提取失败则返回指定的颜色:0x000000
      ......
  • 通过Swatch对象提取(Swatch里面含有很多关于对应颜色的有用信息)
 Palette palette = Palette.from(bitmap).generate();
   Palette.Swatch swatch = palette.getVibrantSwatch();
   //rgb颜色值
   int rgbColor = swatch.getRgb();
   //hsl颜色向量
   float[] hslValues = swatch.getHsl();
   //该颜色在图像中所占的像素数
   int pixelCount = swatch.getPopulation();
   //对应的标题字体颜色
   int titleTextColor = swatch.getTitleTextColor();
   //对应的正文字体颜色
   int bodyTextColor = swatch.getBodyTextColor();

4:来自网上的一张demo示例图:
这里写图片描述

矢量图片(Vector Drawables)

见下一章

using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Xml; using Newtonsoft.Json; namespace Vision.Controls { #region 基础接口定义 public interface IDrawable { Rectangle Bounds { get; } void Draw(Graphics g); event EventHandler StyleChanged; } public interface IPlugin { string Name { get; } VisualElement CreateElement(); } public interface ICommand { void Execute(); void Undo(); } public interface ISerializer { string Format { get; } string Serialize(WorkflowCanvas canvas); void Deserialize(WorkflowCanvas canvas, string data); } #endregion #region 核心实体类 public class Port { public string Name { get; set; } public PortType Type { get; set; } public Point Position { get; set; } // 相对于节点的偏移 public Port ConnectedPort { get; set; } // 连接的目标端口 public VisualElement Parent { get; set; } // 所属元素 public Point ScreenPosition { get { // 确保 Parent 已初始化,且 Bounds 有效 if (Parent == null) return Position; return new Point( Parent.Bounds.X + Position.X, // 用 Bounds(逻辑坐标)替代 Location Parent.Bounds.Y + Position.Y ); } } } public enum PortType { Input, Output } public abstract class VisualElement : UserControl, IDrawable { public virtual Rectangle Bounds { get { scale = WorkflowCanvas.Instance.Scale; return new Rectangle( (int)(LogicLocation.X * scale), (int)(LogicLocation.Y * scale), (int)(LogicSize.Width * scale), (int)(LogicSize.Height * scale) ); } } public abstract void Draw(Graphics g); public event EventHandler StyleChanged; public virtual string Title { get; set; } = "DefaultTitle"; protected void OnStyleChanged() => StyleChanged?.Invoke(this, EventArgs.Empty); public bool IsSelected { get; set; } = false; public Point LogicLocation { get; set; } = new Point(0, 0); public Size LogicSize { get; set; } = new Size(100, 50); public float scale {get; set; } // 新增:Z轴层级(值越大越靠前) public int ZIndex { get; set; } = 0; [Browsable(false)] public new Point Location { get => LogicLocation; set => LogicLocation = value; } [Browsable(false)] public new Size Size { get => LogicSize; set => LogicSize = value; } } public class Connection : IDrawable { public Port StartPort { get; set; } public Port EndPort { get; set; } public Point StartPosition { get; set; } // 临时连接起点 public Point EndPosition { get; set; } // 临时连接终点 public Rectangle Bounds { get { if (StartPort != null && EndPort != null) { var minX = Math.Min(StartPort.ScreenPosition.X, EndPort.ScreenPosition.X); var maxX = Math.Max(StartPort.ScreenPosition.X, EndPort.ScreenPosition.X); var minY = Math.Min(StartPort.ScreenPosition.Y, EndPort.ScreenPosition.Y); var maxY = Math.Max(StartPort.ScreenPosition.Y, EndPort.ScreenPosition.Y); return new Rectangle(minX - 10, minY - 10, maxX - minX + 20, maxY - minY + 20); } return new Rectangle(StartPosition.X, StartPosition.Y, EndPosition.X - StartPosition.X, EndPosition.Y - StartPosition.Y); } } public void Draw(Graphics g) { var scale = WorkflowCanvas.Instance.Scale; g.ScaleTransform(scale, scale); // 应用画布缩放 Point start = StartPort?.ScreenPosition ?? StartPosition; Point end = EndPort?.ScreenPosition ?? EndPosition; g.DrawLine(Pens.Black, start, end); // 绘制箭头(末端) DrawArrow(g, end, start); } private void DrawArrow(Graphics g, Point end, Point start) { // 计算箭头方向(从 start 到 end 的向量) float dx = end.X - start.X; float dy = end.Y - start.Y; float angle = (float)Math.Atan2(dy, dx); int arrowSize = (int)(10 * WorkflowCanvas.Instance.Scale); // 箭头大小适配缩放 // 计算箭头的两个侧点 Point arrowPoint1 = new Point( (int)(end.X - arrowSize * Math.Cos(angle - Math.PI / 6)), (int)(end.Y - arrowSize * Math.Sin(angle - Math.PI / 6)) ); Point arrowPoint2 = new Point( (int)(end.X - arrowSize * Math.Cos(angle + Math.PI / 6)), (int)(end.Y - arrowSize * Math.Sin(angle + Math.PI / 6)) ); // 绘制箭头(填充三角形) g.FillPolygon(Brushes.Black, new Point[] { end, arrowPoint1, arrowPoint2 }); } public event EventHandler StyleChanged; } public partial class VisionNode : VisualElement { public string Title { get; set; } = "Node"; public List<Port> InputPorts { get; } = new List<Port>(); public List<Port> OutputPorts { get; } = new List<Port>(); public IDictionary<string, object> Parameters { get; set; } = new Dictionary<string, object>(); public bool IsSelected { get; set; } public override Rectangle Bounds => new Rectangle(Location, Size); public VisionNode() { DoubleBuffered = true; Size = new Size(100, 50); BackColor = Color.Transparent; InputPorts.Add(new Port { Name = "In1", Type = PortType.Input, Position = new Point(Size.Width/2, this.Location.Y), Parent = this }); OutputPorts.Add(new Port { Name = "Out1", Type = PortType.Output, Position = new Point(Size.Width / 2,this.Location.Y+ Size.Height), Parent = this }); } public override void Draw(Graphics g) { // 绘制节点框等其他内容(可再处理缩放) var pen = StyleManager.Instance.NodeBorderPen; g.DrawRectangle(pen, Bounds); // 若有全局缩放,先重置变换(避免文字被缩放影响) g.ResetTransform(); // 保存原始 Graphics 状态 var state = g.Save(); var font = StyleManager.Instance.NodeFont; // 用物理坐标绘制文字(假设 _scale 是画布缩放因子) SizeF textSize = g.MeasureString(Title, font); var physicalX = Bounds.X + (Bounds.Width - textSize.Width) / 2; var physicalY = Bounds.Y + (Bounds.Height - textSize.Height) / 2; g.DrawString(Title, font, Brushes.Black, new PointF(physicalX, physicalY)); g.Restore(state); } protected override void OnLocationChanged(EventArgs e) { base.OnLocationChanged(e); OnStyleChanged(); } } #endregion #region 性能优化模块 public class QuadTree { private readonly Rectangle _bounds; private List<IDrawable> _objects = new List<IDrawable>(); private QuadTree[] _children; private const int MAX_OBJECTS = 10; private const int MIN_SIZE = 100; public QuadTree(Rectangle bounds) => _bounds = bounds; public Rectangle Bounds => _bounds; public void Insert(IDrawable obj) { if (_children != null) { int index = GetChildIndex(obj.Bounds); if (index != -1) { _children[index].Insert(obj); return; } } _objects.Add(obj); if (_objects.Count > MAX_OBJECTS && _bounds.Width > MIN_SIZE) { Subdivide(); var objs = new List<IDrawable>(_objects); _objects.Clear(); foreach (var o in objs) { int index = GetChildIndex(o.Bounds); if (index != -1) _children[index].Insert(o); else _objects.Add(o); } } } private void Subdivide() { int x = _bounds.X, y = _bounds.Y; int w = _bounds.Width / 2, h = _bounds.Height / 2; _children = new QuadTree[4] { new QuadTree(new Rectangle(x, y, w, h)), new QuadTree(new Rectangle(x + w, y, w, h)), new QuadTree(new Rectangle(x, y + h, w, h)), new QuadTree(new Rectangle(x + w, y + h, w, h)) }; } private int GetChildIndex(Rectangle bounds) { if (_children == null) return -1; int midX = _bounds.X + _bounds.Width / 2; int midY = _bounds.Y + _bounds.Height / 2; bool top = bounds.Y < midY; bool bottom = bounds.Bottom > midY; bool left = bounds.X < midX; bool right = bounds.Right > midX; if (top && left) return 0; if (top && right) return 1; if (bottom && left) return 2; if (bottom && right) return 3; return -1; } public void Clear() { _objects.Clear(); if (_children != null) { for (int i = 0; i < _children.Length; i++) { _children[i].Clear(); _children[i] = null; } _children = null; } } public List<IDrawable> Query(Rectangle area) { var result = new List<IDrawable>(); if (!_bounds.IntersectsWith(area)) return result; result.AddRange(_objects.Where(o => o.Bounds.IntersectsWith(area))); if (_children != null) { foreach (var child in _children) result.AddRange(child.Query(area)); } return result; } public void Query(List<IDrawable> result, Rectangle area) { if (!_bounds.IntersectsWith(area)) return; result.AddRange(_objects.Where(o => o.Bounds.IntersectsWith(area))); if (_children != null) { foreach (var child in _children) { child.Query(result, area); } } } } // 增强 ForceDirectedLayout 类 public class ForceDirectedLayout { private const float Repulsion = 10000f; // 排斥力强度 private const float Attraction = 0.1f; // 吸引力强度 private const float Damping = 0.9f; // 阻尼系数 private const int MaxIterations = 50; // 最大迭代次数 // 存储节点速度 private Dictionary<VisualElement, PointF> _velocities = new Dictionary<VisualElement, PointF>(); public void Layout(IEnumerable<VisualElement> nodes, IEnumerable<Connection> connections) { var nodeList = nodes.ToList(); InitializeVelocities(nodeList); // 迭代计算力和位置 for (int i = 0; i < MaxIterations; i++) { ApplyForces(nodeList, connections); UpdatePositions(nodeList); } } private void InitializeVelocities(List<VisualElement> nodes) { _velocities.Clear(); foreach (var node in nodes) { _velocities[node] = new PointF(0, 0); } } private void ApplyForces(List<VisualElement> nodes, IEnumerable<Connection> connections) { // 计算节点间排斥力 foreach (var node in nodes) { foreach (var other in nodes.Where(n => n != node)) { float dx = node.LogicLocation.X - other.LogicLocation.X; float dy = node.LogicLocation.Y - other.LogicLocation.Y; float distance = (float)Math.Sqrt(dx * dx + dy * dy); if (distance < 1) distance = 1; // 避免除零 float force = Repulsion / (distance * distance); // 更新速度 _velocities[node] = new PointF( _velocities[node].X + dx / distance * force, _velocities[node].Y + dy / distance * force ); } } // 计算连接间吸引力 foreach (var conn in connections) { var startNode = conn.StartPort?.Parent; var endNode = conn.EndPort?.Parent; if (startNode != null && endNode != null) { float dx = endNode.LogicLocation.X - startNode.LogicLocation.X; float dy = endNode.LogicLocation.Y - startNode.LogicLocation.Y; float distance = (float)Math.Sqrt(dx * dx + dy * dy); float force = Attraction * distance; // 更新速度(吸引力方向相反) _velocities[startNode] = new PointF( _velocities[startNode].X + dx * force, _velocities[startNode].Y + dy * force ); _velocities[endNode] = new PointF( _velocities[endNode].X - dx * force, _velocities[endNode].Y - dy * force ); } } } private void UpdatePositions(List<VisualElement> nodes) { foreach (var node in nodes) { // 应用阻尼 _velocities[node] = new PointF( _velocities[node].X * Damping, _velocities[node].Y * Damping ); // 更新位置 node.LogicLocation = new Point( (int)(node.LogicLocation.X + _velocities[node].X), (int)(node.LogicLocation.Y + _velocities[node].Y) ); } } } #endregion #region 样式与主题管理 public class StyleManager { public event EventHandler StyleChanged; public static StyleManager Instance { get; } = new StyleManager(); public Pen NodeBorderPen { get; private set; } = new Pen(Color.Black, 2); public Font NodeFont { get; private set; } = new Font("Segoe UI", 16); public float ZoomThreshold { get; set; } = 0.8f; // LOD阈值 private StyleManager() { } public void ApplyDarkTheme() { NodeBorderPen.Color = Color.White; NodeFont = new Font("Segoe UI", 10, FontStyle.Bold); foreach (var element in WorkflowCanvas.Instance.Elements) OnStyleChanged(); } public bool ShowPortsWhenZoomed(float scale) => scale > ZoomThreshold; public virtual void OnStyleChanged() { StyleChanged?.Invoke(this, EventArgs.Empty); } } #endregion #region 命令模式与撤销、重做 public partial class WorkflowCanvas : Panel { public List<VisualElement> Elements { get; } = new List<VisualElement>(); public List<Connection> Connections { get; } = new List<Connection>(); private QuadTree _quadTree; private Rectangle _dirtyRegion = Rectangle.Empty; private ForceDirectedLayout _layoutEngine = new ForceDirectedLayout(); private Stack<ICommand> _undoStack = new Stack<ICommand>(); private Stack<ICommand> _redoStack = new Stack<ICommand>(); private float _scale = 1.0f; private float _targetScale = 1.0f; private VisualElement _draggingElement; private Point _dragStart; private Port _draggingPort; private PointF _zoomCenter; private PointF _panOffset = PointF.Empty; private Connection _tempConnection; private Rectangle _selectionRect; private bool _isSelecting; private System.Windows.Forms.Timer _zoomTimer; public float Scale { get => _scale; private set { _scale = Math.Max(0.1f, Math.Min(5.0f, value)); // 限制范围 Invalidate(); // 更新画布 } } // 添加公共方法来安全地修改缩放比例 public void SetScale(float newScale) { Scale = newScale; } public static WorkflowCanvas Instance { get; private set; } public WorkflowCanvas() { Instance = this; DoubleBuffered = true; MouseDown += OnMouseDown; MouseMove += OnMouseMove; MouseUp += OnMouseUp; MouseWheel += OnMouseWheel; _quadTree = new QuadTree(new Rectangle(0, 0, 10000, 10000)); _zoomTimer = new System.Windows.Forms.Timer { Interval = 16 }; _zoomTimer.Tick += (sender, e) => ZoomTick(); } // 执行自动布局 public void AutoLayout() { _layoutEngine.Layout(Elements, Connections); Invalidate(); } // 添加布局命令(可选) public class LayoutCommand : ICommand { private readonly WorkflowCanvas _canvas; private Dictionary<VisualElement, Point> _oldPositions = new Dictionary<VisualElement, Point>(); public LayoutCommand(WorkflowCanvas canvas) { _canvas = canvas; // 保存原始位置用于撤销 foreach (var element in canvas.Elements) { _oldPositions[element] = element.LogicLocation; } } public void Execute() => _canvas.AutoLayout(); public void Undo() { foreach (var element in _canvas.Elements) { if (_oldPositions.TryGetValue(element, out var pos)) { element.LogicLocation = pos; } } _canvas.Invalidate(); } } public void ZoomTo(float newScale, Point? zoomCenter = null, bool animated = true) { PointF oldCenter = _zoomCenter==null ? new PointF(Width / 2f, Height / 2f) : _zoomCenter; PointF oldLogicalCenter = new PointF(oldCenter.X / _scale, oldCenter.Y / _scale); _targetScale = Math.Max(0.1f, Math.Min(5.0f, newScale)); if (animated && Math.Abs(_scale - _targetScale) > 0.05f) { _zoomCenter = oldLogicalCenter; _zoomTimer.Start(); } else { _scale = _targetScale; ApplyZoomWithFixedPoint(oldLogicalCenter); Invalidate(); } } private void ZoomTick() { // 缓动算法:平滑过渡到目标缩放值 _scale += (_targetScale - _scale) * 0.2f; // 接近目标时停止动画 if (Math.Abs(_scale - _targetScale) < 0.01f) { _scale = _targetScale; _zoomTimer.Stop(); } ApplyZoomWithFixedPoint(_zoomCenter); Invalidate(); } private void ApplyZoomWithFixedPoint(PointF logicalCenter) { // 计算缩放后的偏移,保持逻辑中心点不变 float dx = logicalCenter.X * (_scale - WorkflowCanvas.Instance.Scale); float dy = logicalCenter.Y * (_scale - WorkflowCanvas.Instance.Scale); // 调整所有元素位置(需根据实际坐标系调整) foreach (var element in Elements) { element.LogicLocation = new Point( (int)(element.LogicLocation.X - dx), (int)(element.LogicLocation.Y - dy) ); } } public void AddElement(VisualElement element) { var newBounds = _quadTree.Bounds; // 确保元素位置在四叉树范围内,否则扩容 if (element.Location.X < 0 || element.Location.Y < 0 || element.Right > newBounds.Right || element.Bottom > newBounds.Bottom) { ResizeQuadTreeBounds(element); } Elements.Add(element); Controls.Add(element); element.Visible = true; // 确保可见 _quadTree.Insert(element); InvalidateRegion(element.Bounds); } public void AddConnection(Connection conn) { Connections.Add(conn); _quadTree.Insert(conn); // 关键:插入四叉树,确保 OnPaint 能查询到 InvalidateRegion(conn.Bounds); } private void ResizeQuadTreeBounds(VisualElement element) { var newBounds = _quadTree.Bounds; newBounds.X = Math.Min(newBounds.X, element.Location.X); newBounds.Y = Math.Min(newBounds.Y, element.Location.Y); newBounds.Width = Math.Max(newBounds.Width, element.Right - newBounds.X); newBounds.Height = Math.Max(newBounds.Height, element.Bottom - newBounds.Y); _quadTree = new QuadTree(newBounds); // 重建四叉树(简易方式,生产需迁移元素) // 迁移已有元素到新四叉树... } public void RemoveElement(VisualElement element) { var connectionsToRemove = Connections.Where(c => c.StartPort?.Parent == element || c.EndPort?.Parent == element).ToList(); foreach (var conn in connectionsToRemove) Connections.Remove(conn); Elements.Remove(element); Controls.Remove(element); InvalidateRegion(element.Bounds); } public void InvalidateRegion(Rectangle region) { _dirtyRegion = Rectangle.Union(_dirtyRegion, region); Invalidate(); } #region 交互处理 private void OnMouseDown(object sender, MouseEventArgs e) { var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale)); _draggingElement = Elements.FirstOrDefault(n => n.Bounds.Contains(scaledPoint)); if (_draggingElement != null) { _dragStart = scaledPoint; _draggingPort = null; // 拖动节点时不处理端口 _tempConnection = null; } else { _draggingPort = CheckPortClick(_draggingElement, scaledPoint); if (_draggingPort != null) { _tempConnection = new Connection { StartPort = _draggingPort, StartPosition = _draggingPort.ScreenPosition }; } else if (e.Button == MouseButtons.Left) { _isSelecting = true; _selectionRect = new Rectangle(scaledPoint, Size.Empty); } } } private Port CheckPortClick(VisualElement element, Point localPoint) { if (element is VisionNode node) { foreach (var port in node.InputPorts.Concat(node.OutputPorts)) { var portCenter = new Point(port.Position.X + 5, port.Position.Y + 5); if (Math.Sqrt(Math.Pow(localPoint.X - portCenter.X, 2) + Math.Pow(localPoint.Y - portCenter.Y, 2)) <= 5) return port; } } return null; } private void OnMouseMove(object sender, MouseEventArgs e) { var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale)); if (_draggingElement != null && _draggingPort == null) { int dx = scaledPoint.X - _dragStart.X; int dy = scaledPoint.Y - _dragStart.Y; // 更新逻辑坐标(带边界限制) int maxLogicX = 10000 - _draggingElement.LogicSize.Width; int maxLogicY = 10000 - _draggingElement.LogicSize.Height; _draggingElement.Location = new Point (_draggingElement.Location.X + dx < 0 ? 0 : _draggingElement.Location.X + dx > maxLogicX ? maxLogicX : _draggingElement.Location.X + dx, _draggingElement.Location.Y + dy < 0 ? 0 : _draggingElement.LogicLocation.Y + dy > maxLogicY ? maxLogicY : _draggingElement.LogicLocation.Y + dy); _dragStart = scaledPoint; // 标记脏区域(旧位置 + 新位置) var oldBounds = new Rectangle( _draggingElement.Bounds.X - (int)(dx * _scale), _draggingElement.Bounds.Y - (int)(dy * _scale), _draggingElement.Bounds.Width, _draggingElement.Bounds.Height ); InvalidateRegion(_draggingElement.Bounds); } else if (_tempConnection != null) { _tempConnection.EndPosition = scaledPoint; InvalidateRegion(_tempConnection.Bounds); } else if (_isSelecting) { _selectionRect.Width = scaledPoint.X - _selectionRect.X; _selectionRect.Height = scaledPoint.Y - _selectionRect.Y; InvalidateRegion(_selectionRect); } } private void OnMouseUp(object sender, MouseEventArgs e) { var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale)); if (_tempConnection != null) { var targetElement = Elements.FirstOrDefault(n => n.Bounds.Contains(scaledPoint)); var targetPort = targetElement != null ? CheckPortClick(targetElement, scaledPoint) : null; if (targetPort != null && _tempConnection.StartPort.Type != targetPort.Type) { _tempConnection.EndPort = targetPort; AddConnection(_tempConnection); // 改用 AddConnection _tempConnection.StartPort.ConnectedPort = targetPort; targetPort.ConnectedPort = _tempConnection.StartPort; InvalidateRegion(_tempConnection.Bounds); } _tempConnection = null; _draggingPort = null; } else if (_isSelecting) { _isSelecting = false; foreach (var element in Elements) element.IsSelected = _selectionRect.IntersectsWith(element.Bounds); InvalidateRegion(_selectionRect); } _draggingElement = null; } private void OnMouseWheel(object sender, MouseEventArgs e) { PointF mouseLogicPos = new PointF( (e.Location.X - _panOffset.X) / _scale, (e.Location.Y - _panOffset.Y) / _scale ); float oldScale = _scale; _scale *= e.Delta > 0 ? 1.1f : 0.9f; _scale = Math.Max(0.1f, Math.Min(5.0f, _scale)); Invalidate(); } #endregion #region 绘制逻辑(脏矩形+LOD+四叉树) protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); // 防止缩放因子过小导致除零或极小值 var safeScale = Math.Max(0.01f, _scale); // 使用浮点计算避免精度丢失 var clip = e.ClipRectangle; var scaledClip = new RectangleF( clip.X / safeScale, clip.Y / safeScale, clip.Width / safeScale, clip.Height / safeScale ); // 转换为整数矩形,确保尺寸至少为1x1 var intClip = new Rectangle( (int)scaledClip.X, (int)scaledClip.Y, Math.Max(1, (int)scaledClip.Width), Math.Max(1, (int)scaledClip.Height) ); var g = e.Graphics; g.ScaleTransform(safeScale, safeScale); // 线程安全访问(使用副本避免并发修改) List<IDrawable> drawables; lock (_quadTree) { drawables = _quadTree.Query(intClip).ToList(); } // 绘制节点 foreach (var element in drawables.OfType<VisualElement>().OrderBy(el => el.ZIndex)) { try { element.Draw(g); } catch (Exception ex) { Debug.WriteLine($"绘制节点时出错: {ex.Message}"); // 可添加降级绘制逻辑 } } // 绘制连接 foreach (var drawable in drawables.OfType<Connection>()) { try { drawable.Draw(g); } catch (Exception ex) { Debug.WriteLine($"绘制连接时出错: {ex.Message}"); } } // 绘制临时连接 if (_tempConnection != null) { _tempConnection.Draw(g); } _dirtyRegion = Rectangle.Empty; } public void BringToFront(VisualElement element) { var maxZIndex = Elements.Max(el => el.ZIndex); element.ZIndex = maxZIndex + 1; Invalidate(); } // 将元素置于底层 public void SendToBack(VisualElement element) { var minZIndex = Elements.Min(el => el.ZIndex); element.ZIndex = minZIndex - 1; Invalidate(); } // 提升层 public void MoveUp(VisualElement element) { var nextZIndex = Elements .Where(el => el.ZIndex > element.ZIndex) .Select(el => el.ZIndex) .DefaultIfEmpty(element.ZIndex + 1) .Min(); element.ZIndex = nextZIndex; Invalidate(); } // 降低层 public void MoveDown(VisualElement element) { var prevZIndex = Elements .Where(el => el.ZIndex < element.ZIndex) .Select(el => el.ZIndex) .DefaultIfEmpty(element.ZIndex - 1) .Max(); element.ZIndex = prevZIndex; Invalidate(); } #endregion #region 命令模式 public void ExecuteCommand(ICommand command) { command.Execute(); _undoStack.Push(command); _redoStack.Clear(); } public void Undo() { if (_undoStack.Any()) { var cmd = _undoStack.Pop(); cmd.Undo(); _redoStack.Push(cmd); Invalidate(); } } public void Redo() { if (_redoStack.Any()) { var cmd = _redoStack.Pop(); cmd.Execute(); _undoStack.Push(cmd); Invalidate(); } } public class AddElementCommand : ICommand { private readonly WorkflowCanvas _canvas; private readonly VisualElement _element; public AddElementCommand(WorkflowCanvas canvas, VisualElement element) { _canvas = canvas; _element = element; } public void Execute() => _canvas.AddElement(_element); public void Undo() => _canvas.RemoveElement(_element); } } #endregion #endregion #region 插件与序列化 public class PluginLoader { public static IEnumerable<IPlugin> Load(string directory) { var result = new List<IPlugin>(); // 临时集合存储结果 foreach (var file in Directory.GetFiles(directory, "*.dll")) { try { var assembly = Assembly.LoadFrom(file); foreach (var type in assembly.GetTypes()) { if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsAbstract) { result.Add((IPlugin)Activator.CreateInstance(type)); // 收集结果 } } } catch (Exception ex) { Debug.WriteLine($"Plugin load failed: {ex}"); } } return result; // 统返回结果 } } public class JsonSerializer : ISerializer { public string Format => "json"; public string Serialize(WorkflowCanvas canvas) { var data = new { Elements = canvas.Elements.Select(e => new { Type = e.GetType().FullName, Location = e.Location, Size = e.Size, Title = (e as VisionNode)?.Title, Parameters = (e as VisionNode)?.Parameters }).ToList(), Connections = canvas.Connections.Select(c => new { StartPort = c.StartPort != null ? new { c.StartPort.Parent.Title, c.StartPort.Name, c.StartPort.Type } : null, EndPort = c.EndPort != null ? new { c.EndPort.Parent.Title, c.EndPort.Name, c.EndPort.Type } : null, StartPosition = c.StartPosition, EndPosition = c.EndPosition }).ToList(), Scale = canvas.Scale }; return JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented); } public void Deserialize(WorkflowCanvas canvas, string data) { // 反序列化逻辑(需映射类型,此处简化) var obj = JsonConvert.DeserializeAnonymousType(data, new { Elements = new List<ElementDTO>(), Connections = new List<ConnectionDTO>(), Scale = 1.0f }); canvas.Elements.Clear(); canvas.Connections.Clear(); canvas.Controls.Clear(); canvas.SetScale(obj.Scale); foreach (var dto in obj.Elements) { var type = Type.GetType(dto.Type); if (type == null || !typeof(VisualElement).IsAssignableFrom(type)) continue; var element = (VisualElement)Activator.CreateInstance(type); element.Location = dto.Location; element.Size = dto.Size; if (element is VisionNode node) { node.Title = dto.Title; node.Parameters = dto.Parameters.ToDictionary(k => k.Key, v => v.Value); } canvas.AddElement(element); } // 重建连接... } private class ElementDTO { public string Type { get; set; } public Point Location { get; set; } public Size Size { get; set; } public string Title { get; set; } public Dictionary<string, object> Parameters { get; set; } } private class ConnectionDTO { public PortRef StartPort { get; set; } public PortRef EndPort { get; set; } public Point StartPosition { get; set; } public Point EndPosition { get; set; } } private class PortRef { public string Title { get; set; } public string Name { get; set; } public PortType Type { get; set; } } } #endregion }使用winform完善功能
最新发布
06-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值