原文:《Programming WPF》翻译 第8章 3.Storyboard

Storyboard是动画的集合。如果你使用了标记,所有的动画必须要被定义在一个Storyboard中。(在代码中创建隔离的动画对象,这是可能的,参见本章后面部分。)一个动画的结构通常是不同于设置了动画的UI的结构上。例如,你可能想要来两个单独的用户界面元素在同一时间被设置动画。因为Storyboard将动画从有动画效果的对象中隔离出来,Storyboard是自由地反射这样的连接,即使这些元素被设置了对象,可能被定义在完全不同的文件部分中。

示例8-15显示了包含了两个椭圆的用户界面的标记。

示例8-15

<Window Text="Two Animations" Width="420" Height="150"

    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"

    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">



    <Window.Storyboards>

        <ParallelTimeline>

            <SetterTimeline TargetName="myEllipse" Path="(Ellipse.Width)">

                <DoubleAnimation From="10" To="300" Duration="0:0:5"

                                 RepeatBehavior="Forever" />

            </SetterTimeline>

            <SetterTimeline TargetName="myOtherEllipse" Path="(Ellipse.Width)">

                <DoubleAnimation From="300" To="10" Duration="0:0:5"

                                 RepeatBehavior="Forever" />

            </SetterTimeline>

        </ParallelTimeline>

    </Window.Storyboards>



    <StackPanel Orientation="Horizontal">

        <Ellipse x:Name="myEllipse" Height="100" Fill="Red" />

        <TextBlock>This is some text</TextBlock>

        <Ellipse x:Name="myOtherEllipse" Height="100" Fill="Blue" />

    </StackPanel>

</Window>

这些椭圆是互不邻近的,但是它们的宽度都以异步的方式设置了动画。这种异步被反射在Storyboard的结构中:这两个动画都是内嵌在同样的ParallelTimeline元素中,指出了这些动画都在相同的时间运行。一个从10到300的动画,另一个是从300到10,因此StackPanel中这三项的总宽度保持为恒定的。

Storyboard必须运行以三种位置中的一种。它们可以放置在Style中、ContentTemplate中、或者顶级元素中。顶级元素是Window和Page,或者派生于此的类。

所有的用户界面元素都有一个Storyboard属性,继承于基类型FrameworkElement。你可能想这意味着你可以添加一个Storyboard到任意一个元素。这于当前是不被支持的。Storyboard只会工作于:Style中、ContentTemplate中、或者顶级元素中。

把动画放入样式的Storyboard中的能力,使你通过样式系统应用动画。这可是很有用的——如果你想使用同样的动画在很多地方。通过把动画放入样式,而不是一个Window或Page,你要避免复制和排除次要矛盾的可能性。示例8-16显示了带有Storyboard的一个样式。

示例8-16

<Window Text="StyleAnimations" Width="220" Height="200"

    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"

    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">



    <Window.Resources>

        <Style TargetType="{x:Type Button}">

            <Setter Property="Height" Value="25" />

            <Setter Property="Background" Value="Green" />



            <Style.Storyboards>

                <SetterTimeline

                        Path="(Button.Background).(SolidColorBrush.Color)">

                    <ColorAnimation To="LimeGreen" Duration="0:0:0.3"

                        AutoReverse="True" RepeatBehavior="Forever" />

                </SetterTimeline>

                <ParallelTimeline RepeatBehavior="Forever" Duration="0:0:2">

                    <SetterTimeline Path="(Button.Width)">

                        <DoubleAnimation From="80" To="90" Duration="0:0:0.1"

                                         AutoReverse="True" />

                    </SetterTimeline>

                    <SetterTimeline Path="(Button.Height)"

                                    BeginTime="0:0:0.4">

                        <DoubleAnimation By="30" Duration="0:0:0.5"

                                         AutoReverse="True"/>

                    </SetterTimeline>

                </ParallelTimeline>

            </Style.Storyboards>

        </Style>

    </Window.Resources>



    <StackPanel Orientation="Vertical">

        <Button HorizontalAlignment="Center">Click me!</Button>

        <Button HorizontalAlignment="Center">No, click me!</Button>

    </StackPanel>



</Window>

这些动画并没有做什么显著不寻常的事情。它们只是改变了一对按钮的大小和颜色,正如图8-8所示。可是,注意到SetterTimeline元素并没有详细指出TargetName。这是因为使用一个样式Storyboard,这里有一个隐式的目标:该样式应用到的元素(这种情形下是一个Button)。同样,如图8-8所示,因为这是一个样式,它定义的动画应用到所有的按钮。

图8-8



如果你为控件定义了一个模板,它可能包含了——没有直接响应到元素可你又想设置动画的属性——的样式,例如,图8-9显示了两对按钮。在顶行,这些按钮显示为自定义的可视化——带有圆形的静态外观。底部按钮是相似的,但是一个放射性的填充被添加到按钮中,并含有一个内部的光源。我们可能想为这个光源设置动画,使这个按钮逐渐地跳动。

图8-9



这个按钮的类型并没有提供我们用来表示光源颜色的属性,因此为了给这个光源设置动画,这个动画需要为控件模板中详细指定的元素设置目标。在这种情形中,我们可以把动画放入模板的Storyboard中,而不是样式的Storyboard中。如果你设置了x:Name属性在模板中相应的元素上,你可以接着在动画中用TargetName引用它。示例8-17显示了图8-9的标记。

示例8-17

<Window xmlns="http://schemas.microsoft.com/winfx/avalon/2005"

    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"

    Text="Style VisualTree Animation" Width="400" Height="150"

    >



    <Window.Resources>

        <Style TargetType="{x:Type Button}">

            <Setter Property="Background" Value="CornflowerBlue" />

            <Setter Property="Height" Value="26" />

            <Setter Property="Template">

                <Setter.Value>



                    <ControlTemplate TargetType="{x:Type Button}">

                        <Grid Height="{TemplateBinding Height}">

                            <RowDefinition Height="1.8*" />

                            <RowDefinition Height="*" />



                            <Rectangle Grid.RowSpan="2" RadiusX="13" RadiusY="13"

                                       Fill="{TemplateBinding Background}"

                                       Stroke="VerticalGradient Black LightGray" />





                            <!-- Glow -->



                            <Rectangle Grid.RowSpan="2" RadiusX="13" RadiusY="13"

                                       x:Name="glow">

                                <Rectangle.Fill>

                                    <RadialGradientBrush Center="0.5, 1"

                                             RadiusX="0.7" RadiusY="0.8">

                                        <RadialGradientBrush.GradientStops>

                                            <GradientStop Offset="0"

                                                          Color="White" />

                                            <GradientStop Offset="1"

                                                          Color="Transparent" />

                                        </RadialGradientBrush.GradientStops>

                                    </RadialGradientBrush>

                                </Rectangle.Fill>

                            </Rectangle>





                            <Rectangle Margin="3,1.1" RadiusX="11" RadiusY="12"

                                       Fill="VerticalGradient #dfff #0fff" />

                            <ContentPresenter Grid.RowSpan="3" Margin="13,2,13,4"

                                              HorizontalAlignment="Center"

                                              VerticalAlignment="Center" />



                        </Grid>



                        <ControlTemplate.Storyboards>

                            <SetterTimeline TargetName="glow"

Path="(Rectangle.Fill).(LinearGradientBrush.GradientStops)[0].(GradientStop.Color)">



                                <ColorAnimation From="#1fff" To="#cfff"

                                    Duration="0:0:1"

                                    AutoReverse="True" RepeatBehavior="Forever"

                                    AccelerationRatio="0.4"

                                    DecelerationRatio="0.6"/>

                            </SetterTimeline>

                        </ControlTemplate.Storyboards>

                    </ControlTemplate>

                </Setter.Value>

            </Setter>



        </Style>

    </Window.Resources>



    <StackPanel VerticalAlignment="Center"

                Orientation="Horizontal">

        <Button Margin="4,0">Click me!</Button>

        <Button Background="DarkRed" Foreground="White">And me!</Button>

    </StackPanel>



</Window>

大多数模板是静态的,但是光源被设置了动画。注意到x:Name属性带有一个glow值在相关的形状上。这个动画在模板的Storyboard中,正如你希望的,它将包括一个单独的SetterTimeline,带有一个被设置为glow的TargetName。这个Path有点复杂,简单的因为我们为一个特定的带有笔刷的GradientStop设定动画。记住轻量级对象——如笔刷或者梯度停止,不能被直接设置动画。替代的,我们不得不生成相应的完整的UI元素。动画设置目标同时使用Path属性来向下导航到我们希望改动的属性。

这个特定的Path引进了一个我们之前没有看到过的新样式:[0]。这个[index]语法用于指出一个项在集合的一个特定的偏移位置。

正如我们在第五章看到的,样式和模板都可以定义触发器以允许属性被设置为自动依照某一个刺激。例如,你可以触发任何Storyboard中的动画——当一个特定的事件发生时。

示例8-18为一个按钮显示了一个样式,带有一个简单的模板——仅绘制了一个矩形在按钮内容的周围。这个模板的Storyboard包含了两个动画。第一个退变颜色到PeachPuff,然会再返回来,另一个则在矩形轮廓的厚度上振荡。注意到,这两个按钮都有一个设置为{x:Null}的BeginTime。这防止了在应用程序开始的时候这些按钮会自动运行动画。

示例8-18

<Style TargetType="{x:Type Button}">

    <Setter Property="Template">

        <Setter.Value>

            <ControlTemplate TargetType="{x:Type Button}">

                <Grid>

                    <Rectangle x:Name="mainRect"  Fill="Aqua" Stroke="Blue" />

                    <ContentPresenter

                        HorizontalAlignment=

                                   "{TemplateBinding HorizontalContentAlignment}"

                        VerticalAlignment=

                                   "{TemplateBinding VerticalContentAlignment}" />

                </Grid>



                <ControlTemplate.Storyboards>



                    <!-- Click animation -->



                    <SetterTimeline x:Name="clickTimeline" BeginTime="{x:Null}"

                                 TargetName="mainRect"

                                 Path="(Rectangle.Fill).(SolidColorBrush.Color)">

                        <ColorAnimation To="PeachPuff" Duration="0:0:0.2"

                                        AutoReverse="True" />

                    </SetterTimeline>





                    <!-- Mouse over animation -->



                    <SetterTimeline x:Name="enterTimeline" BeginTime="{x:Null}"

                                    TargetName="mainRect" Duration="1"

                                    Path="(Rectangle.StrokeThickness)" >

                        <DoubleAnimation To="3" Duration="0:0:0.2"

                                         AutoReverse="True"

                                         RepeatBehavior="Forever" />

                    </SetterTimeline>



                </ControlTemplate.Storyboards>



                <ControlTemplate.Triggers>

                    <EventTrigger RoutedEvent="ButtonBase.Click">

                        <EventTrigger.Actions>

                            <BeginAction TargetName="clickTimeline" />

                        </EventTrigger.Actions>

                    </EventTrigger>



                    <EventTrigger RoutedEvent="Mouse.MouseEnter">

                        <EventTrigger.Actions>

                            <BeginAction TargetName="enterTimeline" />

                        </EventTrigger.Actions>

                    </EventTrigger>



                    <EventTrigger RoutedEvent="Mouse.MouseLeave">

                        <EventTrigger.Actions>

                            <StopAction TargetName="enterTimeline" />

                        </EventTrigger.Actions>

                    </EventTrigger>

                </ControlTemplate.Triggers>



            </ControlTemplate>

        </Setter.Value>

    </Setter>

</Style>

这个动画被模板中的triggers部分中的Eventtrigger元素全部触发。首先会响应到这些按钮的Click事件。(这个事件由按钮的基本类定义,这里是ButtonBase.Click。)无论何时点击这个按钮。这将引起clickTimeline动画的运行,使按钮退变颜色到PeachPuff然后再回到被点击时的样子。

另一个动画有两个Eventtrigger元素,一个用于当鼠标进入控件的时候,另一个用于当鼠标离开控件的时候。这是因为线条宽度的动画会永远重复,如果这只有一个触发器,开始动画在鼠标进入控件的时候,这个动画将会开始而不会停止,由于这是一个顶级的timeline。因此我们需要1秒钟的Eventtrigger,响应到MouseLeave事件——使用StopAction来停止这个动画。

这个示例的鼠标事件命名为Mouse.MouseEnter和Mouse.MouseLeave。这是有一点不寻常的,当事件被命名为定义它们的元素。这些事件是继承于UIElement基类,因此你可以希望它们被命名为UIElement.MouseEnter和UIElement.MouseLeave。可是,这些由UIElement提供的事件只是底层的附属事件外面的包装。底层的事件由System.Windows.Input命名空间的Mouse类定义,这是为什么事件名以Mouse.UIElement开始,简单而便利的包装了这些附属事件作为标准.NET事件。

每个Eventtrigger可以有你想要的任何事件,因此你可以一次性开始或废弃一些动画。

这通常是非常便利的使WPF自动开始和停止你的动画——在事件发生时。这就意味着你不需要写任何代码。可是,这并不总是一个适当的事件来用作触发器,因此在程序上开始一个动画,有时是很有用的。

8.3.1 使用代码加载动画

为了从代码上开始一个动画,理解timeline和时钟之间的不同是必要的。正如我们已经看到的,一个timeline层次是在一段延伸的时间内发生的一个或更多事情的描述——但这只是一个描述。timeline层次的存在,带有一个SetterTimeline和一个DoubleAnimation,并不是足够引起动画的发生。表现这个动画的工作由一个或多个时钟完成。

时钟是一个在运行期创建的对象,对timeline中的当前位置保持跟踪,执行timeline定义的无论什么动作。如果你回想到timeline图表,如图8-7,时钟就是知道我们在时间的那个位置的图表上端。

timeline和时钟的关系并不像代码和多线程的关系。可执行代码定义了要表现什么操作,但是一个线程被要求执行代码。同样的,一个timeline描述了在一段特定的时间长度发生了什么,但是一个时钟被要求运行这个timeline。

WPF自动创建时钟。在创建一个用户界面的时候,它顺便访问相应的Stroyboard属性和为每个timeline创建时钟。如果一个带有storyboard的样式或模板被使用在多个地方,每个实例都有它自己的一组时钟——支持动画独立地运行。这是无妨的,否则,如果你有一个动画在鼠标进入按钮的时候运行,它会为屏幕上所有的按钮同时运行,这将不会是十分有用的。

通常,顶级timeline的时钟是自动开始的,基于他们的BeginTime。可是,如果你详细指定BeginTime为{x:Null},这个时钟将不会开始,因此这个动画不会运行。我们在前面的章节看到,如何使用触发器来加载动画。触发器中的BeginAction只是告诉WPF在触发器发生时,开始相应的timeline的时钟。你也可以编写代码来开始动画。

为了亲自开始一个动画,我们需要得到它的时钟。这些代码需要找到timeline的时钟,看上去有点不同的,取决于你是否处理timeline在一个Style、一个模板、还是一个顶级元素中。

示例8-19

<Window x:Class="StartAnimationWithCode.Window1"

    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"

    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"

    Text="Code" Width="150" Height="100">



    <Window.Storyboards>

        <SetterTimeline BeginTime="{x:Null}" TargetName="myButton"

                x:Name="clickTimeline"

                Path="(Button.Background).(SolidColorBrush.Color)">

            <ColorAnimation To="Red" Duration="0:0:0.2" AutoReverse="True" />

        </SetterTimeline>

    </Window.Storyboards>



    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">

        <Button x:Name="myButton" Background="Aqua">Foo</Button>

    </StackPanel>

</Window>

这个动画将按钮的颜色改变为红色,而且还要返回。由于这个动画的BeginTime被设置为{x:Null},并且没有任何自动的触发器,我们需要写一些代码来运行它们。我们将这么做——通过添加一个Click句柄到示例8-19中的按钮。示例8-20显示了包含这个Click句柄的代码。

示例8-20

using System;

using System.Windows;

using System.Windows.Media.Animation;



namespace StartAnimationWithCode

{

    public partial class Window1 : Window

    {

        public Window1( ) : base( )

        {

            InitializeComponent( );



            myButton.Click += ButtonClick;

        }



        private void ButtonClick(object sender, RoutedEventArgs e)

        {

            Clock clock;

            Timeline clickTimeline = FindName("clickTimeline") as Timeline;

            clock = this.FindStoryboardClock(clickTimeline);



            clock.ClockController.Begin( );

        }

    }

}

这个句柄获取了动画的timeline,接着获取它的Clock。它使用了时钟的控制器来运行这个动画。图8-10显示了这个运行中的动画。

图8-10



如果这个动画存在于一个样式中,这段代码工作得有点不同。示例8-21显示了带有动画的一个样式。(这个动画有和前一个示例同样的效果;它只是以一种不同的方式被应用。)

示例8-21

<Window x:Class="StartAnimationWithCode.StyleAnimationFromCode"

    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"

    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"

    Text="Style" Width="150" Height="100">



    <Window.Resources>

        <Style TargetType="{x:Type Button}">

            <Style.Storyboards>

                <SetterTimeline BeginTime="{x:Null}" x:Name="clickTimeline"

                                Path="(Button.Background).(SolidColorBrush.Color)">

                    <ColorAnimation To="Red" Duration="0:0:0.2"

                                    AutoReverse="True" />

                </SetterTimeline>

            </Style.Storyboards>

        </Style>

    </Window.Resources>



    <Button x:Name="myButton" Background="Aqua"

            HorizontalAlignment="Center" VerticalAlignment="Center">

        Foo

    </Button>

</Window>

这个Click句柄必须要修改,因为动画现在定义在样式中。示例8-22显示了新的句柄。

示例8-22

private void ButtonClick(object sender, RoutedEventArgs e)

{

    Clock clock;

    clock = Style.FindStoryboardClock(myButton, "clickTimeline");



    clock.ClockController.Begin( );

}

当然,如果样式定义了一个模板,我们可能希望直接为模板的一部分定义动画。在这种情形中,动画会存在于模板的Stroyboard中,而不是样式的Stroyboard中。示例8-23显示了包含了一个带Stroyboard的模板的样式。

示例8-23

<Style TargetType="{x:Type Button}">

    <Setter Property="Template">

        <Setter.Value>

            <ControlTemplate TargetType="{x:Type Button}">

                <Grid>

                    <Rectangle x:Name="mainRect"  Fill="Aqua" Stroke="Blue" />

                    <ContentPresenter HorizontalAlignment="{TemplateBinding

                                           HorizontalContentAlignment}"

                                      VerticalAlignment="{TemplateBinding

                                           VerticalContentAlignment}" />

                </Grid>



                <ControlTemplate.Storyboards>

                    <SetterTimeline BeginTime="{x:Null}" TargetName="mainRect"

                              x:Name="clickTimeline"

                              Path="(Rectangle.Fill).(SolidColorBrush.Color)">

                        <ColorAnimation To="Red" Duration="0:0:0.2"

                                        AutoReverse="True" />

                    </SetterTimeline>

                </ControlTemplate.Storyboards>

            </ControlTemplate>

        </Setter.Value>

    </Setter>

</Style>

虽然事实上,动画现在嵌入到样式模板中,而不是样式中,我们仍可以和之前同样的方式加载这个动画——使用示例8-22所示的代码。图8-11显示了结果。

图8-11



在所有的这些示例中,我们使用了时钟的ClickController。这就为控制操作提供了可编程的接口,如开始、暂停、停止、倒带等等。这里我们使用了Begin——立即开始了动画。注意到我们只需要为顶级timeline开始这个时钟,子时钟将会被开始于恰当的时间,作为运行父一级的结果。

《Programming WPF》翻译 第8章 3.Storyboard的更多相关文章

  1. 《Programming WPF》翻译 第8章 5.创建动画过程

    原文:<Programming WPF>翻译 第8章 5.创建动画过程 所有在这章使用xaml举例说明的技术,都可以在代码中使用,正如你希望的.可是,代码可以使用动画在某种程度上不可能在x ...

  2. 《Programming WPF》翻译 第8章 2.Timeline

    原文:<Programming WPF>翻译 第8章 2.Timeline Timeline代表了时间的延伸.它通常还描述了一个或多个在这段时间所发生的事情.例如,在前面章节描述的动画类型 ...

  3. 《Programming WPF》翻译 第8章 1.动画基础

    原文:<Programming WPF>翻译 第8章 1.动画基础 动画包括在一段时间内改变用户界面的某些可见的特征,如它的大小.位置或颜色.你可以做到这一点,非常困难的通过创建一个tim ...

  4. 《Programming WPF》翻译 第5章 7.控件模板

    原文:<Programming WPF>翻译 第5章 7.控件模板 如果仔细的看我们当前的TTT游戏,会发现Button对象并没有完全为我们工作.哪些TTT面板有内圆角? 图5-14 这里 ...

  5. 《Programming WPF》翻译 第5章 6.触发器

    原文:<Programming WPF>翻译 第5章 6.触发器 目前为止,我们已经看到样式,作为一个Setter元素的集合.当应用一个样式时,在Setter元素中描述的设置不会无条件地应 ...

  6. 《Programming WPF》翻译 第9章 5.默认可视化

    原文:<Programming WPF>翻译 第9章 5.默认可视化 虽然为控件提供一个自定义外观的能力是有用的,开发者应该能够使用一个控件而不用必须提供自定义可视化.这个控件应该正好工作 ...

  7. 《Programming WPF》翻译 第9章 6.我们进行到哪里了?

    原文:<Programming WPF>翻译 第9章 6.我们进行到哪里了? 只有当任何内嵌控件都没有提供你需要的底层行为时,你将要写一个自定义控件.当你写一个自定义控件,你将要使用到依赖 ...

  8. 《Programming WPF》翻译 第9章 4.模板

    原文:<Programming WPF>翻译 第9章 4.模板 对一个自定义元素最后的设计考虑是,它是如何连接其可视化的.如果一个元素直接从FrameworkElement中派生,这将会适 ...

  9. 《Programming WPF》翻译 第9章 3.自定义功能

    原文:<Programming WPF>翻译 第9章 3.自定义功能 一旦你挑选好一个基类,你将要为你的控件设计一个API.大部分WPF元素提供属性暴露了多数功能,事件,命令,因为他们从框 ...

随机推荐

  1. Linux企业级项目实践之网络爬虫(2)——网络爬虫的结构与工作流程

    网络爬虫是捜索引擎抓取系统的重要组成部分.爬虫的主要目的是将互联网上的网页下载到本地形成一个或联网内容的镜像备份. 一个通用的网络爬虫的框架如图所示:

  2. 【转】精简深拷贝ArrayList实例

    原文网址:http://gghhgame51333.blog.51cto.com/138362/289383 精简深拷贝ArrayList实例(包括递归和序列化方法) 2007-07-12 16:50 ...

  3. 专注于HTTP的高性能高易用性网络库:Fslib.network库

    博客列表页:http://blog.fishlee.net/tag/fslib-network/ 原创FSLib.Network库(目前专注于HTTP的高性能高易用性网络库) FSLib.Networ ...

  4. poj1220:高精度进制转换模板题

    今天撸3708  一直奇怪的re 就先放下了,写这个题的过程中学习了一个高精度进制转换,用这个模板写了1220 记录一下: #include <iostream> #include < ...

  5. lucene3.6.0 经典案例 入门教程

    第一步:下载并导入lucene的核心包(注意版本问题):  例如Lucene3.6版本:将lucene-core-3.6.0.jar拷贝到项目的libs 文件夹里.  例如Lucene4.6版本:将l ...

  6. RequireJS入门(二)

    上一篇是把整个jQuery库作为一个模块.这篇来写一个自己的模块:选择器. 为演示方便这里仅实现常用的三种选择器id,className,attribute.RequireJS使用define来定义模 ...

  7. 解决方案--java执行cmd命令ProcessBuilder--出错Exception in thread "main" java.io.IOException: Cannot run program "dir d:\": CreateProcess error=2(xjl456852原创)

    当我尝试在java中通过ProcessBuilder运行window的cmd命令时出现错误: public static void main(String [] args) throws IOExce ...

  8. [置顶] ProDinner体验

    最近研究了MVC的经典案例ProDinner. 下载地址是:http://prodinner.codeplex.com/ 部署完毕后,看看效果怎么样: Meals的多选功能非常不错: Meal界面格外 ...

  9. [Regex Expression] Use Shorthand to Find Common Sets of Characters

    In this lesson we'll learn shorthands for common character classes as well as their negated forms. v ...

  10. Android动态加载jar/dex

    前言 在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优 ...