SwiftUI:做一个好看的评分控件

mask在SwiftUI中是用于遮罩控件的,它可以根据我们提供的形状或者图片来裁剪控件的可见区域。比如,我们可以用圆形的mask来让一个图片控件变成圆形,或者用三角形mask一个按钮,只展示按钮的一个角等等

下面先看一个简单的例子,利用mask来显示图片。

image.png

Image("0")
            .resizable()
            .scaledToFit()
            .foregroundColor(.green)

我们使用爱心来显示美女图片,使用 mask很容易到达这个需求。

使用mask显示图形

方法定义如下,需要放回一个View

.mask(<#T##mask: () -> View##() -> View#>)

我们只需要在mask的返回体中加入如下代码:

.mask {
  Image(systemName: "heart.fill")
                    .resizable()
                    .scaledToFit()
}

image.png

mask指定的形状是什么,最终的图形就是什么。所以最终的图形形状都有mask设定的图形决定


使用 mask 创建渐变评分控件

在很多场景中,都可以见到评分控件。它是由5个五角星组成,初始状态为灰色,当我们点击五角星就会点亮颜色变成黄色。

首先,来创建五角星。

image.png

HStack {
            ForEach(1..<6) { index in
                Image(systemName: "star.fill")
                    .font(.largeTitle)
                    .foregroundColor(.orange)
            }


        }

使用上述代码即可创建五个五角星,但是它不具有动态功能。我们来加上点击功能。

首先,我们需要创建一个变量,来保存当前点击的是第几个星星。

@State var rating: Int = 1

其次,我们需要使用不同前景色来区分已点击和未点击的五角星

.foregroundColor(rating >= index ? .orange : .gray)

最后,我们给五角星加入点击事件。并且在点击事件中把当前的index值赋给rating。

.onTapGesture {

    rating = index
 }

下面是效果

ezgif.com-video-to-gif.gif

但是,如何给控件加渐变色呢?我们就要使用mask来完成这个任务。

首先,把创建五角星的代码提取成为一个变量starView。保持主body简单整洁,这个很重要。

var starView: some View {
        HStack {
            ForEach(1..<6) { index in
                Image(systemName: "star.fill")
                    .font(.largeTitle)
                    .foregroundColor(rating >= index ? .orange : .gray)
                    .onTapGesture {
                        rating = index
                    }
            }
        }

    }

其次,我们给StartView添加一个overlay,将mask添加到overlay上。

ZStack {
                    starView
                        .overlay(
                            Rectangle()
                                .foregroundColor(.yellow)
                        )
                }

image.png

我们可以看到,因为overlay的rectangle不知道宽度是多少,所以它会默认等于主视图的宽度。

我们需要使用GometryReader来读取大小,从而控制overlay的宽度来展示底部五角星视图。因为overlay是在五角星视图上面的。所以我们只有控制好上层视图的宽度,才可以看到下层视图

我们还是把Overlay的内容提取成一个变量

var overlayView: some View {


            GeometryReader { geo in


                Rectangle()


                    .foregroundColor(.yellow)

                    .frame(width: CGFloat(rating) / 5 * geo.size.width)

            }


            .allowsHitTesting(false)
        }

frame(width: CGFloat(rating) / 5 * geo.size.width) 用于计算overlay的视图宽度,根据点击的index来决定视图显示的宽度是多少

image.png

当我们点击五角星时,会发现视图的宽度和我们预料的一样。但是还需要最终的一个mask。

在前面的例子中,我们明白。mask作为一个用于遮罩控件。当你给定mask什么形状,最终的物体就会是给定的mask的样子。所以我们把五角星的形状再设定给mask,就可以显示五角星了

ZStack {
      starView
        .overlay(overlayView.mask(starView))                
    }
var overlayView: some View {


            GeometryReader { geo in


                Rectangle()


                    .foregroundColor(.yellow)

                    .frame(width: CGFloat(rating) / 5 * geo.size.width)

            }


    }

此时,就可以显示正常的五角星。

ezgif.com-video-to-gif (1).gif
但是,此时你会发现。可以把灰色五角星点亮,但是却无法再变成灰色。

那是因为,我们把灰色变成黄色时,触发的是真实的五角星的点击事件。但是当我们把五个五角星全部点亮,此时就显示的是OverlayView的视图,OverlayView的视图阻止了底部的点击事件。所以导致无法响应事件,从而无法改变值,也就无法改变颜色了。那么我们需要把OverlayView的点击事件禁用掉。

使用以下方法禁用视图点击事件

.allowsHitTesting(false)

allowsHitTesting

在 SwiftUI 中,allowsHitTesting属性主要用于控制视图是否可以接收点击(hit test)事件。

  • 如果allowsHitTesting设置为true,则该视图可以接收点击事件,默认为true。
  • 如果设置为false,则该视图将不再响应任何点击事件,点击会透传给下层的视图。

需要注意的是它只影响点击事件,其他触摸事件如长按、拖拽等不受影响。

好了,设置属性后,变得正常了。

加个动画

在点击事件中加入动画,让事件感觉动起来

.onTapGesture {

                            withAnimation(.easeInOut) {
                                rating = index
                            }
                        }

ezgif.com-video-to-gif (2).gif

加入渐变

有了以上基础,渐变就很简单了。我们只需要把overlayViewforegroundColor去掉。换成fill,然后在fill中使用一个渐变来填充就OK了

var overlayView: some View {


            GeometryReader { geo in


                Rectangle()


    //                .foregroundColor(.yellow)
                    .fill(
                        LinearGradient(colors: [Color.blue, Color.green], startPoint: .leading, endPoint: .trailing)
                    )
                    .frame(width: CGFloat(rating) / 5 * geo.size.width)
            }
            .allowsHitTesting(false)
        }

ezgif.com-video-to-gif (3).gif

我是用来清爽的颜色来做为渐变色,以上是最终效果。

大家有什么看法呢?欢迎留言讨论。
公众号:RobotPBQ

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYCycTZE' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片