股票场内基金交易,没时间盯盘?
本文通过一个简单的程序来演示 UIButton 对象的视图属性的配置使用和修改。分别建立 7 个 UIButton 对象,实现 UIButton 对象的放大、缩小、移动、高亮时变换背景图片和文字格式的效果。如果所示:
本文工程文件及素材文件
见 wakemeup-xyz/UIButton-practice-view @ GitHub
添加 app 所需图片素材
首先在 xcode 新建一个 Single View Application。在 main.storyboard 按照按钮的大致位置建立 9 个新的 UIButton 对象。
Xcode 中的左侧 Navigator 栏中有一个 Asset.xcassets(在某些 Xcode低版本中为 Images.xcassets)文件夹用图放置 app 所需要的图片素材。将『箭头』文件夹直接拖入 Asset.xcassets,在以后空间中需要使用图片时直接输入文件名就可以了。拖入后 Asset.xcassets 即会显示图片例表,如图所示:
设置背景图片
先将 UIButton 空间都拖拽(或者调整 View-Width/Height)到合适的大小。设置背景图片的方法为:选定当前 按钮控件,在右侧的 Attributes Inspecter 菜单中的 background 下拉菜单选择合适的图片名,(例如btn01,topnormal,leftnormal,rightnormal 等等)。并设置相应的 Title 属性(控件显示的文字),如图所示:
设置 HighLight 效果
在按钮按下时即进入高亮(HighLight)状态,我们需要对高亮状态进行设置以产生动画效果。选定背景为海贼王的 UIButton。它的 type 属性定义了这个 UIButton 的种类,包括了六种选项,分别为:
- Custom: 自定义风格(无边框的那种)
- System: 系统默认风格
- DetailDisclosure: 蓝色小箭头按钮,主要做详细说明用(iOS7 上变成一个亮的蓝色感叹号)
- InfoLight: 亮色感叹号
- InfoDark: 暗色感叹号
- ContactAdd: 十字加号按钮
Custom 与 System 的区别在于:System 在按下高亮时整个按钮都会变成灰白色,但是 Custom 则是根据用户自定义的效果变化。可以都尝试一遍看一看效果变化。由于这个 UIButton 我们需要自定义按下高亮时的变化,因此需要选择为 Custom(自定义).
State Config 属性代表着这一系列属性在 UIButton 的不同状态下的定义。
分别有以下几种状态:
- Default : 常规状态显现;
- Highlighted : 高亮状态(即为按钮被按下时的状态)显现;
- Selected : 选中状态;
- Focused : 获得焦点状态(iOS 9.0 及以后)
- Disabled : 禁用的状态才会显现.
每种状态下的文字内容、颜色、字体、背景图片等等属性都是各自独立的。这次在Default 状态下设置字体颜色 Text Color 为红色。在 Highlighted 状态下设置字体颜色为蓝色,background 为 btn_02。
同理,把其他按钮也需要将 Type 改为 Custom,Highlighted 状态的 background 改为相应的 *highlighted。
IBAction 与 IBOutlet 连线
有一个思路是建立 moveLeft、moveRight、moveUp、moveDown、zoomIn、zoomOut、rotateLeft、rotateRight 这样 8 个监听事件响应各种动作,但是由于这些动作的代码内容差异很小,会导致代码比较麻烦出现很多重复的内容,不推荐采用。
更好的解决方法是建立一个名为 move 的监听事件同时连接这四个箭头,再点击不同的箭头时会返回不同的对象参数,于是产生不同的动作,节约许多公共的代码。实现如下:在 ViewController.m(私有方法)中建立 1 个 IBAction 监听事件为 move。并与 4 个箭头拖拽连线建立关系。就像这样:
1 2 3 4 |
- (IBAction)move:(id)sender { } |
然后把左、上、右、下四个方向的 UIButton 的 tag 属性分别设置为 100,101,102,103 以进行区分监听事件由哪个按钮触发。
同理对于缩放按钮,需要建立 zoom 事件并连接到加减号
1 2 3 |
- (IBAction)zoom:(UIButton *)button { } |
对于旋转按钮,建立 rotate_new 事件并连线
1 2 3 |
- (IBAction)rotate_new:(UIButton *)button { } |
然后还要声明一个属性变量:
1 |
@property(nonatomic, weak) IBOutlet UIButton * headImageView; |
连接到演示海贼王的 UIButton,此后这个按钮即用 headImageView 表示。
实现 UIButton 移动
坐标系
首先需要弄清楚控件的坐标的坐标系,在 UIkit 坐标系中,原点位于左上角,x 轴向右延伸,y 轴向下延伸,如图所示:
UIView 常见坐标属性
-
控件所在矩形框在父控件中的位置和尺寸(以父控件的左上角为坐标原点),可以定义控件的大小和位置。
@property(nonatomic) CGRect frame;
-
控件所在矩形框的位置和尺寸(以自己左上角为坐标原点),只可以定义控件的大小。
@property(nonatomic) CGRect bounds;
-
控件中的点的位置(以父控件的左上角为坐标原点),可以定义控件的位置。
@property(nonatomic) CGPoint center;
-
获得自己的父控件对象
@property(nonatomic,readonly) UIView *superview; -
获得自己的所有子控件对象
@property(nonatomic,readonly,copy) NSArray *subview; -
获得控件的 ID,即 tag
@property(nonatomic) NSInteger tag;
其中 CGRect 、CGPoint 、GAffineTransform为一种结构体,它的定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
typedef float CGFloat; // 32-bit typedef double CGFloat; // 64-bit struct CGPoint { CGFloat x; CGFloat y; }; typedef struct CGPoint CGPoint; struct CGSize { CGFloat width; CGFloat height; }; typedef struct CGSize CGSize; struct CGRect { CGPoint origin; CGSize size; }; typedef struct CGRect CGRect; |
- CGFloat: 浮点值的基本类型
- CGPoint: 表示一个二维坐标系中的点
- CGSize: 表示一个矩形的宽度和高度
- CGRect: 表示一个矩形的位置和大小
OC中修改对象的结构体变量的成员
由上述的格式可以知道,要想移动 headImageView,就需要更改它的 origin 结构体属性的 x,y 变量。但是,由于 OC 中对象的封装性OC 规定不允许直接修改对象的结构体变量的成员,所以需要通过借用一个零时结果体进行修改再赋值过去的方法,步骤如下:
1 2 3 4 5 6 |
//建立临时结构体,获取 headImageView 的 frame 值 CGRect tmpFrame = self.headImageView.frame; //修改 tmpFrame tmpFrame.origin.x -= 10; //将修改后的 tmpFrame 赋给原来的 headImageView self.headImageView.frame = tmpFrame; |
这样 move 事件代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- (IBAction)move:(id)sender { //将 id 类型的sender 转换为 UIButton * //或者可以直接把 move 方法定义为 - (IBAction)move:(UIButton *)button UIButton *button = (UIButton *)sender; CGRect tmpFrame = self.headImageView.frame; switch (button.tag) { case 100:tmpFrame.origin.x -= 5; break; case 101:tmpFrame.origin.y -= 5; break; case 102:tmpFrame.origin.x += 5; break; case 103:tmpFrame.origin.y += 5; break; default:break; } self.headImageView.frame = tmpFrame; } |
但是这里 100、101、102、103 仍然时一个没有意义的数字,为了便于理解,我们可以使用枚举或者是定义常量来替换这个四个数字。
1 2 3 4 5 6 |
typedef enum{ move_left = 100, move_up, move_right, move_down, }moveDirection; |
这样 move 方法就可以改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (IBAction)move:(id)sender { UIButton *button = (UIButton *)sender; CGRect tmpFrame = self.headImageView.frame; switch (button.tag) { case move_left:tmpFrame.origin.x -= 5; break; //左 case move_up:tmpFrame.origin.y -= 5; break; //上 case move_right:tmpFrame.origin.x += 5; break; //右 case move_down:tmpFrame.origin.y += 5; break; //下 default:break; } self.headImageView.frame = tmpFrame; } |
实现 UIButton 放缩
放大缩小即是改变 self.headImageView.frame.size.width 或者 height 的值,可以得到代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (IBAction)zoom:(UIButton *)button { CGRect tmpFrame = self.headImageView.frame; if (button.tag == zoomIn) { tmpFrame.size.height += 10; tmpFrame.size.width += 10; } else { tmpFrame.size.height -= 10; tmpFrame.size.width -= 10; } self.headImageView.frame = tmpFrame; } |
但是这样的放缩是以控件左上角为中心变化的,所以控件的中心再变化,为了让控件的中心维持不动,可以再做对 self.headImageView.frame.origin.x 和 y 做适当的偏移(长宽变化量的一半),代码变为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
- (IBAction)zoom:(UIButton *)button { CGRect tmpFrame = self.headImageView.frame; if (button.tag == zoomIn) { tmpFrame.size.height += 10; tmpFrame.size.width += 10; tmpFrame.origin.x -= 5; tmpFrame.origin.y -= 5; } else { tmpFrame.size.height -= 10; tmpFrame.size.width -= 10; tmpFrame.origin.x += 5; tmpFrame.origin.y += 5; } self.headImageView.frame = tmpFrame; } |
另外还有一个方法就是使用之前讲过的 bounds 属性(只改变大小),只需要改变 height 和 width即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (IBAction)zoom:(UIButton *)button { CGRect tmpBounds = self.headImageView.bounds; if (button.tag == zoomIn) { tmpBounds.size.height += 10; tmpBounds.size.width += 10; } else { tmpBounds.size.height -= 10; tmpBounds.size.width -= 10; } self.headImageView.bounds = tmpBounds; } |
加入动画效果
使用
1 |
+ (void)beginAnimations:(NSString *)animationID context:(void *)context |
第一个参数表示要使用的动画效果的 ID,第二个参数为通过使用代理来设置动画的属性。
此后的代码开始以动画效果表示,直到出现
1 |
+ (void)commitAnimations |
才结束并提交动画。
以放缩为例,把 zoom 方法的代码修改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- (IBAction)zoom:(UIButton *)button { CGRect tmpBounds = self.headImageView.bounds; if (button.tag == zoomIn) { tmpBounds.size.height += 10; tmpBounds.size.width += 10; } else { tmpBounds.size.height -= 10; tmpBounds.size.width -= 10; } [UIView beginAnimations:nil context:nil]; self.headImageView.bounds = tmpBounds; [UIView commitAnimations]; } |
运行的效果如图:
可以看到动画是先以左上角为中心缩放再做平移的,所以动画比较别扭。如果在 beginAnimation 加入代码
1 |
[UIView setAnimationDuration:3]; |
表示把动画的时长设置为 3 秒,这样就可以看清动画的过程了:
同样,移动的动画也可以改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
- (IBAction)move:(id)sender { //将 id 类型的sender 转换为 UIButton * //或者可以直接把 move 方法定义为 - (IBAction)move:(UIButton *)button UIButton *button = (UIButton *)sender; CGRect tmpFrame = self.headImageView.frame; switch (button.tag) { case move_left:tmpFrame.origin.x -= 5; break; //左 case move_up:tmpFrame.origin.y -= 5; break; //上 case move_right:tmpFrame.origin.x += 5; break; //右 case move_down:tmpFrame.origin.y += 5; break; //下 default:break; } [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.3]; self.headImageView.frame = tmpFrame; [UIView commitAnimations]; } |
CGAffineTransform 放射变换矩阵
这个结构体变量可以获得和修改控件的形变属性(可以设置旋转角度、比例缩放、平移)
1 |
@property(nonatomic) CGAffineTransform transform; |
它的定义为
1 2 3 4 5 6 |
struct CGAffineTransform { CGFloat a,b,c,d; CGFloat tx,ty; }; typedef struct CGAffineTransform CGAffineTransform; |
先讲一讲矩阵变换的原理。为了把二维图形的变化统一在一个坐标系里,引入了齐次坐标的概念,即把一个图形用一个三维矩阵表示,其中第三列总是(0,0,1),用来作为坐标系的标准。所以所有的变化都由前两列完成。以上参数在矩阵中的表示为:
1 2 3 |
|a b 0| |c d 0| |tx ty 1| |
运算原理:原坐标为(X,Y,1),根据矩阵的乘法
1 2 3 |
|a b 0| [X,Y,1] * |c d 0| = [aX + cY + tx,bX + dY + ty,1] |tx ty 1| |
-
当 a=d=1,b=c=0 时,
[aX + cY + tx,bX + dY + ty,1] = [X + tx,Y + ty,1],表示按照向量(tx,ty)进行平移。
这也就是函数 CGAffineTransform CGAffineMakeTranslation(CGFloat tx,CGFloat ty) 的计算原理。
-
当 b=c=tx=ty=0 时,
[aX + cY + tx,bX + dY + ty,1] = [aX,dY,1],表示坐标乘以(a,d)进行缩放。
其实这也就是函数
CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) 的计算原理。a 对应于 sx,d 对应于 sy。 -
当 tx=ty=0,a=cos?,b=sin? ,c=-sin?,d=cose
[aX + cY + tx ,bX + dY + ty,1] = [Xcos? – Ysin? ,Xsin? + Ycos? ,1] ,这时候 ? 就是旋转角度。下面给出数学证明便于理解:
先用极坐标表示:用X = cos?,Y = sin?. 旋转 ? 后 X’ = cos(?-?),Y’ = sin(?-?),即 X’ = cos?cos?-sin?sin? ==代换X、Y== Xcos? – Ysin? , Y’ = sin?cos? + cos?sin? ==代换X、Y== Xsin? + Ycos? ,所求得的 X’、Y’即为 Xcos? – Ysin? ,Xsin? + Ycos?。
其实这也就是函数
CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle) 的计算原理。angle 即 ? 的弧度表示。常用的弧度常量有:
123456#define M_PI 3.14159265358979323846264338327950288 ---------pi#define M_PI_2 1.57079632679489661923132169163975144 ---------pi/2#define M_PI_4 0.785398163397448309615660845819875721 ---------pi/4#define M_1_PI 0.318309886183790671537767526745028724 ---------1/pi#define M_2_PI 0.636619772367581343075535053490057448 ---------2/pi#define M_2_SQRTPI 1.12837916709551257389615890312154517 ---------2/sqrt(pi)
利用 CGAffineTransform 实现 UIButton 的平移、放缩、旋转
根据上一节的原理,我们可以使用 CGAffineTransform 来实现 UIButton 的平移、放缩、旋转。先把此前的 move 、 zoom 方法注释掉,并删除相关的连线(一定要做!不然此后点击按钮会触发不存在的事件然后报错)建立新的 movenew 、zoomnew 方法并连线。 然后输入代码实现。
先添加旋转时需要的枚举定义,并把两个按钮的 tag 设置为 100、111。
enum rotate{
rotateleft = 110,
rotateright = 111
};
但是如果像这样
1 2 3 4 5 6 7 8 9 10 11 |
- (IBAction)rotate_new:(UIButton *)button { if (button.tag == rotate_left) { self.headImageView.transform = CGAffineTransformMakeRotation(-M_1_PI/10); } else { self.headImageView.transform = CGAffineTransformMakeRotation(M_1_PI/10); } } |
的话,按钮只有第一次按下才有反应,这是因为,每次形变都是以初始化时为基准的,所以应该引入一个 delta 变量,每次按下时改变 delta 的值。而且,这个 delta 应该是全局变量,这样的话才能在每次调用时不会重置掉 delta 的值
在 ViewController.m 顶部定义
CGFloat deltaAngle = 0;
然后在实现部分添加
- (IBAction)rotate_new:(UIButton *)button
{
if(button.tag == rotate_left)
deltaAngle -= M_1_PI/10;
else
deltaAngle += M_1_PI/10;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
self.headImageView.transform = CGAffineTransformMakeRotation(deltaAngle);
[UIView commitAnimations];
}
然后实现 movenew 、 zoomnew:
同样在顶部添加
1 2 3 4 |
CGFloat deltaZoomX = 1; //不是 0 ,用于乘除法! CGFloat deltaZoomY = 1; CGFloat deltaMoveX = 0; CGFloat deltaMoveY = 0; |
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
- (IBAction)move_new:(UIButton *)button { switch (button.tag) { case move_left:deltaMoveX -= 10;break; case move_right:deltaMoveX += 10;break; case move_up:deltaMoveY -= 10;break; case move_down:deltaMoveY += 10;break; default: break; } [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.5]; self.headImageView.transform = CGAffineTransformMakeTranslation(deltaMoveX, deltaMoveY); [UIView commitAnimations]; } - (IBAction)zoom_new:(UIButton *)button { if (button.tag == zoomIn) { deltaZoomX *= 1.1; deltaZoomY *= 1.1; } else { deltaZoomX /= 1.1; deltaZoomY /= 1.1; } [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.5]; self.headImageView.transform = CGAffineTransformMakeScale(deltaZoomX, deltaZoomY); [UIView commitAnimations]; } |
还有一种方法就是 CGAffineTransform 也提供了基于当前位置的形变函数(名字不带 make)因此,这样也可以用于平移、放缩、旋转,实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
- (IBAction)rotate_new:(UIButton *)button { if(button.tag == rotate_left) deltaAngle -= M_1_PI/10; else deltaAngle += M_1_PI/10; [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.5]; //1. self.headImageView.transform = CGAffineTransformMakeRotation(deltaAngle); //2. self.headImageView.transform = CGAffineTransformRotate(self.headImageView.transform, button.tag == rotate_left ? -M_1_PI/10:M_1_PI/10); [UIView commitAnimations]; } - (IBAction)move_new:(UIButton *)button { switch (button.tag) { case move_left:deltaMoveX -= 10;break; case move_right:deltaMoveX += 10;break; case move_up:deltaMoveY -= 10;break; case move_down:deltaMoveY += 10;break; default: break; } [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.5]; //1. self.headImageView.transform = CGAffineTransformMakeTranslation(deltaMoveX, deltaMoveY); //2. switch (button.tag) { case move_left:self.headImageView.transform = CGAffineTransformTranslate(self.headImageView.transform, -10, 0); break; case move_right:self.headImageView.transform = CGAffineTransformTranslate(self.headImageView.transform, 10, 0); break; case move_up:self.headImageView.transform = CGAffineTransformTranslate(self.headImageView.transform, 0,-10); break; case move_down:self.headImageView.transform = CGAffineTransformTranslate(self.headImageView.transform, 0, 10); break; default: break; } [UIView commitAnimations]; } - (IBAction)zoom_new:(UIButton *)button { if (button.tag == zoomIn) { deltaZoomX *= 1.1; deltaZoomY *= 1.1; } else { deltaZoomX /= 1.1; deltaZoomY /= 1.1; } [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.5]; //1. self.headImageView.transform = CGAffineTransformMakeScale(deltaZoomX, deltaZoomY); //2. self.headImageView.transform = CGAffineTransformScale(self.headImageView.transform, button.tag == zoomIn ? 1.1 : 1/1.1,button.tag == zoomIn? 1.1 : 1/1.1); [UIView commitAnimations]; } |
就可以实现出如下的效果了。
UIButton 创建与配置的代码实现
下面来用代码来配置一个 UIButton,作为 headImageView。
视图的初始化应该在 viewDidLoad 方法中,现在来覆写其父类的 viewDidLoad:
1 2 3 4 5 |
- (void)viewDidLoad { [super viewDidLoad]; } |
然后在 viewDidLoad 方法中创建和配置UIButton:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
- (void)viewDidLoad { [super viewDidLoad]; //申请内存空间 //不设置 buttonType //UIButton * btn = [[UIButton alloc] init]; //设置 buttonType UIButton * btn =[UIButton buttonWithType:UIButtonTypeCustom]; //确定宽、高、X、Y坐标 // 1. //btn.frame = CGRectMake(50, 50, 152, 152); //或者 2. CGRect frame; //对 对象的结构体属性变量的成员 不可以直接赋值,但是对 结构体的成员 可以直接赋值 frame.origin.x = 50; frame.origin.y = 50; frame.size.width = 152; frame.size.height = 152; [btn setFrame:frame]; // [btn buttonType]; //设置 default 状态的标题 Title [btn setTitle:@"HIT ME" forState:UIControlStateNormal]; //设置 default 状态的字体颜色 [btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; //设置 default 状态背景图片 [btn setBackgroundImage:[UIImage imageNamed:@"btn_01"] forState:UIControlStateNormal]; //设置 highlighted 状态属性 [btn setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; [btn setBackgroundImage:[UIImage imageNamed:@"btn_02"] forState:UIControlStateHighlighted]; //把按钮添加到视图 [self.view addSubview:btn]; // 添加按钮的监听方法 (相当于连线) 当按钮发生了 Touch Inside 事件时调用 click 方法 //传进来的第一个参数是它自己本身,因此会把自己传给 button [btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside]; //替代此前的 headImageView 使其指向新的 UIButton self.headImageView = btn; } |
然后添加 click: 方法
1 2 3 4 |
- (void)click:(UIButton *)button { NSLog(@" Being click by %@",button); } |
这样的话便可以实现新的按钮的动态点击、移动旋转放缩的功能了.
想获得去掉 5 元限制的证券账户吗?

如果您想去掉最低交易佣金 5 元限制,使用微信扫描左边小程序二维码,访问微信小程序「优财助手」,点击底部菜单「福利」,阅读文章「通过优财开证券账户无最低交易佣金 5 元限制」,按照文章步骤操作即可获得免 5 元证券账户,股票基金交易手续费率万 2.5。
请注意,一定要按照文章描述严格操作,如错误开户是无法获得免 5 元证券账户的。