WPF学习(12)动画
本篇来学习WPF的动画。什么是动画?动画就是一系列帧。在WPF中,动画就是在一段时间内修改依赖属性值的行为,它是基于时间线Timeline的。有人会说,要动画干嘛,华而不实,而且添加了额外的资源消耗而影响性能。尽管如此,适当的使用动画却可以使你的程序富有更好的表现力和交互性。更加可喜的是,WPF提供了丰富的动画支持,大部分的动画都可以直接通过XAML来呈现而不用去写繁琐的cs代码。在System.Windows.Media.Animation命名空间中,我们发现了许多的类,大体可归类为这么三种:基于线性内插算法动画(简单动画)、基于KeyFrame动画和基于路径动画。下面来分别介绍这三种动画:
首先以图来说明类的继承层次:
这张粗糙的图只是以Double类型的动画为例,因为它是比较全的,全面提到的三种动画类它都具备。支持KeyFrame的类型最多,其次是线性内插,最后是路径,只有三种,除了Double外,还有Matrix和Point。
1.基于线性内插算法动画(简单动画)
基于线性内插动画是在一个开始值到一个结束值之间以逐步增加的方式来改变属性值。有这么几个重要的属性:
From:开始值,当忽略该属性时,动画默认从其使用者属性值开始,所以其使用者属性在执行动画前要被赋值
To:结束值,当忽略该属性时,动画默认从其使用者属性值结束,所以其使用者属性在执行动画前要被赋值
By:递增值,和To一样和From搭配使用,当然可以用To来代替它
Duration:时间间隔,动画的执行时间,可以将一个TimeSpan值直接给它赋值,会自动隐式转换
AutoReverse:在到达结束值后,是否以相反的顺序回到From值
RepeatBehavior:重复行为,默认不重复,可设置其为Forever来重复动画,也可以通过构造器来设置次数和Timespan
FillBehavior:在活动周期结束后的行为方式,有HoldEnd(默认值)和Stop两个值,HoldEnd表示在动画结束后保持该属性值,而Stop则表示在动画结束后回到原来值(不是From值)
例子如下:
private void Button_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
DoubleAnimation widthAnimation = new DoubleAnimation();
widthAnimation.From = ;
widthAnimation.To = ;
//widthAnimation.AutoReverse = true;
widthAnimation.Duration = TimeSpan.FromSeconds();
widthAnimation.FillBehavior = FillBehavior.Stop;
//RepeatBehavior = RepeatBehavior.Forever;
widthAnimation.Completed += widthAnimation_Completed;
button.BeginAnimation(Button.WidthProperty, widthAnimation);
} void widthAnimation_Completed(object sender, EventArgs e)
{
//to do ...
}
我们再来通过Trigger在Xaml页面举个例子:
<Window x:Class="AnimationDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="Grid" x:Key="gridKey">
<Style.Triggers>
<EventTrigger RoutedEvent="Grid.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
From="0" To="1" Duration="0:0:2" AccelerationRatio="1" />
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
From="0" To="1" Duration="0:0:2" AccelerationRatio="1" />
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].Angle"
From="70" To="0" Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid x:Name="grid" Background="Coral" Style="{StaticResource gridKey}">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform />
<RotateTransform />
</TransformGroup>
</Grid.RenderTransform>
<TextBlock TextWrapping="Wrap">
烽火绵延,战场泣杀,谁留锋刃惹人嘲。
弑红双眸,血刃乱划,拼得素衣染泪颊。
阴风低吼,暴雨怒下,徒留艳红干不涸。
只盼苟活,还能归家,再不问誓死拼杀。
此生惟愿与你共活,难得执手画青丝沧桑,不管红尘凡事俗争,只此一生为你描眉点唇。
任苍天笑我懦弱,我依旧充耳不顾,为你甘愿卸甲耕种,只盼安稳走完一生。
如若我此去再无音信,愿你重披嫁衣,让他代我此生疼你若宝。
如若我此去再无归期,愿你冲洗记忆,让他重新入你心底。
我只不甘,再不见你如斯容颜。
我只不愿,再见你眼眸带泪颜。
我只不叹,再不见你怀中展颜。
我只不再,再见盼来世重逢颜。
风烟轻擦,我已杳无牵挂,独余你,是我心底最不愿触及的殇。
</TextBlock>
</Grid>
</Grid>
</Window>
效果如下:
2.基于KeyFrame动画
基于KeyFrame动画可以很方便地实现多个分段和不规则移动的动画。虽然前面的线性内插动画也可通过BeginTime构建多个连续动画来实现,但是显得很复杂。基于KeyFrame动画是由多个段构成的,每一段表示动画的开始值和最终值或者中间值。主要有一个×××KeyFrameCollection类型的KeyFrames属性,它是一个×××KeyFrame的集合。×××KeyFrame有一个KeyTime类型的KeyTime属性和一个Object类型的Value属性,后者表示关键帧的目标值,前者是到达关键帧的目标值的时间。拿Double类型来说吧,DoubleAnimationUsingKeyFrames有个DoubleKeyFrameCollection类型的KeyFrames属性,是DoubleKeyFrame
抽象类型的集合,DoubleKeyFrame类型又有这么几个子类:
LinearDoubleKeyFrame:通过使用线性内插(线性关键帧),可以在前一个关键帧的Double值及其自己的Value值之间进行动画处理
DiscreteDoubleKeyFrame:通过使用离散内插(离散关键帧),可以在前一个关键帧的Double值及其自己的Value值之间进行动画处理
SplineDoubleKeyFrame:通过使用样条内插(样条关键帧),有KeySpline类型的KeySpline属性,它表示关键帧进度的三次方贝塞尔曲线
EasingDoubleKeyFrame:通过使用缓动关键帧,将EasingFunction和关键帧动画关联,有IEasingFunction类型的EasingFunction属性
例子如下:
<Window x:Class="AnimationDemo.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300">
<Window.Resources>
<Style TargetType="Grid" x:Key="gridKey">
<Style.Triggers>
<EventTrigger RoutedEvent="Grid.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
AccelerationRatio="1">
<DoubleAnimationUsingKeyFrames.KeyFrames>
<DoubleKeyFrameCollection>
<LinearDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<LinearDoubleKeyFrame KeyTime="0:0:2" Value="1" />
</DoubleKeyFrameCollection>
</DoubleAnimationUsingKeyFrames.KeyFrames>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
AccelerationRatio="1">
<DoubleKeyFrameCollection>
<LinearDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<LinearDoubleKeyFrame KeyTime="0:0:2" Value="1" />
</DoubleKeyFrameCollection>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="RenderTransform.Children[1].Angle"
AccelerationRatio="1">
<DoubleKeyFrameCollection>
<LinearDoubleKeyFrame KeyTime="0:0:0" Value="70" />
<LinearDoubleKeyFrame KeyTime="0:0:2" Value="50" />
<SplineDoubleKeyFrame KeyTime="0:0:4" Value="30">
<SplineDoubleKeyFrame.KeySpline>
<KeySpline ControlPoint1="0.3,0.7" ControlPoint2="0.7,0.3" />
</SplineDoubleKeyFrame.KeySpline>
</SplineDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:6" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<BounceEase />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleKeyFrameCollection>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid x:Name="grid" Background="Coral" Style="{StaticResource gridKey}">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform />
<RotateTransform />
</TransformGroup>
</Grid.RenderTransform>
<TextBlock TextWrapping="Wrap">
烽火绵延,战场泣杀,谁留锋刃惹人嘲。
弑红双眸,血刃乱划,拼得素衣染泪颊。
阴风低吼,暴雨怒下,徒留艳红干不涸。
只盼苟活,还能归家,再不问誓死拼杀。
此生惟愿与你共活,难得执手画青丝沧桑,不管红尘凡事俗争,只此一生为你描眉点唇。
任苍天笑我懦弱,我依旧充耳不顾,为你甘愿卸甲耕种,只盼安稳走完一生。
如若我此去再无音信,愿你重披嫁衣,让他代我此生疼你若宝。
如若我此去再无归期,愿你冲洗记忆,让他重新入你心底。
我只不甘,再不见你如斯容颜。
我只不愿,再见你眼眸带泪颜。
我只不叹,再不见你怀中展颜。
我只不再,再见盼来世重逢颜。
风烟轻擦,我已杳无牵挂,独余你,是我心底最不愿触及的殇。
</TextBlock>
</Grid>
</Grid>
</Window>
3.基于Path的动画
基于Path的动画是一种很灵活的执行动画的方式,它一般被用于沿着一条路径来移动可视对象。它主要有一个PathGeometry类型的PathGeometry属性和PathAnimationSource类型的Source属性,前者表示指定用于生成此动画输出值的几何图形,后者表示指定PathGeometry属性的方位,默认值为PathAnimationSource.X,表示指定在前进过程中沿动画序列路径的 X 坐标偏移量。
<Window x:Class="AnimationDemo.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window3" Height="500" Width="600">
<Window.Resources>
<PathGeometry x:Key="pathKey">
<PathGeometry.Figures>
<PathFigure IsClosed="True">
<PathFigure.Segments>
<LineSegment Point="300,2" IsStroked="True" IsSmoothJoin="True"/>
<LineSegment Point="300,80" IsStroked="True" IsSmoothJoin="True"/>
<LineSegment Point="2,80" IsStroked="True" IsSmoothJoin="True"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
<Style x:Key="ImgKey" TargetType="Image">
<Style.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingPath Storyboard.TargetProperty="(Canvas.Left)"
PathGeometry="{StaticResource pathKey}" Duration="0:0:5"
RepeatBehavior="Forever" Source="X">
</DoubleAnimationUsingPath>
<DoubleAnimationUsingPath Storyboard.TargetProperty="(Canvas.Top)"
PathGeometry="{StaticResource pathKey}" Duration="0:0:5"
RepeatBehavior="Forever" Source="Y">
</DoubleAnimationUsingPath>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Canvas>
<Path Stroke="Red" StrokeThickness="1" Data="{StaticResource pathKey}" Canvas.Left="10" Canvas.Top="10" />
<Image x:Name="img" Canvas.Left="15" Canvas.Top="15" Style="{StaticResource ImgKey}">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="LightSteelBlue">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="1" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<EllipseGeometry Center="10,10" RadiusX="3" RadiusY="3" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Canvas>
</Window>
4.动画在XAML中的载体--Storyboard
正如开始所说,大部分基于属性的动画都是可以直接在XAML中表达的,但是它需要一个载体,或者叫一个容器,它就是Storyboard,它是动画和希望应用动画的属性的桥梁。Storyboard经常被BeginStoryboard包装作为EventTrigger的TriggerAction,这里BeginStoryboard是一个密封类,而不是FrameworkElement里面的那个方法,它表示一个触发器操作,该操作可启动Storyboard 并将其动画分发给动画的目标对象和属性。Storyboard继承自ParallelTimeline,意味着它可以并行运行多个子时间线,而且它具有控制动画播放的能力,例如Pause、Resume、Skip和Stop等。它用TargetName附加属性表示动画的作用者,用TargetProperty附加属性来指定TargetName的希望改变的属性。
<Window x:Class="AnimationDemo.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window4" Height="300" Width="300">
<Window.Resources>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard HandoffBehavior="Compose">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="FontSize" BeginTime="0:0:0.5" Duration="0:0:0.2" To="18"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard HandoffBehavior="Compose">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="FontSize" BeginTime="0:0:0.5" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ListBox>
<ListBoxItem>Jello</ListBoxItem>
<ListBoxItem>Taffy</ListBoxItem>
<ListBoxItem>Jim</ListBoxItem>
<ListBoxItem>Lily</ListBoxItem>
</ListBox>
</Grid>
</Window>
5.基于Frame的动画
前面介绍的都是基于属性的动画,还有一种比较的动画,既是基于帧的动画,只需要订阅CompositionTarget的静态Rendering事件即可,它在呈现组合树中的对象之前发生,从而为每帧获取内容。
Xaml代码:
<Window x:Class="AnimationDemo.Window7"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window7" Height="300" Width="300">
<Grid Margin="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions> <StackPanel Orientation="Horizontal">
<Button Margin="3" Padding="3" Click="cmdStart_Clicked">Start</Button>
<Button Margin="3" Padding="3" Click="cmdStop_Clicked">Stop</Button>
</StackPanel>
<Canvas Name="canvas" Grid.Row="1" Margin="3"></Canvas>
</Grid>
</Window>
cs代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes; namespace AnimationDemo
{
/// <summary>
/// Window7.xaml 的交互逻辑
/// </summary>
public partial class Window7 : Window
{
public Window7()
{
InitializeComponent();
}
private bool rendering = false;
private void cmdStart_Clicked(object sender, RoutedEventArgs e)
{
if (!rendering)
{
ellipses.Clear();
canvas.Children.Clear(); CompositionTarget.Rendering += RenderFrame;
rendering = true;
}
}
private void cmdStop_Clicked(object sender, RoutedEventArgs e)
{
StopRendering();
} private void StopRendering()
{
CompositionTarget.Rendering -= RenderFrame;
rendering = false;
} private List<EllipseInfo> ellipses = new List<EllipseInfo>(); private double accelerationY = 0.1;
private int minStartingSpeed = ;
private int maxStartingSpeed = ;
private double speedRatio = 0.1;
private int minEllipses = ;
private int maxEllipses = ;
private int ellipseRadius = ; private void RenderFrame(object sender, EventArgs e)
{
if (ellipses.Count == )
{
// Animation just started. Create the ellipses.
int halfCanvasWidth = (int)canvas.ActualWidth / ; Random rand = new Random();
int ellipseCount = rand.Next(minEllipses, maxEllipses + );
for (int i = ; i < ellipseCount; i++)
{
Ellipse ellipse = new Ellipse();
ellipse.Fill = Brushes.LimeGreen;
ellipse.Width = ellipseRadius;
ellipse.Height = ellipseRadius;
Canvas.SetLeft(ellipse, halfCanvasWidth + rand.Next(-halfCanvasWidth, halfCanvasWidth));
Canvas.SetTop(ellipse, );
canvas.Children.Add(ellipse); EllipseInfo info = new EllipseInfo(ellipse, speedRatio * rand.Next(minStartingSpeed, maxStartingSpeed));
ellipses.Add(info);
}
}
else
{
for (int i = ellipses.Count - ; i >= ; i--)
{
EllipseInfo info = ellipses[i];
double top = Canvas.GetTop(info.Ellipse);
Canvas.SetTop(info.Ellipse, top + * info.VelocityY); if (top >= (canvas.ActualHeight - ellipseRadius * - ))
{
// This circle has reached the bottom.
// Stop animating it.
ellipses.Remove(info);
}
else
{
// Increase the velocity.
info.VelocityY += accelerationY;
} if (ellipses.Count == )
{
// End the animation.
// There's no reason to keep calling this method
// if it has no work to do.
StopRendering();
}
}
}
}
}
public class EllipseInfo
{
public Ellipse Ellipse
{
get;
set;
} public double VelocityY
{
get;
set;
} public EllipseInfo(Ellipse ellipse, double velocityY)
{
VelocityY = velocityY;
Ellipse = ellipse;
}
}
}
WPF学习(12)动画的更多相关文章
- WPF学习12:基于MVVM Light 制作图形编辑工具(3)
本文是WPF学习11:基于MVVM Light 制作图形编辑工具(2)的后续 这一次的目标是完成 两个任务. 本节完成后的效果: 本文分为三个部分: 1.对之前代码不合理的地方重新设计. 2.图形可选 ...
- 【WPF学习】第五十三章 动画类型回顾
创建动画面临的第一个挑战是为动画选择正确的属性.期望的结果(例如,在窗口中移动元素)与需要使用的属性(在这种情况下是Canvas.Left和Canvas.Top属性)之间的关系并不总是很直观.下面是一 ...
- 【WPF学习】第五十四章 关键帧动画
到目前为止,看到的所有动画都使用线性插值从起点到终点.但如果需要创建具有多个分段的动画和不规则移动的动画.例如,可能希望创建一个动画,快速地将一个元素滑入到视图中,然后慢慢地将它移到正确位置.可通过创 ...
- WPF学习之绘图和动画
如今的软件市场,竞争已经进入白热化阶段,功能强.运算快.界面友好.Bug少.价格低都已经成为了必备条件.这还不算完,随着计算机的多媒体功能越来越强,软件的界面是否色彩亮丽.是否能通过动画.3D等效果是 ...
- WPF学习之绘图和动画--DarrenF
Blend作为专门的设计工具让WPF如虎添翼,即能够帮助不了解编程的设计师快速上手,又能够帮助资深开发者快速建立图形或者动画的原型. 1.1 WPF绘图 与传统的.net开发使用GDI+进行绘图不 ...
- 【WPF学习笔记】[转]周银辉之WPF中的动画 && 晓风影天之wpf动画——new PropertyPath属性链
(一)WPF中的动画 动画无疑是WPF中最吸引人的特色之一,其可以像Flash一样平滑地播放并与程序逻辑进行很好的交互.这里我们讨论一下故事板. 在WPF中我们采用Storyboard(故事板)的方式 ...
- 【WPF学习笔记】之如何把数据库里的值读取出来然后显示在页面上:动画系列之(六)(评论处有学习资料及源码)
(应博友们的需要,在文章评论处有源码链接地址,以及WPF学习资料.工具等,希望对大家有所帮助) ...... 承接系列五 上一节讲了,已经把数据保存到数据库并且删除数据,本讲是把已经存在的数据从数据库 ...
- 【WPF学习】第五十七章 使用代码创建故事板
在“[WPF学习]第五十章 故事板”中讨论了如何使用代码创建简单动画,以及如何使用XAML标记构建更复杂的故事板——具有多个动画以及播放控制功能.但有时采用更复杂的故事板例程,并在代码中实现全部复杂功 ...
- WPF学习之路初识
WPF学习之路初识 WPF 介绍 .NET Framework 4 .NET Framework 3.5 .NET Framework 3.0 Windows Presentation Found ...
随机推荐
- js创建下载文件
function downloadFile(fileName, content){ var aLink = document.createElement('a'); var blob = new Bl ...
- android app崩溃日志收集以及上传
源代码获取请到github:https://github.com/DrJia/AndroidLogCollector 已经做成sdk的形式,源代码已公开,源代码看不懂的请自行google. 假设想定制 ...
- codeforces293E (树上点分治+树状数组)
和poj1747相比起来,只不过是限制条件多了一维. 而多了这一维,所以需要用树状数组来维护,从而快速得到答案. 因为没注意传进树状数组函数的参数可能是<=0的,导致超时了好久. #pragma ...
- ECSHOP如何增加红包序列号字符
ECSHOP系统线下发放红包时系统生成的红包序列号是在10000的基础上增加四位随机数字.如果当我们要发放大额度红包的时候,这样的序列号规 则难免给人不安全的感觉,万一有无聊的人,蒙几个红包序列号出来 ...
- OpenGL3D迷宫场景设计
近期学习用opengl库来构建一个3D场景,以及实现场景漫游.粒子系统等效果.终于算是是做了一个3D走迷宫游戏吧. 感觉近期学了好多东西,所以有必要整理整理. 一 实现效果 watermark/2/t ...
- android升级软件版本号,您安装后的新版本号,成功安装画面没有出现,或直接回到桌面
Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //an ...
- BI事实上的和维表定义
一个典型的例子是,逻辑业务相比立方体,产品尺寸.时间维度.位置尺寸,分别作为不同的轴.轴的交点是一个详细的事实.这一事实表是多维度的交叉点的一个表.维表是事实的分析的一种形式. 首先介绍下数据库结构中 ...
- SQL注入问题
斌斌 (给我写信) 原创博文(http://blog.csdn.net/binbinxyz),转载请注明出处! 背景:对于ibaits参数引用可以使用#和$两种写法,其中#写法会采用预编译方式,将转义 ...
- Hadoop认知--在不同的阶段
入门阶段 出于兴趣,及工作中的简单有用,大约经过1个月的时间,完毕了对Hadoop的基本认知. 在这个月中我干了例如以下几件事 1.大体看了<Hadoop权威指南>.把里面的代码手工码了一 ...
- iOS开发 编辑框被系统弹出的软键盘遮挡问题
我们在开发注冊界面的时候,最后几个注冊条件经常easy被系统弹出的键盘遮挡,例如以下图: 能够看见,邮箱条件被遮挡掉了,怎么解决呢?我是通过UITextField的代理加计算偏移量: - (void) ...