好久没写过博客了真的是好懒好懒。。。
最近在开发公司的新项目,里头有用到一个圆,呃,确切地说是三段圆弧,圆弧之间有一个点,居然不叫UI给图而是叫我直接画。。。。说实话我刚听到的时候很想骂娘,后来想想就当是练练手吧就答应了,没图我说个JB。
(话说markdown怎么设置居中啊。。。)
就是上面那种图中的圆,不要问盾牌上为什么要放个勾,因为我也不知道究竟为什么要放个勾。大概效果就是这样吧,也是最终出来的效果,废话少说,上代码。
首先是建一个类继承view:
public class SecurityCircle extends View {
private int stokeWidth;
private int defaultPointSize;
private int defaultRadius;
private int radius;
private int defaultSize;
private Paint mPaint = new Paint();
private Context context;
private int color;
private int defaultColor = Color.rgb(248, 248, 255);
public SecurityCircle(Context context) {
this(context, null);
}
public SecurityCircle(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SecurityCircle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
defaultRadius = (int) ScaleUtil.dp2px(context.getResources(), 100);
defaultSize = (int) ScaleUtil.dp2px(context.getResources(), 200);
defaultPointSize = (int) ScaleUtil.dp2px(context.getResources(), 3);
stokeWidth = (int) ScaleUtil.dp2px(context.getResources(), 1);
final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SecurityCircle,
defStyleAttr, 0);
initByAttributes(attributes);
attributes.recycle();
}
}
没啥好说的,就是一个类,然后写一下构造方法,声明了几个默认值,这里说一下自定义属性,自定义属性有几个步骤:
在value文件夹下建立一个attrs.xml文件,在里面声明要使用的属性名:
<declare-styleable name="SecurityCircle">
<attr name="radius" format="dimension"/>
<attr name="circle_color" format="color"/>
</declare-styleable>
OK,再放上在xml里面的用法:
<com.xxx.xxx.xxx.SecurityCircle
android:id="@+id/security_circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
securitycircle:radius="100dp"
securitycircle:color="@color/color_white" />
别忘了加上命名空间:
xmlns:securitycircle="http://schemas.android.com/apk/res-auto"
这样就可以在view中使用自定义的属性了,我比较懒,在这里只写了两个属性,一个是圆的半径,一个是圆的颜色,如果有其他需要,继续加进去就是了,用法是一样的,在自定义的view中去获取它:
private void initByAttributes(TypedArray attributes) {
radius = (int) attributes.getDimension(R.styleable.SecurityCircle_radius, defaultRadius);
color = (int) attributes.getColor(R.styleable.SecurityCircle_circle_color, defaultColor);
}
然后是重写一下onMeasure方法,让我们的控件支持wrap_content和自定义宽高,在这里说一下view的测量模式,一共有三种:EXACTLY,AT_MOST,UNSPECIFIED,对应关系如下图:(图片来自网络)
当view的LayoutParams为warp_content时,不论父容器的MeasureSpec为何种模式,所产生了view的MeasureSpec均为AT_MOST模式。直接的效果就是match_parent的效果,所以自定义view时,想要支持wrap_content,就必须自己做处理,也就是自己定义一个大小作为wrap_content的标准,可以看到我在构造方法里面初始化了一个默认大小:
defaultSize = (int) ScaleUtil.dp2px(context.getResources(), 200);
下面贴上onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));
}
private int measure(int measureSpec) {
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
//EXACTLY,也就是layout_width和layout_height中指定了view的宽高
result = size;
} else {
result = defaultSize;
if (mode == MeasureSpec.AT_MOST) {
//AT_MOST,不管是wrap_content还是match_parent,
//如果不处理的话,默认都是父元素的宽高
result = defaultSize;
}
}
return result;
}
OK,到这里理一下,如果用户指定了view的宽高,则按照用户设置的宽高去设置我们想要得到的view的宽高;如果用户指定的宽高是wrap_content的话,在这里就把宽高定义为默认的宽高defaultSize;如果是match_parent的话,就是父元素的宽高parentSize,在这里解释一下UNSPECIFIED,就是父容器不对view的宽高做大小限定,要多大给多大,这个我们一般不使用,不用管就好。
最后一步就是绘图onDraw了:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(color);
mPaint.setStrokeWidth(ScaleUtil.dp2px(getResources(), 2));
//设置画笔为空心
mPaint.setStyle(Paint.Style.STROKE);
//抗锯齿
mPaint.setAntiAlias(true);
int delta = (int) ScaleUtil.dp2px(getResources(), 3);
RectF rectF = new RectF(delta * 2, delta * 2, radius * 2 - delta * 2, radius * 2 - delta * 2);
canvas.drawArc(rectF, -145, 110, false, mPaint);
canvas.drawArc(rectF, -25, 110, false, mPaint);
canvas.drawArc(rectF, 95, 110, false, mPaint);
//设置为实心去画三个小圆
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(radius, radius * 2 - delta * 2f, defaultPointSize, mPaint);
canvas.drawCircle((float) (radius * (1 - Math.sqrt(3)/2) + delta * 2f),
radius / 2 + delta, defaultPointSize, mPaint);
canvas.drawCircle((float) (radius * (1 + Math.sqrt(3)/2) - delta * 2f),
radius / 2 + delta, defaultPointSize, mPaint);
}
内容不多,就是把一个圆分成了三个扇形,在这里我自己设定了每个扇形是110度,这样两个扇形之间会有10度的空间留出来画小圆,角度什么的,按照自己的需要去设置哈。说一下RectF这个矩形的大小,其实就是画图的时候的边缘,为了不让view的大小吃掉一点边的宽度,在这里设置了一个距离delta去控制绘图的边框,有兴趣的小伙伴可以把这个矩形定义成rectF = (0, 0, radius * 2, radius * 2),看看我说的”吃掉边的宽度”是什么效果。
然后是画小圆,这个因为不是很熟悉android的坐标系,查API查了半天也没实现自己想要的效果,下面给出一张图说明一下坐标系:
图片转自:http://blog.csdn.net/jason0539/article/details/42743531
最后突然发现好像根本不用这样,因为之前绘图的时候已经定义了一个矩形,根据矩形来确定最底部的那个点就行了,确定好了这个点之后,出来的效果是这样的(注意考虑边线的宽度)
看着还行吧,可以把另外两个点加上去了,计算的方法有很多种,我想到的是用三角函数,画了个草图:
B点的横坐标就是半径radius,纵坐标是两倍的半径,我这里把边的宽度也考虑进去了,最后的结果是radius * 2 - delta * 2f,OK,已知B点坐标,又知道半径和角度,三角函数很容易就出来了吧,所以最后画三个圆的代码:
canvas.drawCircle(radius, radius * 2 - delta * 2f, defaultPointSize, mPaint);
canvas.drawCircle((float) (radius * (1 - Math.sqrt(3)/2) + delta * 2f),
radius / 2 + delta, defaultPointSize, mPaint);
canvas.drawCircle((float) (radius * (1 + Math.sqrt(3)/2) - delta * 2f),
radius / 2 + delta, defaultPointSize, mPaint);
再说一下drawCircle的参数好了:drawCircle(float x, float y, float radius, Paint paint);
xy就是圆心的横纵坐标,radius半径,paint就是画笔(感觉好废话)。。。
OK,大功告成了,最后的效果在文章开头,总结一下整个流程:
1.建一个类继承自view;
2.在value文件夹下建一个attrs.xml,在里面定义要使用的自定义属性;
3.在自定义view的构造方法中读取属性值;
4.重写onMeasure,让自定义view支持wrap_content和自定义宽高;
5.重写onDraw,绘图。
挺简单的,但是涵盖的知识点也挺多的,偶尔写一下练练手感觉还是挺不错的。
第一次用markdown写博客,发现别人看着比较方便,自己编辑好累。。排版什么的大家就不要吐槽了,将就着看吧,如果文中有出现错误或者有歧义的地方,欢迎各位看官指正。
转载请注明出处。http://blog.csdn.net/name_Uncle_Wang/article/details/52684635