三個點(或小球)緩沖控件示例

前言

??之前看別人app上緩沖框的實現(xiàn),覺得挺好的,就想實現(xiàn)下。本文實現(xiàn)的是三個動態(tài)點的緩沖框。

最終實現(xiàn)效果

dotsthree.gif

??左邊是三個動態(tài)的點,右邊是一段簡單的說明文字。三個點的大小及透明度依次變化,且有規(guī)律性。

思路及實現(xiàn)

??第一次看到這個效果就知道可以通過自定義控件實現(xiàn):點通過畫圓填充的方式實現(xiàn),點大小的變化通過屬性動畫不斷地修改圓半徑實現(xiàn),透明度通過屬性動畫不斷地給畫筆設(shè)置新的alpha值實現(xiàn)。總共需要畫3個點和一個文本,現(xiàn)在先來看怎么畫出一個點,一個大小及透明度不斷變化的點。

??怎么畫出一個大小及透明度不斷變化的點

??android中畫一個點,其實很簡單

//初始化點的畫筆
mPaint = new Paint();
//畫筆模式這里設(shè)置成填充,因為畫的是點,不是圓形
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);//抗鋸齒
mPaint.setColor(mDotColor);//設(shè)置畫筆顏色,也就是點的顏色

//cx、cy為圓心坐標(biāo),radius為半徑
canvas.drawCircle(cx, cy, radius, mPaint);

??初始化一個畫筆,畫筆模式設(shè)置為填充,再調(diào)用一下canvas的drawCircle方法,指定圓心坐標(biāo)及圓半徑,圓就出來了。

??點好畫,那大小不斷變化的點怎么畫呢?大小不斷變化就需要我們不斷調(diào)整點的半徑,而屬性動畫可以做到在一個值范圍內(nèi)(比如0~1)不斷變化并且我們可以拿到這個變化的值

//初始化一個點的動畫
mValueAnimator = ValueAnimator.ofFloat(0, 1);
//設(shè)置動畫值變化的監(jiān)聽
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        //此處的mCurrentValue就是我們拿到的變化值
        mCurrentValue = (float) animation.getAnimatedValue();
        invalidate();
    }
});
mValueAnimator.setInterpolator(null);
mValueAnimator.setDuration(mDuration);
//無限循環(huán)的動畫才會不停地變化
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);

??ofFloat(0, 1)指定屬性動畫的值在0-1范圍內(nèi)變化,setInterpolator傳入null意思是在0-1范圍內(nèi)線性變化,對屬性動畫進(jìn)行監(jiān)聽得到的mCurrentValue就是值0-1線性變化時某一時刻的值。假設(shè)我們的點最小時的半徑為minRadius,最大時的半徑為maxRadius,那點半徑的變化范圍就是maxRadius - minRadius,據(jù)此我們計算出某一時刻點的半徑:minRadius + (maxRadius - minRadius) * mCurrentValue。注意,此處的mCurrentValue范圍為0-1。那上述對drawCircle的調(diào)用就可以修改成下面這樣:

canvas.drawCircle(cx, cy, minRadius + (maxRadius - minRadius) * mCurrentValue, mPaint);

??隨著mCurrentValue在0-1之間線性變化,點的半徑在minRadius-maxRadius之間線性變化,這樣就畫出了大小不斷變化的點了。那怎么畫出在大小線性變化的點的同時使此點的透明度也線性變化呢?只需在上述drawCircle上方給畫筆mPaint設(shè)置透明度即可。如下:

mPaint.setAlpha((int) (mDotAlpha + (255 - mDotAlpha) * mCurrentValue + 0.5));
canvas.drawCircle(cx, cy, minRadius + (maxRadius - minRadius) * mCurrentValue, mPaint);

??mPaint透明度的取值范圍為0~255,這里0就是全透明了,我們沒有必要讓點最終變透明,所以這里給了一個點透明度的最小值mDotAlpha(詳細(xì)代碼里給的mDotAlpha為128)。255是完全不透明,上述的setAlpha方法可使點的透明度在mDotAlpha-255之間線性變化。

??上面只是說了怎么完成點大小由小到大的變化,那怎么做到點大小由大到小的變化呢?其實有多種方式可以做到這一點,比如我可以把屬性動畫的ofFloat設(shè)置成

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1, 0);

??也可以維持ofFloat不變,在每執(zhí)行第偶數(shù)次動畫時執(zhí)行

mPaint.setAlpha((int) (mDotAlpha + (255 - mDotAlpha) * (1 - mCurrentValue) + 0.5));
canvas.drawCircle(cx, cy, minRadius + (maxRadius - minRadius) * (1 - mCurrentValue), mPaint);

??除了上面這兩種方式,還可以像下面這樣做

//定義動畫的時候這么寫
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 2);

//畫點的時候這么做
//當(dāng)mCurrentValue在0-1之間變化時
if (mCurrentValue >= 0 && mCurrentValue < 1) {
    //0-1之間,不透明度逐漸地增大
    mPaint.setAlpha((int) (mDotAlpha + (255 - mDotAlpha) * mCurrentValue + 0.5));
    //0-1之間,點半徑逐漸增大
    canvas.drawCircle(cx, cy, minRadius + (maxRadius - minRadius) * mCurrentValue, mPaint);
} else if (mCurrentValue >= 1 && mCurrentValue <= 2) {//當(dāng)mCurrentValue在1-2之間變化時
    //1-2之間,不透明度逐漸變小
    mPaint.setAlpha((int) (mDotAlpha + (255 - mDotAlpha) * (1 - 0.5 * mCurrentValue) + 0.5));
    //1-2之間,點半徑逐漸變小
    canvas.drawCircle(cx, cy, minRadius + (maxRadius - minRadius) * (1 - 0.5 * mCurrentValue), mPaint);
} else {
    //其他情況下的透明度就直接設(shè)置成點最小時的透明度和
    mPaint.setAlpha(mDotAlpha);
    canvas.drawCircle(cx, cy, minRadius, mPaint);
}

??如上述代碼所示,在mCurrentValue范圍在0-1之間逐漸變大時,不透明度和點半徑逐漸增大,在1-2范圍內(nèi),不透明度和點半徑逐漸減小。其實上述代碼,還可以進(jìn)一步簡化,因為setAlpha和drawCircle調(diào)用的太多了:

float currentValue = 0;
if (mCurrentValue >= 0 && mCurrentValue < 1) {
    currentValue = mCurrentValue;
} else if (mCurrentValue >= 1 && mCurrentValue <= 2) {//當(dāng)mCurrentValue在1-2之間變化時
    currentValue = 1 - 0.5 * mCurrentValue;
} else {
    currentValue = 0;
}

mPaint.setAlpha((int) (mDotAlpha + (255 - mDotAlpha) * currentValue + 0.5));
canvas.drawCircle(cx, cy, minRadius + (maxRadius - minRadius) * currentValue, mPaint);

??根據(jù)上述思路編寫代碼實現(xiàn)的效果如下:

dotsone.gif

??怎么畫出三個大小及透明度不斷變化的點

??上面已經(jīng)畫出了一個大小及透明度不斷變化的點,那怎么畫出3個呢?首先我們需要研究一下,3個點一起運動時的規(guī)律。如最終效果圖所示,

  • 初始時3個點均為半徑最小且透明度最低的狀態(tài);
  • 之后第一個點開始變化(半徑逐漸變大,透明度逐漸加深),其余兩點仍舊為半徑最小且透明度最低狀態(tài);
  • 當(dāng)?shù)谝粋€點半徑和透明度變化到最大變化范圍的一半時,第二個點開始變化;
  • 當(dāng)?shù)诙€點半徑和透明度變化到最大變化范圍的一半時,第三個點開始變化;
  • 第一個點半徑和透明度變化到最大變化范圍時,隨即折返,半徑和透明度逐漸變小,變化到最小范圍時不再變化;
  • 第二個點和第三個點的變化規(guī)律和第一個點的變化規(guī)律是一致的,只是靠前的點比其后緊跟的點早開始變化一半的時間。當(dāng)?shù)谌齻€點變化到最小范圍時,三個點均為半徑最小且透明度最低的狀態(tài);
  • 回到第一步,上述過程再走一遍

??上述情況,說起來好像有點兒復(fù)雜,其實落實到具體的坐標(biāo)圖上,就是3個偏移的“^”,如下所示

dotsX.jpeg

??解釋一下,上述圖片中的橫軸為mCurrentValue,縱軸表示點半徑radius(透明度類似的)。最靠近縱軸的折線是第一個點的變化規(guī)律,中間那個折線是第二個點的變化規(guī)律,最右邊的折線是第三個點的變化規(guī)律。可據(jù)此變化規(guī)律列出每條折線的方程:

//第一條折線的方程
raduis = minRadius + (maxRadius - minRadius) * mCurrentValue,(mCurrentValue >= 0 && mCurrentValue < 1);
raduis = minRadius + (maxRadius - minRadius) * (1 - 0.5f * mCurrentValue),(mCurrentValue >= 1 && mCurrentValue <= 2);

//第二條折線的方程
raduis = minRadius + (maxRadius - minRadius) * (mCurrentValue - 0.5),(mCurrentValue >= 0.5 && mCurrentValue < 1.5);
raduis = minRadius + (maxRadius - minRadius) * (1 - 0.5f * (mCurrentValue - 0.5f)),(mCurrentValue >= 1.5 && mCurrentValue <= 2);

//第三條折線
raduis = minRadius + (maxRadius - minRadius) * (mCurrentValue - 1f)),(mCurrentValue >= 1 && mCurrentValue < 2);
raduis = minRadius + (maxRadius - minRadius) * (1 - 0.5f * (mCurrentValue - 0.5f))),(mCurrentValue >= 2 && mCurrentValue <= 3);

??列出這些方程,需要一定的數(shù)學(xué)知識。但也只是直線而已,列出方程也只會是一階方程而已。結(jié)合上述方程,設(shè)置一個0-3范圍的屬性動畫,就可以使用一個動畫畫出三個大小及透明度不斷變化的點。在給出畫出三個動態(tài)點代碼之前,我們還需要解決另一個小問題,三個點應(yīng)該在控件的中心位置。首先計算出三個點的總長度:

float dotsLength = 3 * (mMaxRadius * 2 + mSpace) - mSpace;

??其中mSpace指的是兩個點之間的間距。有了此總長度就可以計算出三個點的圓心位置為:

// i: 0、1、 2,分別代表最左邊、中間、左右邊的點的坐標(biāo)
(dotsLength / 2 + (2 * i + 1) * mMaxRadius + i * mSpace,0)

??畫出3個變化的點的代碼如下:

//屬性動畫設(shè)置成0-3
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 3);

//將此view的坐標(biāo)系原點放到控件的中心
canvas.translate(mWidth / 2, mHeight / 2);

//依次畫出三個點
for (int i = 0; i < 3; i++) {

float currentValue = 0f;

switch (i) {
    case 0://畫第一個點
        if (mCurrentValue >= 0 && mCurrentValue < 1) {
            currentValue = mCurrentValue;
        } else if (mCurrentValue >= 1 && mCurrentValue <= 2) {
            currentValue = 1 - 0.5f * mCurrentValue;
        } else {
            currentValue = 0;
        }

        mPaint.setAlpha((int) (mDotAlpha + (255 - mDotAlpha) * currentValue + 0.5));
        canvas.drawCircle(-dotsLength / 2 + (2 * i + 1) * mMaxRadius + i * mSpace, 0,
                mMinRadius + (mMaxRadius - mMinRadius) * currentValue, mPaint);

        break;
    case 1://畫第二個點
        if (mCurrentValue >= 0.5 && mCurrentValue < 1.5) {
            currentValue = mCurrentValue - 0.5f;
        } else if (mCurrentValue >= 1.5 && mCurrentValue <= 2.5) {
            currentValue = 1 - 0.5f * (mCurrentValue - 0.5f);
        } else {
            currentValue = 0;
        }

        mPaint.setAlpha((int) (mDotAlpha + (255 - mDotAlpha) * currentValue + 0.5));
        canvas.drawCircle(-dotsLength / 2 + (2 * i + 1) * mMaxRadius + i * mSpace, 0,
                mMinRadius + (mMaxRadius - mMinRadius) * currentValue, mPaint);

        break;
    case 2://畫第三個點
        if (mCurrentValue >= 1 && mCurrentValue < 2) {
            currentValue = mCurrentValue - 1f;
        } else if (mCurrentValue >= 2 && mCurrentValue <= 3) {
            currentValue = 1 - 0.5f * (mCurrentValue - 1f);
        } else {
            currentValue = 0;
        }

        mPaint.setAlpha((int) (mDotAlpha + (255 - mDotAlpha) * currentValue + 0.5));
        canvas.drawCircle(-dotsLength / 2 + (2 * i + 1) * mMaxRadius + i * mSpace, 0,
                mMinRadius + (mMaxRadius - mMinRadius) * currentValue, mPaint);

        break;
}

??根據(jù)上述代碼運行的結(jié)果如下:

dotstwo.gif

??畫出3個變化的點右側(cè)的文本

??畫文本的代碼其實很簡單

canvas.drawText(mText, x, y, mTextPaint);

??但為了要把三個點以及文本看做一個整體然后居中放置,需要調(diào)整一下上述三個點圓心坐標(biāo)的計算方法:

//3個點及其間距的總長度
float dotsLength = 3 * (mMaxRadius * 2 + mSpace) - mSpace;
//文本的長度
Rect rect = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), rect);
float rectLength = rect.right - rect.left;
//3個點及文本的總長度,mDivider為3個點與文本之間的間距
float length = dotsLength + rectLength + mDivider;

//i:0,1,2
圓心坐標(biāo):(-length / 2 + (2 * i + 1) * mMaxRadius + i * mSpace,0)

??然后畫出垂直居中的文本:

Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
canvas.drawText(mText, -length / 2 + dotsLength + mDivider, (-fontMetrics.top - fontMetrics.bottom) / 2, mTextPaint);

??之前知道使一個文本在一個矩形范圍內(nèi)垂直居中的基線縱坐標(biāo)為:baseY = (rectTop + rectBottom -fontMetrics.top - fontMetrics.bottom) / 2,因為此控件平移了坐標(biāo)系,整個控件坐標(biāo)系的原點位置移到了控件的中心,(rectTop + rectBottom) / 2其實等于0,所以這里直接寫(-fontMetrics.top - fontMetrics.bottom) / 2就可以了。

總結(jié)

??一開始剛寫這個代碼時,總覺得這3個點應(yīng)該是三個動畫。而真正使用3個動畫分別控制這3個點時,卻遇到了動畫同時開啟某個點動畫出現(xiàn)延遲的問題。最終還是決定使用一個動畫來完成,一個動畫的值在變化期間使用不同的計算方法計算出各個點此刻的大小和不透明度,然后畫出,最終解決問題。

??這里是完整代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 如果你是初級階段的水平,熟讀此文并掌握,馬上進(jìn)階為中級水平。絕對不是廣告噢。 常見技巧 經(jīng)典的Photoshop技...
    打豆豆閱讀 9,757評論 0 81
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,932評論 25 709
  • 今天下班比較晚剛好和他下班時間差不多,以為不用等太久,就在目的地地鐵站內(nèi)的候車室等起他來了,看了幾篇“十點讀書”...
    HLL720閱讀 182評論 0 0
  • 今天去居委會辦理居住證,以后很多外地權(quán)益都會越來越好! 記得10年前,這里的基礎(chǔ)設(shè)施都很差,工作環(huán)境也不是很...
    王楓浚閱讀 262評論 0 2
  • 今天是講座的第二天,學(xué)校較遠(yuǎn),選擇了三種出行方式(騎共享單車,地鐵,打車) 打車時,由于不熟悉路況,為了節(jié)省時間,...
    sanyaojing閱讀 509評論 0 0

友情鏈接更多精彩內(nèi)容