編輯:關於Android編程
2016年08月01日新的一周開始了,一篇自定義倒計時View開啟了這周的篇章…
國際慣例,效果圖如下;
帶陰影帶指引點的倒計時View,不要被這下過嚇到,分析一下,難點其實就是那個白色小圓圈的位置,其他的都是我們之前自定義view中用到的知識,甚至還沒有第一篇自定義button邏輯復雜,<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxociAvPg0KPHA+PHN0cm9uZz6/tM/CztLDx9fUvLrKtc/WtcTQp7n7LLrNVUnX9rj2vPK1pbXEttSxyCZtZGFzaDsmbWRhc2g7PC9zdHJvbmc+PC9wPg0KPHA+PGltZyBhbHQ9"這裡寫圖片描述" src="/uploadfile/Collfiles/20160802/201608020923471207.png" title="\" />
簡單的對比一下,是不是有幾分相似,哈哈哈哈–下面我們就手把手來實現這個倒計時的View
超一麻袋,來個GIF看下動起來的效果
老規矩,分析需求,實現步驟也就那麼回事,裡面的坑我會用大字標出來
自定義屬性分析從名字和效果圖分析我至少需要三層的的顏色,加上文字的顏色,描邊的顏色等,
看起來很多屬性,其實這些都是方便我們配置的,不要嫌麻煩,
獲取自定義屬性,
這都是要寫吐的代碼了,這裡就不相信說了,直接粘貼出來,給大家復習下
public ATProgressView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ATProgressView, defStyleAttr, R.style.def_progress_style); int indexCount = typedArray.getIndexCount(); for (int i = 0; i < indexCount; i++) { int attr = typedArray.getIndex(i); switch (attr) { case R.styleable.ATProgressView_outer_layer_solide_color: outLayerSolideColor = typedArray.getColor(attr, Color.BLACK); break; case R.styleable.ATProgressView_outer_layer_stroke_color: outLayerStrokeColor = typedArray.getColor(attr, Color.BLACK); break; //省略其他屬性......套路都一樣 } typedArray.recycle(); initData(); }確定View的尺寸,
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize; int heightSize; if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) { widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics()); widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY); } if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) { heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics()); heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
4.根據效果圖,提供各個圖層之間的比例
private static final float OUTER_LAYER_LARGE_SCALE = 58 / 62.F; private static final float MIDDLE_LAYER_LARGE_SCALE = 51 / 62.F; private static final float SHADOW_LAYER_LARGE_SCALE = 40 / 62.F; private static final float SMALL_CIRCLE_DEGREE_OFFSET = 0.7F; private static final int DEF_VIEW_SIZE = 250;
計算比例的時候盡量安裝UI給px 尺寸進行計算,如果UI沒標注,用Mac自帶的圖像打開用鼠標大概的測量一下,這樣你最後寫出的 view 不會因為不同的尺寸而不成比例,**
**
5.最後一步,就是繪制,
分析下我們這個view我們需要繪制的東西有三層,底層,進度層,文字層,
其中進度層還有一個煩人的小圓圈.
**Android的知識點涉及,繪制圓,繪制扇形,繪制陰影,Java基礎知識 倒計時的實現,Android屬性動畫知識,還有就是
初中數學Sin和Cos的知識以及球圓上任一點的坐標和坐標系象限的知識
**
我們都是有精液的Android開發,以上的知識基本都能搞定,困擾的我的就是那個初中數學的知識,悄悄的告訴你們我也谷歌了這些公式,
ok,我們開始一點點繪制
繪制底層和陰影
/**
* 繪制外層的大圓圈和描邊
*
* @param canvas 畫布
*/
private void drawOuterLayerCircles(Canvas canvas) {
// 采用陰影繪制
outLayerSolidePaint.setShadowLayer(10, 2, 2, shadowLayerColor);
//設置陰影圖層
setLayerType(LAYER_TYPE_SOFTWARE, null);
canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mOuterLayerSolideSize / 2, outLayerSolidePaint);
}
繪制中間層 的進度和小圓點
,代碼量略大,數學公式來了,高能預警
/**
* 繪制中間進度的背景和進度
*
* @param canvas 畫布
*/
private void drawMiddleProgressLayer(Canvas canvas) {
float ddx = (viewSize - mMiddleLayerSize) / 2;
//外切圓的坐標計算,繪制的扇形在矩形內的外切圓,注意畫筆的寬度
RectF oval = new RectF(ddx + midlleLayerProgressWidth, ddx + midlleLayerProgressWidth, viewSize - ddx - midlleLayerProgressWidth, viewSize - ddx - midlleLayerProgressWidth);
canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mMiddleLayerSize / 2 - midlleLayerProgressWidth, progressBgPaint);
// 注意掃過的扇形的范圍(進度)要和繪制的小圓點保持一致,所以我們需要從-90度開始
canvas.drawArc(oval, -90, 360 * drgeePercent, false, progressPaint);
// 由於前面繪制了一個小圓,所以我們弧度的角度不能用於計算圓的坐標,我們需要大概的加上那麼一兩度來計算,
// 由於android坐標系的問題以及角度在不同象限內的問題,所以我們需要計算幾種情況
// 0-90,90-180 ,180-270,270-360
float animDegree = 360 * drgeePercent + SMALL_CIRCLE_DEGREE_OFFSET;
float xiaoyuanDegree;
float xiaoYuanX = 0, xiaoYuanY = 0;
int tempD = (int) animDegree;
if (tempD >= 0 && tempD < 90) {
// 第一象限內,sin和cons正常
xiaoyuanDegree = animDegree;
float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
float sinAX = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
float cosAY = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
xiaoYuanX = (viewSize - 2 * midlleLayerProgressWidth - 2 * sinAX) / 2 + 2 * sinAX;
xiaoYuanY = viewSize / 2 + midlleLayerProgressWidth - cosAY;
} else if (tempD >= 90 && tempD < 180) {
// 第二象限內,sin和cos互換
xiaoyuanDegree = animDegree - 90;
float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
float sinAX = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
float cosAY = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
xiaoYuanX = (viewSize - 2 * midlleLayerProgressWidth - 2 * sinAX) / 2 + 2 * sinAX;
xiaoYuanY = viewSize / 2 + cosAY - midlleLayerProgressWidth;
} else if (tempD >= 180 && tempD < 270) {
// 第三象限,sin和cos正常,但是x和y的坐標計算方法發生改變
xiaoyuanDegree = animDegree - 180;
float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
float sinAX = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 - midlleLayerProgressWidth));
float cosAY = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2));
xiaoYuanX = viewSize / 2 - sinAX;
xiaoYuanY = viewSize / 2 + cosAY - midlleLayerProgressWidth;
} else if (tempD >= 270 && tempD < 360) {
// 第四象限內,sin和cos互換,但是x和y的坐標也發生了改變
xiaoyuanDegree = animDegree - 270;
float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
float sinAX = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
float cosAY = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 - midlleLayerProgressWidth));
xiaoYuanX = viewSize / 2 - sinAX + midlleLayerProgressWidth;
xiaoYuanY = viewSize / 2 - cosAY;
}
canvas.drawCircle(xiaoYuanX, xiaoYuanY, smallCircleSize / 2, smallCirclePaint);
canvas.drawCircle(xiaoYuanX, xiaoYuanY, (smallCircleSize - smallCircleStrokeWidth) / 2, smallCircleInnerPaint);
}
終於跳過了上面的計算,下面的就是剩下繪制文字的知識了so easy
我們先計算出來文字的寬度和高度然後計算出來文字的繪制坐標即可
/**
* 繪制倒計時的描述顯示
*
* @param progressDesc 描述
* @param canvas 畫布
*/
private void drawProgressText(String progressDesc, Canvas canvas) {
Point textPointInView = getTextPointInView(progressDesc);
if (null == textPointInView) return;
canvas.drawText(progressDesc, textPointInView.x, textPointInView.y, mTextDescPaint);
}
private Point getTextPointInView(String textDesc) {
if (null == textDesc) return null;
Point point = new Point();
int textW = (viewSize - (int) mTextDescPaint.measureText(textDesc)) / 2;
Paint.FontMetrics fm = mTextDescPaint.getFontMetrics();
int textH = (int) Math.ceil(fm.descent - fm.top);
point.set(textW, viewSize / 2 + textH / 2 - 20);
return point;
}
/**
* 繪制陰影圓圈
*
* @param canvas 畫布
*/
private void drawShadowCircle(Canvas canvas) {
mShadowLayerInnerPaint.setShadowLayer(10, 2, 2, shadowLayerColor);
//設置陰影圖層
setLayerType(LAYER_TYPE_SOFTWARE, null);
mShadowLayerInnerPaint.setColor(shadowLayerInnerColor);
canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mShadowLayerSize / 2, mShadowLayerInnerPaint);
}
到此我們的Android繪制API全部用完了,就用了一個繪制扇形和圓形的方法,
下面我們看下Java知識,倒計時的方法;
private void startCountDownTaskByRxAndroid() {
// 每隔一秒發送一次事件
Observable.interval(0, 1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
countdownTime = 0;
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Long aLong) {
// 每隔一秒,我們設定的事件就減少一秒
if (countdownTime < -1) {
this.unsubscribe();
}
--countdownTime;
// 簡單的邏輯判斷如果預設事件小於0那麼我們修改文案,
if (countdownTime < 0) {
mTextDescPaint.setTextSize(innerTextSize / 2);
progressDesc = "時間到";
onCompleted();
return;
} else {
mTextDescPaint.setTextSize(innerTextSize);
progressDesc = countdownTime + "″";
}
// 刷新view
invalidate();
}
});
}
最後就是一個動畫的實現,我們可以理解成進度條在CountDown時間內正好從0-360走完,
那麼這個用屬性動畫就行了
public void startCountdown(final OnCountDownFinishListener countDownFinishListener) {
setClickable(false);
final ValueAnimator valA = getValA(countdownTime * 1000);
valA.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 計算當前的角度,並且實時的刷新View,這樣進度就動起來了
drgeePercent = Float.valueOf(valA.getAnimatedValue().toString());
invalidate();
}
});
valA.start();
valA.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//提供一個對外公開的接口,這樣我們的View在倒計時結束後,就能通知UI干活了.....
if (null != countDownFinishListener) {
countDownFinishListener.countDownFinished();
}
super.onAnimationEnd(animation);
if (countdownTime > 0) {
setClickable(true);
} else {
setClickable(false);
}
}
});
startCountDownTaskByRxAndroid();
}
到此,自定義倒計時View結束,下面我把自定義View的全部代碼放到下面,有意向的可以放到AS裡面運行一下,
public class ATProgressView extends View {
private static final float OUTER_LAYER_LARGE_SCALE = 58 / 62.F;
private static final float MIDDLE_LAYER_LARGE_SCALE = 51 / 62.F;
private static final float SHADOW_LAYER_LARGE_SCALE = 40 / 62.F;
private static final float SMALL_CIRCLE_DEGREE_OFFSET = 0.7F;
private static final int DEF_VIEW_SIZE = 250;
private static final float TEST_DEGREE = 70.F;
private int outLayerSolideColor;
private int outLayerStrokeColor;
private int outLayerStrokeWidth;
private int midlleLayerProgressColor;
private int midlleLayerProgressWidth;
private int midlleLayerBgColor;
private int smallCircleSolideColor;
private int smallCircleStrokeColor;
private int smallCircleSize;
private int smallCircleStrokeWidth;
private int shadowLayerColor;
private int shadowLayerInnerColor;
private int innerTextSize;
private int innerTextColor;
private Point circleCenterPoint;
private int viewSize;
private Paint outLayerStrokePaint;
private Paint outLayerSolidePaint;
private Paint progressBgPaint;
private Paint progressPaint;
private Paint smallCirclePaint;
private Paint smallCircleInnerPaint;
private Paint mShadowLayerInnerPaint;
private Paint mTextDescPaint;
private float mOuterLayerLargeSize;
private float mOuterLayerSolideSize;
private float mMiddleLayerSize;
private float mShadowLayerSize;
private String progressDesc;
private float drgeePercent;
private int countdownTime;
public ATProgressView(Context context) {
this(context, null);
}
public ATProgressView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ATProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ATProgressView, defStyleAttr, R.style.def_progress_style);
int indexCount = typedArray.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.ATProgressView_outer_layer_solide_color:
outLayerSolideColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ATProgressView_outer_layer_stroke_color:
outLayerStrokeColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ATProgressView_outer_layer_stroke_width:
outLayerStrokeWidth = typedArray.getDimensionPixelOffset(attr, 0);
break;
case R.styleable.ATProgressView_midlle_layer_progress_color:
midlleLayerProgressColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ATProgressView_midlle_layer_progress_width:
midlleLayerProgressWidth = typedArray.getDimensionPixelOffset(attr, 0);
break;
case R.styleable.ATProgressView_midlle_layer_bg_color:
midlleLayerBgColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ATProgressView_midlle_layer_small_circle_solide_color:
smallCircleSolideColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ATProgressView_midlle_layer_small_circle_stroke_color:
smallCircleStrokeColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ATProgressView_midlle_layer_small_circle_size:
smallCircleSize = typedArray.getDimensionPixelSize(attr, 0);
break;
case R.styleable.ATProgressView_midlle_layer_small_circle_stroke_width:
smallCircleStrokeWidth = typedArray.getDimensionPixelOffset(attr, 0);
break;
case R.styleable.ATProgressView_shadow_layer_color:
shadowLayerColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ATProgressView_shadow_layer_inner_color:
shadowLayerInnerColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ATProgressView_inner_text_size:
innerTextSize = typedArray.getDimensionPixelSize(attr, 0);
break;
case R.styleable.ATProgressView_inner_text_color:
innerTextColor = typedArray.getColor(attr, Color.BLACK);
break;
}
}
typedArray.recycle();
initData();
}
private void initData() {
outLayerStrokePaint = creatPaint(outLayerStrokeColor, 0, Paint.Style.FILL, 0);
outLayerSolidePaint = creatPaint(outLayerSolideColor, 0, Paint.Style.FILL, 0);
progressBgPaint = creatPaint(midlleLayerBgColor, 0, Paint.Style.STROKE, midlleLayerProgressWidth);
progressPaint = creatPaint(midlleLayerProgressColor, 0, Paint.Style.STROKE, midlleLayerProgressWidth);
smallCirclePaint = creatPaint(smallCircleStrokeColor, 0, Paint.Style.FILL, 0);
smallCircleInnerPaint = creatPaint(smallCircleSolideColor, 0, Paint.Style.FILL, 0);
mShadowLayerInnerPaint = creatPaint(shadowLayerInnerColor, 0, Paint.Style.FILL, 0);
mTextDescPaint = creatPaint(innerTextColor, innerTextSize, Paint.Style.FILL, 0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize;
int heightSize;
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
viewSize = w - h >= 0 ? h : w;
circleCenterPoint = new Point(viewSize / 2, viewSize / 2);
mOuterLayerLargeSize = viewSize * OUTER_LAYER_LARGE_SCALE;
mOuterLayerSolideSize = mOuterLayerLargeSize - 2 * outLayerStrokeWidth;
mMiddleLayerSize = viewSize * MIDDLE_LAYER_LARGE_SCALE;
mShadowLayerSize = viewSize * SHADOW_LAYER_LARGE_SCALE;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawOuterLayerCircles(canvas);
drawMiddleProgressLayer(canvas);
drawShadowCircle(canvas);
drawProgressText(progressDesc, canvas);
}
/**
* 繪制倒計時的描述顯示
*
* @param progressDesc 描述
* @param canvas 畫布
*/
private void drawProgressText(String progressDesc, Canvas canvas) {
Point textPointInView = getTextPointInView(progressDesc);
if (null == textPointInView) return;
canvas.drawText(progressDesc, textPointInView.x, textPointInView.y, mTextDescPaint);
}
private Point getTextPointInView(String textDesc) {
if (null == textDesc) return null;
Point point = new Point();
int textW = (viewSize - (int) mTextDescPaint.measureText(textDesc)) / 2;
Paint.FontMetrics fm = mTextDescPaint.getFontMetrics();
int textH = (int) Math.ceil(fm.descent - fm.top);
point.set(textW, viewSize / 2 + textH / 2 - 20);
return point;
}
/**
* 繪制陰影圓圈
*
* @param canvas 畫布
*/
private void drawShadowCircle(Canvas canvas) {
mShadowLayerInnerPaint.setShadowLayer(10, 2, 2, shadowLayerColor);
//設置陰影圖層
setLayerType(LAYER_TYPE_SOFTWARE, null);
mShadowLayerInnerPaint.setColor(shadowLayerInnerColor);
canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mShadowLayerSize / 2, mShadowLayerInnerPaint);
}
/**
* 繪制中間進度的背景和進度
*
* @param canvas 畫布
*/
private void drawMiddleProgressLayer(Canvas canvas) {
float ddx = (viewSize - mMiddleLayerSize) / 2;
//外切圓的坐標計算,繪制的扇形在矩形內的外切圓,注意畫筆的寬度
RectF oval = new RectF(ddx + midlleLayerProgressWidth, ddx + midlleLayerProgressWidth, viewSize - ddx - midlleLayerProgressWidth, viewSize - ddx - midlleLayerProgressWidth);
canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mMiddleLayerSize / 2 - midlleLayerProgressWidth, progressBgPaint);
// 注意掃過的扇形的范圍(進度)要和繪制的小圓點保持一致,所以我們需要從-90度開始
canvas.drawArc(oval, -90, 360 * drgeePercent, false, progressPaint);
// 由於前面繪制了一個小圓,所以我們弧度的角度不能用於計算圓的坐標,我們需要大概的加上那麼一兩度來計算,
// 由於android坐標系的問題以及角度在不同象限內的問題,所以我們需要計算幾種情況
// 0-90,90-180 ,180-270,270-360
float animDegree = 360 * drgeePercent + SMALL_CIRCLE_DEGREE_OFFSET;
float xiaoyuanDegree;
float xiaoYuanX = 0, xiaoYuanY = 0;
int tempD = (int) animDegree;
if (tempD >= 0 && tempD < 90) {
// 第一象限內,sin和cons正常
xiaoyuanDegree = animDegree;
float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
float sinAX = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
float cosAY = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
xiaoYuanX = (viewSize - 2 * midlleLayerProgressWidth - 2 * sinAX) / 2 + 2 * sinAX;
xiaoYuanY = viewSize / 2 + midlleLayerProgressWidth - cosAY;
} else if (tempD >= 90 && tempD < 180) {
// 第二象限內,sin和cos互換
xiaoyuanDegree = animDegree - 90;
float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
float sinAX = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
float cosAY = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
xiaoYuanX = (viewSize - 2 * midlleLayerProgressWidth - 2 * sinAX) / 2 + 2 * sinAX;
xiaoYuanY = viewSize / 2 + cosAY - midlleLayerProgressWidth;
} else if (tempD >= 180 && tempD < 270) {
// 第三象限,sin和cos正常,但是x和y的坐標計算方法發生改變
xiaoyuanDegree = animDegree - 180;
float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
float sinAX = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 - midlleLayerProgressWidth));
float cosAY = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2));
xiaoYuanX = viewSize / 2 - sinAX;
xiaoYuanY = viewSize / 2 + cosAY - midlleLayerProgressWidth;
} else if (tempD >= 270 && tempD < 360) {
// 第四象限內,sin和cos互換,但是x和y的坐標也發生了改變
xiaoyuanDegree = animDegree - 270;
float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
float sinAX = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
float cosAY = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 - midlleLayerProgressWidth));
xiaoYuanX = viewSize / 2 - sinAX + midlleLayerProgressWidth;
xiaoYuanY = viewSize / 2 - cosAY;
}
canvas.drawCircle(xiaoYuanX, xiaoYuanY, smallCircleSize / 2, smallCirclePaint);
canvas.drawCircle(xiaoYuanX, xiaoYuanY, (smallCircleSize - smallCircleStrokeWidth) / 2, smallCircleInnerPaint);
}
/**
* 繪制外層的大圓圈和描邊
*
* @param canvas 畫布
*/
private void drawOuterLayerCircles(Canvas canvas) {
// 采用陰影繪制
outLayerSolidePaint.setShadowLayer(10, 2, 2, shadowLayerColor);
//設置陰影圖層
setLayerType(LAYER_TYPE_SOFTWARE, null);
canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mOuterLayerSolideSize / 2, outLayerSolidePaint);
}
/**
* 初始化畫筆
*
* @param paintColor 畫筆顏色
* @param textSize 文字大小
* @param style 畫筆風格
* @param lineWidth 畫筆寬度
* @return 畫筆
*/
private Paint creatPaint(int paintColor, int textSize, Paint.Style style, int lineWidth) {
Paint paint = new Paint();
paint.setColor(paintColor);
paint.setAntiAlias(true);
paint.setStrokeWidth(lineWidth);
paint.setDither(true);
paint.setTextSize(textSize);
paint.setStyle(style);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
return paint;
}
private ValueAnimator getValA(long countdownTime) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1.F);
valueAnimator.setDuration(countdownTime);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(0);
return valueAnimator;
}
/**
* 開始倒計時任務
*/
public void startCountdown(final OnCountDownFinishListener countDownFinishListener) {
setClickable(false);
final ValueAnimator valA = getValA(countdownTime * 1000);
valA.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
drgeePercent = Float.valueOf(valA.getAnimatedValue().toString());
invalidate();
}
});
valA.start();
valA.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (null != countDownFinishListener) {
countDownFinishListener.countDownFinished();
}
super.onAnimationEnd(animation);
if (countdownTime > 0) {
setClickable(true);
} else {
setClickable(false);
}
}
});
startCountDownTaskByRxAndroid();
}
public void setCountdownTime(int countdownTime) {
this.countdownTime = countdownTime;
progressDesc = countdownTime + "″";
}
private void startCountDownTaskByRxAndroid() {
Observable.interval(0, 1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
countdownTime = 0;
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Long aLong) {
if (countdownTime < -1) {
this.unsubscribe();
}
--countdownTime;
if (countdownTime < 0) {
mTextDescPaint.setTextSize(innerTextSize / 2);
progressDesc = "時間到";
onCompleted();
return;
} else {
mTextDescPaint.setTextSize(innerTextSize);
progressDesc = countdownTime + "″";
}
invalidate();
}
});
}
public interface OnCountDownFinishListener {
void countDownFinished();
}
}
最後源代碼的鏈接如下,希望大家多多fork和star,github地址如下https://github.com/GuoFeilong/ATLoginButton_New
定義(GoF《設計模式》):將對象組合成樹形結構以表示“部分整體”的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。 組件。 未完待續,有不對
有不少朋友都遇到過這種問題,程序執行時切換到後台,然後再重新進入會報異常,
自動提示文本框(AutoCompleteTextView)可以加強用戶體驗,縮短用戶的輸入時間(百度的搜索框就是這個效果)。 首先,在xml中定義AutoComplete
Android插件化的思考——仿QQ一鍵換膚。今天群友希望寫一個關於插件的Blog,思來想去,插件也不是很懂,只是用大致的思路看看能不能模擬一個,