确定一个矩形最少需要四个数据,就是对角线的两个点的坐标值,这里一般采用左上角和右下角的两个点的坐标。
关于绘制矩形,Canvas提供了三种重载方法,第一种就是提供四个数值(矩形左上角和右下角两个点的坐标)来确定一个矩形进行绘制。 其余两种是先将矩形封装为Rect或RectF(实际上仍然是用两个坐标点来确定的矩形),然后传递给Canvas绘制,如下:
为什么会有Rect和RectF两种?两者有什么区别吗?
答案当然是存在区别的,两者最大的区别就是精度不同,Rect是int(整形)的,而RectF是float(单精度浮点型)的。除了精度不同,两种提供的方法也稍微存在差别,在这里我们暂时无需关注,想了解更多参见官方文档 Rect 和 RectF
3.6绘制圆角矩形
下面简单解析一下圆角矩形的几个必要的参数的意思。
很明显可以看出,第二种方法前四个参数和第一种方法的RectF作用是一样的,都是为了确定一个矩形,最后一个参数Paint是画笔,无需多说,与矩形相比,圆角矩形多出来了两个参数rx 和 ry,这两个参数是干什么的呢?
稍微分析一下,既然是圆角矩形,他的角肯定是圆弧(圆形的一部分),我们一般用什么确定一个圆形呢?
答案是圆心 和 半径,其中圆心用于确定位置,而半径用于确定大小。
由于矩形位置已经确定,所以其边角位置也是确定的,那么确定位置的参数就可以省略,只需要用半径就能描述一个圆弧了。
但是,半径只需要一个参数,但这里怎么会有两个呢?
好吧,让你发现了,这里圆角矩形的角实际上不是一个正圆的圆弧,而是椭圆的圆弧,这里的两个参数实际上是椭圆的两个半径,他们看起来个如下图:
红线标注的 rx 与 ry 就是两个半径,也就是相比绘制矩形多出来的那两个参数。
我们了解到原理后,就可以为所欲为了,通过计算可知我们上次绘制的矩形宽度为700,高度为300,当你让 rx大于350(宽度的一半), ry大于150(高度的一半) 时奇迹就出现了, 你会发现圆角矩形变成了一个椭圆, 他们画出来是这样的 ( 为了方便确认我更改了画笔颜色, 同时绘制出了矩形和圆角矩形 ):
实际上在rx为宽度的一半,ry为高度的一半时,刚好是一个椭圆,通过上面我们分析的原理推算一下就能得到,而当rx大于宽度的一半,ry大于高度的一半时,实际上是无法计算出圆弧的,所以drawRoundRect对大于该数值的参数进行了限制(修正),凡是大于一半的参数均按照一半来处理。
3.7绘制椭圆
相对于绘制圆角矩形,绘制椭圆就简单的多了,因为他只需要一个矩形矩形作为参数:
绘制椭圆实际上就是绘制一个矩形的内切图形,如果你传递进来的是一个长宽相等的矩形(即正方形),那么绘制出来的实际上就是一个圆。原理如下,就不多说了:
3.8绘制圆形
绘制圆形有四个参数,前两个是圆心坐标,第三个是半径,最后一个是画笔。
3.9绘制圆弧
绘制圆弧就比较神奇一点了,为了理解这个比较神奇的东西,我们先看一下它需要的几个参数:关键是后边的StartAngle是开始扫描的角度,Sweep angle是从开始位置计算起,扫描多少度。userCenter是否启用中心位置,如果不启用,扇形的面积是扫描之后起点和结束点的连线,如果是true将会包括中心位置,是一个真正的扇形。
我们来看一下实例代码:
在看一下正圆的情况下是什么样子的:
四、简单介绍一下画笔
如果我想绘制一个圆,只要边不要里面的颜色怎么办?很简单,绘制的基本形状Canvas确定,但绘制出来的颜色,具体效果则由Paint确定。设置画笔的填充模式就可以实现我们想要的效果。如果你注意到了的话,在一开始我们设置画笔样式的时候是这样的:
实例代码如下
五、下面我们来做一个百分比的饼状图来实际操作一下
简要介绍画布的操作:
制作一个饼状图如下:
简单分析一下我们所需要的数据和思路
其实根据我们上面的知识已经能自己制作一个饼状图了。不过制作东西最重要的不是制作结果,而是制作思路。 相信我贴上代码大家一看就立刻明白了,非常简单的东西。不过嘛,咱们还是想了解一下制作思路:
先分析饼状图的构成,非常明显,饼状图就是一个又一个的扇形构成的,每个扇形都有不同的颜色,对应的有名字,数据和百分比。
经以上信息可以得出饼状图的最基本数据应包括:名字 数据值 百分比 对应的角度 颜色。
用户关心的数据 : 名字 数据值 百分比
需要程序计算的数据: 百分比 对应的角度
其中颜色这一项可以用户指定也可以用程序指定(我们这里采用程序指定)。
封装数据:
package net.fitrun.mysvg;
/**
* Created by 晁东洋 on 2017/3/28.
* 绘制饼状图的数据
*/
public class PieData {
// 用户关心数据
private String name; // 名字
private float value; // 数值
private float percentage; // 百分比
// 非用户关心数据
private int color = 0; // 颜色
private float angle = 0; // 角度
public PieData(String name, float value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
public float getPercentage() {
return percentage;
}
public void setPercentage(float percentage) {
this.percentage = percentage;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public float getAngle() {
return angle;
}
public void setAngle(float angle) {
this.angle = angle;
}
}
自定义View:
package net.fitrun.mysvg;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
/**
* Created by 晁东洋 on 2017/3/24.
*/
public class CustomView extends View {
private Paint mPaint;
// 颜色表(注意: 此处定义颜色使用的是ARGB,带Alpha通道的)
private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
0xFFE6B800, 0xFF7CFC00};
// 饼状图初始绘制角度
private float mStartAngle = 0;
//数据
private ArrayList<PieData> mDate;
//宽高
private int mWidth,mHeight;
public CustomView(Context context) {
super(context);
//初始化画笔
intiPaint();
}
private void intiPaint() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL); //设置画笔模式为填充
//mPaint.setStrokeWidth(10f); //设置画笔的宽度为10px
mPaint.setAntiAlias(true);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//取出宽度的确切数值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//取出宽度的测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//取出高度的确切数值
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//取出高度的测量模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r, b);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null == mDate)
return;;
float currentStartAngle = mStartAngle; //当前的起始角度,为0度
canvas.translate(mWidth/2,mHeight/2); //将画布的中心圆点移动到中心位置
float r = (float)(Math.min(mWidth,mHeight)/2*0.8); //绘图的半径,取宽和高较小的值
RectF mRectf = new RectF(-r,-r,r,r); //绘图的区域,其实是一个正方形
//开始根据数据绘制扇形
for (int i =0; i<mDate.size();i++){
PieData pie = mDate.get(i);
mPaint.setColor(pie.getColor());
canvas.drawArc(mRectf,currentStartAngle,pie.getAngle(),true,mPaint);
currentStartAngle += pie.getAngle();
}
}
//设置起始角度
public void setStartAngle(int mStartAngle){
this.mStartAngle = mStartAngle;
invalidate(); //刷新界面
}
//设置数据
public void setData(ArrayList<PieData> mData){
this.mDate = mData;
initData(mData);
invalidate();
}
//计算数据
private void initData(ArrayList<PieData> mData) {
if (null == mData || mData.size() ==0){
return;
}else {
float sumValue =0; //记录数据的总和
for (int i=0; i<mData.size();i++){
PieData pie = mData.get(i);
sumValue += pie.getValue();
int j = i % mColors.length;
pie.setColor(mColors[j]); //循环的为每一个设置颜色
}
float sumAngle =0; //总的角度
for (int i=0; i<mData.size();i++){
PieData pie = mData.get(i);
float percentage = pie.getValue()/sumValue; //百分比
float angle = percentage * 360; //对应占的角度是多少
//记录百分比
pie.setPercentage(percentage);
pie.setAngle(angle);
sumAngle += angle;
Log.e("总的角度",pie.getAngle()+"");
}
}
}
//监听键盘的点击事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return super.onKeyDown(keyCode, event);
}
//监听键盘的弹起事件
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return super.onKeyUp(keyCode, event);
}
// 监听轨迹球的移动事件
@Override
public boolean onTrackballEvent(MotionEvent event) {
return super.onTrackballEvent(event);
}
//触摸的事件监听
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
//视图焦点变化的监听
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
}
//包含这一视图的窗口焦点的监听
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
}
//视图被附加add时被调用
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
}
//视图和窗口分离事调用
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
//调用视图的窗口可见性变化时调用
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
}
}
Activity中引用我们的控件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final PorterDuffXfermodeView taiJi = new PorterDuffXfermodeView(this);
ArrayList<PieData> mlist = new ArrayList<>();
for (int i =0; i<5;i++){
float mF = 20f;
mF+= mF;
PieData pieData = new PieData("生产",mF);
mlist.add(pieData);
}
CustomView customView = new CustomView(this);
customView.setData(mlist);
customView.setStartAngle(180);
setContentView(customView);
最后是我们的效果图
最后如果大家喜欢我的学习笔记,可以扫描左边的二维码关注我的微信公众号,有更多的干货,更及时的分享给大家。
参考GcsSloop:github详细内容