转载请注明:http://blog.csdn.net/duguang77/article/details/40869079
前面几篇博客,简单介绍了一下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)]} //求出坐标点P double 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; //圆心点坐标X private float mCenterY; //圆心点坐标Y private int mWidth; //画布的宽 == 手机屏幕的宽 private int mHeight;//画布的高 == 手机屏幕的高 - ActionBar - 顶部title public 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") @Override protected 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)]} //求出坐标点P double 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 - 顶部title public 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") @Override protected 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 - 顶部title private 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) { } @Override protected 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地址:http://blog.csdn.net/duguang77 * */ 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]))); }; }; @Override protected 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上,大家等链接地址吧,如有什么疑问请留言
转载请注明:http://blog.csdn.net/duguang77/article/details/40869079