【自定义控件系列四】android绘制实战(一)通过Canvas+Path+Paint

转载请注明:

前面几篇博客,简单介绍了一下Canvas+Path+Paint的API,下面我们通过这几个类中的方法画出下面的效果图的样式

Demo下载地址:https://github.com/z56402344/Android_Graphics_Instance_One

效果图:

动态效果图:

这样的图在做项目的时候,一般是不会让美工去切图的,一是麻烦,二是没有办法去做好适配,所以大家只能通过绘图类进行绘制了

我们先来看下这个效果图最难的点怎么画.

这张效果图最难的点,我个人认为就是圆上的箭头怎么指向某一个柱状体顶点中间位置

图好像看起来还蛮复杂的,其实这些都是假象,我们先来拆分下图层吧

效果拆分层

简化图

这样拆分出来的图,大家就应该知道这张图示怎么画的吧!

我们来细讲一下,圆心点坐标我们通过

protected void onDraw(Canvas canvas) {super.onDraw(canvas);mWidth = canvas.getWidth();mHeight = canvas.getHeight();mCenterX = mWidth/2;mCenterY = mHeight/4;}继承的View类 OnDraw()方法中的Canvas获取出屏幕一半宽,1/4高的点的位置,这就是上图中的O点坐标,而柱状体我们也是通过自己给的坐标点画出的,所以这两个点都是已知的。

我们最重要的是控制好过圆心,画出三角形,

我们通过之前了解到通过Canvas+Path+Paint组合API可以画出三角形,但是我们并不知道点P和P’的坐标位置,

//开始画三角形Path path = new Path();// 三角形path.moveTo((float)(x2), (float)(y2));//P点坐标path.lineTo((float)(mPointB.x), (float)(mPointB.y));//圆心点坐标path.lineTo((float)x1, (float)y1);//P'点坐标path.close();//闭合画笔路径canvas.drawPath(path, paint);//开始画

通过简化图,我们可以看出,其实就是一个数学问题,,通过点O坐标和点H坐标,OP’和OP边长是自己给定的定值所以也是已知的,OH边长已知,PH和P’H通过勾三股四算出可得,有了这些参数我们就可以组成一个二元一次方程组来算出P和P’坐标如下所示

<span style="white-space:pre"></span>PointBean mPointA;<span style="white-space:pre"></span>//柱状体顶部中心坐标<span style="white-space:pre"></span>PointBean mPointB = new PointBean(760, 400); //初始化时,假设的一个圆心点坐标//下面公式通过圆心点坐标和柱状体顶部中心点坐标,通过二元一次方程组计算出其余两个三角形的坐标点位置// x=x1-a*sin{arctan[(y2-y1)/(x2-x1)]}// y=y1+a*cos{arctan[(y2-y1)/(x2-x1)]}//求出坐标点Pdouble x1 = mPointA.x – 50 * Math.sin(Math.atan((mPointB.y – mPointA.y) / (mPointB.x – mPointA.x)));double y1 = mPointA.y + 50 * Math.cos(Math.atan((mPointB.y – mPointA.y) / (mPointB.x – mPointA.x)));//求出坐标点P'double x2 = mPointA.x + 50 * Math.sin(Math.atan((mPointB.y – mPointA.y) / (mPointB.x – mPointA.x)));double y2 = mPointA.y – 50 * Math.cos(Math.atan((mPointB.y – mPointA.y) / (mPointB.x – mPointA.x)));这就是这图最难的点,知道点P; P’ ,H三点坐标,就可以轻松画出过圆心的三角形了

下面是所有代码,之后我会把项目放到github上,大家可以去下载

/** * 通过柱状体顶部中心点坐标和圆心点坐标,画出过圆心的三角形 * @author DuGuang * */public class CustomTrigon extends View {PointBean mPointA;//柱状体顶部中心坐标PointBean mPointB = new PointBean(760, 400); //初始化时,假设的一个圆心点坐标private float mCenterX;//圆心点坐标Xprivate float mCenterY;//圆心点坐标Yprivate int mWidth;//画布的宽 == 手机屏幕的宽private int mHeight;//画布的高 == 手机屏幕的高 – ActionBar – 顶部titlepublic CustomTrigon(Context context) {super(context);}public CustomTrigon(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public CustomTrigon(Context context, AttributeSet attrs) {super(context, attrs);}@SuppressLint("DrawAllocation")@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mWidth = canvas.getWidth();mHeight = canvas.getHeight();mCenterX = mWidth/2;mCenterY = mHeight/4;mPointA = new PointBean((int)mCenterX, (int)mCenterY);Paint paint = new Paint();paint.setAntiAlias(true);paint.setStyle(Style.FILL);paint.setStrokeWidth(30f);paint.setDither(true);paint.setColor(getResources().getColor(R.color.cril));getDot2(paint, canvas);}public void getDot2(Paint paint, Canvas canvas) {//下面公式通过圆心点坐标和柱状体顶部中心点坐标,通过二元一次方程组计算出其余两个三角形的坐标点位置// x=x1-a*sin{arctan[(y2-y1)/(x2-x1)]}// y=y1+a*cos{arctan[(y2-y1)/(x2-x1)]}//求出坐标点Pdouble x1 = mPointA.x – 50 * Math.sin(Math.atan((mPointB.y – mPointA.y) / (mPointB.x – mPointA.x)));double y1 = mPointA.y + 50 * Math.cos(Math.atan((mPointB.y – mPointA.y) / (mPointB.x – mPointA.x)));//求出坐标点P'double x2 = mPointA.x + 50 * Math.sin(Math.atan((mPointB.y – mPointA.y) / (mPointB.x – mPointA.x)));double y2 = mPointA.y – 50 * Math.cos(Math.atan((mPointB.y – mPointA.y) / (mPointB.x – mPointA.x)));Log.i("dg", "x >>> " + x1 + " y >>> " + y1);//开始画三角形Path path = new Path();// 三角形path.moveTo((float)(x2), (float)(y2));//P点坐标path.lineTo((float)(mPointB.x), (float)(mPointB.y));//圆心点坐标path.lineTo((float)x1, (float)y1);//P'点坐标path.close();//闭合画笔路径canvas.drawPath(path, paint);//开始画}/** * 通过不同等级,塞入一个柱状体顶部中心点坐标 * @param pointB */public void setData(PointBean pointB){mPointB = pointB;invalidate();}}/** * 自定义控件圆形 * @author DuGuang * */public class CustomCircle extends View {private float mCenterX; // 圆形X轴中心private float mCenterY;//圆形Y轴中心private float mCircleSize;//圆形直径大小private Context mContext; private int mWidth;//画布的宽 == 手机屏幕的宽private int mHeight;//画布的高 == 手机屏幕的高 – ActionBar – 顶部titlepublic CustomCircle(Context context) {super(context);init(context);}public CustomCircle(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(context);}public CustomCircle(Context context, AttributeSet attrs) {super(context, attrs);init(context);}/** * 初始化数据 * @param context */private void init(Context context) {this.mContext = context;this.mCenterX = 350f;this.mCenterY = 350f;this.mCircleSize = 285f;}@SuppressLint("DrawAllocation")@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mWidth = canvas.getWidth();mHeight = canvas.getHeight();mCenterX = mWidth/2;mCenterY = mHeight/4;mCircleSize = mHeight/6;//第一个画笔,画出一个空心圆Paint paint = new Paint();paint.setAntiAlias(true); //消除齿距paint.setStyle(Style.STROKE); //空心画笔paint.setStrokeWidth(30f);//画笔宽度paint.setDither(true);//消除脱颖paint.setColor(getResources().getColor(R.color.cril)); //设置画笔颜色//通过Path 用canvas在画布上画出圆形Path path = new Path();path.addCircle(mCenterX, mCenterY, mCircleSize, Path.Direction.CCW);canvas.drawPath(path, paint);//第二个画笔,画出一个实心圆Paint paint_white = new Paint();Path path_white = new Path();paint_white.setAntiAlias(true);paint_white.setStyle(Style.FILL);paint_white.setDither(true);paint_white.setColor(getResources().getColor(R.color.white));//path_white.addCircle(mCenterX, mCenterY, mCircleSize-15, Path.Direction.CCW);path_white.addCircle(mCenterX, mCenterY, 5, Path.Direction.CCW);canvas.drawPath(path_white, paint_white);//第三个画笔,画出一个空心圆Paint paint_STROKE = new Paint();Path path_STROKE = new Path();paint_STROKE.setAntiAlias(true);paint_STROKE.setStyle(Style.STROKE);paint.setStrokeWidth(5f);//画笔宽度paint_STROKE.setDither(true);paint_STROKE.setColor(getResources().getColor(R.color.cril));path_STROKE.addCircle(mCenterX, mCenterY, mCircleSize-25, Path.Direction.CCW);canvas.drawPath(path_STROKE, paint_STROKE);}}/** * 自定义空间,带圆角的柱状体 * @author DuGuang * */public class CustomRect extends View {//圆角柱状体4个角的值private float[] radii = { 12f, 12f, 12f, 12f, 0f, 0f, 0f, 0f };//柱状体的颜色private int[] colors = { R.color.rect_cril_leve1, R.color.rect_cril_leve2,R.color.rect_cril_leve3, R.color.rect_cril_leve4,R.color.rect_cril_leve5, R.color.rect_cril_leve6 };private int mWidth; //画布的宽 == 手机屏幕的宽private int mHeight;//画布的高 == 手机屏幕的高 – ActionBar – 顶部titleprivate int mRectWidth;//矩形宽private int mRectHeight;//矩形高private Paint mPaint;private String mLevel;//画的L1-L3 字样private String mName;//画的初级,高级,专家字样private static float mToY = 15f; //小于1,整体往下移动;大于1,整体往上移动private static float mRectY = 4;//往1方向,矩形长度拉长,往10方向,矩形长度缩短private ArrayList<String> mPointList;//柱状体顶部中心坐标的集合public CustomRect(Context context) {super(context);init(context);}public CustomRect(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(context);}public CustomRect(Context context, AttributeSet attrs) {super(context, attrs);init(context);}private void init(Context context) {}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mPointList = new ArrayList<String>();mWidth = canvas.getWidth();mHeight = canvas.getHeight();mRectWidth = (int) (mWidth / 9.5);mRectHeight = mHeight/2;//循环出6个柱状体for (int i = 0; i < 6; i++) {initBitmaps(canvas,i);}}/** * 画矩形 * @param canvas * @param index */private void initBitmaps(Canvas canvas,int index) {mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setStyle(Style.FILL);mPaint.setStrokeWidth(30f);mPaint.setDither(true);mPaint.setColor(getResources().getColor(colors[index]));//通过Path路径,计算出每个柱状体宽高,并把柱状体顶部中心坐标放入集合//柱状体顶部中心坐标放入集合,用于和圆心中央的三角,通过中心坐标和柱状体坐标,画出三角形Path path = new Path();int width = (int) (mRectWidth/2+(index*mRectWidth*1.5));int height_top = (int) (mRectHeight+(mRectHeight/15)*(6-index)+mRectWidth*1.5);int height_bootom = height_top-mRectHeight/10+(mRectHeight/15)*index;height_top = (int) (height_top – mRectHeight/mRectY);//高度起始位置向0方向移动1/10屏幕path.addRoundRect(new RectF(width, height_top, width+mRectWidth, height_bootom), radii,Path.Direction.CCW);canvas.drawPath(path, mPaint);String RectX = String.valueOf(width+mRectWidth/2);String RectY = String.valueOf(height_top);mPointList.add(RectX+"-"+RectY);Log.i("dg", "mPointList >>> "+ mPointList.size());Path path1 = new Path();path1.addRoundRect(new RectF(width, height_bootom+10, width+mRectWidth, height_bootom+12), radii,Path.Direction.CCW);canvas.drawPath(path1, mPaint);switch (index) {case 0:mLevel = "L1-L3";mName = "入门";break;case 1:mLevel = "L4-L6";mName = "初级";break;case 2:mLevel = "L7-L9";mName = "中级";break;case 3:mLevel = "L10-L12";mName = "中高级";break;case 4:mLevel = "L13-L15";mName = "高级";break;case 5:mLevel = "L16";mName = "专家";break;default:break;}drawLevel(canvas, index, width, height_bootom,mLevel);drawText(canvas, index, width, height_bootom,mName);}/** * 画名称 * @param canvas * @param index * @param width * @param height_bootom * @param name */private void drawText(Canvas canvas, int index, int width, int height_bootom, String name) {Paint paint = new Paint();paint.setAntiAlias(true);paint.setStyle(Style.FILL);paint.setStrokeWidth(30f);paint.setDither(true);paint.setColor(getResources().getColor(colors[index]));paint.setTextSize(30);canvas.drawText(name , width+mRectWidth/5, height_bootom+100, paint);}/** * 画等级 * @param canvas * @param index * @param width * @param height_bootom * @param level */private void drawLevel(Canvas canvas, int index, int width,int height_bootom, String level) {Paint paint = new Paint();paint.setAntiAlias(true);paint.setStyle(Style.FILL);paint.setStrokeWidth(30f);paint.setDither(true);paint.setColor(getResources().getColor(colors[index]));paint.setTextSize(30);if(index ==5){canvas.drawText(level , width+mRectWidth/4, height_bootom+60, paint);}else if(index == 4 || index ==3 ){canvas.drawText(level , width+mRectWidth/20, height_bootom+60, paint);}else{canvas.drawText(level , width+mRectWidth/6, height_bootom+60, paint);}}public ArrayList<String> getPointList() {return mPointList;}public void setPointList(ArrayList<String> mPointList) {this.mPointList = mPointList;}}/** * 主页面 * @author DuGuang * blog地址: * */public class TestCourseReportActivity extends Activity {private FrameLayout mFlMain;private ArrayList<String> mPointList;private CustomRect mCusRect;private CustomTrigon mTrigon;private TextView mTvHideOne, mTvLevel, mTvHideTwo,mTvHide;private View mViewLine;private int mWidth;private int mHeight;private Handler mHandler = new Handler() {public void handleMessage(android.os.Message msg) {//获取5个柱状体顶点中心坐标位置mPointList.addAll(mCusRect.getPointList());String[] split = mPointList.get(5).split("-");mTrigon.setData(new PointBean(Integer.parseInt(split[0]), Integer.parseInt(split[1])));};};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_course_testcourse_report);initView();initData();}private void initView() {mFlMain = (FrameLayout) findViewById(R.id.fl_mian);mCusRect = (CustomRect) findViewById(R.id.cus_rect);mTvHideOne = (TextView) findViewById(R.id.tv_hide_one);mTvLevel = (TextView) findViewById(R.id.tv_level);mTvHideTwo = (TextView) findViewById(R.id.tv_hide_two);mViewLine = findViewById(R.id.view_line);mTvHide = (TextView) findViewById(R.id.tv_hide);}private void initData() {mPointList = new ArrayList<String>();CustomCircle circle = new CustomCircle(this);mTrigon = new CustomTrigon(this);mFlMain.addView(mTrigon,2);mFlMain.addView(circle,3);new Thread() {public void run() {//这里启动线程是为了防止layout布局文件还没有完成,去获取柱状体顶部坐标的时候Null异常SystemClock.sleep(200);mHandler.sendEmptyMessage(0);};}.start();// 获取屏幕宽高(方法1)mWidth = getWindowManager().getDefaultDisplay().getWidth(); // 屏幕宽mHeight = getWindowManager().getDefaultDisplay().getHeight(); // 屏幕高int width = mWidth / 2 – mWidth /8 ;int height = mHeight / 4 – mHeight/12;//这里第一个TextView竟然显示不出来,不知道为什么,做个标记,以后修改FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);params.topMargin = height+40;params.leftMargin = width;mTvHideOne.setLayoutParams(params);FrameLayout.LayoutParams params4 = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);params4.topMargin = height-10;params4.leftMargin = width;mTvHide.setLayoutParams(params4);FrameLayout.LayoutParams params1 = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);params1.topMargin = height+40;params1.leftMargin = width;mTvLevel.setTextColor(getResources().getColor(R.color.text_hide));mTvLevel.setLayoutParams(params1);FrameLayout.LayoutParams params2 = new FrameLayout.LayoutParams(300, 1);params2.topMargin = height+140;params2.leftMargin = width;mViewLine.setBackgroundColor(getResources().getColor(R.color.view_backgroud));mViewLine.setLayoutParams(params2);FrameLayout.LayoutParams params3 = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);params3.topMargin = height+150;params3.leftMargin = width;mTvHideTwo.setTextColor(getResources().getColor(R.color.text_level));mTvHideTwo.setLayoutParams(params3);}}

Demo下载地址:https://github.com/z56402344/Android_Graphics_Instance_One

项目这周周末会发到github上,大家等链接地址吧,如有什么疑问请留言

转载请注明:

自己战胜自己是最可贵的胜利。

【自定义控件系列四】android绘制实战(一)通过Canvas+Path+Paint

相关文章:

你感兴趣的文章:

标签云: