每当第一次打开某个应用时,总会出现一个引导页,引导用户怎么使用这一程序。这个功能是非常实用的。当然根据要求不同,该功能可以做得非常简单直白或者炫酷好看。今天试着自己封装了一下这个功能。
一、目前实现的功能:
今天早上还雄心勃勃想写将这个功能得比较完善,不过由于课程略多(今天上日语默写五十音图跪了)、时间较少(主要还是懒- -|)等因素,目前只实现了部分功能。主要有以下几点:
1、只要用户提供三个数组(背景图片、图片、文字介绍)就能将该引导页展示出来。
2、提供四种动画选项显示该引导页,包括无动画、淡入、从下到上弹出,以及一个3d的混合动画。
3、用户可以随时Skip该引导页。
4、拖到最后一页继续拖的时候将导航页隐藏,展示正文
未实现或不完整的功能:
1、切换时动画,即用户用手拖动界面时的动画。目前只是生硬的改变每个引导页的透明度,效果欠佳。至于复杂的动画更是木有实现。。
2、错误处理、接口设置并不好,由于并未自习测试,可能会有bug,而且类封装得并不完美,例如:用户提供的三个数组必须有相同的count,否则无法正常显示该引导页。
留到以后再改改好了,但愿以后还能捡起来- -
二、效果图
放出渣渣效果,意思意思就好了,至少功能有了
三、思路与接口设计
引导页主要是通过ScrollView来实现的,将每个引导页封装成一个Item类,将每个Item依次放到该ScrollView上并设置scrollview的相关属性即可。而拖动过程中的一系列操作,例如动画等则是通过scrollView的协议方法来实现的。
先来看看IntroductionItem
@class IntroductionItem;
@protocol IntrodectionItemDelegate <NSObject>
- (void)skipButtonTappedInItem:(IntroductionItem *)item;
@end
@interface IntroductionItem : UIView
@property (weak, nonatomic) id<IntrodectionItemDelegate> delegate;
@property (strong, nonatomic) UIImage *bgImage;
@property (strong, nonatomic) UIImage *mainImage;
@property (strong, nonatomic) NSString *textToShow;
@property (strong, nonatomic) UIImageView *bgImageView;
@property (strong, nonatomic) UIImageView *mainImageView;
@property (strong, nonatomic) UILabel *textLabel;
- (id)initWithBGImage:(UIImage *)aBGImage mainImage:(UIImage *)aMianImage contentText:(NSString *)aText;
既然要封装,就要将留给用户的信息,也就是用户可以设置的信息暴露出来。这里其实下面三个View并非直接面向用户的,而是面向接下来的IntroductionView的。用户提供的是上面三个,即背景图片、介绍用图片以及介绍用文字。同时为了在每个Item下发加上SKIP按钮来跳过引导页,为其定义协议,准备让其委托来实现。
接着是IntroductionView类,也是最主要的类
typedef enum : NSUInteger {
CYZAnimationTypeNone,
CYZAnimationType3D,
CYZAnimationTypeDownToUp,
CYZAnimationTypeOpaque
} CYZAnimationType;
@interface IntroductionView : UIView
@property (strong, nonatomic) NSArray *allBGImages;
@property (strong, nonatomic) NSArray *allMainImages;
@property (strong, nonatomic) NSArray *allTitles;
- (id)initWithFrame:(CGRect)frame
allBGImagesArray:(NSArray *)bgImages
allMainImageArray:(NSArray *)mainImages
allTitleArray:(NSArray *)titles;
- (void)showIntroductionItemsWithAnimationType:(CYZAnimationType)animationType;
- (void)setIntroductionAlpha:(CGFloat)alpha;
- (void)hideIntroductionView;
这里主要有三个public方法,一个是展示该引导页,参数为展示的动画类型,对应的一个为隐藏该引导页。另外,还有一个设置透明度的方法,单拿出来的目的是能保证在动画中不会改变用户设置的透明度,其实这是设计问题,待改进。
四、关键代码
其实这两个类的实现都比较容易,Item类只需要将三个View初始化并设置好相关属性即可。View类里主要说下展示动画的方法
- (void)showIntroductionItemsWithAnimationType:(CYZAnimationType)animationType
{
if (animationType == CYZAnimationTypeNone) {
self.alpha = _settingAlpha;
} else if (animationType == CYZAnimationTypeDownToUp) {
self.layer.transform = CATransform3DMakeTranslation(0, self.frame.size.height, 1.0);
[UIView animateWithDuration:kAnimationDuration1x animations:^{
self.alpha = _settingAlpha;
self.layer.transform = CATransform3DIdentity;
}];
} else if (animationType == CYZAnimationTypeOpaque) {
[UIView animateWithDuration:kAnimationDuration1x animations:^{
self.alpha = _settingAlpha;
}];
} else {
CATransform3D transform = self.layer.transform;
transform.m34 = 2.0 / 500.0;
//self.layer.transform = CATransform3DMakeTranslation(-self.frame.size.width, 0, 1.0f);
self.center = CGPointMake(0, self.frame.size.height / 2);
[UIView animateWithDuration:kAnimationDuration3x animations:^{
self.center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
self.alpha = _settingAlpha;
self.layer.transform = CATransform3DMakeScale(0.7f, 0.7f, 1.0f);
self.layer.transform = CATransform3DRotate(self.layer.transform, M_PI / 4, 1.0f, 0.0f, 0.0f);
//self.layer.transform = CATransform3DMakeTranslation(0, 0, 1.0f);
} completion:^(BOOL finished) {
if (finished) {
[UIView animateWithDuration:kAnimationDuration3x animations:^{
self.layer.transform = CATransform3DIdentity;
}];
}
}];
}
}
在最后一个case,也就是3d动画中,我们先取得layer的transform属性,设置其m34为一合理值即可在接下来的变换中产生一定的3d效果(刚看别人代码学得,至于为什么,同求解= =)。然后对其进行变换(移动、缩放、旋转)即可。最后不要忘记在完成的时候在让其恢复成Identity。
隐藏引导页则只需将该View的透明度设为0并从父视图中移除,这里添加了动画效果,即让其在消失的同时适度放大。
- (void)hideIntroductionView
{
[UIView animateWithDuration:kAnimationDuration2x animations:^{
self.layer.transform = CATransform3DMakeScale(1.2f, 1.2f, 1.0f);
self.alpha = 0.0f;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
对于那个生硬的Item间透明度变换效果就是在scrollView的协议方法 scrollViewDidScroll:中根据contentOffset来实现的,我相信那些好看的效果也是通过这个思路来实现的(虽然我没能成功)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (_hasFinished) {
return;
}
//当引导页处于第一页时,阻止用户右划
if (scrollView.contentOffset.x < 0) {
scrollView.scrollEnabled = NO;
} else {
scrollView.scrollEnabled = YES;
}
NSInteger allPages = self.allTitles.count;
NSInteger i_newPage = (NSInteger)scrollView.contentOffset.x / scrollView.frame.size.width;
CGFloat f_newPage = scrollView.contentOffset.x / scrollView.frame.size.width;
CGFloat settingAlpha = f_newPage - i_newPage;
if (f_newPage > allPages - 1) {
[self hideIntroductionView];
_hasFinished = YES;
return;
}
IntroductionItem *currentItem = (IntroductionItem *)[self.itemArray objectAtIndex:_currentPage];
IntroductionItem *newItem;
if (_currentPage > i_newPage) { //往右划,展示上一个界面
newItem = (IntroductionItem *)[self.itemArray objectAtIndex:i_newPage];
currentItem.alpha = settingAlpha;
newItem.alpha = 1.0 - settingAlpha;
} else if (_currentPage == i_newPage) { //往左划,展示下一个界面
newItem = (IntroductionItem *)[self.itemArray objectAtIndex:i_newPage + 1];
newItem.alpha = settingAlpha;
currentItem.alpha = 1.0 - settingAlpha;
}
}
这样就能简单地实现一个引导功能了,在上面添加上合适的图片也能产生很好看的画面的。更好的实现可以搜索源码学习一下,通过这个简单的效果可以熟悉scrollview和animation相关知识。