Xamarin.Forms学习之路——MarvelCard改造版

参考https://github.com/kphillpotts/MarvelCards

效果图

在这里插入图片描述

学习目标

  1. MVVM进一步理解
  2. MessagingCenter.Subscribe<>及MessagingCenter.Send理解
  3. :INotifyPropertyChanged进一步理解
  4. Xamarin.Forms动画入门
  5. SkiaSharp绘制原理进一步理解
  6. Xamarin.Forms隐藏安卓状态栏

项目结构

项目结构如图所示。
图片去这里嫖即可。

在这里插入图片描述

在这里插入图片描述

项目准备

  1. 创建名为MarvelCard的空白Xamarin.Forms项目
  2. 安装Xamarin.Forms.PancakeView、CardsView和SkiaSharp.View.Forms NuGet包
  3. 参考项目结构新建文件

代码——按照如下顺序编写

Model文件夹

Hero.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Runtime.CompilerServices;


namespace MarvelCards.Model
{
    public class Hero:INotifyPropertyChanged
    {
        private string _heroName;
        private string _bio;

        public string HeroName 
        { 
            get { return _heroName; }
            set
            {
                _heroName = value;
                //这个HeroName不是Binding显示,是由SkiaSharp绘制,因此不需要OnPropertyChanged();
                //OnPropertyChanged();
            }
        }
        public string RealName { get; set; }
        public string Image { get; set; }
        public string HeroColor { get; set; }
        public string Bio
        {
            get { return _bio; }
            set
            {
                _bio = value;
                //前端使用Binding绑定的对象必须采用 OnPropertyChanged()才能更改UI
                OnPropertyChanged();
            }
        }
        public List<string> Posters { set; get; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName="")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

ViewModels文件夹

HeroCardViewModel.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using MarvelCards.Model;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MarvelCards.ViewModels
{
    public class HeroCardViewModel//:INotifyPropertyChanged
    {
        public ObservableCollection<Hero> Heroes { get; set; }

        public HeroCardViewModel()
        {
            Heroes = new ObservableCollection<Hero>()
            {
                new Hero()
                {
                    HeroName = "spider man",
                    RealName = "peter parker",
                    Image = "spiderman.png",
                    HeroColor = "#C51925",
                    Posters = new List<string>()
                    {
                        "spiderman_1.png",
                        "spiderman_2.png",
                        "spiderman_3.png",
                        "spiderman_4.png",
                    },
                    Bio = "Peter Benjamin Parker was born to C.I.A. agents Richard and Mary Parker, who were killed when Peter was very young. After the death of his parents, Peter was raised by his Uncle Ben and Aunt May in a modest house in Forest Hills, New York."

                },
                new Hero()
                {
                    HeroName = "iron man",
                    RealName = "tony stark",
                    Image = "ironman.png",
                    HeroColor = "#DF8E04",
                    Posters = new List<string>()
                    {
                        "ironman_1.png",
                        "ironman_2.png",
                        "ironman_3.png",
                        "ironman_4.png",
                    },
                    Bio = "Tony Stark is an eccentric self-described genius, billionaire, playboy and philanthropist and the former head of Stark Industries."
                },
            };

        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnpropertyChanged([CallerMemberName] string propertyName="")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

根目录

CardEvent.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace MarvelCards
{
    public class CardEvent
    {

    }
}

CardState.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace MarvelCards
{
    public enum CardState
    {
        Expanded,
        Collapsed
    }
}

Helper.cs

using System;
using System.Collections.Generic;
using System.Text;

//准备工作
namespace MarvelCards
{
    public class Helper
    {
        public static double LimitToRange(double value, double inclusiveMinimum, double inclusiveMaximum)
        {
            if(value >= inclusiveMaximum)
            {
                return value <= inclusiveMinimum ? value : inclusiveMaximum;
            }

            return inclusiveMinimum;

        }
    }
}

IFontAssetHelper.cs

using System;
using System.Collections.Generic;
using System.Text;
using SkiaSharp;

namespace MarvelCards
{
    public interface IFontAssetHelper
    {
        SKTypeface GetSKiaTypefaceFromAssetFont(string fontName);
    }
}

MarvelCards.Andriod目录

FontAssetHelper.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using MarvelCards.Droid;
using System.IO;
using SkiaSharp;
using Xamarin.Forms;

[assembly: Dependency(typeof(FontAssetHelper))]
namespace MarvelCards.Droid
{
    class FontAssetHelper : IFontAssetHelper
    {
        public SKTypeface GetSKiaTypefaceFromAssetFont(string fontName)
        {
            //throw new NotImplementedException();

            SKTypeface typeFace;
            //using 自动释放所新建的对象;
            using ( var asset = Android.App.Application.Context.Assets.Open(fontName))
            {
                var fontStream = new MemoryStream();
                asset.CopyTo(fontStream);
                fontStream.Flush();
                fontStream.Position = 0;
                typeFace = SKTypeface.FromStream(fontStream); 
            }

            return typeFace;
        }
    }
}
  1. 向Assets文件加入字体.ttf文件,copy即可,向Resourses文件加入图片,copy即可

回归MarvelCards项目

Views文件夹

HeroCard.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="MarvelCards.Views.HeroCard"
             xmlns:pancake ="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             >

    <Grid>
        <skia:SKCanvasView
            x:Name="CardBackground"
            HorizontalOptions="Fill"
            PaintSurface="CardBackground_PaintSurface"
            VerticalOptions="Fill"></skia:SKCanvasView>
        
        <Image
            x:Name="HeroImage"
            HeightRequest="300"
            Source="{Binding Image}"
            TranslationY="50"
            VerticalOptions="Start"
            WidthRequest="300"></Image>

        <Label 
            x:Name="LearnMoreLabel"
            Margin="40,570,0,0"
            HorizontalOptions="Start"
            Style="{StaticResource Key=LearnMore}"
            Text="learn more →"
            VerticalOptions="Start">
            <Label.GestureRecognizers>
                <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer>
            </Label.GestureRecognizers>
        </Label>

        <Image
            x:Name="MarvelLogoImage"
            Margin="0,480,40,0"
            HorizontalOptions="End"
            Opacity="0"
            Source="marvel_logo"
            VerticalOptions="Start"></Image>

        <Grid
            x:Name="HeroDetails"
            Margin="0,0,0,40"
            InputTransparent="True">
            <Grid.RowDefinitions>
                <RowDefinition Height="530"></RowDefinition>
                <RowDefinition Height="*"></RowDefinition>
            </Grid.RowDefinitions>

            <BoxView x:Name="HeroDetailsDivider"
                     Grid.Row="1"
                     Margin="40,0,40,0"
                     HeightRequest="1"
                     Opacity="0"
                     VerticalOptions="Start"
                     Color="#EAEAEA"></BoxView>

            <ScrollView
                x:Name="HeroDetailsScroll"
                Grid.Row="1"
                Margin="40,20,40,0"
                Opacity="0"
                Orientation="Vertical">
                <StackLayout>
                    <Label 
                        Style="{StaticResource Key=BioText}"
                        Text="{Binding Bio}"></Label>
                    <BoxView
                        Margin="0,20,0,0"
                        HeightRequest="1"
                        VerticalOptions="Start"
                        Color="#EAEAEA"></BoxView>
                    <Label
                        Style="{StaticResource Key=SubHeader}"
                        Text="movie"
                        TextColor="Black"></Label>

                    <FlexLayout
                        BindableLayout.ItemsSource="{Binding Posters}"
                        HorizontalOptions="Fill"
                        JustifyContent="SpaceEvenly"
                        Wrap="Wrap">
                        <BindableLayout.ItemTemplate>
                            <DataTemplate>
                                <pancake:PancakeView Margin="5" CornerRadius="20">
                                    <Image Source="{Binding}"></Image>
                                </pancake:PancakeView>
                            </DataTemplate>
                        </BindableLayout.ItemTemplate>
                    </FlexLayout>
                    <Label 
                        Text="Wrap"
                        FontAttributes="Bold"
                        HorizontalOptions="Center">
                        <Label.GestureRecognizers>
                            <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped_1"></TapGestureRecognizer>
                        </Label.GestureRecognizers>
                    </Label>
                    
                    
                </StackLayout>
                
            </ScrollView>
                
                
                
          
            
        </Grid>
        
    </Grid>
</ContentView>
    

HeroCard.xaml.cs


using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using MarvelCards.ViewModels;
using MarvelCards.Model;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;

namespace MarvelCards.Views
{
    //[DesignTimeVisible(true)]
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class HeroCard : ContentView
    {
        private Hero _viewModel;
        private readonly float _density;
        private readonly float _cardTopMargin;
        private float _cornerRadius = 60f;
        private CardState _cardState = CardState.Collapsed;
        private float _gradientHeight = 200f;
        private double _heroImagePosY;

        private static SKTypeface _typeface;

        //缓存Skia的颜色和画笔
        SKColor _heroColor;
        SKPaint _heroPaint;
        private double _cardTopAnimPosition;
        private float _gradientTransitionY;

        //字体font和标签label
        private SKPaint _heroNamePaint;
        private float _HeroNamePosY;
        private float _HeroNamePosX;
        private SKPaint _realNamePaint;
        private float _realNamePosY;
        private float _realNameOffsetY;
        private float _heroNameOffsetY;

        //滚动队列(保证只有两个值)
        Queue<double> scrollQueue = new Queue<double>();

        //控制文字显示的Flag
        private int textFlag = 1;
        //控制ScrollView收缩因数
        private bool shrink = false;
        public HeroCard()
        {
            InitializeComponent();
            //BindingContext = new HeroCardViewModel();
            

            _density = (float)Xamarin.Essentials.DeviceDisplay.MainDisplayInfo.Density;
            _cardTopMargin = 200f * _density;
            _cornerRadius = 30f * _density;

            //加载字体
            if(_typeface == null)
            {
                _typeface = DependencyService.Get<IFontAssetHelper>().GetSKiaTypefaceFromAssetFont("MontserratAlternates-Bold.ttf");

            }

            HeroDetailsScroll.Scrolled += HeroDetailsScroll_Scrolled;
        }

        /// <summary>
        /// 页面滚动效果
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HeroDetailsScroll_Scrolled(object sender, ScrolledEventArgs e)
        {
            //throw new NotImplementedException();
            
            Debug.WriteLine("Y值"+HeroDetailsScroll.ScrollY);
            Debug.WriteLine("边界" + HeroDetailsScroll.Bounds);
            double rightNowScrollY = HeroDetailsScroll.ScrollY;
            bool scrollStatue = true;
            double parallax = 0;//差值,当滑动速度parallax>50时,收缩scrollview
            
            scrollQueue.Enqueue(rightNowScrollY);
            if(scrollQueue.Count == 2)
            {
                double beforeScrollY = scrollQueue.Dequeue();
                scrollStatue = rightNowScrollY > beforeScrollY ? true : false;
                parallax = Math.Abs(rightNowScrollY - beforeScrollY);
                //向上滚动
                if(scrollStatue == false)
                {
                    if(shrink == true)
                        shrink = true;
                    else
                    {
                        if(parallax >=20)
                        {
                            shrink = true;
                        }
                        else
                        {
                            shrink = false;
                        }
                    }
                    
                }
                //向下滚动
                else
                {
                    shrink = false;
                }
                Debug.WriteLine("当前滚动" + rightNowScrollY);
                Debug.WriteLine("之前滚动" + beforeScrollY);
                Debug.WriteLine("scrollStatue" + scrollStatue);
            }
            



            var anim = new Animation();
            if (!shrink)
            {
                shrink = false;
                anim.Add(0.00,0.15, new Animation(
                    v =>
                    {
                        HeroImage.HeightRequest = v;
                    },
                    HeroImage.HeightRequest,
                    0,
                    Easing.Linear
                    ));
                anim.Add(0.00, 0.15, new Animation(
                    v =>
                    {
                        HeroDetails.Margin = new Thickness(0, v, 0, 40);
                        //HeroDetails.TranslationY = v;
                        //Debug.WriteLine(HeroDetails.Height);
                    },
                    HeroDetails.Margin.Top,
                    -500,
                    Easing.Linear
                    ));
                anim.Add(0, 0.01, new Animation(
                    v =>
                    {
                        Debug.WriteLine('*');
                        textFlag = 0;
                        CardBackground.InvalidateSurface();//可以调用CardBackground_PaintSurface
                    },
                    0,
                    0,
                    Easing.Linear));
                anim.Add(0, 0.15, new Animation(
                    v =>
                    {
                        MarvelLogoImage.Opacity = v;
                    },
                    MarvelLogoImage.Opacity,
                    0,
                    Easing.Linear));
                anim.Commit(this, "ScrollAnim", 16, 2000);
            }
            else
            {
                ShrinkScrollView();
            }
        }


        /// <summary>
        /// 当切换英雄card时
        /// </summary>
        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();

            if (BindingContext == null) 
                return;

            _viewModel = BindingContext as Hero;

            //由于Skia Drawing不支持Binding机制,所以
            //当角色改变时记录paint对象。
            _heroColor = Color.FromHex(_viewModel.HeroColor).ToSKColor();
            _heroPaint = new SKPaint() { Color = _heroColor };
            _gradientTransitionY = float.MaxValue;

            _heroNamePaint = new SKPaint
            {
                Typeface = _typeface,
                IsAntialias = true,//是否抗锯齿
                Color = SKColors.White,
                TextSize = 60f * _density

            };

            _HeroNamePosY = 450f * _density;
            _HeroNamePosX = 40f * _density;

            _realNamePaint = new SKPaint
            {
                Typeface = _typeface,
                IsAntialias = true,//是否抗锯齿
                Color = SKColors.White,
                TextSize = 25f * _density
            };
            _realNamePosY = 550f * _density;

            //初始化值
            _cardTopAnimPosition = _cardTopMargin;

            //重绘画板
            CardBackground.InvalidateSurface();
        }

        /// <summary>
        /// MainImage =>{
        ///     MainImage = HeroImage;
        /// }
        /// </summary>
        public Image MainImage => HeroImage;

        private void CardBackground_PaintSurface(object sender, SkiaSharp.Views.Forms.SKPaintSurfaceEventArgs e)
        {
            //PiatSurface有时候在bindingContext设置之前调用;
            //这里保证bindingcontext已经设置完毕
            if (_viewModel == null)
                return;

            Debug.WriteLine("绘制");
            SKImageInfo info = e.Info;
            SKSurface surface = e.Surface;
            SKCanvas canvas = surface.Canvas;

            canvas.Clear();

            //绘制上方的颜色
            canvas.DrawRoundRect(
                rect: new SKRect(0, (float)_cardTopAnimPosition, info.Width, info.Height),
                r: new SKSize(_cornerRadius, _cornerRadius),
                paint:_heroPaint);

            //渐变色处理
            var gradientRect = new SKRect(0, _gradientTransitionY, info.Width, 
                _gradientTransitionY + _gradientHeight);
            var gradientPaint = new SKPaint() { Style = SKPaintStyle.Fill };
            gradientPaint.Shader = GetGradientShader(_heroColor, SKColors.White);

            //渐变色绘制
            canvas.DrawRect(gradientRect, gradientPaint);

            //绘制白色底部
            SKRect bottomRect = new SKRect(0, _gradientTransitionY + _gradientHeight,
                info.Width, info.Height);
            canvas.DrawRect(bottomRect, new SKPaint() { Color = SKColors.White });

            //绘制英雄名字
            DrawHeroNameText(canvas);
            DrawRealNameText(canvas);
        }

        private void DrawRealNameText(SKCanvas canvas)
        {
            //throw new NotImplementedException();
            var textPos = new SKPoint(_HeroNamePosX, _realNamePosY + _realNameOffsetY);
            if (textFlag == 1)
                //如果可以画,对_realNamePaint使用渐变着色器
                _realNamePaint.Shader = GetGradientShader(SKColors.White, SKColors.Black);
            else
                _realNamePaint.Shader = null;

            canvas.DrawText(_viewModel.RealName, textPos, _realNamePaint);

        }

        private void DrawHeroNameText(SKCanvas canvas)
        {
            //throw new NotImplementedException();
            //对_heroNamePaint使用渐变着色器
            if (textFlag == 1)
                _heroNamePaint.Shader = GetGradientShader(SKColors.White, SKColors.Black);
            else
                _heroNamePaint.Shader = null;
            //拆分英雄名
            var textbits = _viewModel.HeroName.Split(' ');

            //逐行绘制
            for (int i = 0; i < textbits.Length; i++)
            {
                var textHeight = _heroNamePaint.TextSize;
                var textPos = new SKPoint(_HeroNamePosX, _HeroNamePosY + _heroNameOffsetY + (i * textHeight));
                canvas.DrawText(textbits[i], textPos, _heroNamePaint);
            }
        }

        private SKShader GetGradientShader(SKColor fromColor, SKColor toColor)
        {
            //throw new NotImplementedException();
            return SKShader.CreateLinearGradient(
                start: new SKPoint(0, _gradientTransitionY),
                end: new SKPoint(0, _gradientTransitionY + _gradientHeight),
                colors: new SKColor[] { fromColor, toColor },
                colorPos: new float[] { 0, 1 },
                mode: SKShaderTileMode.Clamp);
        }

        private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
        {
            //去处理卡片伸展
            GotoState(CardState.Expanded);
        }

        public void GotoState(CardState cardState)
        {
            //throw new NotImplementedException();
            //看看是不是真的切换了卡片状态
            if (_cardState == cardState)
                return;

            //向MainPage发送信息,控制MainPage的控件行为
            MessagingCenter.Send<CardEvent>(new CardEvent(), cardState.ToString());
            //HeroCard页动画
            AnimateTransition(cardState);
            _cardState = cardState;
            HeroDetails.InputTransparent = _cardState == CardState.Collapsed;
            
        }

        private void AnimateTransition(CardState cardState)
        {
            var parentAnimation = new Animation();

            //处理卡片伸展动画
            if(cardState == CardState.Expanded)
            {
                parentAnimation.Add(0.00, 0.10, CreateCardAnimition(cardState));
                parentAnimation.Add(0.00, 0.25, CreateLearnMoreAnimation(cardState));
                parentAnimation.Add(0.00, 0.50, CreatImageAnimation(cardState));
                parentAnimation.Add(0.10, 0.50, CreateHeroNameAnimation(cardState));
                parentAnimation.Add(0.15, 0.50, CreateRealNameAnimation(cardState));
                parentAnimation.Add(0.50, 0.75, CreateGradientAnimation(cardState));

                //详情页HeroDetailsScroll的动画
                parentAnimation.Add(0.60, 0.85, new Animation(
                    v =>
                    {
                        HeroDetailsScroll.TranslationY = v;
                    },
                    200,
                    0,
                    Easing.SpringOut
                    ));
                parentAnimation.Add(0.60, 0.85, new Animation(
                    v =>
                    {
                        HeroDetailsScroll.Opacity = v;
                    },
                    0,
                    1,
                    Easing.Linear
                    ));

                //HeroDetailsDivider动画
                parentAnimation.Add(0.75, 0.85, new Animation(
                    v =>
                    {
                        HeroDetailsDivider.TranslationY = v;
                    },
                    -20,
                    0,
                    Easing.Linear
                    ));
                parentAnimation.Add(0.75, 0.85, new Animation(
                    v =>
                    {
                        HeroDetailsDivider.Opacity = v;
                    },
                    0,
                    1,
                    Easing.Linear
                    ));

                //漫威logo动画
                parentAnimation.Add(0.6, 0.85, new Animation(
                    v =>
                    {
                        MarvelLogoImage.Scale = v;
                    },
                    0,
                    1,
                    Easing.SpringOut
                    ));
                parentAnimation.Add(0.6, 0.85, new Animation(
                    v =>
                    {
                        MarvelLogoImage.Opacity = v;
                    },
                    0,
                    1,
                    Easing.Linear
                    ));
            }
            //处理卡片折叠动画
            else
            {
                parentAnimation.Add(0.00, 0.25, CreateGradientAnimation(cardState));
                parentAnimation.Add(0.25, 0.45, CreatImageAnimation(cardState));
                parentAnimation.Add(0.35, 0.45, CreateLearnMoreAnimation(cardState));
                parentAnimation.Add(0.25, 0.45, CreateCardAnimition(cardState));
                parentAnimation.Add(0.30, 0.50, CreateHeroNameAnimation(cardState));
                parentAnimation.Add(0.25, 0.50, CreateRealNameAnimation(cardState));

                //详情页HeroDetailsScroll的动画
                parentAnimation.Add(0.00, 0.35, new Animation(
                    v =>
                    {
                        HeroDetailsScroll.TranslationY = v;
                    },
                    0,
                    200,
                    Easing.SpringIn
                    ));
                parentAnimation.Add(0.00, 0.35, new Animation(
                    v =>
                    {
                        HeroDetailsScroll.Opacity = v;
                    },
                    1,
                    0,
                    Easing.Linear
                    ));

                //HeroDetailsDivider动画
                parentAnimation.Add(0.00, 0.10, new Animation(
                    v =>
                    {
                        HeroDetailsDivider.TranslationY = v;
                    },
                    0,
                    -20,
                    Easing.Linear
                    ));
                parentAnimation.Add(0.00, 0.10, new Animation(
                    v =>
                    {
                        HeroDetailsDivider.Opacity = v;
                    },
                    1,
                    0,
                    Easing.Linear
                    ));

                //漫威logo动画
                parentAnimation.Add(0.00 ,0.15 , new Animation(
                    v =>
                    {
                        MarvelLogoImage.Scale = v;
                    },
                    1,
                    0,
                    Easing.SpringOut
                    ));
                parentAnimation.Add(0.00, 0.15, new Animation(
                    v =>
                    {
                        MarvelLogoImage.Opacity = v;
                    },
                    1,
                    0,
                    Easing.Linear
                    ));
            }

            //播放动画
            parentAnimation.Commit(this, "CardExpand", 16, 2000);
        }

        private Animation CreateGradientAnimation(CardState cardState)
        {
            double start;
            double end;
            //处理卡片伸展动画
            if (cardState == CardState.Expanded)
            {
                _gradientTransitionY = CardBackground.CanvasSize.Height;
                start = _gradientTransitionY;
                end = -_gradientHeight;
            }
            //处理卡片折叠动画
            else
            {
                _gradientTransitionY = -_gradientHeight;
                start = _gradientTransitionY;
                end = CardBackground.CanvasSize.Height;
            }

            var gradientAnimation = new Animation(
                callback: v =>
                {
                    Debug.WriteLine("这里是gradientAnimation回滚函数");
                    Debug.WriteLine("V:" + v);
                    _gradientTransitionY = (float)v;
                    CardBackground.InvalidateSurface();
                },
                start: start,
                end: end,
                easing: Easing.Linear,
                finished: () =>
                {
                    Debug.WriteLine("动画结束");
                }
                ) ;

            return gradientAnimation;
        }

        private Animation CreateLearnMoreAnimation(CardState cardState)
        {
            //处理卡片顶部位置
            //如果卡片是伸展状态,则开始于0,否则开始于100
            var learnMoreAnimStart = cardState == CardState.Expanded ? 0 : 100;
            //如果卡片是伸展状态,则终止于100,否则终止于0
            var learnMoreAnimEnd = cardState == CardState.Expanded ? 100 : 0;

            var learnMoreAnim = new Animation(
                callback: v =>
                {
                    Debug.WriteLine("这里是learnMoreAnim回滚函数");
                    Debug.WriteLine("V:" + v);
                    LearnMoreLabel.TranslationX = v;
                    LearnMoreLabel.Opacity = 1 - (v / 100);
                },
                start:learnMoreAnimStart,
                end:learnMoreAnimEnd,
                easing:Easing.SinInOut,
                 finished: () =>
                 {
                     Debug.WriteLine("动画结束");
                 }
                );
            return learnMoreAnim;
        }

        private Animation CreateRealNameAnimation(CardState cardState)
        {
            //处理名字顶部位置
            var nameAnimStart = cardState == CardState.Expanded ? 0 : -50;
            var nameAnimEnd = cardState == CardState.Expanded ? -50 : 0;

            var nameAnim = new Animation(
                v =>
                {
                    Debug.WriteLine("这里是RealNameAnimation回滚函数");
                    Debug.WriteLine("V:" + v);
                    _realNameOffsetY = (float)v * _density;
                    CardBackground.InvalidateSurface();
                },
                nameAnimStart,
                nameAnimEnd,
                Easing.SpringOut,
                () =>
                {
                    Debug.WriteLine("动画结束");
                }
                );

            return nameAnim;
        }

        private Animation CreateHeroNameAnimation(CardState cardState)
        {
            //处理名字顶部位置
            var nameAnimStart = cardState == CardState.Expanded ? 0 : -50;
            var nameAnimEnd = cardState == CardState.Expanded ? -50 : 0;

            var nameAnim = new Animation(
                v =>
                {
                    Debug.WriteLine("这里是HeroNameAnimation回滚函数");
                    Debug.WriteLine("V:" + v);
                    _heroNameOffsetY = (float)v * _density;
                    CardBackground.InvalidateSurface();
                },
                nameAnimStart,
                nameAnimEnd,
                Easing.SpringOut,
                () =>
                {
                    Debug.WriteLine("动画结束");
                }
                );

            return nameAnim;
        }

        private Animation CreatImageAnimation(CardState cardState)
        {
            var imageAnimStart = cardState == CardState.Expanded ? 50 : 0;
            var imageAnimEnd = cardState == CardState.Expanded ? 0 : 50;

            var imageAnim = new Animation(
                v =>
                {
                    Debug.WriteLine("这里是ImageAnimation回滚函数");
                    Debug.WriteLine("V:" + v);
                    HeroImage.TranslationY = v;
                    _heroImagePosY = HeroImage.TranslationY;
                },
                imageAnimStart,
                imageAnimEnd,
                Easing.SpringOut,
                () =>
                {
                    Debug.WriteLine("动画结束");
                }
                );
            return imageAnim;
        }

        private Animation CreateCardAnimition(CardState cardState)
        {
            var cardAnimStart = cardState == CardState.Expanded ? _cardTopMargin : -_cornerRadius;
            var cardAnimEnd = cardState == CardState.Expanded ? -_cornerRadius : _cardTopMargin;

            var cardAnim = new Animation(
                v =>
                {
                    Debug.WriteLine("这里是CardAnimition回滚函数");
                    Debug.WriteLine("V:" + v);
                    _cardTopAnimPosition = v;
                    CardBackground.InvalidateSurface();
                },
                cardAnimStart,
                cardAnimEnd,
                Easing.SinInOut,
                () =>
                {
                    Debug.WriteLine("动画结束");
                }
                );
            return cardAnim;
        }

        private void TapGestureRecognizer_Tapped_1(object sender, EventArgs e)
        {
            ShrinkScrollView();
            //测试INotifyPropertyChanged
            //_viewModel.HeroName = "Mother Fucker";
            //_viewModel.Bio = "Mother fuck";
        }

        public void ShrinkScrollView()
        {
            shrink = true;
            var anim = new Animation();
            anim.Add(0.00, 0.15, new Animation(
                   v =>
                   {
                       HeroImage.HeightRequest = v;
                   },
                   HeroImage.HeightRequest,
                   300,
                   Easing.Linear
                   ));
            anim.Add(0.00, 0.15, new Animation(
                v =>
                {

                    HeroDetails.Margin = new Thickness(0, v, 0, 40);
                    //HeroDetails.TranslationY = v;
                    //Debug.WriteLine(HeroDetails.Height);
                },
                HeroDetails.Margin.Top,
                0,
                Easing.Linear
                ));
            anim.Add(0, 0.01, new Animation(
                v =>
                {
                    CardBackground.InvalidateSurface();
                },
                0,
                0,
                Easing.Linear));
            anim.Add(0, 0.01, new Animation(
                v =>
                {
                    Debug.WriteLine('*');
                    textFlag = 1;
                    CardBackground.InvalidateSurface();//可以调用CardBackground_PaintSurface
                },
                0,
                0,
                Easing.Linear));
            anim.Add(0, 0.15, new Animation(
               v =>
               {
                   MarvelLogoImage.Opacity = v;
               },
               MarvelLogoImage.Opacity,
               1,
               Easing.Linear));

            anim.Commit(this, "ShrinkAnim", 16, 2000);
        }
    }
}

根目录

MainPage.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"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="MarvelCards.MainPage"
             xmlns:cards="clr-namespace:PanCardView;assembly=PanCardView"
             xmlns:views="clr-namespace:MarvelCards.Views"
             xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core">
    <Grid>
        <cards:CardsView
            x:Name="MainCardView"
            IsCyclical="True"
            WidthRequest="300"
            ItemsSource="{Binding Heroes}">
            <cards:CardsView.ItemTemplate>
                <DataTemplate>
                    <views:HeroCard Margin="5,0,5,-30"></views:HeroCard>
                </DataTemplate>
            </cards:CardsView.ItemTemplate>
        </cards:CardsView>

        <Label
            x:Name="MoviesHeader"
            Margin="{OnPlatform iOS=30,Android=10}"
            HorizontalOptions="Center"
            Style="{StaticResource Key=TitleHeader}"
            Text="movies"
            VerticalOptions="Start"></Label>

        <Image
            x:Name="BackArrow"
            Margin="{OnPlatform iOS='40,33,0,0',Android='40,15,0,0'}"
            HorizontalOptions="Start"
            Source="back_arrow"
            VerticalOptions="Start">
            <Image.GestureRecognizers>
                <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer>
            </Image.GestureRecognizers>
        </Image>


    </Grid>
</ContentPage>

MainPage.xaml.cs

using MarvelCards.ViewModels;
using MarvelCards.Views;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace MarvelCards
{
    // Learn more about making custom code visible in the Xamarin.Forms previewer
    // by visiting https://aka.ms/xamarinforms-previewer
    [DesignTimeVisible(true)]
    public partial class MainPage : ContentPage
    {
        private double _heroImageTranslationY = 50;
        private double _movementFactor = 100;

        public MainPage()
        {
            InitializeComponent();
            BindingContext = new HeroCardViewModel();
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();
            MainCardView.UserInteracted += MainCardView_UserInteracted;
            //订阅HeroCard发送的信息,如果信息是'CardState.Expanded.ToString()',则调用CardExpand函数
            MessagingCenter.Subscribe<CardEvent>(this, CardState.Expanded.ToString(), CardExpand);
        }

        protected override void OnDisappearing()
        {
            base.OnDisappearing();
            //当前页取消互动函数
            MainCardView.UserInteracted -= MainCardView_UserInteracted;
            //当前页取消订阅
            MessagingCenter.Unsubscribe<CardEvent>(this, CardState.Expanded.ToString());

        }

        private void CardExpand(CardEvent obj)
        {
            //throw new NotImplementedException();
            //不允许用户左右滑动PanCardView
            MainCardView.IsUserInteractionEnabled = false;
            //标题进入动画
            AnimateTitle(CardState.Expanded);

        }

        private void AnimateTitle(CardState cardState)
        {
            //throw new NotImplementedException();
            var translationY = cardState == CardState.Expanded ? 0 - (MoviesHeader.Height + MoviesHeader.Margin.Top) : 0;
            var opacity = cardState == CardState.Expanded ? 0 : 1;

            var titleAnim = new Animation();
            if(cardState == CardState.Expanded)
            {
                titleAnim.Add(0.00, 0.25, new Animation(
                    v =>
                    {
                        MoviesHeader.TranslationY = v;
                    },
                    MoviesHeader.TranslationY,
                    translationY,
                    Easing.Linear,
                    () =>
                    {
                        Debug.WriteLine("动画结束");
                    }
                    ));
                titleAnim.Add(0.00, 0.25, new Animation(
                   v =>
                   {
                       MoviesHeader.Opacity = v;
                   },
                   MoviesHeader.Opacity,
                   opacity,
                   Easing.Linear,
                   () =>
                   {
                       Debug.WriteLine("动画结束");
                   }
                   ));
            }
            else
            {
                titleAnim.Add(0.75, 1.00, new Animation(
                    v =>
                    {
                        MoviesHeader.TranslationY = v;
                    },
                    MoviesHeader.TranslationY,
                    translationY,
                    Easing.Linear,
                    () =>
                    {
                        Debug.WriteLine("动画结束");
                    }
                    ));
                titleAnim.Add(0.75, 1.00, new Animation(
                   v =>
                   {
                       MoviesHeader.Opacity = v;
                   },
                   MoviesHeader.Opacity,
                   opacity,
                   Easing.Linear,
                   () =>
                   {
                       Debug.WriteLine("动画结束");
                   }
                   ));
            }
            titleAnim.Commit(this, "titleAnim", 16, 1000);

        }

        /// <summary>
        /// 处理页面滑动
        /// </summary>
        /// <param name="view"></param>
        /// <param name="args"></param>
        private void MainCardView_UserInteracted(PanCardView.CardsView view, PanCardView.EventArgs.UserInteractedEventArgs args)
        {
            //throw new NotImplementedException();
            if(args.Status == PanCardView.Enums.UserInteractionStatus.Running)
            {
                //获取当前视野内卡片页面
                var card = MainCardView.CurrentView as HeroCard;
                Debug.WriteLine("当前卡片页面对象" + card);
                //滑动到百分之多少
                var percentFromCenter = Math.Abs(args.Diff / this.Width);
                
                //处理缩放
                if((percentFromCenter > 0) && (card.Scale == 1))
                {
                    card.ScaleTo(0.95, 50);
                }

                //基于card滑动位置更新card页面元素
                AnimateFrontCardDuringSwipe(card, percentFromCenter);

                //获取下一张即将进入视野的卡片页面
                var nextCard = MainCardView.CurrentBackViews.First() as HeroCard;

                //基于card滑动位置更新nextCard页面元素
                AnimateIncomingCardDuringSwipe(nextCard, percentFromCenter);
                    
            }

            if(args.Status == PanCardView.Enums.UserInteractionStatus.Ended|| args.Status == PanCardView.Enums.UserInteractionStatus.Ending)
            {
                //在滑动的最后,确保元素更新成功
                var card = MainCardView.CurrentView as HeroCard;
                AnimateFrontCardDuringSwipe(card, 0);
                card.ScaleTo(1, 50);
            }
            
            
        }

        /// <summary>
        /// 基于card滑动位置更新nextCard页面元素
        /// </summary>
        /// <param name="nextCard"></param>
        /// <param name="percentFromCenter"></param>
        private void AnimateIncomingCardDuringSwipe(HeroCard nextCard, double percentFromCenter)
        {
            //throw new NotImplementedException();
            //下一张图片淡入
            nextCard.MainImage.Opacity = Helper.LimitToRange(percentFromCenter * 1.5, 0, 1);

            // 下一张图片缩放而入
            nextCard.MainImage.Scale = Helper.LimitToRange(percentFromCenter * 1.1, 0, 1);

            //控制下一张图片滑动时y偏移量
            var offset = _heroImageTranslationY + (_movementFactor * (1 - (percentFromCenter * 1.1)));
            nextCard.MainImage.TranslationY = Helper.LimitToRange(offset, _heroImageTranslationY, _heroImageTranslationY + _movementFactor);
        }

        /// <summary>
        /// 基于card滑动位置更新card页面元素
        /// </summary>
        /// <param name="card"></param>
        /// <param name="percentFromCenter"></param>
        private void AnimateFrontCardDuringSwipe(HeroCard card, double percentFromCenter)
        {
            //throw new NotImplementedException();

            //控制滑动时卡片透明度
            MainCardView.CurrentView.Opacity = Helper.LimitToRange((1 - (percentFromCenter)) * 2, 0, 1);

            //控制滑动时卡片缩放
            card.MainImage.Scale = Helper.LimitToRange((1 - (percentFromCenter) * 1.5), 0, 1);

            //控制滑动时y偏移量
            card.MainImage.TranslationY = _heroImageTranslationY + (_movementFactor * percentFromCenter);

            //控制图片透明度
            card.MainImage.Opacity = Helper.LimitToRange((1 - (percentFromCenter) * 1.5), 0, 1);
        }

        private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
        {
            //收起标题
            AnimateTitle(CardState.Collapsed);

            //通知卡片折叠
            var card = MainCardView.CurrentView as HeroCard;
            card.ShrinkScrollView();
            card.GotoState(CardState.Collapsed);
            

            //允许滑动卡片
            MainCardView.IsUserInteractionEnabled = true;
        }
    }
}

App.xaml

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="MarvelCards.App">
    <Application.Resources>
        <OnPlatform x:Key="HeaderFont" x:TypeArguments="x:String">
            <On Platform="Android" Value="MontserratAlternates-Bold.ttf#MontserratAlternates-Bold" />
            <On Platform="iOS" Value="MontserratAlternates-Bold" />
        </OnPlatform>

        <OnPlatform x:Key="BodyFont" x:TypeArguments="x:String">
            <On Platform="Android" Value="MontserratAlternates-Medium.ttf#MontserratAlternates-Medium" />
            <On Platform="iOS" Value="MontserratAlternates-Medium" />
        </OnPlatform>


        <Style x:Key="Header" TargetType="Label">
            <Setter Property="FontFamily" Value="{StaticResource HeaderFont}" />
            <Setter Property="FontSize" Value="60" />
            <Setter Property="TextColor" Value="White" />
        </Style>
        <Style x:Key="SubHeader" TargetType="Label">
            <Setter Property="FontFamily" Value="{StaticResource HeaderFont}" />
            <Setter Property="FontSize" Value="24" />
            <Setter Property="TextColor" Value="White" />
        </Style>
        <Style x:Key="LearnMore" TargetType="Label">
            <Setter Property="FontFamily" Value="{StaticResource BodyFont}" />
            <Setter Property="FontSize" Value="20" />
            <Setter Property="TextColor" Value="#FFB854" />
        </Style>
        <Style x:Key="TitleHeader" TargetType="Label">
            <Setter Property="FontFamily" Value="{StaticResource BodyFont}" />
            <Setter Property="FontSize" Value="26" />
            <Setter Property="TextColor" Value="Black" />
        </Style>

        <Style x:Key="BioText" TargetType="Label">
            <Setter Property="FontFamily" Value="{StaticResource BodyFont}" />
            <Setter Property="FontSize" Value="14" />
            <Setter Property="TextColor" Value="#716F6F" />
        </Style>


        <Style TargetType="Grid">
            <Setter Property="RowSpacing" Value="0" />
            <Setter Property="ColumnSpacing" Value="0" />
        </Style>

        <Style TargetType="StackLayout">
            <Setter Property="Spacing" Value="0" />
        </Style>

    </Application.Resources>
</Application>

App.xaml.cs

using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace MarvelCards
{
    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();

            MainPage = new MainPage();
        }

        protected override void OnStart()
        {
        }

        protected override void OnSleep()
        {
        }

        protected override void OnResume()
        {
        }
    }
}

MarvelCards.Android项目

MainActivity.cs

using System;

using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace MarvelCards.Droid
{
    [Activity(Label = "MarvelCards", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            //隐藏状态栏,全屏显示
            Window.AddFlags(WindowManagerFlags.Fullscreen);
            Window.ClearFlags(WindowManagerFlags.ForceNotFullscreen);


            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            LoadApplication(new App());
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值