第二十七章:自定义渲染器(四)

简介: 渲染器和属性(2)现在,对于iOS,EllipseUIView类是存在的,可以使用EllipseUIView作为本机控件来编写EllipseViewRenderer。 从结构上讲,这个类几乎与Windows渲染器相同:using System.

渲染器和属性(2)

现在,对于iOS,EllipseUIView类是存在的,可以使用EllipseUIView作为本机控件来编写EllipseViewRenderer。 从结构上讲,这个类几乎与Windows渲染器相同:

using System.ComponentModel;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.EllipseView), 
                                 typeof(Xamarin.FormsBook.Platform.iOS.EllipseViewRenderer))]
namespace Xamarin.FormsBook.Platform.iOS
{
    public class EllipseViewRenderer : ViewRenderer<EllipseView, EllipseUIView>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<EllipseView> args)
        {
            base.OnElementChanged(args);
            if (Control == null)
            {
                SetNativeControl(new EllipseUIView());
            }
            if (args.NewElement != null)
            {
                SetColor();
            }
        }
        protected override void OnElementPropertyChanged(object sender,
                                                         PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(sender, args);
            if (args.PropertyName == EllipseView.ColorProperty.PropertyName)
            {
                SetColor();
            }
        }
        void SetColor()
        {
            if (Element.Color != Color.Default)
            {
                Control.SetColor(Element.Color.ToUIColor());
            }
            else
            {
                Control.SetColor(UIColor.Clear);
            }
        }
    }
}

此渲染器和Windows版本之间唯一的真正区别是Control属性设置为ColorUIView的实例,底部的SetColor方法的主体是不同的。 它现在调用ColorUIView中的SetColor方法。 这个SetColor方法也可以在名为ToUIColor的Xamarin.Forms.Platform.iOS库中使用公共扩展方法
将Xamarin.Forms颜色转换为iOS颜色。
您可能已经注意到,Windows渲染器和iOS渲染器都不必担心调整大小。 您很快就会看到,EllipseView可以设置为各种大小,并且在Xamarin.Forms布局系统中计算的大小将变为本机控件的大小。
遗憾的是,这不是Android渲染器的情况。 Android渲染器需要一些大小调整逻辑。 与iOS一样,Android也缺少呈现椭圆的本机控件。 因此,Xamarin.FormsBook.Platform.Android库包含一个名为EllipseDrawableView的类,它从View派生并绘制一个椭圆:

using Android.Content;
using Android.Views;
using Android.Graphics.Drawables;
using Android.Graphics.Drawables.Shapes;
using Android.Graphics;
namespace Xamarin.FormsBook.Platform.Android
{
    public class EllipseDrawableView : View
    {
        ShapeDrawable drawable;
        public EllipseDrawableView(Context context) : base(context)
        {
            drawable = new ShapeDrawable(new OvalShape());
        }
        protected override void OnDraw(Canvas canvas)
        {
            base.OnDraw(canvas);
            drawable.Draw(canvas);
        }
        public void SetColor(Xamarin.Forms.Color color)
        {
            drawable.Paint.SetARGB((int)(255 * color.A),
                                   (int)(255 * color.R),
                                   (int)(255 * color.G),
                                   (int)(255 * color.B));
            Invalidate();
        }
        public void SetSize(double width, double height)
        {
            float pixelsPerDip = Resources.DisplayMetrics.Density;
            drawable.SetBounds(0, 0, (int)(width * pixelsPerDip),
                                     (int)(height * pixelsPerDip));
            Invalidate();
        }
    }
}

在结构上,这类似于为iOS定义的EllipseUIView类,除了构造函数为椭圆创建一个ShapeDrawable对象,并且OnDraw覆盖渲染它。
此类有两种方法来设置此椭圆的属性。 SetColor方法转换Xamarin.Forms颜色以设置ShapeDrawable对象的Paint属性,SetSize方法将设备无关单位的大小转换为像素,用于设置ShapeDrawable对象的边界。 SetColor和SetSize都以对Invalidate的调用结束,以使绘图表面无效并生成对OnDraw的另一个调用。
Android渲染器使用EllipseDrawableView类作为其本机对象:

using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Xamarin.FormsBook.Platform.EllipseView), 
                          typeof(Xamarin.FormsBook.Platform.Android.EllipseViewRenderer))]
namespace Xamarin.FormsBook.Platform.Android
{
    public class EllipseViewRenderer : ViewRenderer<EllipseView, EllipseDrawableView>
    {
        double width, height;
        protected override void OnElementChanged(ElementChangedEventArgs<EllipseView> args)
        {
            base.OnElementChanged(args);
            if (Control == null)
            {
                SetNativeControl(new EllipseDrawableView(Context));
            }
            if (args.NewElement != null)
            {
                SetColor();
                SetSize();
            }
        }
        protected override void OnElementPropertyChanged(object sender,
                                                         PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(sender, args);
            if (args.PropertyName == VisualElement.WidthProperty.PropertyName)
            {
                width = Element.Width;
                SetSize();
            }
            else if (args.PropertyName == VisualElement.HeightProperty.PropertyName)
            {
                height = Element.Height;
                SetSize();
            }
            else if (args.PropertyName == EllipseView.ColorProperty.PropertyName)
            {
                SetColor();
            }
        }
        void SetColor()
        {
            Control.SetColor(Element.Color);
        }
        void SetSize()
        {
            Control.SetSize(width, height);
        }
    }
}

请注意,OnElementPropertyChanged方法需要检查Width和Height属性的更改并将它们保存在字段中,以便可以将它们组合到SetSize调用EllipseDrawableView的单个Bounds设置中。
所有渲染器到位后,是时候看它是否有效。 EllipseDemo解决方案还包含指向Xamarin.FormsBook.Platform解决方案的各个项目的链接,EllipseDemo中的每个项目都包含对Xamarin.FormsBook.Platform中相应库项目的引用。
EllipseDemo中的每个项目还包含对相应库项目中的Toolkit.Init方法的调用。这并不总是必要的。但请记住,各种渲染器不会被任何项目中的任何代码直接引用,并且某些优化可能导致代码在运行时无法使用。对Toolkit.Init的调用避免了这种情况。
EllipseDemo中的XAML文件创建了几个具有不同颜色和大小的EllipseView对象,其中一些受到大小限制,而其他对象则允许填充其容器:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:platform=
                 "clr-namespace:Xamarin.FormsBook.Platform;assembly=Xamarin.FormsBook.Platform"
             x:Class="EllipseDemo.EllipseDemoPage">
    <Grid>
        <platform:EllipseView Color="Aqua" />
        <StackLayout>
            <StackLayout.Padding>
                <OnPlatform x:TypeArguments="Thickness"
                            iOS="0, 20, 0, 0" />
            </StackLayout.Padding>
            <platform:EllipseView Color="Red"
                                  WidthRequest="40"
                                  HeightRequest="80"
                                  HorizontalOptions="Center" />
            <platform:EllipseView Color="Green"
                                  WidthRequest="160"
                                  HeightRequest="80"
                                  HorizontalOptions="Start" />
            <platform:EllipseView Color="Blue"
                                  WidthRequest="160"
                                  HeightRequest="80"
                                  HorizontalOptions="End" />
            <platform:EllipseView Color="#80FF0000"
                                  HorizontalOptions="Center" />
            <ContentView Padding="50"
                         VerticalOptions="FillAndExpand">
                <platform:EllipseView Color="Red"
                                      BackgroundColor="#80FF0000" />

            </ContentView>
        </StackLayout>
    </Grid>
</ContentPage>

请特别注意倒数第二个EllipseView,它给自己一个半透明的红色。 对于填充页面的大椭圆的Aqua,这应该呈现为中灰色。
最后一个EllipseView为自己提供半透明红色的BackgroundColor设置。 同样,这应该在大的Aqua椭圆上呈灰色,但在白色背景下呈浅红色,在黑色背景下呈暗红色。 他们来了:
2019_06_11_093306
一旦你有一个EllipseView,当然你会想要写一个弹跳球程序。 BouncingBall解决方案还包含指向Xamarin.FormsBook.Platform解决方案中所有项目的链接,并且所有应用程序项目都引用了相应的库项目。 BouncingBall PCL还引用了一个名为Vector2的结构的Xamarin.FormsBook.Toolkit库,这是一个二维向量。
XAML文件将EllipseView定位在页面的中心:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:platform=
                 "clr-namespace:Xamarin.FormsBook.Platform;assembly=Xamarin.FormsBook.Platform"
             x:Class="BouncingBall.BouncingBallPage">
    <platform:EllipseView x:Name="ball"
                          WidthRequest="100"
                          HeightRequest="100"
                          HorizontalOptions="Center"
                          VerticalOptions="Center" />

</ContentPage>

代码隐藏文件启动了两个“永远”运行的动画。第一个动画在构造函数中定义,并动画弹跳球的Color属性,每隔10秒通过彩虹的颜色。
第二个动画在屏幕的四个“墙壁”上弹回球。 对于通过while循环的每个循环,代码首先确定它将首先击中哪个墙以及以与设备无关的单位到该墙的距离。 朝向while循环结束的中心的新计算是球撞击墙壁时的位置。 新的矢量计算基于现有矢量和垂直于其击中的表面的矢量(称为法向矢量)确定偏转矢量:

public partial class BouncingBallPage : ContentPage
{
    public BouncingBallPage()
    {
        InitializeComponent();
        // Color animation: cycle through rainbow every 10 seconds.
        new Animation(callback: v => ball.Color = Color.FromHsla(v, 1, 0.5),
        start: 0,
       end: 1
       ).Commit(owner: this,
        name: "ColorAnimation",
        length: 10000,
        repeat: () => true);
        BounceAnimationLoop();
    }
    async void BounceAnimationLoop()
    {
        // Wait until the dimensions are good.
        while (Width == -1 && Height == -1)
        {
            await Task.Delay(100);
        }
        // Initialize points and vectors.
        Point center = new Point();
        Random rand = new Random();
        Vector2 vector = new Vector2(rand.NextDouble(), rand.NextDouble());
        vector = vector.Normalized;
        Vector2[] walls = { new Vector2(1, 0), new Vector2(0, 1), // left, top
                            new Vector2(-1, 0), new Vector2(0, -1) }; // right, bottom
        while (true)
        {
            // The locations of the four "walls" (taking ball size into account).
            double right = Width / 2 - ball.Width / 2;
            double left = -right;
            double bottom = Height / 2 - ball.Height / 2;
            double top = -bottom;
            // Find the number of steps till a wall is hit.
            double nX = Math.Abs(((vector.X > 0 ? right : left) - center.X) / vector.X);
            double nY = Math.Abs(((vector.Y > 0 ? bottom : top) - center.Y) / vector.Y);
            double n = Math.Min(nX, nY);
            // Find the wall that's being hit.
            Vector2 wall = walls[nX < nY ? (vector.X > 0 ? 2 : 0) : (vector.Y > 0 ? 3 : 1)];
            // New center and vector after animation.
            center += n * vector;
            vector -= 2 * Vector2.DotProduct(vector, wall) * wall;
            // Animate at 3 msec per unit.
            await ball.TranslateTo(center.X, center.Y, (uint)(3 * n));
        }
    }
}

当然,静态照片无法捕捉到动画的激动人心的动作:
2019_06_11_093633

目录
相关文章
|
6月前
|
存储
CocosCreator3.8研究笔记(二十二)CocosCreator 动画系统-动画剪辑和动画组件介绍
CocosCreator3.8研究笔记(二十二)CocosCreator 动画系统-动画剪辑和动画组件介绍
118 0
|
15天前
|
iOS开发 UED
实现一个自定义的iOS动画效果
【4月更文挑战第9天】本文将详细介绍如何在iOS平台上实现一个自定义的动画效果。我们将通过使用Core Animation框架来实现这个动画效果,并展示如何在不同的场景中使用它。文章的目标是帮助读者理解如何使用Core Animation框架来创建自定义动画,并提供一个简单的示例代码。
14 1
An动画基础之按钮动画与基础代码相结合
An动画基础之按钮动画与基础代码相结合
560 0
An动画基础之按钮动画与基础代码相结合
|
前端开发
实例|APICloud AVM框架封装滑动单元格组件
滑动单元格组件原理是主题部分把按钮进行遮挡,按钮通过绝对定位,定位在最右边,通过监听触摸事件(touch),判断滑动的方向和计算滑动的距离以此来判定显示和隐藏按钮。
148 0
SwiftUI—预览视图在正常模式和黑暗模式下的效果
SwiftUI—预览视图在正常模式和黑暗模式下的效果
117 0
SwiftUI—预览视图在正常模式和黑暗模式下的效果
SwiftUI直通车系列(5)—— 自定义绘制
SwiftUI直通车系列(5)—— 自定义绘制
127 0
SwiftUI直通车系列(5)—— 自定义绘制
SwiftUI直通车系列(5)—— 自定义绘制(二)
SwiftUI直通车系列(5)—— 自定义绘制
133 0
SwiftUI直通车系列(5)—— 自定义绘制(二)
|
数据安全/隐私保护 iOS开发 开发者
iOS开发CoreGraphics核心图形框架之九——PDF文件的渲染与创建(一)
iOS开发CoreGraphics核心图形框架之九——PDF文件的渲染与创建
394 0
iOS开发CoreGraphics核心图形框架之九——PDF文件的渲染与创建(一)
|
数据安全/隐私保护 iOS开发 开发者
iOS开发CoreGraphics核心图形框架之九——PDF文件的渲染与创建(二)
iOS开发CoreGraphics核心图形框架之九——PDF文件的渲染与创建
284 0
iOS开发CoreGraphics核心图形框架之九——PDF文件的渲染与创建(二)
第二十七章:自定义渲染器(六)
有趣的是,Android SeekBar小部件具有与Steps属性等效的功能,但不等同于Minimum和Maximum属性! 这怎么可能? SeekBar实际上定义了一个名为Max的整数属性,SeekBar的Progress属性始终是一个从0到Max的整数。
735 0

热门文章

最新文章