GratuityView: 虎撲體育打賞控件

前言

NBA全明星周末!重要事情說(shuō)一遍!
作為一個(gè)經(jīng)常在虎撲上看NBA直播的小球迷,老早就留意到了打賞加油的小控件,今天趕緊的,讓我們來(lái)實(shí)現(xiàn)它吧~搞起!
</br>

虎撲原型和效果圖圖

先看下虎撲原型:

原型.png

挺好看的一個(gè)控件嘛有木有~,看下我們的效果圖:

原始效果.gif

點(diǎn)擊效果.gif

色彩效果.gif

</br>
基本效果就是這樣了。
</br>
</br>

開(kāi)始我們的自定義

1.分析原型

先來(lái)分析一波原型圖

原型

對(duì)view的元素分析。這個(gè)view有兩個(gè)主要的元素: 圓以及灰色背景框。
圓有兩個(gè)元素:text 以及背景顏色 color;
背景框:某個(gè)顏色繪制出來(lái)的圓弧圖形,并且該圓弧所在圓的半徑與大圓相等。

對(duì)view的結(jié)構(gòu)分析。
view是多個(gè)圓組成的結(jié)構(gòu),以大圓為基圓,左邊展開(kāi)的小圓都包含在一個(gè)基圓的空間內(nèi)并且與該基圓圓心重合。背景框可以拆解成左邊一個(gè)半基圓的弧形,加上右邊一個(gè)矩形。
用圖來(lái)演示一下:

結(jié)構(gòu)圖

ok,分析完畢~

2.動(dòng)手
1 )先繪制圓

圓是這個(gè)控件里面很重要的一個(gè)元素,大圓和小圓都應(yīng)該使用到同一個(gè)控件。因?yàn)榇髨A是基圓,我們先來(lái)畫個(gè)圓的控件,就叫BaseView。
定義一個(gè)BaseView,讓它繼承自view,再在onDraw( )方法內(nèi)繪制圓和文本text,比較簡(jiǎn)單,直接上代碼:
繪制圓

canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2,
Math.min(getMeasuredHeight(), getMeasuredWidth()) / 2, mCirclePaint);

繪制文本

mTextPaint.getTextBounds(fstText, 0, fstText.length(), fstRect);
canvas.drawText(fstText, getMeasuredWidth() / 2 - fstRect.width() / 2,
getMeasuredHeight() / 2 + fstRect.height() / 2, mTextPaint);

繪制文本無(wú)非多一點(diǎn)對(duì)文本位置的修正,恩,不難~

2 )繪制控件

從原型可以看出,基圓處在了父控件的最右邊。或者可以說(shuō),當(dāng)前控件假若從左邊展開(kāi),BaseView在最右;控件從右邊展開(kāi),BaseView位于父控件最左。因此我們可以自定義一個(gè)view去繼承RelativeLayout,這樣BaseView的處置就比較容易實(shí)現(xiàn)了。
定義一個(gè)GratuityView,繼承自RelativeLayout.

第一步,將我們寫好的BaseView添加到最右邊;
重寫onLayout( )方法,添加一個(gè)BaseView, 并添加上BaseView的點(diǎn)擊事件。貼上代碼:

private void addBaseView(int widthSize, int heightSize) {
        if (added || widthSize < 1 || heightSize < 1) return;
        added = !added;
        BaseView                    baseView = new BaseView(mContext);
        RelativeLayout.LayoutParams params   = new            RelativeLayout.LayoutParams(size, size);
        params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
        baseView.setLayoutParams(params);
        if (isNewLine)
            baseView.setText(fstText, secText);
        else baseView.setText(fstText);
        baseView.setTextSize(mBaseTextSize);
        baseView.setTextColor(mBaseTextColor);
        baseView.setCircleColor(mBasegroundColor);
        addView(baseView);
        baseView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                //TODO
            }
        });

    }

</br>
第二步,完成BaseView的點(diǎn)擊事件
BaseView的點(diǎn)擊事件有兩種,一種是展開(kāi)事件Expand, 一種是收縮事件 collapse.
當(dāng)BaseView被點(diǎn)擊時(shí),展開(kāi)動(dòng)畫開(kāi)始,獲取當(dāng)前動(dòng)畫進(jìn)度不斷地調(diào)用onDraw( )方法去繪制背景框(包括一個(gè)矩形和一個(gè)弧形),貼上代碼:

private void startExpandAnimation() {
        if (collapseAnimation != null && collapseAnimation.isRunning()) {
            return;
        }
        expand = !expand;
        animated = true;
        if (mWidthMode == AT_MOST)
            AnimLength = (mContainedCount + 1) * size - radius * 2;
        else
            AnimLength = mWidth - radius * 2;      //總運(yùn)動(dòng)長(zhǎng)度
        expandAnimation = ValueAnimator.ofInt(AnimLength);
        expandAnimation.setDuration(mAnimDuration);
        expandAnimation.start();
        expandAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
        expandAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mCurrentValue = (int) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
    }

這里有一個(gè)點(diǎn)需要注意的:當(dāng)前動(dòng)畫的距離。
假如,當(dāng)前展開(kāi)后只有3個(gè)子view,那么控件完整顯示需要的寬度就是
mWidth=(3+1)*基圓直徑。當(dāng)getMeasureWidth()比所需寬度mWidth大,動(dòng)畫距離

AnimLength = mWidth - radius * 2

當(dāng)getMeasureWidth( )比所需寬度mWidth小,動(dòng)畫距離

AnimLength = (mContainedCount + 1) * size - radius * 2

中間灰色部分為動(dòng)畫運(yùn)動(dòng)的距離


ggv.png

接下來(lái)在onDraw( )內(nèi)繪制背景框~
這里使用了xformode里面的模式,將灰色矩形與BaseView相交部分產(chǎn)生的顏色區(qū)域忽略掉。

//寬度過(guò)長(zhǎng)
int diff    = getMeasuredWidth() - mWidth;
int layerId = canvas.saveLayer(diff, top, right, bottom, mPaint, Canvas.ALL_SAVE_FLAG);
//繪制弧形
fstRectF.left = diff + AnimLength - mCurrentValue;                
fstRectF.right = diff + AnimLength - mCurrentValue + radius * 2;
fstRectF.top = top;
fstRectF.bottom = bottom;
canvas.drawArc(fstRectF, 90, 180, true, backgroundPaint);
//繪制矩形
canvas.drawRect(diff + mWidth - radius - mCurrentValue, top, right - radius, bottom, backgroundPaint);
//繪制xformode源圖像
fstRectF.left = right - size;
fstRectF.right = right;
fstRectF.top = top;
fstRectF.bottom = bottom;
canvas.drawArc(fstRectF, 90, 180, true, backAlphaPaint);
canvas.restoreToCount(layerId);

據(jù)說(shuō)收縮的方法和展開(kāi)類似哦~??

</br>
第三步,添加子BaseView
在展開(kāi)的方法 startExpandAnimation( )內(nèi)添加子view。添加的位置很好計(jì)算,因?yàn)樽觱iew都添加到基圓的中心,基圓大小

size = Math.min(getMeasuredHeight(), getMeasuredWidth());

添加子view

private void addRewardView() {
        for (int i = 0; i < mContainedCount; i++) {
            int                         rewardSize = (int) (2 * 1.0f / 3 * Math.min(getMeasuredHeight(), getMeasuredWidth()));
            BaseView                    rewardView = new BaseView(mContext);
            RelativeLayout.LayoutParams params     = new RelativeLayout.LayoutParams(rewardSize, rewardSize);
            params.rightMargin = radius - rewardSize / 2;
            params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            params.addRule(RelativeLayout.CENTER_VERTICAL);
            rewardView.setLayoutParams(params);
            addView(rewardView);
            mRewardViewList.add(rewardView);
    }

</br>
第四步,子view添加動(dòng)畫和點(diǎn)擊事件
子view開(kāi)始出現(xiàn)的位置為BaseView圓心,往左展開(kāi)包括了一個(gè)水平向左的平移動(dòng)畫,逆時(shí)針?lè)较虻男D(zhuǎn)動(dòng)畫,出現(xiàn)和消失時(shí)的淡入淡出動(dòng)畫;貼出展開(kāi)動(dòng)畫吧

private void startChildExpandAnimation() {
        for (int i = 0; i < mContainedCount; i++) {
            final BaseView view = mRewardViewList.get(i);
            view.setVisibility(VISIBLE);
            final int      desX           = size * (mContainedCount - i);
            ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationX", 0, -desX);
            objectAnimator.setDuration(mAnimDuration);
            objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            objectAnimator.start();
            objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float value = (float) animation.getAnimatedValue();
                    value = Math.abs(value);
                    //淡出
                    if (value <= radius * 1.0f * 33 / 20) {
                        float alpha = value * 1.0f / radius - 0.65f;
                        view.setAlpha(alpha);
                    } else view.setAlpha(1.0f);
                    //旋轉(zhuǎn)
                    float ratation = -value * 1.0f / desX * 90;
                    view.setRotation(ratation);
                }
            });
        }
    }

</br>
第五步,完善代碼
完善部分包括完善自定義的屬性,GratuityView對(duì)外提供的方法,以及一些特殊情況的處理。比如展開(kāi)和收縮時(shí)屏蔽子view的點(diǎn)擊事件,當(dāng)getMeasureWidth()小于所需寬度時(shí)對(duì)動(dòng)畫繪制的位置要重新計(jì)算,如何保存不同子view各自的文字設(shè)置以及設(shè)置雙行文本 等等。

</br>

end~

終于搞完咯!德瑪西亞!

轉(zhuǎn)載請(qǐng)注明出處哦謝謝

下載

https://github.com/dengzq/GratuityView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,087評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 177,521評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,493評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,207評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,603評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,813評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,364評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,110評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,305評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,532評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,953評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,209評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,033評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,268評(píng)論 2 375

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,693評(píng)論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,158評(píng)論 4 61
  • 文/奎尼 大學(xué)之前我遇到兩個(gè)很招人喜歡的女孩子。 Maggie長(zhǎng)相清秀,膚白貌佳,朱唇皓齒。安安身材豐滿,明眸善睞...
    奎尼閱讀 1,827評(píng)論 27 44
  • 用法1:當(dāng)作屬性用 //枚舉值 enum ClickType {case Login, OnlineQuote, ...
    trinity_閱讀 602評(píng)論 0 0
  • 從2006到2016,十年時(shí)光轉(zhuǎn)瞬即逝,一個(gè)人能有多少個(gè)十年呢?突然之間感覺(jué)北漂的這十年很短也很長(zhǎng)。對(duì)于歷經(jīng)滄桑的...
    mikoWoo1閱讀 230評(píng)論 0 2