我应该使用Xamarin Forms的哪些功能来实现" 3D"旋转,就像Instagram一样

时间:2017-04-30 23:45:48

标签: xamarin xamarin.ios xamarin.forms xamarin-studio

Instagram在应用程序某些部分的用户之间切换时使用3D旋转,如下所示。

enter image description here

我一直在尝试使用Xamarin Forms

  • 转换
  • 动画
  • Skia Sharp

但是无法在Xamarin上重新创建它。在Xamarin Forms上进行全屏旋转的正确技术是什么?

2 个答案:

答案 0 :(得分:1)

你必须求助于自定义渲染器并在C#中实现类似的东西:https://github.com/katleta3000/RotateController(这个例子在Swift for iOS中,也可能类似于Android的类似)。

据我所知,纯Xamarin表格不可能。

这就是C#中提供的示例的样子(注意:我没有测试结果,因此以下代码中有两个TODO):

// Example Swift code licensed by MIT license. Converted to C#.
// Original author: katleta3000

using System;
using CoreAnimation;
using UIKit;

namespace MyProject.iOS
{
    public enum RotationDirection : int
    {
        Right = 0, 
        Left
    }

    public enum RotationType : int
    {
        Push = 0,
        Pop
    }

    class FCBlackBackgroundView : UIView
    {

    }

    class FCRotateLayer : CALayer
    {
    }

    public class RotatingController : UINavigationController
    {
        public void PushViewController(UIViewController controller, RotationDirection rotateDirection) {
            Perform3DRotate(RotationType.Push, rotateDirection, controller);
        }

        public void PopViewController(RotationDirection rotateDirection) {
            Perform3DRotate(RotationType.Pop, rotateDirection, null);
        }

        // MARK: - private

        private void Perform3DRotate(RotationType type, RotationDirection rotateDirection, UIViewController controller) {
            var layer = RotationLayer();

            var cube = CubeTransform(rotateDirection, layer: layer);

            if (type == RotationType.Push) {
                this.PushViewController(controller, animated: false);
            } else if (type == RotationType.Pop) {
                this.PopViewController(animated: false);
            }
            layer.AddSublayer(LayerFromView(this.View, transform: cube));

            this.View.AddSubview(BackgroundView(UIColor.White));

            this.View.Layer.AddSublayer(layer);

            layer.AddAnimation(RotationAnimation(rotateDirection), "rotate");

        }

        private CATransform3D CubeTransform(RotationDirection rotateDirection, CALayer layer)
        {
            var cube = CATransform3D.MakeTranslation(0, 0, 0);

            layer.AddSublayer(LayerFromView(this.View, transform: cube));

            cube.Rotate(Radians(90), 0, 1, 0);

            cube.Translate(CubeSize(), 0, 0);

            if (rotateDirection == RotationDirection.Left) {
                cube.Rotate(Radians(90), 0, 1, 0);

                cube.Translate(CubeSize(), 0, 0);

                cube.Rotate(Radians(90), 0, 1, 0);

                cube.Translate(CubeSize(), 0, 0);

            }
            return cube;

        }

        private FCRotateLayer RotationLayer()
        {
            FCRotateLayer layer = new FCRotateLayer();

            layer.Frame = this.View.Frame;

            layer.AnchorPoint = new CoreGraphics.CGPoint(0.5, 0.5);

            CATransform3D transform = CATransform3D.Identity;

            transform.m34 = 1.0f / -750;

            layer.SublayerTransform = transform;

            return layer;

        }

        private CAAnimation RotationAnimation(RotationDirection direction) 
        {
            CATransaction.Flush();

            var animationGroup = new CAAnimationGroup();

            animationGroup.Duration = 0.4;

            CABasicAnimation rotation = null;
            CABasicAnimation translationX = null;

            if (direction == RotationDirection.Right) {
                rotation = CABasicAnimation.FromKeyPath("sublayerTransform.rotation.y");

                rotation.To = FromObject(Radians(-90));

                translationX = CABasicAnimation.FromKeyPath("sublayerTransform.translation.x");

                translationX.To = FromObject(-TranslationForAnimation());

            }
            else if (direction == RotationDirection.Left) {
                rotation = CABasicAnimation.FromKeyPath("sublayerTransform.rotation.y");

                rotation.To = FromObject(Radians(90));

                translationX = CABasicAnimation.FromKeyPath("sublayerTransform.translation.x");

                translationX.To = FromObject(TranslationForAnimation());

            }

            var translationZ = CABasicAnimation.FromKeyPath("sublayerTransform.translation.z");

            translationZ.To = FromObject(-TranslationForAnimation());

            animationGroup.Animations = new CAAnimation[] { rotation, translationX, translationZ };

            animationGroup.FillMode = CoreAnimation.CAFillMode.Forwards; //TODO: maybe convert nsstring to string

            animationGroup.RemovedOnCompletion = false;

            //TODO: unsubscribe necessary?
            animationGroup.AnimationStopped += (sender, e) =>
            {
                var layers = this.View.Layer.Sublayers;
                if (layers != null) {
                    foreach (var layer in layers) {
                        if (layer is FCRotateLayer) {
                            layer.RemoveFromSuperLayer();
                        }
                    }
                }
                foreach(var view in this.View.Subviews) {
                    if (view is FCBlackBackgroundView) {
                        view.RemoveFromSuperview();
                    }
                }
            };

            animationGroup.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut);

            return animationGroup;
        }

        private CALayer LayerFromView(UIView view) {
            var rect = CoreGraphics.CGRect.FromLTRB(0, 0, this.View.Bounds.Size.Width, this.View.Frame.Size.Height);

            CALayer imageLayer = new CALayer();

            imageLayer.AnchorPoint = new CoreGraphics.CGPoint(1, 1);

            imageLayer.Frame = rect;
            UIGraphics.BeginImageContextWithOptions(view.Frame.Size, false, UIScreen.MainScreen.Scale);
            var context = UIGraphics.GetCurrentContext();
            if (context != null) {
                view.Layer.RenderInContext(context);
            }
            UIImage image = UIGraphics.GetImageFromCurrentImageContext();

            UIGraphics.EndImageContext();

            imageLayer.Contents = image.CGImage;
            return imageLayer;
        }

        private CALayer LayerFromView(UIView view, CATransform3D transform) {
            var layer = LayerFromView(view);

            layer.Transform = transform;
            return layer;
        }

        private FCBlackBackgroundView BackgroundView(UIColor color)
        {
            var view = new FCBlackBackgroundView
            {
                Frame = this.View.Frame
            };

            view.BackgroundColor = color;
            return view;
        }

        private float Radians(float degrees) {
            return degrees * (float)Math.PI / 180;
        }

        private nfloat TranslationForAnimation() {
            return CubeSize() / 2;
        }

        private nfloat CubeSize() {
            return UIScreen.MainScreen.Bounds.Width;
        }
    }
}

答案 1 :(得分:0)

这是链接的swift控制器的C#版本。我无法从Swift翻译的一些东西。如果有人可以验证/思考数字转换,那就太好了。

using CoreAnimation;
using CoreGraphics;
using UIKit;


namespace hackettyHack
{
    public enum RotationDirection //int
    {
        // case Right = 0, Left
        Right,
        Left
    }
    public enum RotationType //int
    {
        // case Push = 0, Pop   }
        Push,
        Pop
    }


    class FCBlackBackgroundView : UIView
    {

    }

    class FCRotateLayer : CALayer
    {
    }

    public class RotatingController : UINavigationController
    {
        public void PushViewController(UIViewController controller, RotationDirection rotateDirection)
        {
            Perform3DRotate(RotationType.Push, rotateDirection, controller);
        }
        public void PopViewController(UIViewController controller, RotationDirection rotateDirection)
        {
            Perform3DRotate(RotationType.Pop, rotateDirection, controller);
        }

        void Perform3DRotate(RotationType rotationType, RotationDirection rotateDirection, UIViewController v)
        {
            var layer = RotationLayer();

            var cube = CubeTransform(rotateDirection, layer);

            switch (rotationType)
            {
                case RotationType.Pop:
                    this.PopViewController(animated: false);
                    break;
                case RotationType.Push:
                    this.PushViewController(this, animated: false); //todo: make sure the SWIFT line below for controller! means THIS
                                                                    // self.pushViewController(controller!, animated: false)
                    break;
                default:
                    break;
            }

            layer.AddSublayer(LayerFromView(this.View, cube));

            this.View.AddSubview(BackgroundView(UIColor.White));

            this.View.Layer.AddSublayer(layer);

            layer.AddAnimation(RotationAnimation(rotateDirection), forkey: "rotate");//FIXME  parameter
        }

        CATransform3D CubeTransform(RotationDirection rotateDirection, CALayer layer)
        {
            var cube = CATransform3D.MakeTranslation(0, 0, 0);//FIXME and all below

            layer.AddSublayer(LayerFromView(this.View, transform: cube);

            cube = CATransform3D.Rotate(cube, CGFloat(radians(90)), 0, 1, 0);

            cube = CATransform3D.Translate(cube,CubeSize(), 0, 0);
            if (rotateDirection == RotationDirection.Left)
            {
                cube = CATransform3D.Rotate(cube, CGFloat(radians(90)), 0, 1, 0);

                cube = CATransform3D.Translate(cube, CubeSize(), 0, 0);

                cube = CATransform3D.Rotate(cube, CGFloat(radians(90)), 0, 1, 0);

                cube = CATransform3D.Translate(cube, CubeSize(), 0, 0);

            }
            return cube;
        }

        FCRotateLayer RotationLayer()
        {
            var layer = new FCRotateLayer();

            layer.Frame = this.View.Frame;

            layer.AnchorPoint = new CoreGraphics.CGPoint(x: 0.5, y: 0.5);

            CATransform3D transform = CATransform3D.Identity;

            transform.m34 = 1 / -750;

            layer.SublayerTransform = transform;

            return layer;
        }

        CAAnimation RotationAnimation(RotationDirection direction)
        {
            CATransaction.Flush();

            var animationGroup = new CAAnimationGroup();

            animationGroup.Duration = 0.4;

            var rotation = new CABasicAnimation();
            var translationX = new CABasicAnimation();

            if (direction == RotationDirection.Right)
            {
                rotation = new CABasicAnimation() { KeyPath = "sublayerTransform.rotation.y" };

                //rotation.To   = NSNumber(float: radians(-90))//FIXME

                translationX = new CABasicAnimation() { KeyPath = "sublayerTransform.rotation.x" };

                    translationX.To = NSNumber(float: Float(TranslationForAnimation()));//FIXME
            }
            else if (direction == RotationDirection.Left)
            {
                rotation = new CABasicAnimation() { KeyPath = "sublayerTransform.rotation.y" };

                //rotation.To = NSNumber(float: radians(90))//FIXME

                translationX = new CABasicAnimation() { KeyPath = "sublayerTransform.rotation.x" };

                translationX.To = NSNumber(float: Float(TranslationForAnimation()));//FIXME
            }
            var translationZ = new CABasicAnimation() { KeyPath = "sublayerTransform.translation.z" };

            translationZ.To = NSNumber(float: Float(TranslationForAnimation()));//FIXME

            animationGroup.Animations = new CAAnimation[3] { rotation, translationX, translationZ };

            animationGroup.FillMode = kCAFillModeForwards; /// //FIXME TBD what is the constant? where is it?

            animationGroup.RemovedOnCompletion = false;

            animationGroup.Delegate = this; // or should this be = to aiimationGroup? 

            animationGroup.TimingFunction = CAMediaTimingFunction.FromName("kCAMediaTimingFunctionEaseInEaseOut");

            return animationGroup;
        }

        CALayer LayerFromView(UIView view)
        {
            var rect = new CGRect(0, 0, this.View.Bounds.Size.Width, this.View.Bounds.Size.Height);

            var imageLayer = new CALayer();

            imageLayer.AnchorPoint = new CGPoint(1, 1);

            imageLayer.Frame = rect;

            UIGraphics.BeginImageContextWithOptions(view.Frame.Size, false, UIScreen.MainScreen.Scale);

            // No idea how to convert "if..let..??" from swift.
            // 
            //if let context = UIGraphicsGetCurrentContext() {//FIXME
            //view.layer.renderInContext(context)
            //}
            UIImage image = new UIImage( = UIGraphics.GetImageFromCurrentImageContext();//FIXME

            UIGraphics.EndImageContext();

            imageLayer.Contents = image.CGImage;


            return imageLayer;
        }

        CALayer LayerFromView(UIView view, CATransform3D transform)
        {
            var layer = LayerFromView(view);
            layer.Transform = transform;
            return layer;
        }

        System.nfloat Radians(System.nfloat degrees)
        {
            return degrees * float(M_PI) / 180;  //FIXME
        }

        System.nfloat TranslationForAnimation()
        {
            return CubeSize() / 2;
        }

        System.nfloat CubeSize()
        {
            return UIScreen.MainScreen.Bounds.Width;
        }

        FCBlackBackgroundView BackgroundView(UIColor color)
        {
            var view = new FCBlackBackgroundView() { Frame = this.View.Frame };
            view.BackgroundColor = color;

            return view;
        }

    }
}


/*

public override func animationDidStop(anim: CAAnimation, finished flag: Bool)//FIXME no suitable overrride found
{
if let layers = self.view.layer.sublayers {
for layer in layers {
if layer.isKindOfClass(FCRotateLayer.classForCoder()) {
layer.removeFromSuperlayer()

}
}
}
for view in self.view.subviews {
if view.isKindOfClass(FCBlackBackgroundView.classForCoder()) {
view.removeFromSuperview()

}
}
}



* 
*/