仿新版ofo共享單車小黃人動態效果

賣報的小畫家Sure發表於2017-07-05

前言
最近升級小黃車到最新版本,發現ofo與小黃人合作,生產了一批小黃人版小黃車,甚是可愛~,並且App的介面也進行了相應的效果改變,用車按鈕變成了小黃人的頭像,小黃人的眼睛還可以跟隨裝置的傾斜進行轉動。很是靈動啊!為小黃車的PM點個贊!

ofo效果
ofo效果

程式碼實現效果
程式碼實現效果

作為一隻程式猿,心心念唸的當然是如何實現這個效果!剛好最近工作不太忙,遂決定手動仿照下~純屬娛樂,大神勿噴。若有更好的實現方式請聯絡我。

正文
首先在iTunes上下載了ofo的ipa,解壓拿到了素材檔案。

素材
素材

本以為還要為背景做個模糊的陰影效果,沒想到素材中背景已經新增好陰影,那這部就可以略掉啦。看來ofo的設計大大們對於程式猿們還是很貼心的。

首先我們自定義View,新增上小黃人的臉並給他戴上眼鏡。

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self addSubview:self.backgroundImageView];
        [self addSubview:self.grassImageView];
    }
    return self;
}
- (UIImageView*)backgroundImageView {
    if (!_backgroundImageView) {
        _backgroundImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"minions_background"]];
    }
    return _backgroundImageView;
}
- (UIImageView*)grassImageView {
    if (!_grassImageView) {
        _grassImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"minions_grass"]];
    }
    return _grassImageView;
}
- (void)layoutSubviews {
    [super layoutSubviews];
    _backgroundImageView.frame = self.bounds;
    _grassImageView.frame = self.bounds;
}複製程式碼

這部分程式碼很基礎,沒什麼要說的,懶載入宣告控制元件,在layoutSubviews中調解對應座標即可。
通過如上程式碼我們目前可以實現如下效果

效果圖
效果圖

接下來就到最重要的部分啦,就是為小黃人新增眼睛,並且使眼球可以跟隨裝置傾斜而轉動。

我們公開類SureMinionsEyesView用於書寫眼睛檢視。對於眼睛跟隨裝置傾斜而轉動的效果,我們需要通過重力感應來實現。對於重力感應,我們需要使用iOS中的CoreMotion框架。其中包括加速計、陀螺儀、磁力計等。程式碼中我們需要使用CMMotionManager物件進行響應功能的實現。

CMMotionManager的基本使用步驟為例項化->判斷功能是否可用->設定更新頻率->開啟更新監測。程式碼如下,已新增必要註釋

@property (nonatomic, strong) CMMotionManager *motionManager;

_motionManager = [[CMMotionManager alloc] init];
//判斷加速計是否可用
if ([_motionManager isAccelerometerAvailable]){
    //設定更新頻率0.01為100HZ
    _motionManager.accelerometerUpdateInterval = 0.01;
    //開啟更新監測
    [_motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
        if (!error) {

        } else {
             NSLog(@"%@",error);
        }
    }];
}複製程式碼

withHandler這個Block中的accelerometerData引數可獲得當前裝置x,y,z軸的加速度。因此我們可以根據當前加速度動態的更新眼球的位置,並且需要保證眼球不會超出眼眶的範圍。

//眼球將要移動到的x軸座標
CGFloat pointX = _eyesImageView.center.x + accelerometerData.acceleration.x * _velocity;
//眼球將要移動到的y軸座標
CGFloat pointY = _eyesImageView.center.y - accelerometerData.acceleration.y * _velocity;
//限制眼球不能超出眼眶範圍
if (pointX < _eyesImageView.bounds.size.width / 2) {
    pointX = _eyesImageView.bounds.size.width / 2;
} else if(pointX > self.bounds.size.width - _eyesImageView.bounds.size.width / 2){
    pointX = self.bounds.size.width - _eyesImageView.bounds.size.width / 2;
}
if (pointY < _eyesImageView .bounds.size.height / 2) {
    pointY = _eyesImageView.bounds.size.height / 2;
}else if (pointY > self.bounds.size.height - _eyesImageView.bounds.size.height / 2){
    pointY = self.bounds.size.height - _eyesImageView.bounds.size.height / 2;
}
//更新眼睛位置
_eyesImageView.center = CGPointMake(pointX, pointY);複製程式碼

通過如上程式碼我們即可讓眼球跟隨裝置傾斜而移動,並且將其限制在眼眶範圍內,但是存在的問題是,當前眼眶也就是self為正方形,因此當眼球移動到邊角位置時就會出現如下效果,明顯效果很不美觀。

眼球處於眼眶邊角位置
眼球處於眼眶邊角位置

因此我們需要將眼球的移動範圍再加以限制,使得其移動在圓形的範圍中。具體的實現為通過UIBezierPath繪製圓,判斷當前眼球的中心點是否在圓內,若在圓外獲取當前眼球中心點和圓點之間的直線,得到直線與當前圓的交點座標。這一部分耗費了一些時間,畢竟一些數學知識早已還給老師們了。。。這裡大家可以仔細想想,思路如圖:

思路圖
思路圖

程式碼如下

//半徑
CGFloat r = self.bounds.size.width / 2 - self.eyesImageView.bounds.size.width / 2;
//圓點
CGPoint center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.width / 2);
//當前眼睛中心點
CGPoint currentPoint = _eyesImageView.center;

UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.eyesImageView.bounds.size.width / 2, self.eyesImageView.bounds.size.width / 2, self.bounds.size.width  - self.eyesImageView.bounds.size.width , self.bounds.size.width - self.eyesImageView.bounds.size.width)];
//判斷眼睛是否在圓內
if (CGPathContainsPoint(path.CGPath, NULL, self.eyesImageView.center, NO)) {
    //在圓內
}else{//在圓外
    CGFloat distance = sqrt(pow(center.x - currentPoint.x, 2) + pow(currentPoint.y - center.y, 2));

    CGFloat x = center.x - r / distance * (center.x - currentPoint.x);
    CGFloat y = center.y + r / distance * (currentPoint.y - center.y);

    self.eyesImageView.center = CGPointMake(x, y);
}複製程式碼

通過如上程式碼的再次限制,我們即可實現眼球在圓形範圍內移動的效果了。最終效果圖如下。

最終效果圖.gif
最終效果圖.gif

另外ofo客戶端中眼球自身還可進行旋轉,初步猜測是使用了重力感應中的陀螺儀,可監測當前裝置x,y,z軸的角度變化,從而更新眼球的旋轉角度,時間和精力有限就沒有進行實現,有興趣的童鞋可以進行嘗試哦~demo中獲取角度變化程式碼已新增,可開啟註釋進行測試。

_motionManager.gyroUpdateInterval = 0.01;
[_motionManager startGyroUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMGyroData * _Nullable gyroData, NSError * _Nullable error) {
    NSLog(@"%f%f%f",gyroData.rotationRate.x,gyroData.rotationRate.y,gyroData.rotationRate.z);
}];複製程式碼

暫時寫到這裡,demo已上傳GitHub,需要的童鞋可進行下載
github.com/LSure/SureO…
⚠️注意:demo需真機執行~娛樂製作~喜歡的可以點個贊或者關注我。比心(。・ω・。)ノ♡
簡書地址:www.jianshu.com/u/57d9688d4…

補充
對於小黃人眼球自身的轉動效果還可以通過iOS中的物理引擎UIDynamic進行實現,具體即為眼球新增重力行為和碰撞行為,然後更改重心位置即可。簡單寫了個demo,可供參考。暫未整合進原始demo中~因為還要計算當前重心位置。發呆(━┳━ ━┳━)。精力有限,感興趣的童鞋繼續整合吧。
demo地址github.com/LSure/SureO…
demo效果,不要被嚇到啊,哈哈~

眼睛轉動特效.gif
眼睛轉動特效.gif

暫時寫到這裡~

相關文章