android 自定義view-水波紋進(jìn)度球

android 進(jìn)階之路-自定義view-水波紋進(jìn)度球

如果你是老司機(jī)咒循,一看標(biāo)題就會(huì)就return吧,嘻嘻棒口。
<br />
在我們的日常開發(fā)中自定義控件還是用的挺多的狡逢,設(shè)計(jì)師或者產(chǎn)品為了更好的漂亮倔约,美觀,交互都會(huì)做一些牛逼的ui效果圖腮猖,但是最后實(shí)現(xiàn)的還是我們程序員啊税稼。
所以說 自定義view你還是得會(huì)的。<br />
要開車了哦狞贱,請刷卡...<br />
滴刻获,老司機(jī)卡<br />
滴,學(xué)生卡<br />
滴瞎嬉,...<br />


刷卡

今天我們要實(shí)現(xiàn)的這個(gè)view沒有太多交互性的view蝎毡,所以就繼承view<br />
自定義view的套路,套路很深

  • 獲取我們自定義屬性attrs(可省略)
  • 重寫onMeasure方法氧枣,計(jì)算控件的寬和高
  • 重寫onDraw方法沐兵,繪制我們的控件

這么看來,自定義view的套路很清晰嘛便监。

我們看下今天的效果圖扎谎,其中一個(gè)是放慢的效果(時(shí)間調(diào)的長)


效果圖1

效果圖2

我們按照套路來。

一.自定義屬性


  <declare-styleable name="WaveProgressView">
        <attr name="radius" format="dimension|reference" />
        <attr name="radius_color" format="color|reference" />
        <attr name="progress_text_color" format="color|reference" />
        <attr name="progress_text_size" format="dimension|reference" />
        <attr name="progress_color" format="color|reference" />
        <attr name="progress" format="float" />
        <attr name="maxProgress" format="float" />
    </declare-styleable>
    

看下效果圖我們就知道因該需要哪些屬性烧董。就不說了毁靶。
然后就是獲取我們的這些屬性,就是用TypedArray來獲取解藻。當(dāng)然是在構(gòu)造中獲取老充,一般我們會(huì)復(fù)寫構(gòu)造方法,少參數(shù)調(diào)用參數(shù)多的螟左,然后走到參數(shù)最多的那個(gè)啡浊。
下面是獲取自定義屬性的代碼:

TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.WaveProgressView, defStyleAttr, R.style.WaveProgressViewDefault);
        radius = (int) a.getDimension(R.styleable.WaveProgressView_radius, radius);
        textColor = a.getColor(R.styleable.WaveProgressView_progress_text_color, 0);
        textSize = a.getDimensionPixelSize(R.styleable.WaveProgressView_progress_text_size, 0);
        progressColor = a.getColor(R.styleable.WaveProgressView_progress_color, 0);
        radiusColor = a.getColor(R.styleable.WaveProgressView_radius_color, 0);
        progress = a.getFloat(R.styleable.WaveProgressView_progress, 0);
        maxProgress = a.getFloat(R.styleable.WaveProgressView_maxProgress, 100);
        a.recycle();
        

注: R.style.WaveProgressViewDefault是這個(gè)控件的默認(rèn)樣式觅够。

二.onMeasure測量

我們重寫這個(gè)方法主要是根據(jù)父控件的寬和高來設(shè)置自己的寬和高。

   @Override   
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //計(jì)算寬和高
        int exceptW = getPaddingLeft() + getPaddingRight() + 2 * radius;
        int exceptH = getPaddingTop() + getPaddingBottom() + 2 * radius;
        int width = resolveSize(exceptW, widthMeasureSpec);
        int height = resolveSize(exceptH, heightMeasureSpec);
        int min = Math.min(width, height);

        this.width = this.height = min;

        //計(jì)算半徑,減去padding的最小值
        int minLR = Math.min(getPaddingLeft(), getPaddingRight());
        int minTB = Math.min(getPaddingTop(), getPaddingBottom());
        minPadding = Math.min(minLR, minTB);
        radius = (min - minPadding * 2) / 2;

        setMeasuredDimension(min, min);
    } 

首先該控件的寬和高肯定是一樣的巷嚣,因?yàn)槭莻€(gè)圓嘛喘先。其實(shí)是寬和高與半徑和內(nèi)邊距(padding)有關(guān),這里的內(nèi)邊距廷粒,我們?nèi)∩舷伦笥易钚〉囊粋€(gè)窘拯。寬和高也選擇取最小的。
this.width = this.height = min; 包含左右邊距坝茎。
resolveSize這個(gè)方法很好的為我們實(shí)現(xiàn)了我們想要的寬和高我慢看下源碼涤姊。

  public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

如果我們自己寫也是這樣寫。
最后通過setMeasuredDimension設(shè)置寬和高嗤放。

三.onDraw繪制

關(guān)于繪制有很多android 提供了很多API思喊,這里就不多說了。
繪制首先就是一些畫筆的初始化次酌。
需要提一下繪制path路徑的畫筆設(shè)置為PorterDuff.Mode.SRC_IN模式恨课,這個(gè)模式只顯示重疊的部分。

   pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        pathPaint.setColor(progressColor);
        pathPaint.setDither(true);
        pathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
         

我們要將所有的繪制 繪制到一個(gè)透明的bitmap上岳服,然后將這個(gè)bitmap繪制到canvas上剂公。

if (bitmap == null) {
            bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888);
            bitmapCanvas = new Canvas(bitmap);
        }
                      

為了方便計(jì)算和繪制,我將坐標(biāo)系平移padding的距離

 bitmapCanvas.save();
        //移動(dòng)坐標(biāo)系
        bitmapCanvas.translate(minPadding, minPadding);
 // .... some thing
 bitmapCanvas.restore();

3.1繪制圓

     bitmapCanvas.drawCircle(radius, radius, radius, circlePaint);

3.2繪制PATH 路徑.

一是要實(shí)現(xiàn)波紋的左右飄吊宋,和上下的振幅慢慢的減小
繪制這個(gè)之前我們需要知道二階貝塞爾曲線的大致原理纲辽。
簡單的說就是知道:P1起始點(diǎn),P2是終點(diǎn),P1是控制點(diǎn).利用塞爾曲線的公式就可以得道沿途的一些點(diǎn)贫母,最后把點(diǎn)連起來就是嘍文兑。
下面這個(gè)圖片來于網(wǎng)絡(luò):

二階貝塞爾曲線

在android-sdk里提供了繪制貝塞爾曲線的函數(shù)rQuadTo方法

public void rQuadTo(float dx1, float dy1, float dx2, float dy2) 
  • dx1:控制點(diǎn)X坐標(biāo),表示相對上一個(gè)終點(diǎn)X坐標(biāo)的位移坐標(biāo)腺劣,可為負(fù)值,正值表示相加因块,負(fù)值表示相減橘原;
  • dy1:控制點(diǎn)Y坐標(biāo),相對上一個(gè)終點(diǎn)Y坐標(biāo)的位移坐標(biāo)涡上。同樣可為負(fù)值趾断,正值表示相加,負(fù)值表示相減吩愧;
  • dx2:終點(diǎn)X坐標(biāo)芋酌,同樣是一個(gè)相對坐標(biāo),相對上一個(gè)終點(diǎn)X坐標(biāo)的位移值雁佳,可為負(fù)值脐帝,正值表示相加同云,負(fù)值表示相減;
  • dy2:終點(diǎn)Y坐標(biāo)堵腹,同樣是一個(gè)相對炸站,相對上一個(gè)終點(diǎn)Y坐標(biāo)的位移值【吻辏可為負(fù)值旱易,正值表示相加,負(fù)值表示相減腿堤;
    這四個(gè)參數(shù)都是傳遞的都是相對值阀坏,相對上一個(gè)終點(diǎn)的位移值。

要實(shí)現(xiàn)振幅慢慢的減小我們可以調(diào)節(jié)控制點(diǎn)的y坐標(biāo)即可笆檀,即:
float percent=progress * 1.0f / maxProgress;
就可以得到[0忌堂,1]的
一個(gè)閉區(qū)間,[0误债,1]這貨好啊浸船,我喜歡,可以來做很多事情寝蹈。
這樣我們就可以根據(jù)percent來調(diào)節(jié)控制點(diǎn)的y坐標(biāo)了李命。

//根據(jù)直徑計(jì)算繪制貝賽爾曲線的次數(shù)
            int count = radius * 4 / 60;
            //控制-控制點(diǎn)y的坐標(biāo)
            float point = (1 - percent) * 15;
            for (int i = 0; i < count; i++) {
                path.rQuadTo(15, -point, 30, 0);
                path.rQuadTo(15, point, 30, 0);
            }

這里給出一個(gè)振幅的原理圖:


振幅

然后就是根據(jù)寬來循環(huán)周期就可以了
要實(shí)現(xiàn)左右波紋只需要控制閉合路徑的左上角的x坐標(biāo)即可,當(dāng)然也是根據(jù)percent嘍箫老。
大家可以結(jié)合下面這個(gè)圖來理解下上面的話封字。


原理圖

path繪制的完整代碼片段。

  //繪制PATH
        //重置繪制路線
        path.reset();
        float percent=progress * 1.0f / maxProgress;
        float y = (1 - percent) * radius * 2;
        //移動(dòng)到右上邊
        path.moveTo(radius * 2, y);
        //移動(dòng)到最右下方
        path.lineTo(radius * 2, radius * 2);
        //移動(dòng)到最左下邊
        path.lineTo(0, radius * 2);
        //移動(dòng)到左上邊
        // path.lineTo(0, y);
        //實(shí)現(xiàn)左右波動(dòng),根據(jù)progress來平移
        path.lineTo(-(1 -percent) * radius*2, y);
        if (progress != 0.0f) {
            //根據(jù)直徑計(jì)算繪制貝賽爾曲線的次數(shù)
            int count = radius * 4 / 60;
            //控制-控制點(diǎn)y的坐標(biāo)
            float point = (1 - percent) * 15;
            for (int i = 0; i < count; i++) {
                path.rQuadTo(15, -point, 30, 0);
                path.rQuadTo(15, point, 30, 0);
            }
        }
        //閉合
        path.close();
        bitmapCanvas.drawPath(path, pathPaint);

3.3繪制進(jìn)度的文字

這個(gè)就比較簡單了耍鬓,繪制在控件的中間即可阔籽。關(guān)于文字的坐標(biāo)計(jì)算還是很好理解的。

  //繪制文字
        String text = progress + "%";
        float textW = textPaint.measureText(text);
        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
        float baseLine = radius - (fontMetrics.ascent + fontMetrics.descent) / 2;
        bitmapCanvas.drawText(text, radius - textW / 2, baseLine, textPaint);

最后別忘了把我們的bitmap繪制到canvas上牲蜀。
canvas.drawBitmap(bitmap, 0, 0, null);
哦笆制,最后是實(shí)用方法,這里我們不用thread+handler涣达,我們用屬性動(dòng)畫在辆。
你懂的!6忍Α匆篓!,like

   ObjectAnimator objectAnimator0 = ObjectAnimator.ofFloat(waveProgressView_0, "progress", 0f, 100f);
        objectAnimator0.setDuration(3300);
        objectAnimator0.setInterpolator(new LinearInterpolator());
        objectAnimator0.start();
        

至此寇窑,也就實(shí)現(xiàn)了我們的效果鸦概。
最后給出源碼的下載地址:
star了不迷路哦。
https://github.com/ta893115871/WaveProgressView


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末甩骏,一起剝皮案震驚了整個(gè)濱河市窗市,隨后出現(xiàn)的幾起案子先慷,更是在濱河造成了極大的恐慌,老刑警劉巖谨设,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熟掂,死亡現(xiàn)場離奇詭異,居然都是意外死亡扎拣,警方通過查閱死者的電腦和手機(jī)赴肚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來二蓝,“玉大人誉券,你說我怎么就攤上這事】蓿” “怎么了踊跟?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鸥诽。 經(jīng)常有香客問我商玫,道長,這世上最難降的妖魔是什么牡借? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任拳昌,我火速辦了婚禮,結(jié)果婚禮上钠龙,老公的妹妹穿的比我還像新娘炬藤。我一直安慰自己,他們只是感情好碴里,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布沈矿。 她就那樣靜靜地躺著,像睡著了一般咬腋。 火紅的嫁衣襯著肌膚如雪羹膳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天根竿,我揣著相機(jī)與錄音溜徙,去河邊找鬼。 笑死犀填,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嗓违。 我是一名探鬼主播九巡,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蹂季!你這毒婦竟也來了冕广?” 一聲冷哼從身側(cè)響起疏日,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撒汉,沒想到半個(gè)月后沟优,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睬辐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年挠阁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溯饵。...
    茶點(diǎn)故事閱讀 40,438評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侵俗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丰刊,到底是詐尸還是另有隱情隘谣,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布啄巧,位于F島的核電站寻歧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秩仆。R本人自食惡果不足惜码泛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逗概。 院中可真熱鬧弟晚,春花似錦、人聲如沸逾苫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铅搓。三九已至瑟押,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間星掰,已是汗流浹背多望。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氢烘,地道東北人怀偷。 一個(gè)月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像播玖,于是被迫代替她去往敵國和親椎工。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評論 2 359

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