iOS动画(1):Core Animation核心动画

在了解核心动画之前需要先了解CALayer的基本知识,因为CAAnimation的实现是基于CALayer之上。(不了解的童鞋可查阅资料)

CAMediaTiming协议

CALayerCAAnimation都遵守CAMediaTiming 协议,所以CAAnimation在使用过程中协议属性的设置至关重要。

CAMediaTiming协议属性如下(已做注释):


@protocol CAMediaTiming

//对象的开始时间,指动画开始之前的的时间。默认是0.
@property CFTimeInterval beginTime;

//对象的持续时间,默认是0.
@property CFTimeInterval duration;

//图层的比率,用来衡量最初时间和当前时间。例如:如果比率为2,当前花费的时间就是最初时间的0.5。默认是1。
@property float speed;

/*时间轴偏移量。将时间轴移动至偏移位置,再执行整个动画时长。假设动画时长5秒,偏移量为13,
则开始位置为13 % 5 = 3,再执行一次动画时长5秒,即在时长位置3处结束。*/
@property CFTimeInterval timeOffset;

//重复次数
@property float repeatCount;

//对象的重复持续时间。默认是0。
@property CFTimeInterval repeatDuration;

//是否自动换向。如果为YES,那么动画执行完会按照原来的路径返回。默认是NO。
@property BOOL autoreverses;

/*决定当前对象过了非active时间段的行为. 比如动画开始之前、动画结束之后。
如果是CAAnimationd对象,则需要将其removedOnCompletion设置为NO,要不然fillMode不起作用
*/
@property(copy) NSString *fillMode;

@end

补充:

fillMode值类型 注明
kCAFillModeRemoved 默认值。动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
kCAFillModeForwards 动画结束后,layer会一直保持着动画最后的状态
kCAFillModeBackwards 动画开始前,只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始
kCAFillModeBoth 动画开始之前,layer处于动画初始状态,动画结束后layer保持动画最后的状态

CAAnimation

CAAnimation主要分为:转场动画(CATransition)、关键帧动画(CAKeyframeAnimation)、基本动画(CABasicAnimation)、组合动画(CAAnimationGroup)。
(为了更容易看懂每个动画之间的关系,整理了CAAnimation层级结构图。)

CAAnimation结构图.png

CAAnimation的属性以及方法对继承自它的四种动画类型是通用的,下面是CAAnimation的属性列表。

/*速度控制函数*/
@property(nullable, strong) CAMediaTimingFunction *timingFunction;

/*动画代理。会在动画开始和结束时调用对应代理方法。*/
@property(nullable, strong) id <CAAnimationDelegate> delegate;

/*动画执行完毕后是否从图层上移除。默认是YES*/
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;

补充:

timingFunction类型 注明
kCAMediaTimingFunctionLinear(线性) 匀速状态
kCAMediaTimingFunctionEaseIn(渐入) 缓慢进入,然后加速离开
kCAMediaTimingFunctionEaseOut(渐出) 全速进入,然后减速到达最终位置
kCAMediaTimingFunctionEaseInEaseOut(渐进渐出) 缓慢进入,中间加速,最后减速到达最终位置。默认类型

下面具体看下每一种动画类型如何使用,以及各自的使用场景。

1、转场动画(CATransition)

CATransition类用来实现layer的转场过渡动画。
首先需要了解下CATransition的属性(已做备注)。

@interface CATransition : CAAnimation

/* 过渡动画名。当前合法的动画类型有:`fade', `moveIn', `push' and `reveal'. 默认是`fade'.  */
@property(copy) NSString *type;

/* 一个可选择的动画子类型. 用来指定动画的运动方向,合法的值是: `fromLeft', `fromRight', `fromTop' and * `fromBottom'. */
@property(nullable, copy) NSString *subtype;

/* 这两个属性用来控制过渡动画的开始、结束执行过程,可以让动画停留在某个动画点上。合法的值是在0~1范围内。endProgress必须比startProgress的值要大。默认值是0—1.
*/
@property float startProgress;
@property float endProgress;

/*filter对象用来实现过渡动画。如果设置了filter,那么为layer设置的type和subtype属性将被忽略。*/
@property(nullable, strong) id filter;

@end

补充:

type类型还有很多私有API效果,如果代码中使用的话可能会导致app审核不通过。
基本效果
fade //交叉淡化过渡
push //新视图把旧视图推出去
moveIn //新视图移到旧视图上面
reveal //将旧视图移开,显示下面的新视图
私有API:
cube //立方体翻滚效果
oglFlip //上下左右翻转效果
suckEffect //收缩(不支持过渡方向)
rippleEffect //滴水(不支持过渡方向)
pageCurl //向上翻页
pageUnCurl //向下翻页
cameraIrisHollowOpen //相机镜头打开(不支持过渡方向)
cameraIrisHollowClose //相机镜头关上(不支持过渡方向)

设置type的代码不在此处展示,只需要修改type类型即可实现。下面看下不同type的动画效果。(因为gif图大小限制,所以公有和私有API效果分开展示)

基本API运行效果:

公用API效果.gif

私有API的运行效果如下:

私有API效果展示.gif

现在通过代码实例来看一下StartProgress\EndProgress的使用。
注:为了能清楚看出设置Progress的效果,我把duration时间设置为5s。通过对比能够看出来设置StartProgress、EndProgress前后的区别,可根据自己的实际需要进行设置。

    UILabel * label1 = [[UILabel alloc]init];
    label1.backgroundColor = [UIColor lightGrayColor];
    label1.text = @"label1";
    label1.textColor = [UIColor blackColor];
    [self.view addSubview:label1];
    
    UILabel * label2 = [[UILabel alloc]init];
    label2.text = @"label2";
    label2.backgroundColor = [UIColor lightGrayColor];
    label2.textColor = [UIColor blackColor];
    [self.view addSubview:label2];

    //label1的的动画
    CATransition *animation1 = [CATransition animation];
    animation1.duration = 5.0f;
    animation1.type =  kCATransitionPush;
    animation1.subtype = kCATransitionFromBottom;//过渡方向
    [animation1 setStartProgress:0.5];//设置动画起点(匀速过渡动画路径的50%处开始)
    [animation1 setEndProgress:0.8];//设置动画终点(匀速过渡动画路径的80%处结束)
    [label1.layer addAnimation:animation1 forKey:@"animation"];
    
    //label2的的动画
    CATransition *animation2 = [CATransition animation];
    animation2.duration = 5.0f;
    animation2.type =  kCATransitionPush;
    animation2.subtype = kCATransitionFromBottom;
    [label2.layer addAnimation:animation2 forKey:@"animation"];

    
    [label1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(@20);
        make.top.equalTo(@180);
        make.width.height.mas_equalTo(100);
        
    }];
    [label2 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(@-20);
        make.top.equalTo(@180);
        make.width.height.mas_equalTo(100);
        
    }];
   

运行结果如下:

Transition.gif

2、基本动画(CABasicAnimation)

CAPropertyAnimation是通过animationWithKeyPath方法来进行实例化的,所以CABasicAnimation、CAKeyframeAnimation都可以通过animationWithKeyPath的值来设定动画样式。

animationWithKeyPath常用值如下:

KeyPath 效果
transform.scale 整体比例转换
transform.scale.x 宽的比例转换
transform.scale.y 高的比例转换
transform.rotation.x 围绕x轴旋转
transform.rotation.y 围绕y轴旋转
transform.rotation.z 围绕z轴旋转(平面效果是围绕中心点旋转 )
transform.translation.x 沿x方向位置移动
transform.translation.y 沿y方向位置移动
transform.translation.z 沿z方向位置移动
position 位置移动(关键帧动画使用时可以不设置方向,用贝塞尔曲线绘制路径)
position.x 沿x方向位置移动
position.y 沿y方位置向移动
position.z 沿z方向位置移动
bounds 坐标大小转换
opacity 改变透明度

CABasicAnimation的属性列表(已做备注):

@interface CABasicAnimation : CAPropertyAnimation

@property(nullable, strong) id fromValue;//所改变属性的起始值
@property(nullable, strong) id toValue;//所改变属性的结束时的值
@property(nullable, strong) id byValue;//所改变属性相同起始值的改变量

@end

CABasicAnimation三种动画方式的实现代码示例:

          //移动动画
        case 0:
        {
            CABasicAnimation *Animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
            Animation.duration = 1.0f;//动画时长
            Animation.fromValue = @(imageView.center.y-30);// 起始帧
            Animation.toValue = @(imageView.center.y+50);//终止帧
            Animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];//速度控制方式:匀速线性
            Animation.repeatCount = 1;//重复次数
            Animation.removedOnCompletion = NO;
            Animation.fillMode = kCAFillModeForwards;//动画结束后,layer会一直保持着动画最后的状态
            [self.view.layer addAnimation:Animation forKey:@"AnimationMoveY"];
        }
            break;
            
            //旋转动画:x、y、z三个方向
        case 1:{
            CABasicAnimation *Animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];//围绕Y轴方向旋转
            Animation.duration = 1.0f;
            Animation.repeatCount = 1;
            Animation.fromValue = @(0);
            Animation.toValue = @(M_PI);//旋转一周
            Animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
            [imageView.layer addAnimation:Animation forKey:@"RotationY"];
        }
            break;
            
            // 缩放动画:x、y、z
        case 2:{
            CABasicAnimation *Animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; // 设定为缩放
            Animation.duration = 1.5f;
            Animation.repeatCount = 1;
            Animation.autoreverses = YES; // 动画结束时执行逆动画,回到起始帧
            Animation.fromValue = [NSNumber numberWithFloat:1.0]; // 开始时的倍率
            Animation.toValue = [NSNumber numberWithFloat:2.0]; // 结束时的倍率
//            Animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
//            Animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.3, 0.3, 1.0)];
            [imageView.layer addAnimation:Animation forKey:@"scale-layer"];
        }
            break;
            
            //平移动画:必须设定方向
        case 3:{
            CABasicAnimation *Animation=[CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
            
            Animation.duration = 1;
            
            Animation.repeatCount = 0;
            
            Animation.removedOnCompletion = FALSE;
            
            Animation.fillMode = kCAFillModeForwards;
            
            Animation.autoreverses = YES;
            
            Animation.fromValue = [NSNumber numberWithFloat:0];
            
            Animation.toValue = [NSNumber numberWithFloat:60];
            
            [self.view.layer addAnimation:Animation forKey:@"animateLayer"];
        }
            break;


运行效果如下:


BasicAnimation.gif

这只是基本动画的简单实现,稍微复杂的动画可以通过借助组合动画实现,后面会有介绍。

3、关键帧动画(CAKeyframeAnimation)

CAKeyframeAnimation的属性列表(已做备注)。


@interface CAKeyframeAnimation : CAPropertyAnimation
/*存储关键帧(keyframe)元素。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧。*/
@property(nullable, copy) NSArray *values;

/*设置CGPathRef\CGMutablePathRef,让layer跟着设定的路径移动。path只对CALayer的anchorPoint和position起作用。如果设置了path,values将被忽略。*/
@property(nullable) CGPathRef path;

/*为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平均分配的。*/
@property(nullable, copy) NSArray<NSNumber *> *keyTimes;

/*速度控制函数,有kCAMediaTimingFunctionLinear、kCAMediaTimingFunctionEaseIn、kCAMediaTimingFunctionEaseOut、kCAMediaTimingFunctionEaseInEaseOut四种方式,默认是最后一种*/
@property(nullable, copy) NSArray<CAMediaTimingFunction *> *timingFunctions

/*计算模式。主要针对的是每一帧的内容为一个座标点的情况,也就是对anchorPoint 和 position 进行的动画。
目前提供如下几种模式:kCAAnimationLinear、kCAAnimationDiscrete 、kCAAnimationPaced
、kCAAnimationCubic 、kCAAnimationCubicPaced。*/
@property(copy) NSString *calculationMode;

/* 该值控制着曲线的紧密度(正值将越紧,负值将越宽松)*/
@property(nullable, copy) NSArray<NSNumber *> *tensionValues;

/*该值控制片段之间的链接(正值将有锋利的圆角,负值将是倒立的圆角)*/
@property(nullable, copy) NSArray<NSNumber *> *continuityValues;

/*该值定义了曲线发生的地点(正值将在控制点前移动曲线,负值将在控制点后移动)*/
@property(nullable, copy) NSArray<NSNumber *> *biasValues;

/*定义是否沿着路径旋转匹配对象动画路径切线,值可能为kCAAnimationRotateAuto和kCAAnimationRotateAutoReverse。默认是nil。如果没有路径对象,设置该属性值将无效,kCAAnimationRotateAutoReverse为了匹配正切将添加180°。*/
@property(nullable, copy) NSString *rotationMode;
@end

下面是对calculationMode的几种模式的具体注释:

calculationMode 注释
kCAAnimationLinear 线性。关键帧为坐标点时,直接直线相连进行插值计算。
kCAAnimationDiscrete 离散不连续的。只展示关键帧的状态,没有中间过程,没有动画。
kCAAnimationPaced 节奏。动画均匀进行,不按keyTimes设置的或者按关键帧平分时间,所以keyTimes和timingFunctions无效
kCAAnimationCubic 以关键帧为坐标点进行圆滑曲线相连后插值计算,目的是使得运行的轨迹变得圆滑。对于曲线的形状还可以通过tensionValues、continuityValues、biasValues进行调整自定义
kCAAnimationCubicPaced 在kCAAnimationCubic的基础上使动画变得均匀,就是系统时间内运动的距离相同。如果设定的话keyTimes、timingFunctions无效。

注:
CAKeyframeAnimationCABasicAnimation都是CApropertyAnimation的子类,区别是:CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation可以使用values保存这些数值,但是CAKeyframeAnimation的实现方式也不止values,还有path。

实现关键帧动画两种方式的代码示例(values和path):

-(void)AnimationClick:(UIButton *)button{
    switch (button.tag) {
            //Values实现
        case 0:
        {
            CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
   
            //动画路径的values
            NSValue *value1=[NSValue valueWithCGPoint:CGPointMake(100, 250)];
            
            NSValue *value2=[NSValue valueWithCGPoint:CGPointMake(200, 250)];
            
            NSValue *value3=[NSValue valueWithCGPoint:CGPointMake(200, 300)];
            
            NSValue *value4=[NSValue valueWithCGPoint:CGPointMake(100, 300)];
            
            NSValue *value5=[NSValue valueWithCGPoint:CGPointMake(100, 400)];
            
            NSValue *value6=[NSValue valueWithCGPoint:CGPointMake(200, 400)];
            
            animation.values=@[value1,value2,value3,value4,value5,value6];
            
            //重复次数
            animation.repeatCount=MAXFLOAT;
            
            //设置是否原路返回
            animation.autoreverses = YES;
            
            //设置移动速度,越小越快
            animation.duration = 4.0f;
            
            animation.removedOnCompletion = NO;
            
            animation.fillMode = kCAFillModeForwards;
            
            animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
            [imageView.layer addAnimation:animation forKey:nil];
            
        }
            break;
            
            //通过Path方式
        case 1:{
            //创建动画对象
            CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
            
            //创建一个CGPathRef对象,就是动画的路线
            CGMutablePathRef path = CGPathCreateMutable();
 
           //设置开始位置
            CGPathMoveToPoint(path,NULL,100,250);
            
            //沿直线移动
            CGPathAddLineToPoint(path,NULL, 200, 250);
            
            CGPathAddLineToPoint(path,NULL, 200, 300);
            
            CGPathAddLineToPoint(path,NULL, 100, 300);
            
            CGPathAddLineToPoint(path,NULL, 100, 400);
            
            CGPathAddLineToPoint(path,NULL, 200, 400);
            
            //沿着曲线移动
            CGPathAddCurveToPoint(path,NULL,200.0,500.0,250.0,500.0,250.0,400.0);
            
            CGPathAddCurveToPoint(path,NULL,250.0,500.0,300.0,500.0,300.0,400.0);
               
            //自动沿着弧度移动
            
            CGPathAddEllipseInRect(path, NULL, CGRectMake(300, 400, 70, 70));  
            
            animation.path=path;
            
            CGPathRelease(path);
            
            animation.autoreverses = YES;
            
            animation.repeatCount=1;
            
            animation.removedOnCompletion = NO;
            
            animation.fillMode = kCAFillModeForwards;
            
            animation.duration = 8.0f;
            
            animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
            
            [imageView.layer addAnimation:animation forKey:nil];
            
           //绘制出动画路径,方便观察
            CAShapeLayer *line = [[CAShapeLayer alloc]init];

            line.fillColor = [UIColor clearColor].CGColor;

            line.borderWidth = 1;

            line.strokeColor = [UIColor redColor].CGColor;

            line.path = path;

            [self.view.layer addSublayer:line];
        }
            break;

        default:
            break;
    }
}


运行结果如下:

KeyFrameAnimation.gif

4、组合动画(CAAnimationGroup)

CAAnimationGroup只有一个属性animations。

@interface CAAnimationGroup : CAAnimation

/* 存储CAAnimation对象的数组。数组的每一个元素在规定的duration时间内同时进行。*/

@property(nullable, copy) NSArray<CAAnimation *> *animations;

@end

做一个简单的加入购物车动画。代码如下::

 //贝塞尔曲线路径
    UIBezierPath *movePath = [UIBezierPath bezierPath];
    [movePath moveToPoint:CGPointMake(0.0, 64.0)];
    [movePath addQuadCurveToPoint:CGPointMake(300, 400) controlPoint:CGPointMake(200, 100)];
    
    //关键帧动画(设置位置)
    CAKeyframeAnimation * posAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    posAnim.path = movePath.CGPath;
    
    //缩放动画
    CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    scaleAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
    scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.3, 0.3, 1.0)];
    
    //旋转动画
    CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];//围绕Y轴方向旋转
    rotationAnimation.duration = 0.2;
    rotationAnimation.repeatCount = MAXFLOAT;
    rotationAnimation.fromValue = @(0);
    rotationAnimation.toValue = @(M_PI);//旋转一周
    
    //透明度动画
    CABasicAnimation *opacityAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
    opacityAnim.fromValue = [NSNumber numberWithFloat:1.0];
    opacityAnim.toValue = [NSNumber numberWithFloat:0.6];

    //动画组
    CAAnimationGroup *animGroup = [CAAnimationGroup animation];
    animGroup.animations = [NSArray arrayWithObjects:posAnim, scaleAnimation,rotationAnimation, opacityAnim,nil];
    animGroup.duration = 1;
    animGroup.autoreverses = NO;
    animGroup.fillMode = kCAFillModeForwards;
    [goodsView.layer addAnimation:animGroup forKey:nil];

运行效果:

GroupAnimation.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容