支持各种形状的旋转,平移和缩放。控件IsHitTestVisible = false,即可拖动,旋转和缩放。

Manipulations.axaml代码

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Height="359" Width="607"
        x:Class="AvaloniaUI.Manipulations"
        Title="Manipulations">
    <Grid>
        <Canvas Name="canvas" Background="Transparent">
            <Image Canvas.Top="10" Canvas.Left="10" Width="200" Source="avares://AvaloniaUI/Resources/Images/Koala.jpg"/>
            <Image Canvas.Top="30" Canvas.Left="350" Width="200" Source="avares://AvaloniaUI/Resources/Images/Penguins.jpg"/>
            <Image Canvas.Top="100" Canvas.Left="200" Width="200" Source="avares://AvaloniaUI/Resources/Images/Tulips.jpg"/>
            <Rectangle Canvas.Left="10" Canvas.Top="200" Stroke="White" StrokeThickness="2" Fill="LightBlue" Width="150" Height="150"/>
            <Button Canvas.Left="350" Canvas.Top="300" Width="68" Height="28" IsHitTestVisible="False">点击我</Button>
        </Canvas>
    </Grid>
</Window>

Manipulations.axaml.cs代码

using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;

namespace AvaloniaUI;

public partial class Manipulations : Window
{
    private Control? active;
    private Rectangle selectionBorder;
    private Point lastPoint;
    private int? activePointerId;
    private bool isLeftDragging = false;
    private bool isRightRotating = false;
    public Manipulations()
    {
        InitializeComponent();
        //初始化矩阵
        foreach (var c in canvas.Children)
        {
            if (c.RenderTransform is not MatrixTransform)
            {
                c.RenderTransform = new MatrixTransform(); // 初始化矩阵
            }
        }
        canvas.PointerPressed += OnPointerPressed;
        canvas.PointerReleased += OnPointerReleased;
        canvas.PointerMoved += OnPointerMoved;
        canvas.PointerWheelChanged += OnPointerWheelChanged;
        selectionBorder = new Rectangle()
        {
            Stroke=Brushes.Red,
            StrokeThickness=2,
            IsHitTestVisible=false,
            StrokeDashArray = new AvaloniaList<double> { 5, 3 },
            IsVisible = false,
        };
        canvas.Children.Add(selectionBorder);   
    }
    private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
    {
        var props = e.GetCurrentPoint(this).Properties;
        isLeftDragging = props.IsLeftButtonPressed;
        isRightRotating = props.IsRightButtonPressed;
        lastPoint = e.GetPosition(canvas);
        if (!canvas.Children.Any(c =>
        {
            var bounds = c.Bounds;

            // 本地矩形的四个顶点(未变换)
            var localPoints = new[]
            {
            new Point(0, 0),
            new Point(bounds.Width, 0),
            new Point(bounds.Width, bounds.Height),
            new Point(0, bounds.Height)
        };

            // 转换本地点到 canvas 坐标系
            var transform = c.TransformToVisual(canvas);//不要使用c.RenderTransform,此是视觉上的转换,忽略了内边距、Clip、阴影、形状裁剪等
            if (transform is null)
                return false;

            var transformedPoints = localPoints
                .Select(p => transform.Value.Transform(p))
                .ToArray();

            // 命中测试
            bool res = PointInPolygon(lastPoint, transformedPoints);

            //Console.WriteLine($"是否选中 {res},点击了 {lastPoint}, {string.Join(" ", transformedPoints)}");

            if (res)
                active = c;

            return res;
        }))
        {
            active = null;
            activePointerId = 0;
            selectionBorder.IsVisible = false;
            isLeftDragging = isRightRotating = false;
            return;
        }
        if (isLeftDragging)
        {
            //更新selectionBorder
            Canvas.SetLeft(selectionBorder, Canvas.GetLeft(active!));
            Canvas.SetTop(selectionBorder, Canvas.GetTop(active!));
            selectionBorder.Width = active!.Bounds.Width;
            selectionBorder.Height = active!.Bounds.Height;
            selectionBorder.RenderTransform = active!.RenderTransform;
            selectionBorder.IsVisible = true;
            activePointerId = e.Pointer.Id;
        }
    }

    private void OnPointerMoved(object? sender, PointerEventArgs e)
    {
        if (active is null ||
            e.Pointer.Id != activePointerId ||
            active.RenderTransform is not MatrixTransform mt)
            return;

        var current = e.GetPosition(canvas);
        var delta = current - lastPoint;

        if (isLeftDragging)
        {
            // 拖动(平移)
            mt.Matrix = mt.Matrix * Matrix.CreateTranslation(delta);
        }
        else if (isRightRotating)
        {
            // 计算图像本地中心点(未变换), 默认RenderTransformOrigin=RelativePoint.Center
            var localCenter = new Point(0,0);

            // 计算中心点经过当前矩阵变换后的画布坐标
            var transformedCenter = mt.Matrix.Transform(localCenter);

            // 绕变换后中心点旋转
            mt.Matrix = mt.Matrix
                * Matrix.CreateRotation(Math.PI * delta.X / 180, transformedCenter);
        }
        lastPoint = current;   
    }

    private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
    {
        if (e.Pointer.Id != activePointerId)
            return;

        isLeftDragging = false;
        isRightRotating = false;
    }

    private void OnPointerWheelChanged(object? sender, PointerWheelEventArgs e)
    {
        if (active is null ||
            e.Pointer.Id != activePointerId ||
            active.RenderTransform is not MatrixTransform mt)
            return;

        var scale = e.Delta.Y > 0 ? 1.1 : 0.9;

        mt.Matrix = mt.Matrix
            * Matrix.CreateScale(scale, scale);
    }
    private bool PointInPolygon(Point p, Point[] polygon)
    {
        int n = polygon.Length;
        bool inside = false;

        for (int i = 0, j = n - 1; i < n; j = i++)
        {
            var pi = polygon[i];
            var pj = polygon[j];

            // 检查点在 Y 区间,并且是否在边的左侧
            if (((pi.Y > p.Y) != (pj.Y > p.Y)) &&
                (p.X < (pj.X - pi.X) * (p.Y - pi.Y) / (pj.Y - pi.Y) + pi.X))
            {
                inside = !inside;
            }
        }

        return inside;
    }
}

运行效果

 

posted on 2025-07-13 06:29  dalgleish  阅读(25)  评论(0)    收藏  举报