前段时间做项目遇到播放器自定义进度条的需求,刚开始想的是继承自系统的UISlider去做,内部重写系统方法完成样式定制。后来遇到头疼的问题,在iphone6、7手机上样式显示正常,遇到plus手机进度条样式显示有问题,缓存进度跟最底部的进度高度不匹配,有偏移,调了很多次,都以徒劳告终,后来没办法,自己自定义了一套进度条的实现方案,配合iOS自有手势操作,完成基本功能操作。下面介绍下自定义实现类:
这块功能主要涉及到两个类文件,ZTCSlider,ZTCProgressView:
一、ZTCSlider类,负责进度条交互操作、进度条变化
ZTCSlider.h,介绍初始化方法,及相关操作回调,返回进度值;播放进度、缓存进度可通过外部调用setter方法传入
#import <UIKit/UIKit.h>
/**
获取滑动进度回调
@param value 滑动进度
*/
typedef void (^MGBroadcastGetSlideValueHandler)(CGFloat value);
/**
开始拖动进度块回调
*/
typedef void (^MGBroadcastSliderPanBeginHandler)(void);
/**
结束拖动进度块回调
@param value 进度值
*/
typedef void (^MGBroadcastSliderPanEndHandler)(CGFloat value);
/**
点击进度条某一部分
@param value 进度值
*/
typedef void (^MGBroadcastSliderTapSliderHandler)(CGFloat value);
@interface ZTCSlider : UIView
/**
滑条初始化方法
@param sliderWidth 滑块宽度
@param sliderColor 滑块颜色
@param progressHeight 进度条高度
@param progressBgColor 进度套背景色
@param progressPlayedColor 进度条播放后的颜色
@param progressCachedColor 进度条缓存的颜色
@param isShowCorner 是否设置进度条圆角效果
*/
- (instancetype)initWithSliderWidth:(CGFloat)sliderWidth
sliderColor:(UIColor *)sliderColor
progressHeight:(CGFloat)progressHeight
progressBgColor:(UIColor *)progressBgColor
progressPlayedColor:(UIColor *)progressPlayedColor
progressCachedColor:(UIColor *)progressCachedColor
showCorner:(BOOL)isShowCorner;
///播放值
@property (nonatomic, assign) CGFloat slideValue;
///缓存值
@property (nonatomic, assign) CGFloat cacheValue;
/**
开始拖动的操作
*/
@property (nonatomic, copy) MGBroadcastSliderPanBeginHandler panBeginHandler;
/**
结束拖动的操作
*/
@property (nonatomic, copy) MGBroadcastSliderPanEndHandler panEndHandler;
/**
获取进度值
*/
@property (nonatomic, copy) MGBroadcastGetSlideValueHandler getSlideValueHandler;
/**
点击进度条某个部分的操作
*/
@property (nonatomic, copy) MGBroadcastSliderTapSliderHandler tapSliderHandler;
@end
ZTCSlider.m
#import "ZTCSlider.h"
#import "ZTCProgressView.h"
@interface ZTCSlider()
///滑块
@property (nonatomic, strong) UIView *sliderView;
///h进度条
@property (nonatomic, strong) ZTCProgressView *progressView;
@property (nonatomic, strong) UIPanGestureRecognizer *panGesture;
@property (nonatomic, strong) UITapGestureRecognizer *tapGesture;
@property (nonatomic, assign) CGFloat sliderWidth;
@property (nonatomic, strong) UIColor *sliderColor;
@property (nonatomic, assign) CGFloat progressHeight;
@property (nonatomic, strong) UIColor *progressBgColor;
@property (nonatomic, strong) UIColor *progressPlayedColor;
@property (nonatomic, strong) UIColor *progressCachedColor;
@property (nonatomic, assign) BOOL isShowCorner;
@end
@implementation ZTCSlider
- (instancetype)initWithSliderWidth:(CGFloat)sliderWidth
sliderColor:(UIColor *)sliderColor
progressHeight:(CGFloat)progressHeight
progressBgColor:(UIColor *)progressBgColor
progressPlayedColor:(UIColor *)progressPlayedColor
progressCachedColor:(UIColor *)progressCachedColor
showCorner:(BOOL)isShowCorner {
self = [super init];
if (self) {
_sliderWidth = sliderWidth;
_sliderColor = sliderColor;
_progressHeight = progressHeight;
_progressBgColor = progressBgColor;
_progressPlayedColor = progressPlayedColor;
_progressCachedColor = progressCachedColor;
_isShowCorner = isShowCorner;
[self setUpUI];
}
return self;
}
- (void)setUpUI {
[self addSubview:self.progressView];
[self addSubview:self.sliderView];
self.userInteractionEnabled = YES;
[self addGestureRecognizer:self.panGesture];
[self addGestureRecognizer:self.tapGesture];
}
- (void)setSlideValue:(CGFloat)slideValue {
if (_slideValue != slideValue) {
_slideValue = slideValue;
CGRect frame = self.sliderView.frame;
frame.origin.x = _slideValue *(CGRectGetWidth(self.bounds) - CGRectGetWidth(self.sliderView.bounds));
self.progressView.playValue = _slideValue;
}
}
- (void)setCacheValue:(CGFloat)cacheValue {
if (_cacheValue != cacheValue) {
_cacheValue = cacheValue;
self.progressView.cacheValue = cacheValue;
}
}
- (void)tapGesture:(UITapGestureRecognizer *)tap {
CGPoint point = [tap locationInView:tap.view];
if (point.x <= _sliderWidth / 2) {
point.x = _sliderWidth / 2;
}
if (point.x > CGRectGetWidth(self.bounds) - _sliderWidth / 2) {
point.x = CGRectGetWidth(self.bounds) - _sliderWidth / 2;
}
CGPoint center = self.sliderView.center;
center.x = point.x;
self.sliderView.center = center;
CGRect frame = self.sliderView.frame;
CGFloat value = frame.origin.x / (CGRectGetWidth(self.bounds) - CGRectGetWidth(self.sliderView.bounds));
self.progressView.playValue = value;
if (self.tapSliderHandler) {
self.tapSliderHandler(value);
}
}
- (void)panGesture:(UIPanGestureRecognizer *)pan {
//移动的距离
CGPoint point = [pan translationInView:pan.view];
CGRect frame = self.sliderView.frame;
frame.origin.x += point.x;
if (frame.origin.x + CGRectGetWidth(frame) >= CGRectGetWidth(self.bounds)) {
frame.origin.x = CGRectGetWidth(self.bounds) - CGRectGetWidth(frame);
}
if (frame.origin.x <= 0) {
frame.origin.x = 0;
}
self.sliderView.frame = frame;
if (pan.state == UIGestureRecognizerStateBegan) {
if (self.panBeginHandler) {
self.panBeginHandler();
}
}
CGFloat value = frame.origin.x / (CGRectGetWidth(self.bounds) - CGRectGetWidth(frame));
self.progressView.playValue = value;
if (self.getSlideValueHandler) {
self.getSlideValueHandler(value);
}
if (pan.state == UIGestureRecognizerStateEnded) {
if (self.panEndHandler) {
self.panEndHandler(value);
}
}
[pan setTranslation:CGPointZero inView:pan.view];
}
-(void)layoutSubviews {
[super layoutSubviews];
_progressView.frame = CGRectMake(0, (CGRectGetHeight(self.frame) - _progressHeight) * 0.5, CGRectGetWidth(self.frame), _progressHeight);
_progressView.layer.cornerRadius = _isShowCorner ? _progressHeight * 0.5 : 0;
_sliderView.frame = CGRectMake(0, (CGRectGetHeight(self.frame) - _sliderWidth) / 2, _sliderWidth, _sliderWidth);
}
- (ZTCProgressView *)progressView {
if (!_progressView) {
_progressView = [[ZTCProgressView alloc] initWithProgressBgColor:_progressBgColor
progressPlayedColor:_progressPlayedColor
progressCachedColor:_progressCachedColor];
_progressView.clipsToBounds = YES;
}
return _progressView;
}
- (UIPanGestureRecognizer *)panGesture {
if (!_panGesture) {
_panGesture =[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
}
return _panGesture;
}
- (UITapGestureRecognizer *)tapGesture {
if (!_tapGesture) {
_tapGesture =[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
_tapGesture.numberOfTapsRequired = 1;
}
return _tapGesture;
}
- (UIView *)sliderView {
if (!_sliderView) {
_sliderView =[[UIView alloc] init];
_sliderView.backgroundColor = _sliderColor ?: [UIColor whiteColor];
_sliderView.layer.cornerRadius = _sliderWidth * 0.5;
_sliderView.clipsToBounds = YES;
_sliderView.layer.shadowOpacity = 0.8;
_sliderView.layer.shadowOffset = CGSizeMake(0, 2);
_sliderView.layer.shadowColor = [UIColor blackColor].CGColor;
}
return _sliderView;
}
@end
二、ZTCProgressView,负责进度条的显示样式
ZTCProgressView.h
#import <UIKit/UIKit.h>
@interface ZTCProgressView : UIView
/**
设置进度条样式
@param progressBgColor 进度条背景色,最底部
@param progressPlayedColor 进度条已经播放的颜色
@param progressCachedColor 视频已缓存部分的颜色
*/
- (instancetype)initWithProgressBgColor:(UIColor *)progressBgColor
progressPlayedColor:(UIColor *)progressPlayedColor
progressCachedColor:(UIColor *)progressCachedColor;
///播放进度
@property (nonatomic, assign) CGFloat playValue;
///加载进度
@property (nonatomic, assign) CGFloat cacheValue;
@end
ZTCProgressView.m
#import "ZTCProgressView.h"
@interface ZTCProgressView()
@property (nonatomic, strong) UIView *cacheView;
@property (nonatomic, strong) UIView *playProgressView;
@property (nonatomic, strong) UIColor *progressBgColor;
@property (nonatomic, strong) UIColor *progressPlayedColor;
@property (nonatomic, strong) UIColor *progressCachedColor;
@end
@implementation ZTCProgressView
- (instancetype)initWithProgressBgColor:(UIColor *)progressBgColor
progressPlayedColor:(UIColor *)progressPlayedColor
progressCachedColor:(UIColor *)progressCachedColor {
self = [super init];
if (self) {
_progressBgColor = progressBgColor;
_progressPlayedColor = progressPlayedColor;
_progressCachedColor = progressCachedColor;
[self setUpUI];
}
return self;
}
- (void)setUpUI {
self.backgroundColor = _progressBgColor;
[self addSubview:self.cacheView];
[self addSubview:self.playProgressView];
}
- (void)setPlayValue:(CGFloat)playValue {
_playValue = playValue;
CGRect frame = self.playProgressView.frame;
frame.size.width = playValue * CGRectGetWidth(self.bounds);
self.playProgressView.frame = frame;
}
- (void)setCacheValue:(CGFloat)cacheValue {
_cacheValue = cacheValue;
CGRect frame = self.cacheView.frame;
frame.size.width = cacheValue * CGRectGetWidth(self.bounds);;
self.cacheView.frame = frame;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.playProgressView.frame = CGRectMake(0, 0, _playValue * CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
self.cacheView.frame = CGRectMake(0, 0, _cacheValue * CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
}
- (UIView *)cacheView {
if (!_cacheView) {
_cacheView = [[UIView alloc] init];
_cacheView.backgroundColor = _progressCachedColor;
}
return _cacheView;
}
- (UIView *)playProgressView {
if (!_playProgressView) {
_playProgressView =[[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, self.bounds.size.height)];
_playProgressView.backgroundColor = _progressPlayedColor;
}
return _playProgressView;
}
@end
三、ZTCSlider实现调用:
ZTCSlider *slider = [[ZTCSlider alloc] initWithSliderWidth:25 sliderColor:[UIColor whiteColor] progressHeight:5 progressBgColor:[UIColor darkGrayColor] progressPlayedColor:[UIColor blueColor] progressCachedColor:[UIColor lightGrayColor] showCorner:YES];
[slider setPanBeginHandler:^{
NSLog(@"开始拖动");
}];
[slider setTapSliderHandler:^(CGFloat value) {
NSLog(@"点击位置:%f", value);
}];
[slider setGetSlideValueHandler:^(CGFloat value) {
NSLog(@"获取进度:%f", value);
}];
[slider setPanEndHandler:^(CGFloat value) {
NSLog(@"拖动结束位置:%f", value);
}];
slider.frame = CGRectMake(50, 200, 275, 35);
slider.backgroundColor = [UIColor orangeColor];
[self.view addSubview:slider];
注:实际开发中,进度条大多会跟播放器同步使用,ZTCSlider类中的进度条操作回调传值可用来控制播放器的进度。
显示效果:
说明:这里的橙色是创建的slider的实际大小,所以对于系统的UISlider手势操作范围小的问题,也完美的解决了;蓝色是已经播放的进度,深灰色是背景颜色,缓存进度由于我这里没有嵌入播放器,没有展示出来。可根据需要嵌入播放器中来加载显示缓存进度。