可互動的有趣的LoadingView

佛系programer發表於2018-12-09

前言(廢話)

之前,公司專案剛上線,有點空閒期,於是就想做點優化。使用者在使用APP操作的時候,很多時候會有操作等待,這個時候如果不進行處理,給使用者的感覺就是APP卡頓,所以基本上我們都會在使用者等待的時候加一個loading檢視。心塞的是Google爸爸原生的loading是一個菊花效果,醜的一批,我相信大家都見識過。而且配上自己家APP的風格,完全不搭啊有木有~所以現在很多流行的APP都有一個具有自己產品的風格的loading,因此風格各異。很少有通用且能增加使用者體驗的一個效果。然後偶然看到了CatLoadingView這個專案,覺得很有趣。但是通過閱讀原始碼,我覺得作者有一些地方存在一些問題,例如在不同的機型上動畫效果會出現差異,再者,對於開源來說,給使用者的自定義擴充套件程度不高;而且我覺得,這種效果要是能再多點使用者互動就會錦上添花更完美了,思索了片刻之後,擼起袖子就是幹!

概述

CatMouseView是一個可與使用者互動的有趣的貓鼠動畫自定義View,可用於使用者操作時的loading等場景。在動畫過程中,使用者可點選螢幕捕捉老鼠,隨著老鼠被抓住的次數增加,遊戲的難度會逐步遞增(老鼠跑動速度加快),以此來增加使用者體驗效果,避免使用者等待的無聊。效果圖如下

image

安裝

Step 1. Add the JitPack repository to your build file

allprojects {
  repositories {
    ...
    maven { url 'https://jitpack.io' }
  }
}
複製程式碼

Step 2. Add the dependency

dependencies {
  implementation 'com.github.HeYongRui:CatMouseView:v1.0.0'
}
複製程式碼

分析

通過檢視CatLoadingView的原始碼,理解了作者的思路,整個效果第一眼看起來覺得會很複雜,其實經過拆分,就變得很簡單。其實就是利用動畫對老鼠和貓眼進行同步旋轉。眼瞼的部分就是一個自定義矩形view根據貓眼的旋轉角度動態更新眼瞼高度即可,這樣就可以避免CatLoadingView中出現的機型適配問題了。然後就是使用者互動點選螢幕捕捉老鼠了。這裡也是利用了一個自定義view,將其寬高設為和老鼠圖示一樣大小,然後通過點選事件的處理判斷使用者點選的是否在一個和老鼠重疊的圓環中,然後利用角度差進行判斷是否點中老鼠。點中後,動畫暫停,展示捕捉成功的UI,停留兩秒後再次播放動畫,並加快旋轉速度,提升遊戲難度。底部是漸顯提示文字,使用者可控制顯示狀態。

實現

首先設定貓眼和老鼠的同步旋轉動畫。因為抓住老鼠後需要接著上次的動畫繼續播放,所以我這裡用的是屬性動畫,可以直接儲存view更改的狀態,獲取到當前的旋轉角度,然後設定為旋轉一週(360°)的無線迴圈動畫。動畫時長會隨著老鼠被抓次數的增加而減短,每次縮短500毫秒,直至最短時間。部分虛擬碼如下:

  float rotation = mouse.getRotation();
  rotation = rotation > 720 ? rotation % 720 : rotation;
  ObjectAnimator mouseRotationAnim = ObjectAnimator.ofFloat(mouse, "rotation", 360f + rotation, rotation);
  long duration = mAnimDuration - mCatchTime * 500;//根據抓住老鼠的次數動態設定旋轉時間,提升遊戲難度,每次加快500毫秒
  duration = duration < 400 ? 400 : duration;//控制最快時間為400毫秒
  mouseRotationAnim.setDuration(duration);//設定動畫時間
  mouseRotationAnim.setInterpolator(new LinearInterpolator());//動畫時間線性漸變
  mouseRotationAnim.setRepeatCount(ObjectAnimator.INFINITE);
  mouseRotationAnim.setRepeatMode(ObjectAnimator.RESTART);
  ObjectAnimator leftEyeRotationAnim = mouseRotationAnim.clone();
  leftEyeRotationAnim.setTarget(eyeLeft);
  ObjectAnimator rightEyeRotationAnim = mouseRotationAnim.clone();
  rightEyeRotationAnim.setTarget(eyeRight);
  mAnimatorSet = new AnimatorSet();
  mouseRotationAnim.addUpdateListener(valueAnimator -> {
        mRotateAngle = (float) valueAnimator.getAnimatedValue();
        updateEyelidHeight(mRotateAngle);
    });
  mAnimatorSet.playTogether(mouseRotationAnim, leftEyeRotationAnim, rightEyeRotationAnim);
  mAnimatorSet.start();
複製程式碼

然後是貓眼瞼的部分,貓眼瞼就是一個簡單的自定義view,覆蓋在貓眼的上半部分即可,然後根據貓眼的旋轉角度動態設定貓眼瞼的高度,這裡由於旋轉動畫暫停後可以繼續從當前位置再繼續旋轉,所以週期為720°,對旋轉角度處理後,當貓眼的處理後旋轉角度在60°~180°(右眼上半部分)的時候,眼瞼高度需要逐漸減小,當處理後旋轉角度在180°~300°(左眼上半部分)時,眼瞼高度逐漸增大;其餘部分眼瞼高度不變為最高。部分虛擬碼如下:
rotateAngle = rotateAngle > 360 ? rotateAngle % 360 : rotateAngle;
if (60 <= rotateAngle && rotateAngle < 180) {
    eyelidLeft.setEyelidHeightPercent((180 - rotateAngle) / 120f);
    eyelidRight.setEyelidHeightPercent((180 - rotateAngle) / 120f);
    return;
}
if (180 <= rotateAngle && rotateAngle <= 300) {
    eyelidLeft.setEyelidHeightPercent(1 - (300 - rotateAngle) / 120f);
    eyelidRight.setEyelidHeightPercent(1 - (300 - rotateAngle) / 120f);
    return;
}
eyelidLeft.setEyelidHeightPercent(1);
eyelidRight.setEyelidHeightPercent(1);
複製程式碼

然後就是手勢互動的部分了,這裡也是自定義一個和老鼠view一樣大小和位置的隱形view,然後判斷點選的範圍是否在老鼠所處的重疊圓環中,是就在點選處繪製一個貓爪,並利用動畫旋轉角度和貓爪點選角度比較,在一定角度差中就認為抓住老鼠,抓住老鼠後,停止動畫並展示笑臉貓view,然後等待兩秒後,繼續開始動畫並加快老鼠速度。部分虛擬碼如下:
mTouchX = event.getX();
mTouchY = event.getY();
float touchAngle = 0;
//點選的位置到圓心距離的平方
double distance = Math.pow(mTouchX - mRadius, 2) + Math.pow(mTouchY - mRadius, 2);
//判斷點選的座標是否在環內(利用三角函式)
if (distance < Math.pow(mRadius, 2) && distance > Math.pow(0.72 * mRadius, 2)) {
    int which = touchOnWhichPart(event);
    switch (which) {
        case PART_ONE:
            touchAngle = (float) (Math.atan2(mTouchX - mRadius, mRadius - mTouchY) * 180 / PI);
        break;
        case PART_TWO:
            touchAngle = (float) (Math.atan2(mTouchY - mRadius, mTouchX - mRadius) * 180 / PI + 90);
        break;
        case PART_THREE:
            touchAngle = (float) (Math.atan2(mRadius - mTouchX, mTouchY - mRadius) * 180 / PI + 180);
        break;
        case PART_FOUR:
            touchAngle = (float) (Math.atan2(mRadius - mTouchY, mRadius - mTouchX) * 180 / PI + 270);
        break;
    }
    //點選的是圓環內,繪製貓爪,並回撥當前角度值
    mIsDrawCatClaw = true;
    invalidate();
    if (mListener != null) mListener.onClickAngle(this, touchAngle);
}
複製程式碼
double touchAngle = angle > 180 ? (angle - 180) : (angle + 180);
float rotateAngle = mRotateAngle > 360 ? mRotateAngle % 360 : mRotateAngle;
double abs = Math.abs((rotateAngle - touchAngle));
if (abs <= 10) {//如果老鼠頭的旋轉角度和點選處角度誤差不超過10°,就認為抓住了老鼠
    stopRotateAnim();
    catClawView1.setCanClick(false);
    smileCat.setVisibility(View.VISIBLE);
    mGraduallyTextView.stopLoading();
    mGraduallyTextView.setText("C A U G H T ");
    mCatchTime++;
    //等待2秒後加大遊戲難度
    mRunnable = () -> {
        smileCat.setVisibility(View.GONE);
        catClawView1.invalidate();
        catClawView1.setCanClick(true);
        startRotateAnim();
        mGraduallyTextView.setText("C A T C H I N G ... ");
        mGraduallyTextView.startLoading();
    };
    mHandler.postDelayed(mRunnable, 2000);
}
複製程式碼

以上就是關鍵部分的分析和程式碼,另外還有一些自定義屬性的配置,使用者可以自己配置,提高擴充套件性。

使用

Java:

 CatMouseView catMouseView = new CatMouseView(this);
 catMouseView.setBgFilletRadius(30);
 catMouseView.setAnimDuration(3000);
 catMouseView.setBgColor(Color.MAGENTA);
 catMouseView.setIsShowGraduallyText(true);
 catMouseView.startAnim();
複製程式碼

XML

 <com.heyongrui.catmouseview.library.CatMouseView
        android:id="@+id/loadingview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:anim_duration="2000"
        app:bg_color="@color/colorPrimary"
        app:bg_fillet_radius="@dimen/dp_10"
        app:gradually_text="L O A D I N G..."
        app:is_show_gradually_text="false"/>
複製程式碼

也可以直接使用封裝好的Dialog形式

 CatMouseDialog loadingDialog = new CatMouseDialog(MainActivity.this);
 loadingDialog.show();
複製程式碼

github原始碼已上傳,喜歡的給個star,歡迎小夥伴fork~

相關文章