当前位置: 首页 > 编程日记 > 正文

【iOS】自定义控件入门:可拖动的环形进度

有时候UIKit的标准控件并不能满足我们的需求,因此我们可以通过自定义控件得到满足我们需求的控件,例如这篇文章将教你如何自定义一个圆形的进度条,并且用户可以通过拖动进度条上的手柄来改变进度值。主要参考了这篇文章:HOW TO BUILD A CUSTOM CONTROL IN IOS。广告时间:我的一个免费APP:午睡闹钟 使用了这个控件,欢迎大家在AppStore搜索午睡闹钟进行下载使用。

我们的自定义控件继承自UIControl类,它是UIView的子类,是所有UIKit控件(UIButton, UISlider, UISwitch等等等)的父类。UIControl实例的作用是创建相应的逻辑来将action分发到相应的target,90%的情况下,它会根据自身的状态(例如Highlighted, Selected和Disabled等)来绘制用户界面。

UIControl主要完成三个重要任务:

  • 绘制用户界面
  • 跟踪用户的交互操作
  • Target-Action模式

因此,对于这篇文章中要完成的圆形进度条,我们将要完成以下任务:

绘制一个用户可以通过拖动手柄滑块来进行交互的用户界面,用户的操作会被转化为target对应的actions,控件将滑块的frame origin转换为0-360之间的一个值,并用于target/action上。下面将分三步进行讲解,这三步对应上面提到的三个重要任务。

1.1绘制用户界面


如图,我们将通过Core Graphics来绘制灰色的进度条背景、红色的进度条、滑块手柄,关于Core Graphics的基础知识,本文不作详细介绍.。

- (void)drawRect:(CGRect)rect {[super drawRect:rect];CGContextRef context = UIGraphicsGetCurrentContext();//1.绘制灰色的背景CGContextAddArc(context, self.frame.size.width/2, self.frame.size.height/2, radius, 0, M_PI*2, 0);[[UIColor grayColor] setStroke];CGContextSetLineWidth(context, _lineWidth);CGContextSetLineCap(context, kCGLineCapButt);CGContextDrawPath(context, kCGPathStroke);//2.绘制进度CGContextAddArc(context, self.frame.size.width/2, self.frame.size.height/2,radius,0, ToRad(_angle), 0);[[UIColor redColor] setStroke];CGContextSetLineWidth(context, _lineWidth);CGContextSetLineCap(context, kCGLineCapRound);CGContextDrawPath(context, kCGPathStroke);//3.绘制拖动小块CGPoint handleCenter =  [self pointFromAngle: (self.angle)];CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 3,[UIColor blueColor].CGColor);[[UIColor redColor] setStroke];CGContextSetLineWidth(context, _lineWidth*2);CGContextAddEllipseInRect(context, CGRectMake(handleCenter.x, handleCenter.y, _lineWidth*2, _lineWidth*2));CGContextDrawPath(context, kCGPathStroke);
}

其中pointFromAngle方法是从给定的角度得到圆环上对应的经纬度,只是简单的使用了一下基本的三角函数关系:
-(CGPoint)pointFromAngle:(int)angleInt{//中心点CGPoint centerPoint = CGPointMake(self.frame.size.width/2 - _lineWidth, self.frame.size.height/2 - _lineWidth);//根据角度得到圆环上的坐标CGPoint result;result.y = round(centerPoint.y + radius * sin(ToRad(angleInt))) ;result.x = round(centerPoint.x + radius * cos(ToRad(angleInt)));return result;
}


1.2跟踪用户的交互操作

完成了1.1,现在我们已经绘制好了用户界面,但是它还不能响应我们的触摸事件。我们只要重写(override)UIControl的三个方法,就可以跟踪用户操作。

1.2.1开始跟踪触摸事件

当用户在UIControl的bound内进行触摸,beginTrackingWithTouch方法会被调用,此方法会返回BOOL类型,返回Yes表示要继续跟踪触摸事件。

-(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{[super beginTrackingWithTouch:touch withEvent:event];return YES;
}

1.2.2 持续跟踪触摸时间

上面的方法返回了Yes,表示要继续跟踪触摸事件,所以当用户在屏幕上拖动时,continueTrackingWithTouch会被调用,该方法返回的BOOL值表示是否继续跟踪touch事件。通过该方法我们将根据用户触摸的位置更新手柄的位置。

-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{[super continueTrackingWithTouch:touch withEvent:event];//获取触摸点CGPoint lastPoint = [touch locationInView:self];//使用触摸点来移动小块[self movehandle:lastPoint];//发送值改变事件[self sendActionsForControlEvents:UIControlEventValueChanged];return YES;
}
其中,我们调用了movehandle:方法来更新滑动手柄的位置:

-(void)movehandle:(CGPoint)lastPoint{//获得中心点CGPoint centerPoint = CGPointMake(self.frame.size.width/2,self.frame.size.height/2);//计算中心点到任意点的角度float currentAngle = AngleFromNorth(centerPoint,lastPoint,NO);int angleInt = floor(currentAngle);//保存新角度self.angle = angleInt;//重新绘制[self setNeedsDisplay];
}

AngleFromNorth(CGPoint p1,CGPoint p2,BOOL flipped)方法是计算中心点到任意点的角度,

是从苹果是示例代码clockControl中拿来的函数,比较复杂(数学没学好...),就直接当作苹果提供的一个方法来调用就行。

static inline float AngleFromNorth(CGPoint p1, CGPoint p2, BOOL flipped) {CGPoint v = CGPointMake(p2.x-p1.x,p2.y-p1.y);float vmag = sqrt(SQR(v.x) + SQR(v.y)), result = 0;v.x /= vmag;v.y /= vmag;double radians = atan2(v.y,v.x);result = ToDeg(radians);return (result >=0  ? result : result + 360.0);
}

1.2.3结束跟踪

当结束跟踪时,这个方法会被调用,我们的例子中不需要override这个方法

现在,圆形滑块控件可以工作了,拖动滑动手柄看看。

1.3 Target-Action模式

如果希望自己定制的控件与UIControl行为保持一致,那么当控件的值发生变化时,需要进行通知处理:使用sendActionsForControlEvents方法,并制定特定的事件类型,值改变对应的事件一般是UIControlEventValueChanged。

苹果已经预定义了许多事件类型(Xcode中,在UIControlEventValueChanged上cmd + 鼠标单击)。如果你的控件是继承自UITextField,那么我们可能会对UIControlEventEdigitingDidBegin感兴趣,如果要做一个touch Up action,可以使用UIControlTouchUpInside。在本文前部分的continueTrackingWithTouch方法里面,我们已经调用了sendActionsForControlEvents方法。这样处理之后,当控件值发生变化时,每一个对象(观察者——注册该事件)都会收到响应的通知。


2.使用自定义控件

除了通过用户触摸事件来改变进度,有时我们还想通过代码进行修改,我们只要自己实现存储角度的属性angle的set方法:
-(void)changeAngle:(int)angle{_angle = angle;[self sendActionsForControlEvents:UIControlEventValueChanged];[self setNeedsDisplay];
}

好了,现在我们在viewcontroller中使用我们的控件:
    JXCircleSlider *slider = [[JXCircleSlider alloc] initWithFrame:CGRectMake(0, 0, 250, 250)];slider.center = self.view.center;[slider addTarget:self action:@selector(newValue:) forControlEvents:UIControlEventValueChanged];[slider changeAngle:120];[self.view addSubview:slider];

我们先进行初始化,然后设置为居中显示,注册了值改变响应事件,当进度值改变时,会调用一下方法
-(void)newValue:(JXCircleSlider*)slider{NSLog(@"newValue:%d",slider.angle);
}

项目源代码:https://github.com/dolacmeng/JXCircleSlider

最终效果如下:






相关文章:

在.NET2.0中解析Json和Xml

在.NET2.0中解析Json和Xml 在.NET解析json有很多方法,这里介绍最简单也用的最多的一种。 一、添加引用 解析Json,先下载开源控件 Newtonsoft.Json.dll 下载地址:http://files.cnblogs.com/gosky/Newtonsoft.Json%E9%9B%86%E5%90%88.zip 解压以…

虚拟机的基本操作

1、用户界面 [kioskfoundation156 Desktop]$ kiosk #打开shell的用户 #分隔符 foundation156 #主机名称 Desktop #工作目录名称 $ ##身份提示符,#表示超级用户,$表示普通用户 特别注意&a…

strong assign属性

strong:这要求运行时自动地保留对这个对象的引用。换而言之,ARC(Automatic Reference Counting)在运行时会一直把这个对象保留在内存里,直到它不再被任何其他对象引用。之后,其所占的内存会被自动释放。assign:表示这…

iOS7的界面上移问题

第一种方法:修改BaseSDK XCode5的默认BaseSDK是iOS7,所以要修改成工程文件创建时的BaseSDK。但是XCode5中默认只带有iOS7的SDK,所以要想能做到更改SDK,我们就要添加旧的SDK。 1.从苹果开发者中心下载旧版本XCode,https…

【Android】ActionBar的使用(1)

前(fei)言(hua):转行iOS开发半年,很久没接触Android了,前几天去上课,听着实在无聊,随手拿了同学的一本《Android UI设计》,发现有好多基础知识自己虽然用过&a…

装饰器及例题分析

知识点: 装饰器的定义: - 装饰器的实现是函数里面嵌套函数; - 装饰器的本质是一个函数, 它可以让其他函数在不需要做任何代码改动的前提下增加额外的功能; - 装饰器需要传递一个函数, 返回值也是一个函数对象. 1、map函数 def …

iOS开发系列--让你的应用“动”起来

概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建基础动画、关键帧动画、动画组、转场动画,如何通…

ios app 砸壳

这里介绍使用dumpdecrypted砸壳。原理是用DYLD_INSERT_LIBRARIES这个环境变量加载脱壳的动态链接库dumpdecrypted.dylib 1.ssh连接上越狱的机器,输入密码alpine ssh root192.168.7.116 2.打开要砸的app,ps aux | grep var找到它的目录 yigewangde-iPhone…

基于visual Studio2013解决面试题之0804复杂链表

题目解决代码及点评/*复杂链表的拷贝,现在有一个复杂链表,完成一个clone函数拷贝一个链表复杂链表是指struct Node{struct Node* _next;struct Node* _sibling; // sibling指向链表中任意一个节点,或者…

python考试编程题

3. a: while True: s raw_input(变量名为:) if s exit: print 退出 break #判断是否由字母或下划线组成 if s[0].isalpha() or s[0] _: for i in s[1:]: if not (i.isalnum() or i _): print %s变量…

【分享】bootstrap学习笔记

一、基础知识 1.整体架构以响应式设计为理念,css组件、js插件jquery、基础布局组件和12栅格系统搭建。1.1响应式设计:结合media query查询,适应更多设备,自动适应用户的设备环境,不必为每个终端做一个特定的版本。2.cs…

大三下学期总结

本学期的最后一门考试已经考完了,就相当于本学期要结束了,本学期结束了,就相当于大学的学习生活接近尾声了。感觉大三下开学也只在不久之前,但是真的要结束了,我觉得这学期实在是过得太充实了,一直是在追着…

通过 cygwin64 自己编译对应的 Tera Term cyglaunch.exe

步骤如下: 将 cygterm.tar.gz解压到任意目录,当然要cygwin容易操作。(本例直接放到$HOME目录下,启动cygwin后的默认目录,如果之前没有更改的话)将 Makefile 中的 -mno-cygwin 选项删除。执行make&#xff0…

面向对象概念及三大特点

面向对象: 面向对象的基本概念 面向对象 oop : object oriented programming 我们之前学习的编程方式就是面向过程的 面向过程和面向对象,是两种不同的编程方式 对比面向过程的特点,可以更好的了解什么是面向对象 过程和函数(都是对一段…

【Android】ViewPager实现无限循环滚动

最近做的一个项目,客户要求在ViewPager实现的主页面中滑动到最后一页后继续滑动能返回到第一页,也就是实现无限循环滚动,效果如下: 看了下ViewPager没有滑到尽头的回调方法,因此想到的解决方案是,在原来的最…

LabVIEW 三维机器人展示

本程序是使用solidworks绘制模型图,通过LabVIEW 2013导入wrl文件完成. 程序效果如下: (工程文件) (3维机器人模型) (控制界面,未做美化....因为实际上这部分程序只是一小部分,主程序运行时,这部分面板是不显示的~\(≧▽≦)/~啦啦啦) 程序下载: 百度网盘: http://pan.baidu.com/…

SqlServer2008查询性能优化_第一章

转载于:https://www.cnblogs.com/MiaoXin/p/5813207.html

Python之类

1、类的结构 1.使用面向对象开发,第一步是设计类 2.使用 类名() 创建对象,创建对象的动作有两步 1.在内存中为对象分配空间 2.调用初始化方法__init___ 为对象初始化 3.对象创建后,内存中就有了一个对象的实实在在的存在--实例 因此…

祝大家圣诞节快乐!

在圣诞节来临之际&#xff0c;分享这份网页的代码&#xff0c;祝愿大家圣诞节快乐&#xff01;点击这里查看效果 以下是源代码&#xff0c;可以保存到html文件打开看效果&#xff1a; 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ht…

【iOS官方文档翻译】UICollectionView与UICollectionViewFlowLayout

&#xff08;一&#xff09;先来简单回顾一下UICollectionView *UICollectionView的简单使用可以看我以前写的这篇博文&#xff1a;UICollectionView的基本使用 UICollectionView与UITableView很相似&#xff0c;必须实现两个代理&#xff1a;UICollectionViewDataSource和UI…

python之文件

1、什么是文件指针&#xff1f; 文件指针 文件指针标记从哪个位置开始读取数据 第一次打开文件时&#xff0c;通常文件指针会指向文件的开始位置 当执行了read方法后&#xff0c;文件指针会移动到读取内容的末尾 创建一个当前目录下的文件pass 内容为&#xff1a; westos …

一个6年的菜鸟,在4年之前做的一些功能(二)

前戏&#xff1a;针对上一篇列出来的功能点&#xff0c;今天和大家分享下这个自定义的公式是怎么设计的&#xff0c;由于我的第一篇博客在首页被管理员移走了&#xff0c;大家可以点击这里来跳转&#xff0c;看下第一篇的目录结构。本人作为老菜鸟&#xff0c;和大家分享的也是…

Oracle11.2新特性之listagg函数 (行列转换)

SELECT regexp_substr(公司1,贵公司2, [^,], 1, LEVEL, i) FROM dualCONNECT BY LEVEL < length(公司1,贵公司2) - length(REPLACE(公司1,贵公司2, ,, )) 1 以上为字符串带分隔符的转换为列 Oracle11.2新增了LISTAGG函数&#xff0c;可以用于字符串聚集&#xff0c;测试如下…

Mac 安装Android Studio

一、下载 可以通过以下这些网站下载Mac版的Android Studio http://www.androiddevtools.cn http://www.android-studio.org 二、安装 双击下载好的dmg文件&#xff0c;拖曳安装。 拷贝完成之后&#xff0c;在应用程序中打开Android Studio。这时候会引导进行环境安装和设置…

iscsi网络磁盘共享

iscsi(Internet SCSI)支持从客户端&#xff08;发起端&#xff09;通过IP向远程服务器上的ISCSI存储设备&#xff08;目标&#xff09;发送scsi命令。ISCSI限定名称用于确定发起端和目标&#xff0c;并采用iqn.yyy-mm.{reverse domain}:label的格式。 默认情况下&#xff0c;网…

转: IO设计模式:Reactor和Proactor对比

转: https://segmentfault.com/a/1190000002715832 平时接触的开源产品如Redis、ACE&#xff0c;事件模型都使用的Reactor模式&#xff1b;而同样做事件处理的Proactor&#xff0c;由于操作系统的原因&#xff0c;相关的开源产品也少&#xff1b;这里学习下其模型结构&#xff…

程序员该怎样放松?8个好网站推荐

1&#xff09;看风景程序员每天在液晶屏幕下工作很长时间&#xff0c;应该让干涩的眼睛得到好好的放松&#xff0c;偶尔看看窗外的绿树或远处的风景可能是不错的方式&#xff0c;但是这在北京上海广州这样的大城市里不太现实&#xff0c;所以这里推荐一个纯粹的分享旅行照片的网…

【iOS】UIButton 图标在上文字在下

iOS默认的UIButton是图片在左文字在右&#xff0c;但是很多时候需求是图片在上文字在下&#xff08;如效果图1、效果图2&#xff09;&#xff0c;因此想到&#xff0c;通过写一个JXButton继承自UIButton&#xff0c;并重写相关方法即可&#xff0c;以后项目中需要用的话就直接使…

多线程和mysql

多线程 1、多线程的定义&#xff1a; 什么是线程&#xff1a; 线程是操作系统能够进行运算调度的最小单位&#xff08;程序执行流的最小单元&#xff09;。它被包含在进程之中&#xff0c; 是进程中的实际运作单位。一个进程中可以并发多个线程&#xff0c;每条线程并行执行不…

poj2154-color-polyan次二面体+欧拉函数优化

N<1e9,O(nlogn)的做法会超时。从枚举置换转变为枚举轮换长度&#xff0c;然后可以利用欧拉函数&#xff0c;把复杂度变为O(√n * logn) 1 /*--------------------------------------------------------------------------------------*/2 3 #include <algorithm>4 #in…