0 Comments

标题图像显示转速计

构建目标不仅可以很好地区分Debug版本和Release版本。您还可以将它们用于定位应用bet188地址的不同环境或配置。现在,我一直喜欢为用户提供最好的应用bet188地址性能的想法-换句话说;我想启用 虚拟机

不幸的是,当使用 Visual Studio 2019 (在编写16.5.4时)禁用了启用LLVM的选项。

在Visual Studio中显示禁用的LLVM选项-和尖叫的表情符号

问题 is under consideration by the team, and for the time being, there is no way to enable LLVM via the UI Wizard in Visual Studio. Now one way to solve this is to clone your solution on to a machine running macOS and then enabling it in Visual Studio for Mac. But under Windows, the only option is to open up the csproj file and enable LLVM manually:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- Stuff -->
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Gnabber|iPhone'">
    <OutputPath>bin\iPhone\Gnabber\</OutputPath>
    <DefineConstants>__IOS__;__MOBILE__;__UNIFIED__;</DefineConstants>
    <Optimize>true</Optimize>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <UseVSHostingProcess>false</UseVSHostingProcess>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
    <!-- Add the line bellow -->
    <MtouchUseLlvm>true</MtouchUseLlvm>
  </PropertyGroup>
  <!-- More Stuff -->
</Project>

谢谢, 维克多·加西亚·阿普里亚(Victor Garcia Aprea) 向我指出这一点,我希望这对任何遇到相同问题的人都能有所帮助。如果要检查

You can find a small sample Project with a custom build target Gnabber up on 的GitHub.

高温超导

1 Comments

pexels-photo-292426

无论是使用Xamarin Forms还是本机Xamarin.iOS / Xamarin.Android编写移动应用bet188地址,有时要求都要求在服务器上发生某些更改时尽快更新您的应用bet188地址。这可能是聊天应用bet188地址,股票报价器或任何监视应用bet188地址,向后端当前正在运行的进程的用户显示实时数据。

假设我们要创建一个简单的聊天应用bet188地址。如果我们沿HTTP走传统的“创建读取更新删除(CRUD)”应用bet188地址的路径,则可能选择每隔几秒钟轮询一次服务器以读取最新值。尽管此方法可提供结果,但它也有一些缺点。仅举几例:即使没有任何变化也可以发出请求,攀升电池使用量类别的排行榜,而且您的服务器会因请求而烦恼,只能告诉他们:“对不起,还没有消息...”-因此,如果不进行民意调查,请推送。这就是WebSocket进入的地方。

WebSockets的唯一问题是.Net中的实现与金属接近。这导致开发人员必须进行额外的实施工作。幸运的是,.Net开发人员可以使用SignalR随WebSockets附带所需的所有样板代码。 Web开发人员还将告诉您有关支持SingalR的后备选项的信息。作为Xamarin开发人员,您很可能永远不会使用这些功能。但是,通过处理连接,渠道或写给特定的已连接客户端的便捷性,您很可能会感到高兴。

SignalR已经存在了一段时间。它已经被移植到 .Net核心 并与.Net标准兼容。这是个好消息,因为我们可以将SignalR客户端直接添加到我们的Xamarin Forms应用bet188地址中-无需平台特定/包装代码。但是在开始实现客户端之前,我们首先必须创建一个启用SignalR的后端。

如果您不熟悉SignalR,请注意SignalR和SignalR Core之间有很大的不同。如果您今天使用ASP.NET Core或Azure Functions编写新应用bet188地址。您将要使用 SignalR核心 否则,您将进行令人讨厌的错误搜寻,最后以手掌拍打额头为名

服务器

在实现后端时,我们可以在两个选项之间进行选择。我们要么使用ASP.NET Core来实现SignalR,要么决定使用Azure SignalR核心服务。后者可以集成到ASP.NET Core或Azure Function应用中。 Azure选项还具有扩展功能-换句话说,您可以立即使用SignalR获得多达100万个同时连接。

有关如何设置Azure Functions和SignalR组合的更多详细信息,请在官方网站上找到说明 文件资料.

对于我们简单的聊天应用bet188地址,我们需要一种让客户发送和接收消息的方法。为此,我们将必须创建一个SignalR Hub,它提供一种发送消息的方法:

[FunctionName("SendMessage")]
public static Task SendMessage(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")]string message,
    [SignalR(HubName = "SignalRDemo")]IAsyncCollector<SignalRMessage> signalRMessages)
{
    return signalRMessages.AddAsync(
        new SignalRMessage
        {
            Target = "NewMessage",
            Arguments = new[] { message }
        });
}

调用时,该方法会将消息发送到所有连接的客户端。这使我们进入了下一个观点。每个聊天参与者都必须首先连接到集线器,以便可以接收消息。因此,让我们实现该注册方法:

[FunctionName("Negotiate")]
public static SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
[SignalRConnectionInfo(HubName = "SignalRDemo")] SignalRConnectionInfo connectionInfo)
{
    // connectionInfo contains an access key token with a name identifier claim set to the authenticated user
    return connectionInfo;
}

的 naming of the method is a convention from SignalR. In other words, you must name the method Negotiate, or your code will not work. No, I do not want to elaborate on how I found this one out the hard way 😉

有了功能和SignalR服务,我们现在可以将重点转移到客户身上。

移动客户端

在移动客户端上,我们希望能够接收消息并键入对该组的响应。我们简单的应用bet188地址将不得不承受在连接时仅接收消息的局限性。至少目前是这样。但是这就是聊天的全部荣耀。

SignalRChat

Now let's have a look at the ChatService which connects us to the backend and receives messages:

public async Task Connect()
{
    if (_connection.State == HubConnectionState.Connected) return;

    _connection.On<string>("NewMessage", (messageString) =>
    {
        var message = JsonConvert.DeserializeObject<Message>(messageString);
        _newMessage.OnNext(message);
        Debug.WriteLine(messageString);
    });

    await _connection.StartAsync();
}

请注意,在连接到后端之前,我们先注册了接收器方法。这样,我们一旦连接到SignalR服务就开始接收更新。现在,在实现接收方方法时,必须确保类型签名与我们先前在服务器上定义的方法匹配。如果类型或名称不匹配,您将永远不会收到任何消息。

由于阅读只是乐趣的一半,所以让我们实现发送消息:

public async Task Send(Message message)
{
    var serializedPayload = JsonConvert.SerializeObject(message);

    var response = await _httpClient.PostAsync("//gnabbersignalr.azurewebsites.net/api/SendMessage", new StringContent(serializedPayload));
    Debug.WriteLine(await response.Content.ReadAsStringAsync());
}

如果您从消息流中获得了足够的信息,但是想让您的眼睛凝视一个裸露的应用bet188地址,那么这里就是断开连接的方法:

public async Task Disconnect()
{
    await _connection.DisposeAsync();

    _connection = new HubConnectionBuilder()
        .WithUrl(backendUrl)
        .Build();
}

我希望您能看到使用SignalR可以轻松实现与服务器的双向通信层。这将使您的(移动)客户端能够近乎实时地发送和接收数据。使用SignalR的另一个副作用是您可以轻松地通过Web客户端扩展应用bet188地址。由于您最喜欢的JavaScript框架将允许您使用 SignalR客户端。如果您准备开始使用SignalR,请务必查看 docs.

您可以找到整个示例,包括上的所有UI代码。 的GitHub.

该博客是 十月Xamarin挑战。因此,在编写Xamarin应用bet188地址时,请务必查看其他文章以获得更多最佳实践。

高温超导

4 Comments

走向光明的自动扶梯的图像

这篇文章是Xamarin月的一部分,该月是关于社区和爱的。维护良好的UI和用户体验是产品团队或开发人员如何向用户表示爱意的一种方式。因此,专注于一个小细节,如果做对了,总是能让我微笑-

Xamarin Forms应用bet188地址以其加载时间而闻名。虽然总是希望更快的加载时间,但这是一个很好的起点。有时,没有办法让用户等待,而后台进程正在执行时,则最好执行其任务。但是,速度还有另一种选择:分散注意力。分心是飞机在机上娱乐时的作用,也是Twitter等某些应用bet188地址在启动时带有动画徽标时所做的事情。由于Xamarin Apps属于后者,因此让我们看看如何通过一些精美的动画Xamarin Hexagon改善我们的启动体验。

但是,在开始动画部分之前,恐怕我们必须快速浏览一下我们的一个平台项目-即Android项目。

在Android上启动Xamarin.Forms时的空洞感

您是否想知道为什么Xamarin应用在Android上的启动屏幕体验与iOS或UWP不同?在启动Xamarin.iOS应用bet188地址时,我们会立即看到徽标,而在Android上启动同一应用bet188地址时,黑屏会盯着我们。为什么呢?

Screenshot_1550416214

Just point it out: this is not the fault of Xamarin Forms, it is more a difference in the two platforms. While iOS forces you to provide a startup storyboard (a single view), there is no such thing under Android. At least that may seem so at first. However, from where is this blank screen? You probably already know that the starting point of a Xamarin.Forms app on Android is the MainActivity.cs or to be more precise that one activity which has the following attribute set:

[Activity( ... Theme = "@style/MainTheme", MainLauncher = true, ... ]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
	// ...
}

One attribute that is getting set is the theme. This theme is where Android "draws it's inspiration" for the splash screen. We can find it defined under Resources\values\styles.xml. Now to replicate the startup image, we first have to define a layout in Resources\drawables\splash_screen.xml along the following lines:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item>
    <color android:color="@color/colorPrimary"/>
  </item>
  <item>
    <bitmap
        android:src="@drawable/SplashScreen"
        android:tileMode="disabled"
        android:gravity="center" />
  </item>
</layer-list>

Now we can modify styles.xml by adding new style with the following lines:

<?xml version="1.0" encoding="utf-8" ?>
<resources>

  <!-- ... -->

  <style name="SplashTheme" parent ="的 me.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">@drawable/splash_screen</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowFullscreen">true</item>
  </style>
</resources>

Starting the app and we see the Xamarin logo while starting up. Unfortunately, it does not go away when we get to our Hello World page in Xamarin Forms. The reason being that we have overwritten the default style which is also used by our Xamarin.Forms app. However, we can fix this by adding an activity solely to display this new style, once the new SplashActivity.cs is rendered we switch over to the current MainActivity.cs. The MainActivity.cs uses the original style and starts the Xamarin.Forms part of our app.

Screenshot_1550416896

如果我们让应用bet188地址立即运行该应用bet188地址。我们确实看到了启动屏幕,启动应用bet188地址后该屏幕消失了。因此,现在我们拥有与iOS和UWP相当的Android,让我们换档并实现有弹性的启动动画。

有弹性的启动动画

从Twitter应用bet188地址中汲取一些启发,让我们的徽标以类似的方式反弹。我们在Xamarin.Forms中实现了六边形的动画。动画可以-在一个真实的应用bet188地址中-在我们启动时给我们一些时间。因此,我们再次需要一个初始屏幕,但是这次是Xamarin.Forms视图。 XAML在中心显示一个图像:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="CustomSplash.SplashPage"
             BackgroundColor="#2196F3">
    <ContentPage.Content>
        <Grid>
            <Image x:Name="SplashIcon"
                   HorizontalOptions="Center"
                   VerticalOptions="Center"
                   Source="SplashScreen.png" />
        </Grid>
    </ContentPage.Content>
</ContentPage>

的 XAML is ready. However, this would solely extend the static native splash screens. The animation takes place in the code behind. We can override the OnAppearing method and add some animation logic to it:

await SplashIcon.ScaleTo(0.5, 500, Easing.CubicInOut);
var animationTasks = new[]{
    SplashIcon.ScaleTo(100.0, 1000, Easing.CubicInOut),
    SplashIcon.FadeTo(0, 700, Easing.CubicInOut)
};
await Task.WhenAll(animationTasks);

首先,我们缩小图像,然后扩大并同时使其透明。组合动画使我们的应用bet188地址具有很好的流畅效果。虽然我们现在可以将这只小狗放入一个循环中,并尽力做到永无止境……但很可能我们只想展示一次然后转到我们的主页。以下几行实现了这一点:

Navigation.InsertPageBefore(new MainPage(), Navigation.NavigationStack[0]);
await Navigation.PopToRootAsync(false);

的 above lines insert the main page as the first page in the navigation stack. In other words, we insert the main page before the splash screen. Then we PopToRoot so the splash screen is no longer present on the navigation. So while the lines might look a bit odd at first. They prevent the user from navigating back to the splash page. Further, it allows the splash page to be garbage collected. Bottom line all the things we want to do with a splash screen once it has served its purpose.

生成的应用bet188地址如下所示:

iOS上的动画启动画面

我坚信,这些小事情可以走很长一段路,从一开始就向您的用户表明您关心您的应用bet188地址。虽然原生启动屏幕是一个好的开始。动画加载屏幕可以让您多花一些时间来启动应用,同时分散用户的注意力。您可以在以下位置找到整个演示应用bet188地址 的GitHub.

请务必在中查看其他博客文章 Xamarin宇宙 and happy coding!

0 Comments

When ever you want to display a list or collection of information under iOS tables are often the choice you will end up using. But what if you have content that changes and you want to update the data in a cell? Well that's what bindings are for right? But how do you use bindings in a UITableViewCell? Well let's check it out.

欢迎使用我们的倒计时应用bet188地址。如您所见,它基本上是一个计时器列表,这些计时器显示在一个单元格中,并随着它们的滴答滴答而更新。

CellBinding

If we look at the basic setup of a displayed table, we will have UITableView which uses a UITableViewSource for managing the collection to be displayed and the rendering of the UITableViewCells. Then there is the view model which is providing a list of items, in our case timers, that should be displayed. So let's go through each of the items from View Model to Cell.

视图模型

MVVM灯 comes with helpers that allow you to convert an ObservableCollection to a UITableViewSource. Choosing this option will further ensure ensure the UI is updated when we add or remove an element to our collection. So let's take it:

public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        // ...
        Countdowns = new ObservableCollection<CountdownViewItem>();
        Countdowns.Add(new CountdownViewItem(new TimeSpan(0, 13, 37)));
    }

    // ...
    public ObservableCollection<CountdownViewItem> Countdowns { get; private set; }

    // ...
}

CountDownViewItem hold's the state of the individual item. Hence the timer logic is embedded into it and will also implement the RaisePropertyChanged.

public class CountdownViewItem : ViewModelBase
{
    DateTime _expirationTimestamp;

    public CountdownViewItem(TimeSpan timespan)
    {
        if (timespan == null) throw new ArgumentNullException(nameof(timespan));
        _expirationTimestamp = DateTime.UtcNow + timespan;
        Countdown();
    }

    string _remainingTimeString;
    public string RemainingTimeString
    {
        get
        {
            return _remainingTimeString;
        }
        set
        {
            if (value == _remainingTimeString) return;
            _remainingTimeString = value;
            RaisePropertyChanged(nameof(RemainingTimeString));
        }
    }

    private async void Countdown()
    {
        while (DateTime.UtcNow < _expirationTimestamp)
        {
            TimeSpan remainingTime = _expirationTimestamp - DateTime.UtcNow;
            RemainingTimeString = remainingTime.ToString(@"hh\:mm\:ss");
            await Task.Delay(millisecondsDelay: 490);
        }

        RemainingTimeString = "Timer Expired";
    }
}

因此,让我们将这些视图模型用于UI中。

表格视图

One can choose to create the table either in a story board or in code. Choosing the code path we would have a UIViewController that creates the UITableView, set's the layout and creates the UITableSource from the view models collection:

class ViewController : UIViewController
{
    // private members

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        // Setup view and layouting

        // Setup bindings
        _tableViewController = Vm.Countdowns.GetController(CreatePersonCell, BindCellDelegate);
        _tableViewController.TableView = CountdownsTableView;

        AddTimerButton.SetCommand("TouchUpInside", Vm.AddCountdownCommand);
    }

    private void BindCellDelegate(UITableViewCell cell, CountdownViewItem countdownViewItem, NSIndexPath path)
    {
        var bindableCell = (CustomCell) cell;
        bindableCell.Configure(countdownViewItem);
    }

    private UITableViewCell CreatePersonCell(NSString cellIdentifier)
    {
        return CountdownsTableView.DequeueReusableCell(nameof(CustomCell));
    }
}

请注意,我们正在使用自定义单元格,并且正在将其注册到其中,以便我们可以重用该单元格。这将使我们的应用能够重复使用创建的单元格,即改善内存占用量,性能并被视为最佳实践。我们将很快讨论该单元的细节。

如果您正在寻找情节提要方法,请注意,此步骤仅此步骤会有所不同。看看 的GitHub 样本存储库。

细胞

的 cell is invoked when via the BindCellDelegate method. So we can populate our cell with binding like this:

public class CustomCell : UITableViewCell
{
    CountdownViewItem _countdownViewItem;
    // ui label and constructors

    private void InitCell()
    {
        // Layout cell
    }

    public Binding<string, string> _timerBinding;

    internal void Configure(CountdownViewItem countdownViewItem)
    {
        _countdownViewItem = countdownViewItem;
        _timerBinding = this.SetBinding(() => _countdownViewItem.RemainingTimeString, () => RemainingTimeLabel.Text);
    }
}

将传入的视图项存储为方法中的成员变量,这一点非常重要。如果不这样做,则只会看到初始值,绑定将永远不会更新视图!

由于我们正在重用单元格,因此我们必须假设一个单元格可能已被上一项使用,并且可能存在现有绑定。

Let's remember that Bindings boil down to events. And when we register an event handler we should always ensure that we are not creating a memory leak I.e. that we deregister the event handler. MVVM Light provides the method Detach which will deregister the event handler(s) behind the binding. Now why is this important? Well lists tend to come with multiple items. Having a memory leak for each item tends to be a bad design strategy to say the least. Therefor it is important to ensure that the binding in the cells are detached when it is no longer used.

This is also the reason why we are using a custom cell. It allows us to override the method PrepareForReuse which is invoked every time a cell is being reused. In this method we can restore the cell to it's initial state:

public override void PrepareForReuse()
{
    base.PrepareForReuse();
    _timerBinding?.Detach();
}

And that is how you can create bindings in a UITableViewCell.

结论

In this blog post we saw how we can create a binding for a UITableViewCell. We also covered how to avoid memory leaks when reusing cells (and you should generally reuse cells). Generally do not use bindings for static content in cells as they might impact your performance. When ever possible do not change the layout (constraints) due to a change in the content. Though not supported by the UITableViewSource created by MVVM Light at the time of writing, try using different custom cells for different layouts.

您可以在以下位置找到整个应用示例 的GitHub.

0 Comments

有没有想过其他应用bet188地址是如何进行平滑屏幕过渡的?使用过渡确实可以使应用bet188地址更加精致。但是,更重要的是,它在用户体验(UX)方面将为您的应用bet188地址带来优势。

在这篇文章中,我们将实现以下过渡:

CustomTransition

在我们深入研究代码之前。请注意,每个转换都遵循以下步骤:

  • 在目标View Controller上配置过渡
  • 实施过渡代表
  • 实施过渡动画
  • 如果适用,请执行解雇过渡动画

您不必实施解雇过渡。但是,如果您导航回到原始页面。您确实应该这样做。

配置自定义过渡

题外话:社区尚未达成共识,最好的方法是编写UI。有些人喜欢用代码(例如我自己)来完成它,有些人则喜欢使用情节提要。因此,我们将仅介绍它们两者。好消息是,除了配置外,其余代码是相同的。

使用代码配置自定义过渡

iOS中的过渡实际上是在目标View Controller上定义的。在我们的示例中,我们将在用户选择按钮后立即导航到下一个View Controller:

_button.TouchUpInside += (e, s) =>
{
    var vc = new ModalViewController(this)
    {
        ModalPresentationStyle = UIModalPresentationStyle.Custom,
        TransitioningDelegate = new GrowTransitioningDelegate(_button)
    };

    NavigationController.PresentViewController(vc, true, null);
};

Note that we configure the ModalPresentationStyle and TransitionDelegate on the view controller that we are navigating to. The GrowTransitionDelegate takes the originating UIView. If you are not using Storyboards you can skip the section and dive right into how the GrowTransitioningDelegate is implemented.

使用情节提要板配置自定义过渡

如果您使用情节提要,您将很熟悉segue。要定义自定义过渡动画,您将必须按以下方式配置segue:

将情节提要剧板配置为使用“当前模式”,并将“显示和过渡”设置为“默认”。

的 n in the originating view controller you can override the PrepareForSegue method:

public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue(segue, sender);

    var destinationVC = segue.DestinationViewController as SecondViewController;
    destinationVC.Callee = this;
    destinationVC.TransitioningDelegate = new GrowTransitioningDelegate(sender as UIView);
    destinationVC.ModalPresentationStyle = UIModalPresentationStyle.Custom;
}

的 target view controller in our case is name SecondViewController. It looks fairly similar to the event handler we defined before. On the destination view controller we set the TransitioningDelegate to GrowTransitioningDelegate which takes the originating UIView as constructor parameter. The only difference is that we do no longer pass the instance of the calling view controller as constructor parameter but use a property named Callee on the SecondViewController.

设置代表

GrowTransitionDelegate inherits from UIViewControllerTransitioningDelegate from which we can override methods to add a custom animation for presenting as follows:

public class GrowTransitioningDelegate : UIViewControllerTransitioningDelegate
{
    readonly UIView _animationOrigin;

    public GrowTransitioningDelegate(UIView animationOrigin)
    {
        _animationOrigin = animationOrigin;
    }

    public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForPresentedController(UIViewController presented, UIViewController presenting, UIViewController source)
    {
        var customTransition = new GrowTransitionAnimator(_animationOrigin);
        return customTransition;
    }
}

Let's follow along the scenario of the user navigating to another page. The GetAnimationControllerForPresentedController provides an animation implementation that inherits from IUIViewControllerAnimatedTransitioning. The originating UIView, a button in this example, is passed to the animation in the constructor. And that is all the transitioning delegate has to implement. So let's see how we implement the actual animation.

实施过渡动画

的 animations are defined in a class that inherit from UIViewControllerAnimatedTransitioning. The following two methods have to implemented:

public class GrowTransitionAnimator : UIViewControllerAnimatedTransitioning
{
    readonly UIView _animationOrigin;

    public GrowTransitionAnimator(UIView animationOrigin)
    {
        _animationOrigin = animationOrigin;
    }

    public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
    {
        // The animation 
    }

    public override double TransitionDuration(IUIViewControllerContextTransitioning transitionContext)
    {
        return 0.3;
    }
}

TransitionDuration method defines how long the animation will take. Usually this should be around 300 to 500 milliseconds. In the AnimateTransition method the actual transition and animation are defined.

iOS SDK提供了用于创建动画(例如过渡动画)的功能。使用这些将大大减少创建动画以定义UI对象的初始和所需目标状态所需的工作。动画的渲染由iOS在GPU上执行。因此,不仅因为您的应用看起来很棒,而且GPU会在早餐时吃转换,所以它也将变得生动 Smile

AnimateTransition method usually follows the following pattern:

  1. 获取您的源视图和目标视图控制器并查看
  2. 获取动画容器视图并添加目标
  3. 定义动画状态
    1. 目标视图的最终状态
    2. 目标视图的初始状态
  4. 执行动画并在完成时通知过渡上下文

因此,让我们逐步进行此步骤。首先,让我们获取源和目标视图控制器和视图:

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // Get the from and to View Controllers and their views
    var fromVC = transitionContext.GetViewControllerForKey(UITransitionContext.FromViewControllerKey);
    var fromView = fromVC.View;
    
    var toVC = transitionContext.GetViewControllerForKey(UITransitionContext.ToViewControllerKey);
    var toView = toVC.View;
     
    // ...
}

然后获取动画容器并将目标添加到其中:

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // ...

    // Add the to view to the transition container view
    var containerView = transitionContext.ContainerView;
    containerView.AddSubview(toView);
        
    // ...
}

现在来定义动画的部分。我们想在用户选择的按钮中间开始动画,即我们的动画类已经通过构造函数收到了该消息。

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // ...

    // Set the desired target for the transition
    var appearedFrame = transitionContext.GetFinalFrameForViewController(toVC);
    
    // Set how the animation shall start
    var initialFrame = new CGRect(_animationOrigin.Frame.GetMidX(), _animationOrigin.Frame.GetMidY(), 0, 0);
    var finalFrame = appearedFrame;
    toView.Frame = initialFrame;
    
    // ...
}

现在剩下要做的就是执行动画并等待结果。然后将结果用于向过渡上下文发出动画已完成的信号:

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // ...

    var isAnimationCompleted = await UIView.AnimateAsync(TransitionDuration(transitionContext), () => {
        toView.Frame = finalFrame;
    });
    
    transitionContext.CompleteTransition(isAnimationCompleted);
}

我们的过渡动画完成了。如果您要实现其他动画。例如,动画从一个角落开始。更改动画的原点将为您提供所需的效果。

副作用 While animations are great and you will perhaps notice that some UI elements are drawn early and then move into position during the transition. This can feel slightly off. So while working with animations it can make sense to move the layout code of the UI components to the ViewDidAppear method.

创建关闭动画

For having the inverse animation for dismissing the view. First the GetAnimationControllerForDismissedController method has to be overwritten in the delegate class (same one as before):

public class GrowTransitioningDelegate : UIViewControllerTransitioningDelegate
{
    readonly UIView _animationOrigin;

    public GrowTransitioningDelegate(UIView animationOrigin)
    {
        _animationOrigin = animationOrigin;
    }

    public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForPresentedController(UIViewController presented, UIViewController presenting, UIViewController source)
    {
        // ...
    }

    public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForDismissedController(UIViewController dismissed)
    {
        var customTransition = new ShrinkTransitionAnimator(_animationOrigin);
        return customTransition;
    }
}

的 n create an implementation which inherits UIViewControllerAnimatedTransitioning and has the desired reverse animation.

public class ShrinkTransitionAnimator : UIViewControllerAnimatedTransitioning
{
    readonly UIView _animationOrigin;

    public ShrinkTransitionAnimator(UIView animationTarget)
    {
        _animationOrigin = animationTarget;
    }

    public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
    {
        // Get the from and to View Controllers and their views
        var fromVC = transitionContext.GetViewControllerForKey(UITransitionContext.FromViewControllerKey);
        var fromView = fromVC.View;

        var toVC = transitionContext.GetViewControllerForKey(UITransitionContext.ToViewControllerKey);
        var toView = toVC.View;

        // Add the to view to the transition container view
        var containerView = transitionContext.ContainerView;

        // Set the desired target for the transition
        var appearedFrame = transitionContext.GetFinalFrameForViewController(fromVC);

        // Set how the animation shall end
        var finalFrame = new CGRect(_animationOrigin.Frame.GetMidX(), _animationOrigin.Frame.GetMidY(), 0, 0);
        fromView.Frame = appearedFrame;

        var isAnimationCompleted = await UIView.AnimateAsync(TransitionDuration(transitionContext), () => {
            fromView.Frame = finalFrame;
        });

        fromView.RemoveFromSuperview();

        transitionContext.CompleteTransition(isAnimationCompleted);
    }

    public override double TransitionDuration(IUIViewControllerContextTransitioning transitionContext)
    {
        return 0.3;
    }
}

尽管看起来很相似,但仍有一些要点。首先,这次的起点是我们要关闭的视图控制器。由于它已经是“视图”树的一部分,因此不再需要添加。相反,一旦动画完成,您实际上将要删除它。

结论

In this post we went through the steps required to create a custom transition. Further we looked at how we can implement the dismiss animation. The steps can be reused for different kind of animations which are defined in the implementation of the UIViewControllerAnimatedTransitioning class.

您可以在以下位置找到完整的代码示例 的GitHub.