前面主要說了自定義View的一些知識恨搓,這篇文章主要是利用自定義View做一個仿小米運(yùn)動計步功能的控件,如下圖所示:
分析一下思路:
1.畫背景
2.畫一個最外部的圓
3.畫圓上的小圓點
4.畫豎線筏养,環(huán)繞一周
5.畫圓環(huán)
6.畫文字
7.添加動畫
為了可以自定義各個控件的顯示效果斧抱,自定義View的屬性還是必要的。
自定義屬性
自定義屬性主要是自定義了各個部件的顏色渐溶,format是該屬性的取值類型辉浦。
這里要注意的是,自定義屬性的name定義成了XiaoMiStep掌猛,那么自定義View的名字也要是XiaoMiStep盏浙,保持一致。
<declare-styleable name="XiaoMiStep">
<!--背景-->
<attr name="backGroundColor" format="color"></attr>
<!--最外層圓-->
<attr name="outerCircleColor" format="color"></attr>
<!--外層圓上的小圓點顏色-->
<attr name="outerDotColor" format="color"></attr>
<!--豎線的顏色-->
<attr name="lineColor" format="color"></attr>
<!--圓環(huán)的顏色-->
<attr name="ringColor" format="color"></attr>
<!--步數(shù)顏色-->
<attr name="stepNumColor" format="color"></attr>
<!--其他字的顏色-->
<attr name="othetTextColor" format="color"></attr>
</declare-styleable>
然后就是在布局文件中申明我們的自定義view荔茬。
這樣废膘,自定義View XiaoMiStep在xml布局文件里引用的時候,代碼如下:
<com.example.ahuang.viewandgroup.view.XiaoMiStep
android:id="@+id/xiaoMiStep"
android:layout_width="match_parent"
android:layout_height="wrap_content"
custom:backGroundColor="#0FA9C1"
custom:lineColor="#8ED8E5"
custom:othetTextColor="#8ED8E5"
custom:outerCircleColor="#8ED8E5"
custom:outerDotColor="#ffffff"
custom:ringColor="#8ED8E5"
custom:stepNumColor="#ffffff"/>
記得最后要引入我們的命名空間
xmlns:custom="http://schemas.android.com/apk/res-auto"
引入我們自定義的屬性
獲得atts.xml定義的屬性值
自定義View一般需要實現(xiàn)一下三個構(gòu)造方法慕蔚,這三個構(gòu)造方法是一層調(diào)用一層的丐黄,屬于遞進(jìn)關(guān)系。因此孔飒,我們只需要在最后一個構(gòu)造方法中來獲得View的屬性了灌闺。
- 通過theme.obtainStyledAttributes()方法獲得自定義控件的主題樣式數(shù)組
- 就是遍歷每個屬性來獲得對應(yīng)屬性的值艰争,也就是我們在xml布局文件中寫的屬性值
- 就是在循環(huán)結(jié)束之后記得調(diào)用array.recycle()來回收資源
public XiaoMiStep(Context context) {
this(context, null);
}
public XiaoMiStep(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public XiaoMiStep(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//獲得atts.xml定義的屬性值,存儲在TypedArray中
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.XiaoMiStep, defStyleAttr, 0);
int n = ta.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = ta.getIndex(i);
switch (attr) {
case R.styleable.XiaoMiStep_backGroundColor: //背景顏色
background_color = ta.getColor(attr, Color.WHITE); //默認(rèn)為白色
break;
case R.styleable.XiaoMiStep_outerCircleColor: //最外側(cè)圓
outer_circle_color = ta.getColor(attr, Color.WHITE);
break;
case R.styleable.XiaoMiStep_outerDotColor: //最外側(cè)圓上的小圓點
outer_dot_color = ta.getColor(attr, Color.WHITE);
break;
case R.styleable.XiaoMiStep_lineColor: //最外側(cè)線的顏色
line_color = ta.getColor(attr, Color.WHITE);
break;
case R.styleable.XiaoMiStep_ringColor: //圓環(huán)的顏色
ring_color = ta.getColor(attr, Color.WHITE);
break;
case R.styleable.XiaoMiStep_stepNumColor: //步數(shù)的顏色
step_num_color = ta.getColor(attr, Color.WHITE);
break;
case R.styleable.XiaoMiStep_othetTextColor: //其他文字顏色
othet_text_color = ta.getColor(attr, Color.WHITE);
break;
}
}
ta.recycle();
init();
}
初始化畫筆
private void init() {
mPaint = new Paint(); //畫筆
mPaint.setAntiAlias(true);
arcPaint = new Paint(); //圓環(huán)畫筆
arcPaint.setAntiAlias(true);
textPaint = new Paint(); //文字畫筆
textPaint.setAntiAlias(true);
pointPaint = new Paint(); //點
pointPaint.setAntiAlias(true);
animSet = new AnimatorSet(); //動畫組合
}
重寫onMesure方法確定view大小
當(dāng)你沒有重寫onMeasure方法時候桂对,系統(tǒng)調(diào)用默認(rèn)的onMeasure方法:
@OverrideprotectedvoidonMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
這個方法的作用是:測量控件的大小甩卓。其實Android系統(tǒng)在加載布局的時候是由系統(tǒng)測量各子View的大小來告訴父View我需要占多大空間,然后父View會根據(jù)自己的大小來決定分配多大空間給子View蕉斜。MeasureSpec的specMode模式一共有三種:
MeasureSpec.EXACTLY:父視圖希望子視圖的大小是specSize中指定的大杏馐痢;一般是設(shè)置了明確的值或者是MATCH_PARENT
MeasureSpec.AT_MOST:子視圖的大小最多是specSize中的大姓恕机错;表示子布局限制在一個最大值內(nèi),一般為WARP_CONTENT
MeasureSpec.UNSPECIFIED:父視圖不對子視圖施加任何限制父腕,子視圖可以得到任意想要的大腥醴恕;表示子布局想要多大就多大璧亮,很少使用萧诫。
想要設(shè)置WARP_CONTENT,只要重寫onMeasure方法
另外枝嘶,在onMeasure()方法里實現(xiàn)了動畫效果财搁。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthMode = MeasureSpec.getMode(widthMeasureSpec); //寬度的測量模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec); //寬度的測量值
int heightMode = MeasureSpec.getMode(heightMeasureSpec); //高度的測量模式
int heightSize = MeasureSpec.getSize(heightMeasureSpec); //高度的測量值
//如果布局里面設(shè)置的是固定值,這里取布局里面的固定值;如果設(shè)置的是match_parent,則取父布局的大小
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
//如果布局里面沒有設(shè)置固定值,這里取布局的寬度的1/2
width = widthSize * 1 / 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
//如果布局里面沒有設(shè)置固定值,這里取布局的高度的3/4
height = heightSize * 3 / 4;
}
widthBg = width;
heightBg = height;
ra_out_circle = heightBg * 3 / 9;
ra_inner_circle = heightBg * 3 / 10;
line_length = 30;
setMeasuredDimension(width, height);
startAnim();
}
重寫onDraw方法進(jìn)行繪畫
代碼已經(jīng)很詳細(xì)了。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制底層背景
mPaint.setColor(background_color);
mPaint.setStyle(Paint.Style.FILL);
RectF rectF_back = new RectF(0, 0, widthBg, heightBg);
canvas.drawRect(rectF_back, mPaint);
//繪制最外層的圓
mPaint.setColor(outer_circle_color);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(3);
canvas.drawCircle(widthBg / 2, heightBg / 2, ra_out_circle, mPaint);
//繪制圓上的小圓點
pointPaint.setColor(outer_dot_color);
pointPaint.setStrokeWidth(10);
canvas.drawCircle((float) (widthBg / 2 + ra_out_circle * Math.cos(angle * 3.14 / 180)), (float) (heightBg / 2 + ra_out_circle * Math.sin(angle * 3.14 / 180)), 10, pointPaint);
//畫line
drawLines(canvas);
//畫圓弧
arcPaint.setStyle(Paint.Style.STROKE);
arcPaint.setStrokeWidth(30);
arcPaint.setColor(ring_color);
RectF arcRect = new RectF((widthBg / 2 - ra_inner_circle + line_length / 2), (heightBg / 2 - ra_inner_circle + line_length / 2), (widthBg / 2 + ra_inner_circle - line_length / 2), (heightBg / 2 + ra_inner_circle - line_length / 2));
canvas.drawArc(arcRect, -90, currentFootNumPre, false, arcPaint);
//繪制步數(shù)
textPaint.setColor(step_num_color);
textPaint.setStrokeWidth(25);
textPaint.setTextSize(widthBg / 6);
canvas.drawText(String.valueOf(currentFootNum), (widthBg / 3 - 50), heightBg / 2 + 50, textPaint);
textPaint.setStrokeWidth(10);
textPaint.setColor(othet_text_color);
textPaint.setTextSize(widthBg / 20);
canvas.drawText("步", (widthBg / 2 + 200), heightBg / 2 + 50, textPaint);
//繪制公里
currentDistance = (float) (myFootNum * 6.4 / 8000);
//小數(shù)點后一位
java.text.DecimalFormat df = new java.text.DecimalFormat("#.0");
String currentDis = df.format(currentDistance);
canvas.drawText(currentDis + "公里", (widthBg / 3 - 30), heightBg / 2 + 150, textPaint);
//中間豎線
mPaint.setStrokeWidth(8);
canvas.drawLine(widthBg / 2 + 10, heightBg / 2 + 110, widthBg / 2 + 10, heightBg / 2 + 155, mPaint);
//繪制卡路里
currentCal = myFootNum * 230 / 8000;
canvas.drawText(String.valueOf(currentCal) + "千卡", (widthBg / 2 + 40), heightBg / 2 + 150, textPaint);
}
private void drawLines(Canvas canvas) {
mPaint.setColor(line_color);
mPaint.setStrokeWidth(4);
for (int i = 0; i < 360; i++) {
canvas.drawLine(widthBg / 2, (heightBg / 2 - ra_inner_circle), widthBg / 2, (heightBg / 2 - ra_inner_circle + line_length), mPaint);
canvas.rotate(1, widthBg / 2, heightBg / 2);
}
}
默認(rèn)一圈代表8000步躬络,6.4公里,230千卡搭儒,初始步數(shù)根據(jù)以下代碼設(shè)置穷当。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_xiao_mi_setp);
ButterKnife.bind(this);
mXiaoMiStep.setMyFootNum(4500);
}
public void setMyFootNum(int myFootNum) {
this.myFootNum = myFootNum;
}
實現(xiàn)動畫
主要是
外圓上的小圓點動畫,是根據(jù)角度確定淹禾。
步數(shù)動畫在 0-myFootNum之間
畫圓弧的動畫在 0-myFootNum * 360 / 8000
private void startAnim() {
//小圓點動畫
final ValueAnimator dotAnimator =ValueAnimator.ofInt(-90, (myFootNum*360/8000-90));
dotAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
angle = (int) dotAnimator.getAnimatedValue();
postInvalidate();
}
});
dotAnimator.setInterpolator(new LinearInterpolator());
//步數(shù)動畫實現(xiàn)
final ValueAnimator walkAnimator = ValueAnimator.ofInt(0, myFootNum);
walkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentFootNum = (int) walkAnimator.getAnimatedValue();
postInvalidate();
}
});
//畫弧動畫的實現(xiàn)
final ValueAnimator arcAnimator = ValueAnimator.ofInt(0, (myFootNum * 360 / 8000));
arcAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentFootNumPre = (int) arcAnimator.getAnimatedValue();
postInvalidate();
}
});
animSet.setDuration(3000);
animSet.playTogether(walkAnimator, arcAnimator, dotAnimator);
animSet.start();
}
效果圖如下所示馁菜,當(dāng)然,你也可以通過改變xml布局的custom選項铃岔,來改變各個部分的顏色汪疮。