好了,吐槽时间到.自定义view是Android开发知识体系中的重点,也是难点.好多小伙伴(也包括我)之前对自定义view也是似懂非懂.那种感觉老难受了.因此作为社会主义好青年,怎么能够不加钻研呢?那可不是你我的风格哦.因此,我将通过几篇博文来展示自定义view的基本流程.另外,我说一下,关于自定义view的学习呢,我们还是得动手敲代码,实践出真知!因此,我强烈建议同志们先从最基本,最简单的自定义view画起,这样在自定义view的过程中,才能了解其原理,从而为以后画出各种各样炫酷的view,打下基础. OK,本片博文将通过自定义圆环,带你去探索自定义view的奥妙…
俗话说无规矩不成方圆,无图全是扯淡!
上图就是自定义圆环的指导图(非常重要).下面就根据这个神图,来向大家介绍下如何自定义圆环?
Step 1:画里面的白色小圆
事实上,里面的白色圆圈如果不画的话,我们也可以画圆环,但是,但是,但是,重要的事情说三遍.强烈建议不要遗漏.因为在画圆时候,我们能对半径等参数有更加清晰,便于后续圆环的绘制.再说了,画个圆又咋了,无非几行代码的事.
Step 2:画矩形(正方形)
这个正方形是画圆环的最核心的步骤.如果想要画出图中绿色的圆环,这个正方形是必须的.注意看正方形是红边圆圈的外接圆.而这个所谓的红色圆圈正好处在圆环中间位置.图中一目了然,不在瞎啰嗦了.
Step 3:画圆环
其实到了这一步,我们的圆环就已经出来了.此处我们可以做的是圆环的属性的调节,比如,你可以调节圆环的颜色啥的
###关于自定义view的基本知识储备
如果你对自定义view的基本流程已经有了一个基本的认知的话,可以跳过次步骤.如果你不是很熟悉的话,推荐你先阅读下Android自定义View的官方套路 ,里面介绍的还可以.
对Android有一些了解的朋友都知道,android为我们提供的很多View都是继承与View的。所以我们自定义的View当然也是继承于View,当然如果你要自定义的View拥有某些android已经提供的控件的功能,你可以直接继承于已经提供的控件。
public class SuperCircleView extends View {
public SuperCircleView(Context context) {
this(context, null);
}
public SuperCircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SuperCircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SuperCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
.....
.....
}
}
大部分情况我们的自定义View需要有更多的灵活性,比如我们在xml中指定了颜色大小等属性,在程序运行时候控件就能展示出相应的颜色和大小。所以我们需要自定义属性自定义属性通常写在资源文件res/values/attrs.xml文件中.
贴出attr_super_circle.xml属性文件
<resources>
<declare-styleable name="SuperCircleView">
<attr name="min_circle_radio" format="integer"/>
<attr name="ring_width" format="float"/>
<attr name="circle_color" format="color"/>
<attr name="max_circle_color" format="color"/>
<attr name="ring_normal_color" format="color"/>
<attr name="ring_color_select" format="integer"/>
<attr name="maxValue" format="integer" />
<attr name="value" format="integer" />
declare-styleable>
resources>
其实这一步,正常情况下是应该放在自定义圆环完成之后.但是为了演示Step4中获取自定义属性的逻辑,暂时放在这一步了.由于通常我们需要拿到属性做一些事情,因此就需要在xml中设置了控件自定义属性。否则定义自定义属性就没有意义了。
直接上布局文件activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center">
<com.example.zq.drawcircledemo.SuperCircleView
android:id="@+id/superview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:maxValue="100"
app:value="20"
app:ring_width="60" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="60dp"
android:text="信息完成度"
android:textColor="#CFD5DE"
android:textSize="18sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textColor="#506946"
android:textSize="80sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="%"
android:textSize="28sp" />
LinearLayout>
FrameLayout>
LinearLayout>
布局文件中的
app:maxValue="100"
app:ring_width="60"
设置的属性就是我们在attr.xml中定义的.至于如何获取这些自定义属性的数值,后面会展示.
有三个构造方法(一个参数、两个参数、三个参数),其中两个参数的构造方法必须有。
在 public SuperCircleView(Context context, AttributeSet attrs, int defStyleAttr){…}中进行初始化的操作.基本是一些获取自定义属性的操作
public class SuperCircleView extends View {
private final String TAG = "SuperCircleView";
private ValueAnimator valueAnimator;
private int mViewCenterX; //view宽的中心点(可以暂时理解为圆心)
private int mViewCenterY; //view高的中心点(可以暂时理解为圆心)
private int mMinRadio; //最里面白色圆的半径
private float mRingWidth; //圆环的宽度
private int mMinCircleColor; //最里面圆的颜色
private int mRingNormalColor; //默认圆环的颜色
private Paint mPaint;
private int color[] = new int[3]; //渐变颜色
private RectF mRectF; //圆环的矩形区域
private int mSelectRing = 0; //要显示的彩色区域(岁数值变化)
private int mMaxValue;
public SuperCircleView(Context context) {
this(context, null);
}
public SuperCircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SuperCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuperCircleView);
//最里面白色圆的半径
mMinRadio = a.getInteger(R.styleable.SuperCircleView_min_circle_radio, 300);
//圆环宽度
mRingWidth = a.getFloat(R.styleable.SuperCircleView_ring_width, 40);
//最里面的圆的颜色(绿色)
mMinCircleColor = a.getColor(R.styleable.SuperCircleView_circle_color, context.getResources().getColor(R.color.green));
//圆环的默认颜色(圆环占据的是里面的圆的空间)
mRingNormalColor = a.getColor(R.styleable.SuperCircleView_ring_normal_color, context.getResources().getColor(R.color.gray));
//圆环要显示的彩色的区域
mSelectRing = a.getInt(R.styleable.SuperCircleView_ring_color_select, 0);
mMaxValue = a.getInt(R.styleable.SuperCircleView_maxValue, 100);
a.recycle();
//抗锯齿画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//防止边缘锯齿
mPaint.setAntiAlias(true);
//需要重写onDraw就得调用此
this.setWillNotDraw(false);
//圆环渐变的颜色
color[0] = Color.parseColor("#FFD300");
color[1] = Color.parseColor("#FF0084");
color[2] = Color.parseColor("#16FF00");
}
}
首先说一些,自定义view的话一般会重写一下三个方法:
onDraw(): 是用来绘制View图像,这个方法必须有.
onMeasure(): 用于改变View 的大小。
onLayout(): 用于改变View在父控件中的位置
Ok,继续.
(1).确定待画里面圆形以及矩形的位置
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//view的宽和高,相对于父布局(用于确定圆心)
int viewWidth = getMeasuredWidth();
int viewHeight = getMeasuredHeight();
mViewCenterX = viewWidth / 2;
mViewCenterY = viewHeight / 2;
//画矩形
mRectF = new RectF(mViewCenterX - mMinRadio - mRingWidth / 2, mViewCenterY - mMinRadio - mRingWidth / 2, mViewCenterX + mMinRadio + mRingWidth / 2, mViewCenterY + mMinRadio + mRingWidth / 2);
}
(2).绘制圆环
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(mMinCircleColor);
canvas.drawCircle(mViewCenterX, mViewCenterY, mMinRadio, mPaint);
//画默认圆环
drawNormalRing(canvas);
//画彩色圆环
drawColorRing(canvas);
}
/**
* 画默认圆环
*
* @param canvas
*/
private void drawNormalRing(Canvas canvas) {
Paint ringNormalPaint = new Paint(mPaint);
ringNormalPaint.setStyle(Paint.Style.STROKE);
ringNormalPaint.setStrokeWidth(mRingWidth);
ringNormalPaint.setColor(mRingNormalColor);//圆环默认颜色为灰色
canvas.drawArc(mRectF, 360, 360, false, ringNormalPaint);
}
/**
* 画彩色圆环
*
* @param canvas
*/
private void drawColorRing(Canvas canvas) {
Paint ringColorPaint = new Paint(mPaint);
ringColorPaint.setStyle(Paint.Style.STROKE);
ringColorPaint.setStrokeWidth(mRingWidth);
ringColorPaint.setShader(new SweepGradient(mViewCenterX, mViewCenterX, color, null));
//逆时针旋转90度
canvas.rotate(-90, mViewCenterX, mViewCenterY);
canvas.drawArc(mRectF, 360, mSelectRing, false, ringColorPaint);
ringColorPaint.setShader(null);
}
ok,代码中的注释已经很详细了,我就不再细说了.
我只想说一下,代码中在绘制彩色圆环的时canvas.rotate(-90, mViewCenterX, mViewCenterY);注释中已经说了左边旋转90度.那么问题来了为什么要逆时针旋转90度呢?
这个是因为在后面画渐变色的圆弧时,drawArc和SweepGradient这两个类的起始点0度不是在我们习惯的圆环最上面那个点,而是从圆环最右边那个点开始,所以逆时针旋转90度就能让它从最上面的点开始.
这下明白了吧.如果你还体会不深刻的话,就让你看一下,假如不逆时针旋转90度的话,是什么样的效果吧.
看到需要逆时针旋转90度的区别了吧.
通过上述步骤我们所需的圆环基本完成了.但是,要知道,画圆环不是目的,目的是让圆环去传表达或者描述一些信息,常用的场景,比如用来显示计步器的步数,显示文件下载的完成度等等.因此我们可以适当的添加点监听事件.
//***************************************用于更新圆环表示的数值*****************************************************
/**
* 设置当前值
*
* @param value
*/
public void setValue(int value,TextView textView) {
if (value > mMaxValue) {
value = mMaxValue;
}
int start = 0;
int end = value;
startAnimator(start, end, 2000,textView);
}
private void startAnimator(int start, int end, long animTime, final TextView textView) {
valueAnimator = ValueAnimator.ofInt(start, end);
valueAnimator.setDuration(animTime);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i(TAG, "onAnimationUpdate: animation.getAnimatedValue()::"+animation.getAnimatedValue());
int i = Integer.valueOf(String.valueOf(animation.getAnimatedValue()));
textView.setText(i + "");
//每个单位长度占多少度
mSelectRing=(int) (360 * (i / 100f));
Log.i(TAG, "onAnimationUpdate: mSelectRing::"+mSelectRing);
invalidate();
}
});
valueAnimator.start();
}
主程序 MainActivity.java:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
SuperCircleView mSuperCircleView;
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.tv);
mSuperCircleView = findViewById(R.id.superview);
mSuperCircleView.setValue(100, textView);
mSuperCircleView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//随机设定圆环大小
int i = new Random().nextInt(100) + 1;
Log.i(TAG, "onClick: i::" + i);
mSuperCircleView.setValue(i, textView);
}
});
}
}
上述代码中设计属性动画ValueAnimator的相关知识,有兴趣的同学自己学习下.
好了,至此,自定义圆环结束.如果有疑问的话,请留言.诺诺的说一句,如果你感觉这篇文章写的还行的话,请给个赞.我感觉我需要被鼓励,哈哈,开玩笑的…
附上示例:
https://download.csdn.net/download/zhangqunshuai/10486568
参考文章:
一步步做Android自定义圆环百分比控件
Android自定义View的官方套路
Android 自定义view实现圆环