草庐IT

iOS 动画方法内容整理

selice 2023-03-28 原文
Core Animation继承关系图.png

动画在UI交互中是一种增强用户体验的利器,目前看到几乎每一个移动App都会使用到各种动画效果。
在IOS开发中实现动画效果通常有三种方式。

  • 1、基于UIView,为了方便实现简单的动画封装的UIView Animation。
  • 2、基于CALayer的Core Animation框架,这是动画的基础框架。
  • 3、在游戏开发中经常用到的基于物理模拟的动画框架UIKit Dynamics。【未涉及,暂不讲解】
先放置了一个View测试
#define Size(x)  ((x)*[[UIScreen mainScreen] bounds].size.width/375.f)
#define kScreenHeight  [[UIScreen mainScreen] bounds].size.height
#define kScreenWidth  [[UIScreen mainScreen] bounds].size.width
@property (nonatomic, strong) UIView *greenView;

-(void)initCusView{
     self.greenView                  = [[UIView alloc]initWithFrame:CGRectMake(0, Size(0), Size(100), Size(100))];
     self.greenView.backgroundColor  = UIColor.greenColor;
     [self.view addSubview:self.greenView];
}
UIView Animation
UIView Animation 参数说明:
duration : 动画经历时长
delay : 延迟时间,在该延迟时间后才执行动画
options : 系统提供了许多动画执行的方式,比如以下几个
enum {
       //这部分是基础属性的设置
       UIViewAnimationOptionLayoutSubviews            = 1 <<  0,//设置 子视图随父视图展示动画
       UIViewAnimationOptionAllowUserInteraction      = 1 <<  1,//允许在动画执行时用户与其进行交互
       UIViewAnimationOptionBeginFromCurrentState     = 1 <<  2,//允许在动画执行时执行新的动画
       UIViewAnimationOptionRepeat                    = 1 <<  3,//设置动画循环执行
       UIViewAnimationOptionAutoreverse               = 1 <<  4,//设置动画反向执行,必须和重复执行一起使用
       UIViewAnimationOptionOverrideInheritedDuration = 1 <<  5,//强制动画使用内层动画的时间值
       UIViewAnimationOptionOverrideInheritedCurve    = 1 <<  6,//强制动画使用内层动画曲线值
       UIViewAnimationOptionAllowAnimatedContent      = 1 <<  7,//设置动画视图实时刷新
       UIViewAnimationOptionShowHideTransitionViews   = 1 <<  8,//设置视图切换时隐藏,而不是移除
       UIViewAnimationOptionOverrideInheritedOptions  = 1 <<  9,//
       //这部分属性设置动画播放的线性效果
       UIViewAnimationOptionCurveEaseInOut            = 0 << 16,//淡入淡出 首末减速
       UIViewAnimationOptionCurveEaseIn               = 1 << 16,//淡入 初始减速
       UIViewAnimationOptionCurveEaseOut              = 2 << 16,//淡出 末尾减速
       UIViewAnimationOptionCurveLinear               = 3 << 16,//线性 匀速执行   
       //这部分设置UIView切换效果(转场动画使用)
       UIViewAnimationOptionTransitionNone            = 0 << 20,
       UIViewAnimationOptionTransitionFlipFromLeft    = 1 << 20,//从左边切入
       UIViewAnimationOptionTransitionFlipFromRight   = 2 << 20,//从右边切入
       UIViewAnimationOptionTransitionCurlUp          = 3 << 20,//从上面立体进入
       UIViewAnimationOptionTransitionCurlDown        = 4 << 20,//从下面立体进入
       UIViewAnimationOptionTransitionCrossDissolve   = 5 << 20,//溶解效果
       UIViewAnimationOptionTransitionFlipFromTop     = 6 << 20,//从上面切入
       UIViewAnimationOptionTransitionFlipFromBottom  = 7 << 20,//从下面切入
  };
animation : UIView动画结束时的状态 ( 比如 : UIView移动到另一点,变成某一种颜色,放大(缩小)后的比例,变化到某一透明度,视图旋转到某一角度)
completion : 动画结束时的回调(这里可以处理一些事件)
usingSpringWithDamping  : 阻尼(弹性系数)
initialSpringVelocity : 初始速率

///基础动画,结束无回调
[UIView animateWithDuration:1 animations:^{
            
}];
///基础动画,结束带回调
[UIView animateWithDuration:1 animations:^{
            
} completion:^(BOOL finished) {

}];
///进阶动画--带动画执行的方式,结束带回调
[UIView animateWithDuration:1 delay:1 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{
            
} completion:^(BOOL finished) {

}];

///进阶动画--可设置弹跳效果,结束带回调 带动画执行的方式
[UIView animateWithDuration:1 delay:1 usingSpringWithDamping:0.11 initialSpringVelocity:1 options:UIViewAnimationOptionAutoreverse animations:^{
            
} completion:^(BOOL finished) {
        
}];

可以用来做什么呢:
设置UIView的属性:例如
frame
bounds
center
transform
alpha
backgroundColor
contentStretch

看一下实例:

 self.greenView = [[UIView alloc]initWithFrame:CGRectMake(Size(30), Size(100), Size(100), Size(100))];
 self.greenView.backgroundColor = UIColor.greenColor;
 [self.view addSubview:self.greenView];

///开始动画
-(void)startAnimation{
    
    [UIView animateWithDuration:1 animations:^{
///缩放比例
        self.greenView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.5, 1.5);
///位置调整
        CGRect rect     = self.greenView.frame;
        rect.origin.x += Size(50);
        rect.origin.y += Size(50);
        self.greenView.frame = rect;
///透明度变化
        self.greenView.alpha = 0.5;
///color变化
        self.greenView.backgroundColor = UIColor.redColor;
///圆角改变
        self.greenView.layer.cornerRadius = Size(50);
        self.greenView.clipsToBounds = YES;
    }];
}


我们可以看到self.greenView通过UIView Animation动画将某些属性进行了改变。

现在我们适当的加入一些动画执行的方式【options】

UIViewAnimationOptionRepeat:持续重复动画内容
UIViewAnimationOptionCurveEaseIn:淡入
UIViewAnimationOptionCurveEaseOut:淡出
UIViewAnimationOptionCurveEaseInOut:淡入淡出
UIViewAnimationOptionCurveLinear:匀速运动

[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
        CGRect rect  = self.greenView.frame;
        rect.origin.x += Size(275);
        self.greenView.frame = rect;
    } completion:^(BOOL finished) {
}];
动画执行.gif

在开发中可以添加特定的options满足不同的动画需要。

弹簧效果:


弹簧效果.gif
    [UIView animateWithDuration:3 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
        CGRect rect     = self.greenView.frame;
        rect.origin.x += Size(275);
        self.greenView.frame = rect;
    } completion:^(BOOL finished) {
        
    }];

CABasicAnimation

CABasicAnimation 为layer属性提供了基础的帧动画能力,创建一个CABasicAnimation的实例,使用继承自CAPropertyAnimation的animationWithKeyPath:方法,来指定要添加动画的layer属性的keypath

CABasicAnimation常用的有如下几个属性:
//动画改变属性
@property(nullable, copy) NSString *keyPath;
// 指定执行动画layer的起始值
@property(nullable, strong) id fromValue;
// 指定结束动画layer的结束值
@property(nullable, strong) id toValue;
// 指定执行动画的时候的相对值
@property(nullable, strong) id byValue;

CABasicAnimation 相关常用属性
/* 基础动画开始时刻 CACurrentMediaTime() + 秒数  默认为0.0 这个属性在组动画中很有用 它根据父动画组的持续时间,指定了开始播放动画的时间* /
@property CFTimeInterval beginTime;
/* 基础动画的重复次数 默认为0.0*/
@property float repeatCount;
/*画应该被重复多久。动画会一直重复,直到设定的时间流逝完。它不应该和 repeatCount 一起使用*/
@property CFTimeInterval repeatDuration;
/* 基础动画的时间间隔 默认为0.25*/
@property CFTimeInterval duration;
/* 设置为YES的时候,当动画时间间隔过了后,动画就会移除*/
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;
/* 当动画时间间隔后,指定动画对象的表现。
* 本地时间可能在时间间隔后或者是元素从当前表现层移除后被压缩 
* 合法的值有kCAFillModeForwards、kCAFillModeBackwards、kCAFillModeBoth kCAFillModeRemoved
*/
@property(copy) NSString *fillMode;
//设定动画的速度变化 默认:nil 使用写法:[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
//动画结束时执行逆动画
@property BOOL autoreverses;
CABasicAnimation的写法。
移动动画

让一个view向左平移,在x方向上从屏幕x中间线型移动到左边消失,耗时1.5秒的动画

移动动画.gif
第一种方法:
// 创建动画  使用动画改变属性 position.x
 CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.x"];
 // 指定基础动画的时间间隔,以秒为单位默认值是 0.25
positionAnima.duration   = 1.5;
//改变前的属性
positionAnima.fromValue  = @((kScreenWidth-Size(100))/2);
//改变后的属性
positionAnima.toValue    = @(-self.greenView.frame.size.width / 2);
//保存最新状态
positionAnima.fillMode   = kCAFillModeForwards;
//动画组件不被移除
positionAnima.removedOnCompletion = NO;
[self.greenView.layer addAnimation:positionAnima forKey:@"position.x"];

第二种方法:
// 创建动画
CABasicAnimation *positionAnima = [CABasicAnimation animation];
// 动画改变属性
positionAnima.keyPath      = @"position";
positionAnima.duration     = 1.5;
// 改变后的属性
positionAnima.toValue = [NSValue valueWithCGPoint:CGPointMake(-Size(50), Size(100+50))];
// 动画组件不被移除
positionAnima.removedOnCompletion = NO;
 // 保存最新状态
positionAnima.fillMode = kCAFillModeForwards;
[self.greenView.layer addAnimation:positionAnima forKey:@"position"];
    
区别:设置的keypath不同第一种方法指定了左移,第二种方法可以是上下左右移动,fromValue、toValue需要根据keypath属性改变
旋转动画
5s完成旋转一周的顺时针旋转的view  并持续旋转

CABasicAnimation * rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.toValue       = [NSNumber numberWithFloat: M_PI * 2.0  ];
rotationAnimation.duration      = 5;
rotationAnimation.cumulative    = YES;
rotationAnimation.repeatCount   = HUGE_VAL;
rotationAnimation.removedOnCompletion = NO;
[self.greenView.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
缩放动画
/* 放大缩小 */
 
// 设定为缩放
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
// 动画选项设定
animation.duration = 2.5; // 动画持续时间
animation.repeatCount = 1; // 重复次数
animation.autoreverses = YES; // 动画结束时执行逆动画
// 缩放倍数
animation.fromValue = [NSNumber numberWithFloat:1.0]; // 开始时的倍率
animation.toValue     = [NSNumber numberWithFloat:2.0]; // 结束时的倍率
// 添加动画
[self.greenView.layer addAnimation:animation forKey:@"scale-layer"];

KeyPath的改变动画的效果就不一样,开发中改变KeyPath的属性可以实现大多数我们需要的动画执行的效果

animationWithKeyPath值类型:
position      =   路径(一般用CAKeyframeAnimation)
transform.rotation     = 旋转
transform.rotation.x  = x旋转
transform.rotation.y  = y旋转
transform.rotation.z  = z旋转(顺逆时针)
transform.translation = 平移
transform.translation.x = x平移
transform.translation.y = y平移
transform.scale             = 比例转换
transform.scale.x = x的比例转换
transform.scale.y = y的比例转换
transform.scale.y = y的比例转换
transform.rotation.z = 平面圆的转换
opacity = 透明度
margin
zPosition
backgroundColor = 背景颜色
cornerRadius = 圆角
borderWidth
bounds
contents
contentsRect
cornerRadius
frame  坐标
hidden  隐藏
mask
masksToBounds
shadowColor
shadowOffset
shadowOpacity
shadowRadius
大家可以尝试使用不同的keypath看看动画效果

用CABasicAnimation执行动画,在动画结束后会回归动画开始前的状态。想要解决的话,必须设置“removedOnCompletion”和“fillMode”这两个属性。

// 动画终了后不返回初始状态
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
但是

由于在开发过程中光是CABasicAnimation的fromValue、toValue起点和终点设置是无法满足我们希望在动画中途进行更多的变化的需求,所以我们需要认识一下CAKeyframeAnimation

CAKeyframeAnimation[关键帧动画]

从上面的继承图我们看出CAKeyframeAnimation 比CABasicAnimation多了更多的可设置属性

/* 提供关键帧数据的数组,数组中的每一个值都对应一个关键帧。根据动画类型(keyPath)的不同 ,
值的类型不同*/
@property(nullable, copy) NSArray *values;
/*基于点的属性的路径,即动画属性类型为CGPoint。如: position、anchorPoint、transform.translation等
如果为此属性指定非空值,则会忽略values属性*/
@property(nullable) CGPathRef path;
/* keyTimes的值与values中的值一一对应指定关键帧在动画中的时间点,取值范围为[0,1]。当keyTimes没有设置的时候,
各个关键帧的时间是平分的*/
@property(nullable, copy) NSArray*keyTimes;
/*指定每个关键帧之间的动画缓冲效果,timingFunctions.count = keyTimes.count-1*/
@property(nullable, copy) NSArray*timingFunctions;
/*关键帧间插值计算模式*/
@property(copy) NSString *calculationMode;
/*针对cubic 计算模式的动画,这些属性提供对插值方案的控制。每个*关键帧都可以具有与之相关的
张力、连续性和偏差值,这些值的范围在[-1,1]内(这定义了Kochanek-*Bartels样条,见http://en.wikipedia.org/wiki/Kochanek-Bartels_spline)。

*tensionValues控制曲线的“紧密度”(正值更紧,负值更圆)。
*continuityValues控制段的连接方式(正值表示锐角,负值表示倒角)。
*biasValues定义曲线发生的位置(正值在控制点之前移动曲线,负值在控制点之后移动它)。

*每个数组中的第一个值定义第一个控制点的切线的行为,第二个值控*制第二个点的切线,依此类推。任何未指定的值都默认为零
 *(如果未指定,则给出Catmull-Rom样条曲线)。 
*/
@property(nullable, copy) NSArray*tensionValues;
@property(nullable, copy) NSArray*continuityValues;
@property(nullable, copy) NSArray *biasValues;

/*定义沿路径动画的对象是否旋转以匹配路径切线*/
@property(nullable, copy) NSString *rotationMode;

关键帧动画其实通过一组动画类型的值(或者一个指定的路径)和这些值对应的时间节点以及各时间节点的过渡方式来控制显示的动画。关键帧动画可以通过path属性和values属性来设置动画的关键帧。

通过path设置动画
关键帧-path.gif
绕线一周动画
CGMutablePathRef path = CGPathCreateMutable();
//第一个关键帧  -100,-100
CGPathMoveToPoint(path, NULL, 0, 0);
//第二个关键帧  100,-100
CGPathAddLineToPoint(path, NULL, 100, 0);
//第三个关键帧  100,100
 CGPathAddLineToPoint(path, NULL, 100, 100);
//第四个关键帧  -100,100
CGPathAddLineToPoint(path, NULL, 0, 100);
//第五个关键帧  -100,-100
CGPathAddLineToPoint(path, NULL, 0, 0);
     
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"transform.translation";
animation.path = path;
animation.duration = 4;
animation.keyTimes = @[@(0),@(0.1),@(0.5),@(0.75),@(1)];
animation.timingFunctions = @[[CAMediaTimingFunction functionWithControlPoints:1 :0.5 :0.5 :0.5],
                                      [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
                                      [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
                                      [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
    //动画结束后保持动画最后的状态,两个属性需配合使用
 animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
        
CGPathRelease(path);
[self.greenView.layer addAnimation:animation forKey:@""];
通过values设置动画
关键帧-values.gif
 /// 放大缩小放大缩小【隐藏】
CAKeyframeAnimation* animation  = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
animation.duration              = 2.0;// 动画时间
animation.removedOnCompletion    = NO;
animation.values                = @[@1,@1.2,@1,@1.2,@0];
//动画结束后保持动画最后的状态,两个属性需配合使用
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[self.greenView.layer addAnimation:animation forKey:nil];

CAAnimationGroup[动画组]

可以保存一组动画CAKeyframeAnimation、CABasicAnimation对象,将CAAnimationGroup对象加入图层后,组中所有动画对象可以同时并发运行。

购物车动画
动画组.gif
 /* 动画1(在XY轴方向移动) */
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform.translation"];
// 终点设定
animation1.toValue = [NSValue valueWithCGPoint:CGPointMake(kScreenWidth-Size(50), kScreenHeight-Size(50))]; // 終点

/* 动画2(绕Z轴中心旋转) */
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
// 设定旋转角度
animation2.fromValue = [NSNumber numberWithFloat:0.0]; // 开始时的角度
animation2.toValue = [NSNumber numberWithFloat:4 * M_PI]; // 结束时的角度
     
 /* 动画组 */
 CAAnimationGroup *group = [CAAnimationGroup animation];
// 动画选项设定
group.duration = 3.0;
group.repeatCount = 1;
// 添加动画
group.animations = [NSArray arrayWithObjects:animation1, animation2, nil];
[self.greenView.layer addAnimation:group forKey:@"move-rotate-layer"];

注意:默认情况下,一组动画对象是同时运行的,也可以通过设置单个动画对象的beginTime属性来更改动画的开始时间,单个动画的执行效果可以与动画组执行效果属性分开设定,根据需要调整改变。

有关iOS 动画方法内容整理的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  5. Ruby 方法() 方法 - 2

    我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

  6. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  8. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  9. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  10. ruby - 多个属性的 update_column 方法 - 2

    我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2

随机推荐