zoukankan      html  css  js  c++  java
  • 利用OpacityMask制作打洞效果

    起因

    项目上存在一个连线功能,在设计的原型中,在连线中间文字上下各有15像素的空白。接手的同事觉得没思路,问我能不能在不影响连线后面的背景情况下解决该问题。我就抽了点时间给他写了个Demo。回家后趁热打铁,重新写了个Demo,添加和完善了些功能。下面是效果图:

    1

    代码实现

    OpacityMask

    在最开始看到效果图的时候,我就想到利用OpacityMask来解决问题。可能这个属性平时很多朋友都没注意到,因为一般情况下用Opacity就足够了。

    OpacityMask定义在UIElement中,类型为Brush。仅使用提供的 Brush 的任意 Alpha 通道值。 Brush 呈现内容的其他通道(红色、绿色或蓝色)被忽略。具体来说,在Brush中Alpha通道值为0的地方将为透明,不为0的将显示在UIElement中定义的背景。下面就以Demo中的三个例子简单分享下怎么利用OpacityMask。

    矩形空洞

    OpacityMask是一个VisualBrush,VisualBrush中有一个三行三列的Grid,Grid中除第二行第二列的单元格外,其余的单元格均用黑色的矩形填充。这样就会在第二行第二列的单元格处形成一个空洞。

    主要代码在RectangleHoleConverter中,代码如下:

    namespace HoleWithOpacityMask
    {
        using System;
        using System.Globalization;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Data;
        using System.Windows.Media;
        using System.Windows.Shapes;
    
        /// <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 maskGrid = new Grid { Width = hostWidth, Height = hostHeight };
    
                var opacityStartColumnDefinition = new ColumnDefinition { Width = new GridLength(maskStartWidth) };
                var transparentColumnDefinition = new ColumnDefinition { Width = new GridLength(maskTotalWidth) };
                ColumnDefinition opacityEndColumnDefinition = new ColumnDefinition();
                opacityEndColumnDefinition.Width = new GridLength(1.0, GridUnitType.Star);
                maskGrid.ColumnDefinitions.Add(opacityStartColumnDefinition);
                maskGrid.ColumnDefinitions.Add(transparentColumnDefinition);
                maskGrid.ColumnDefinitions.Add(opacityEndColumnDefinition);
    
                var opacityStartRowDefinition = new RowDefinition { Height = new GridLength(maskStartHeight) };
                var transparentRowDefinition = new RowDefinition { Height = new GridLength(maskTotalHeight) };
                RowDefinition opacityEndRowDefinition = new RowDefinition();
                opacityEndRowDefinition.Height = new GridLength(1.0, GridUnitType.Star);
                maskGrid.RowDefinitions.Add(opacityStartRowDefinition);
                maskGrid.RowDefinitions.Add(transparentRowDefinition);
                maskGrid.RowDefinitions.Add(opacityEndRowDefinition);
    
                for (int i = 0; i < 3; i++)
                {
                    for (int j = 0; j < 3; j++)
                    {
                        if ((i != 1) || (j != 1))
                        {
                            Rectangle opacityRectangle = new Rectangle { Fill = Brushes.Black };
                            Grid.SetRow(opacityRectangle, i);
                            Grid.SetColumn(opacityRectangle, j);
                            maskGrid.Children.Add(opacityRectangle);
                        }
                    }
                }
    
                return new VisualBrush(maskGrid);
            }
    
            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));
            }
        }
    }

    椭圆形空洞

    OpacityMask是一个DrawingBrush,DrawingBrush是利用GeometryDrawing绘制。而GeometryDrawing中是一个由黑色填充的形状,该形状是在矩形中除去一个椭圆。这样就会在矩形中形成一个空洞。

    主要代码在EllipseHoleConverter中,代码如下:

    namespace HoleWithOpacityMask
    {
        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);
                var drawingBrush = new DrawingBrush(new GeometryDrawing(Brushes.Black, null, combinedGeometry));
    
                return drawingBrush;
            }
    
            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            {
                return new[] { Binding.DoNothing };
            }
        }
    }

    图形空洞

    其实最常用的还是ImageBrush,因为很多复杂的效果用VisualBrush或者DrawingBrush来实现效果很复杂且不清晰。在上面的Demo中我使用的是一张PNG图片,当然一般也用PNG图标,谁叫它支持透明呢。图片是

    pig

    XAML代码如下:

    <Border Width="180"
            Height="180"
            Margin="3"
            Background="Aquamarine">
        <Border Background="LightPink">
            <Border.OpacityMask>
                <ImageBrush ImageSource="pig.png" />
            </Border.OpacityMask>
        </Border>
    </Border>

    容易看出,PNG图片中透明的就真的透明了(直接看到外层Border上级的背景BurlyWood了),不透明的地方就使用了当前Border设置的背景色LightPink。

    更多

    还可使用线性渐变(LinearGradientBrush)、径向渐变(RadialGradientBrush)实现更多有趣的效果,下图是MSDN中的一个例子

    Snap2

    纯色画刷(SolidColorBrush)基本用不到,要么透明,要么不透明,还不如直接设置Opacity。

    下载链接

    博客园:HoleWithOpacityMask

  • 相关阅读:
    uni app 零基础小白到项目实战
    uni-app 事件以及事件绑定
    uni-app 事件以及事件绑定
    uni-app 组件
    uni-app 组件
    uni-app 网络请求
    uni-app 网络请求
    uni-app 图片上传实战
    uni-app 图片上传实战
    uni-app下拉刷新加载刷新数据
  • 原文地址:https://www.cnblogs.com/yiyan127/p/WPF-HoleWithOpacityMask.html
Copyright © 2011-2022 走看看