1. 程式人生 > >利用Clip製作打洞效果

利用Clip製作打洞效果

原文: 利用Clip製作打洞效果

起因

如上篇博文所說,連線原型需要在中間文字上下各留15畫素的空白。設計師完成原型之後,問我有沒有辦法實現,我說,我能想到兩種實現方式。其中一種就是上篇博文所說的OpacityMask。第二種就是使用Clip了。下面是效果圖:

Snap1

程式碼實現

Clip

Clip定義在UIElement中,型別為Geometry 。MSDN中的解釋是獲取或設定用於定義元素內容邊框的幾何圖形。實際上不光可以在邊框處留住空白,在UI元素裡面留出空白也是可以的,只要定義好相關的形狀。

矩形空洞

在一個大矩形中去除一個小矩形就行了。

主要程式碼在RectangleHoleConverter中,程式碼如下:

namespace HoleWithClip
{
    using System;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Media;

    /// <summary>
    /// 矩形空洞的轉換器
    /// </summary>
    public class RectangleHoleConverter : IMultiValueConverter
    
{ /// <summary> /// 轉換成矩形空洞 /// </summary> /// <param name="values"> /// 轉換值列表,第一個表示起始寬度,第二個表示起始高度, /// 第三個表示總寬度,第四個表示總高度 /// 第五個表示宿主寬度,第六個表示宿主寬度 /// </param> /// <param name="targetType"></param> /// <param name="parameter"></param> /// <param name="culture"></param> /// <returns></returns>
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values == null || values.Length != 6 || values.HaveNullItem() || !values.IsAllInstanceOfType(typeof(double))) { return DependencyProperty.UnsetValue; } var maskStartWidth = (double)values[0]; var maskStartHeight = (double)values[1]; var maskTotalWidth = (double)values[2]; var maskTotalHeight = (double)values[3]; var hostWidth = (double)values[4]; var hostHeight = (double)values[5]; if (hostWidth == 0.0 || hostHeight == 0.0) { return null; } var maskRectangle = new RectangleGeometry(new Rect(new Size(hostWidth, hostHeight))); var maskEllipse = new RectangleGeometry(new Rect( new Point(maskStartWidth, maskStartHeight), new Size(maskTotalWidth, maskTotalHeight))); var combinedGeometry = Geometry.Combine(maskRectangle, maskEllipse, GeometryCombineMode.Exclude, null); return combinedGeometry; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { return new[] { Binding.DoNothing }; } } }

其中用到了兩個擴充套件方法如下:

namespace HoleTest
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    public static class EnumerableExtension
    {
        /// <summary>
        /// 列舉器中是否存在null條目
        /// </summary>
        /// <typeparam name="T">元素型別</typeparam>
        /// <param name="enumerable">元素列舉</param>
        /// <returns>存在null條目返回true,否則返回false</returns>
        public static bool HaveNullItem<T>(this IEnumerable<T> enumerable)
        {
            return enumerable.Any(item => item == null);
        }

        /// <summary>
        /// 列舉器中是否全為指定型別的例項
        /// </summary>
        /// <typeparam name="T">元素型別</typeparam>
        /// <param name="enumerable">元素列舉</param>
        /// <returns>全為指定型別的例項返回true,否則返回false</returns>
        public static bool IsAllInstanceOfType<T>(this IEnumerable<T> enumerable, Type type)
        {
            return enumerable.All(item => type.IsInstanceOfType(item));
        }
    }
}

橢圓形空洞

跟矩形空洞類似,在大的矩形中間排除一個橢圓。

主要程式碼在EllipseHoleConverter中,程式碼如下:

namespace HoleWithClip
{
    using System;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Media;

    /// <summary>
    /// 橢圓形空洞的轉換器
    /// </summary>
    public class EllipseHoleConverter : IMultiValueConverter
    {
        /// <summary>
        /// 轉換成矩形空洞
        /// </summary>
        /// <param name="values">
        /// 轉換值列表,第一個表示起始寬度,第二個表示起始高度,
        /// 第三個表示總寬度,第四個表示總高度
        /// 第五個表示宿主寬度,第六個表示宿主寬度
        /// </param>
        /// <param name="targetType"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values == null || values.Length != 6 || values.HaveNullItem()
                || !values.IsAllInstanceOfType(typeof(double)))
            {
                return DependencyProperty.UnsetValue;
            }

            var maskEllipseCenterX = (double)values[0];
            var maskEllipseCenterY = (double)values[1];
            var maskRadiusX = (double)values[2];
            var maskRadiusY = (double)values[3];
            var hostWidth = (double)values[4];
            var hostHeight = (double)values[5];
            if (hostWidth == 0.0 || hostHeight == 0.0)
            {
                return null;
            }

            var maskRectangle = new RectangleGeometry(new Rect(new Size(hostWidth, hostHeight)));
            var maskEllipse = new EllipseGeometry(
                new Point(maskEllipseCenterX, maskEllipseCenterY),
                maskRadiusX,
                maskRadiusY);
            var combinedGeometry = Geometry.Combine(maskRectangle, maskEllipse, GeometryCombineMode.Exclude, null);

            return combinedGeometry;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            return new[] { Binding.DoNothing };
        }
    }
}

不規則空洞

我們各種Shape中最常用的是Path,因為許多複雜的效果能夠在設計工具中繪製成Path。與此類似,Geometry中最強大的還是PathGeometry。上面圖片中的五角形就是將Path資料轉換成PathGeometry的。XAML程式碼如下:

<Border Width="180"
        Height="180"
        Margin="3"
        Background="Aquamarine">
    <Border Background="LightPink" Clip="M90.000003,0.5 L111.12693,68.873594 L179.50001,68.871913 L124.18408,111.12744 L145.31405,179.49999 L90.000003,137.24175 L34.685959,179.49999 L55.815923,111.12744 L0.50000013,68.871913 L68.87308,68.873594 z" />
</Border>

話說,只看XAML程式碼真看不出這是什麼鬼東西。

下載連結

部落格園:HoleWithClip

OpacityMask與Clip區別

可以看出,通過OpacityMask和Clip都能實現打洞效果。但是通過OpactiyMask實現的效果在空洞中間會引發相應的事件,而Clip則不會,原因是在於UIElement中作命中測試考慮到了Clip的存在。應根據實際需求選擇不同的實現方式。