教你手寫一個貝塞爾曲線效果的Loading View

一直對貝塞爾曲線的效果感興趣, 然后前一陣子看到同事寫的一個貝塞爾曲線做的動畫loading, 我也學著寫了一下.

我們先看一下效果圖。我覺得在加載時使用一個這個動畫還是很不錯的伦籍。具體大小和速度可以自己配置瘾晃。

loadingview.gif

BesselLoadingView是一個貝塞爾曲線效果的加載過渡動畫贷痪。使用canvas繪制的自定義view。


引入

project's build.gradle (工程下的 build.gradle)

  allprojects {
    repositories {
      ...
      maven { url 'https://jitpack.io' }
    }
  }

module's build.gradle (模塊的build.gradle)

  dependencies {
          compile 'com.github.Jerey-Jobs:BesselLoadingView:1.1'
  }

Usage/用法

上圖效果的layout是這樣的.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.jerey.besselloadingview.MainActivity">

    <com.jerey.besselloadingviewlib.BesselLoadingView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:loadingduration="4000"
        app:loadingcolor="#555555"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.2"/>

    <com.jerey.besselloadingviewlib.BesselLoadingView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.4"/>

    <com.jerey.besselloadingviewlib.BesselLoadingView
        android:layout_width="400dp"
        android:layout_height="150dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.6"/>

</android.support.constraint.ConstraintLayout>


編寫過程


首先我們提供三個可配置選項,分別是顏色,動畫周期時長,圓的半徑(半徑同時會根據(jù)設置的大小變化)

<declare-styleable name="BesselLoadingView">
    <attr name="loadingradius" format="dimension"></attr>
    <attr name="loadingcolor" format="color"></attr>
    <attr name="loadingduration" format="integer"></attr>
</declare-styleable>

與一般自定義View一樣,我們在構造方法中獲取自定義的幾個屬性,

public BesselLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initUI(context, attrs);
}

private void initUI(Context context, AttributeSet attrs) {
    mPaint = new Paint();
    //路徑
    mPath = new Path();
    mCirclesX = new int[3];
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BesselLoadingView);
    mLoadingColor = ta.getColor(R.styleable.BesselLoadingView_loadingcolor, DEFAULT_COLOR);
    mRadius = ta.getDimension(R.styleable.BesselLoadingView_loadingradius, DEFAULT_RADIUS);
    mDuration = ta.getInt(R.styleable.BesselLoadingView_loadingduration, DEFAULT_DURATION);

    mPaint.setColor(mLoadingColor);
    mPaint.setAntiAlias(true); //抗鋸齒

    mRadiusFloat = mRadius * 0.9f;

}

onMeasure

構造方法里面我們只初始化了一些必要的配置參數(shù), 但是我們的圓與圓之間的距離啊什么的還沒初始化,我們在onMeasure中進行初始化一些大小的參數(shù)

我做了比如配置的半徑比實際的上下高度還要大的情況下自動縮小啊,等一系列自適應操作.并且默認為

    android:layout_width="wrap_content"
    android:layout_height="wrap_content"

時,默認大小為480像素寬,100像素高

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int mWidth;
    int mHeight;

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    if (widthMode == MeasureSpec.EXACTLY) {
        mWidth = widthSize;
    } else {
        mWidth = getPaddingLeft() + 480 + getPaddingRight();
    }

    if (heightMode == MeasureSpec.EXACTLY) {
        mHeight = heightSize;
    } else {
        mHeight = getPaddingTop() + 100 + getPaddingBottom();
    }

    setMeasuredDimension(mWidth, mHeight);
    log("width: " + mWidth + " h: " + mHeight);
    //計算x方向三個圓心   -.-.-.-
    int lenth = mWidth / 4;
    for (int i = 0; i < 3; i++) {
        mCirclesX[i] = lenth * (i + 1);
    }

    //計算三個圓心Y坐標
    mCirClesY = mHeight / 2;
    //三個初始圓的半徑
    mRadius = mHeight / 3;
    mRadiusFloat = mRadius * 0.9f;
    log("mCirclesX: " + mCirclesX[0] + "," + mCirclesX[1] + "," + mCirclesX[2] + "  Y: " + mCirClesY);

    if (mRadius >= lenth / 4) {
        log("圓的半徑大于間隙了,自動縮小");
        mRadius = lenth / 4;
        mRadiusFloat = mRadius * 0.9f;
    }

    mMinDistance = lenth;

    log("mMinDistance " + mMinDistance);

    ValueAnimator valueAnimator = ValueAnimator.ofFloat(mRadius, mWidth - mRadius);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.setDuration(mDuration);
    valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
    valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mFloatX = (float) animation.getAnimatedValue();
            postInvalidate();
        }
    });
    valueAnimator.start();
}

onDraw

接下來重點來了, 我們需要畫幾個東西, 三個固定圓, 一個浮動圓, 貝塞爾曲線, 固定圓半徑變化.

在繪制貝塞爾曲線的時候,需要先計算浮動圓離哪個固定圓最近,然后繪制相聚最近的圓的貝塞爾曲線,再計算距離,計算出圓應該變化多大.

    @Override
    protected void onDraw(Canvas canvas) {
        //畫三個圓
        for (int i = 0; i < 3; i++) {
            canvas.drawCircle(mCirclesX[i], mCirClesY, mRadius, mPaint);
        }
        //畫滑動圓
        canvas.drawCircle(mFloatX, mCirClesY, mRadiusFloat, mPaint);

        drawBesselLine(canvas);
    }

    /**
     * 繪制貝塞爾曲線與定點圓變大
     *
     * @param canvas
     */
    private void drawBesselLine(Canvas canvas) {
        float minDis = mMinDistance;
        int minLocation = 0;
        for (int i = 0; i < 3; i++) {
            float dis = Math.abs((mFloatX - mCirclesX[i]));
            if (dis < minDis) {
                minDis = dis;
                minLocation = i;
            }
        }
        // log("最小距離為 " + minDis + "位置:" + minLocation);
        if (minDis < mMinDistance) {

            float middleX = (mCirclesX[minLocation] + mFloatX) / 2;
            //繪制上半部分貝塞爾曲線
            mPath.moveTo(mCirclesX[minLocation], mCirClesY + mRadius);
            mPath.quadTo(middleX, mCirClesY, mFloatX, mCirClesY + mRadiusFloat);

            mPath.lineTo(mFloatX, mCirClesY - mRadiusFloat);

            mPath.quadTo(middleX, mCirClesY, mCirclesX[minLocation], mCirClesY - mRadius);

            mPath.lineTo(mCirclesX[minLocation], mCirClesY + mRadius);
            mPath.close();

            canvas.drawPath(mPath, mPaint);
            mPath.reset();
            //浮動圓靠近固定圓變大
            float f = 1 + (mMinDistance - minDis * 2) / mMinDistance * 0.2f;
            log("dis% : " + (mMinDistance - minDis) / mMinDistance + "  f = " + f);
            canvas.drawCircle(mCirclesX[minLocation], mCirClesY, mRadius * f, mPaint);
        }
    }

至此蹦误,我們的自定義View便OK了劫拢, 若有幫助,歡迎star

具體代碼見 https://github.com/Jerey-Jobs/BesselLoadingView

老鐵們强胰,來波Star舱沧。

靜態(tài)圖.png

本文作者:Anderson/Jerey_Jobs

博客地址 : 夏敏的博客/Anderson大碼渣/Jerey_Jobs

簡書地址 : Anderson大碼渣

github地址 : Jerey_Jobs

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市偶洋,隨后出現(xiàn)的幾起案子熟吏,更是在濱河造成了極大的恐慌,老刑警劉巖涡真,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肾筐,居然都是意外死亡哆料,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門吗铐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來东亦,“玉大人,你說我怎么就攤上這事〉湔螅” “怎么了奋渔?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長壮啊。 經(jīng)常有香客問我嫉鲸,道長,這世上最難降的妖魔是什么歹啼? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任玄渗,我火速辦了婚禮,結果婚禮上狸眼,老公的妹妹穿的比我還像新娘藤树。我一直安慰自己,他們只是感情好拓萌,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布岁钓。 她就那樣靜靜地躺著,像睡著了一般微王。 火紅的嫁衣襯著肌膚如雪屡限。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天骂远,我揣著相機與錄音囚霸,去河邊找鬼。 笑死激才,一個胖子當著我的面吹牛拓型,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瘸恼,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼劣挫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了东帅?” 一聲冷哼從身側響起压固,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎靠闭,沒想到半個月后帐我,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡愧膀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年拦键,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檩淋。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡芬为,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媚朦,我是刑警寧澤氧敢,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站询张,受9級特大地震影響孙乖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瑞侮,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一的圆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧半火,春花似錦越妈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至店归,卻和暖如春阎抒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背消痛。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工且叁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秩伞。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓逞带,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纱新。 傳聞我的和親對象是個殘疾皇子展氓,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容