前言
NBA全明星周末!重要事情說(shuō)一遍!
作為一個(gè)經(jīng)常在虎撲上看NBA直播的小球迷,老早就留意到了打賞加油的小控件,今天趕緊的,讓我們來(lái)實(shí)現(xiàn)它吧~搞起!
</br>
虎撲原型和效果圖圖
先看下虎撲原型:
挺好看的一個(gè)控件嘛有木有~,看下我們的效果圖:
</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)演示一下:
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)的距離
接下來(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)注明出處哦謝謝