新的一年开始了,一直没发表过文章,感觉还是要弄些总结出来有助于自己的提高,刚好这几天比较有空,之前有写个小控件,稍微优化了下,先上效果图吧。
五维效果图
六维效果图
看效果图也不难就是三个图形叠加起来的,那思路就是一个图形一个图形的画上去,先上代码吧
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author qrx
* @date 2016/3/30
* @time 15:08
* @description
*/
public class MultidimensionalViewextends View {
/**
* 背景画笔
*/
private PaintmBgPaint;
/**
* 线条画笔
*/
private PaintmLinePaint;
/**
* 标题画笔
*/
private PaintmTitlePaint;
/**
* 粗白色线画笔
*/
private PaintmBoldLinePaint;
/**
* 背景路径
*/
private PathmBgPath;
/**
* 阴影路径
*/
private PathmShadowPath;
/**
* 阴影画笔
*/
private PaintmShadowPaint;
/**
* 高
*/
private int height;
/**
* 宽
*/
private int width;
/**
* 内边长
*/
private int inSideLength;
/**
* 背景上下边距
*/
private int paddingTopAndBottom =0;
/**
* 背景各顶点数组
*/
private PointF[]mBgPoints;
/**
* 背景各顶点数组
*/
private PointF[]mDeepBgPoints;
/**
* 小标题数组
*/
private PointF[]mTitlePoints;
/**
* 小白点的半径
*/
private float circleRadius;
/**
* 顶点个数
*/
private int mPointCounts =5;
/**
* 中心坐标
*/
private PointFcenterPoint;
/**
* 小标题
*/
private Stringtitle[];
/**
* 数据 key标题 value值
*/
private Mapdata;
/**
* 各数据坐标
*/
private PointF[]mDataPoints;
/**
* 用于测量文本大小
*/
private Paint.FontMetricsfm;
/**
* 动画
*/
private AnimationmAnimation;
private int mPaintAlpha;
public MultidimensionalView(Context context) {
this(context, null);
}
public MultidimensionalView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MultidimensionalView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode != MeasureSpec.UNSPECIFIED) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
height = MeasureSpec.getSize(heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
paddingTopAndBottom = Math.max(getPaddingTop(),getPaddingBottom());
if(paddingTopAndBottom ==0){
paddingTopAndBottom = (int) ScreenUtil.dp2px(getContext(), 60);
}
inSideLength =height /2 -paddingTopAndBottom;
circleRadius = ScreenUtil.dp2px(getContext(), 4);
centerPoint =new PointF(w /2, inSideLength +paddingTopAndBottom);
}
/**设置数据 key-->顶点标题 value --> 数值[0,100]
* @param data 数据map
*/
public void bindData(LinkedHashMap data) {
if (data !=null) {
this.data = data;
mPointCounts = data.size();
title =new String[mPointCounts];
data.keySet().toArray(title);
}
initAnimation();
}
/**
* 动画
*/
private void initAnimation() {
if (mAnimation ==null) {
mAnimation =new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mPaintAlpha = (int) (interpolatedTime *255);
invalidate();
}
};
mAnimation.setInterpolator(new DecelerateInterpolator(3.2f));
}
if (!mAnimation.hasStarted()) {
mAnimation.cancel();
}
mAnimation.setDuration(10000);
startAnimation(mAnimation);
}
/**
* 度数转换成弧度
*
* @param degrees
* @return
*/
private double getRadians(int degrees) {
return degrees * Math.PI /180;
}
/**
* 初始化
*/
private void init() {
mBgPaint =new Paint(Paint.ANTI_ALIAS_FLAG);
mBgPaint.setStyle(Paint.Style.FILL);
mBgPath =new Path();
mLinePaint =new Paint(Paint.ANTI_ALIAS_FLAG);
mTitlePaint =new Paint(Paint.ANTI_ALIAS_FLAG);
mTitlePaint.setTextSize(ScreenUtil.dp2px(getContext(), 14));
fm =mTitlePaint.getFontMetrics();
mShadowPath =new Path();
mShadowPaint =new Paint(Paint.ANTI_ALIAS_FLAG);
mShadowPaint.setStyle(Paint.Style.FILL);
mShadowPaint.setColor(getContext().getResources().getColor(R.color.white));
mShadowPaint.setAlpha(100);
mBoldLinePaint =new Paint(Paint.ANTI_ALIAS_FLAG);
mBoldLinePaint.setStyle(Paint.Style.STROKE);
mBoldLinePaint.setColor(getContext().getResources().getColor(R.color.white));
mBoldLinePaint.setStrokeWidth(ScreenUtil.dp2px(getContext(), 2));
}
/**
* 画背景
*/
private void drawBackground(Canvas canvas) {
initBgPoints();
mBgPath.reset();
mBgPath.moveTo(mBgPoints[0].x, mBgPoints[0].y);//第一个点 顶点
for (int i =1; i
mBgPath.lineTo(mBgPoints[i].x, mBgPoints[i].y);
}
mBgPath.close();
mBgPaint.setColor(getContext().getResources().getColor(R.color.bg_blue_color));
mBgPaint.setAlpha(mPaintAlpha);
canvas.drawPath(mBgPath, mBgPaint);
}
/**
* 画背景
*/
private void drawDeepBackground(Canvas canvas) {
initDeepBgPoints();
mBgPath.reset();
mBgPath.moveTo(mDeepBgPoints[0].x, mDeepBgPoints[0].y);//第一个点 顶点
for (int i =1; i
mBgPath.lineTo(mDeepBgPoints[i].x, mDeepBgPoints[i].y);
}
mBgPath.close();
mBgPaint.setColor(getContext().getResources().getColor(R.color.bg_blue_deep_color));
mBgPaint.setAlpha(mPaintAlpha);
canvas.drawPath(mBgPath, mBgPaint);
}
/**
* 初始化深色背景各顶点
*/
private void initDeepBgPoints() {
initBgPoints();
if (mDeepBgPoints ==null) {
mDeepBgPoints =new PointF[mPointCounts];
for (int i =0; i
mDeepBgPoints[i] = getMiddlePointFromCentre(mBgPoints[i]);
}
}
}
/**
* 画网
*
* @param canvas
*/
private void drawMesh(Canvas canvas) {
mLinePaint.setColor(getContext().getResources().getColor(R.color.white));
mLinePaint.setStrokeWidth(1);
mLinePaint.setAlpha(mPaintAlpha);
for (int i =0; i
canvas.drawLine(centerPoint.x, centerPoint.y, mBgPoints[i].x, mBgPoints[i].y, mLinePaint);
}
}
/**
* 获取两个点的中点
*
* @param p1 第一个点
* @param p2 第二个点
* @return
*/
private PointFgetMiddlePoint(PointF p1, PointF p2) {
return new PointF((p1.x + p2.x) /2, (p1.y + p2.y) /2);
}
/**
* 获取两个点的中点
*
* @param p1 第一个点
* @return
*/
private PointFgetMiddlePointFromCentre(PointF p1) {
return new PointF((p1.x +centerPoint.x) /2, (p1.y +centerPoint.y) /2);
}
/**
* 初始化各顶点 利用中心坐标求出各顶点的坐标
*/
private void initBgPoints() {
if (mBgPoints ==null) {
mBgPoints =new PointF[mPointCounts];
double degrees = getRadians(360/mPointCounts);
for (int i =0; i
mBgPoints[i] =new PointF((float) (centerPoint.x +inSideLength * Math.sin(degrees * i)), (float) (centerPoint.y -inSideLength * Math.cos(degrees * i)));
}
}
}
@Override
protected void onDraw(Canvas canvas) {
//画背景
drawBackground(canvas);
//画深色背景
drawDeepBackground(canvas);
//画网格 中心到各顶点线
drawMesh(canvas);
//画标题
drawTitle(canvas);
//画坐标点
drawCoordinate(canvas);
//画每个坐标点连接起来的面积区域
drawShadow(canvas);
//连接每个坐标
drawBoldLine(canvas);
}
/**
* 画粗线
*
* @param canvas
*/
private void drawBoldLine(Canvas canvas) {
mBoldLinePaint.setAlpha(mPaintAlpha);
for (int i =0; i
if (mDataPoints[i].y !=centerPoint.y
&&mDataPoints[(i +1) %mPointCounts].y !=centerPoint.y) {
canvas.drawLine(mDataPoints[i].x, mDataPoints[i].y,
mDataPoints[(i +1) %mPointCounts].x,
mDataPoints[(i +1) %mPointCounts].y,
mBoldLinePaint);
}
}
}
/**
* 画阴影
*
* @param canvas
*/
private void drawShadow(Canvas canvas) {
mShadowPaint.setAlpha(mPaintAlpha*100/255);
mShadowPath.moveTo(mDataPoints[0].x, mDataPoints[0].y);
for (int i =1; i
mShadowPath.lineTo(mDataPoints[i].x, mDataPoints[i].y);
}
mShadowPath.close();
canvas.drawPath(mShadowPath, mShadowPaint);
}
/**
* 画具体坐标
*
* @param canvas
*/
private void drawCoordinate(Canvas canvas) {
initDataPoints();
mLinePaint.setAlpha(mPaintAlpha);
for (int i =0; i
if (mDataPoints[i].y !=centerPoint.y) {
//排除数据为0的情况
//画圆
canvas.drawCircle(mDataPoints[i].x, mDataPoints[i].y, circleRadius, mLinePaint);
}
}
}
/**
* 初始化坐标
*/
private void initDataPoints() {
mDataPoints =new PointF[mPointCounts];
for (int i =0; i
mDataPoints[i] = getDataPoint(mBgPoints[i], data.get(title[i]));
}
}
/**
* 获取指定数值的点,根据相应的百分比
*
* @param p 顶点
* @param value 数值
* @return
*/
private PointFgetDataPoint(PointF p, int value) {
PointF pointF =new PointF();
pointF.x = (float) ((value *1.0 /100 *inSideLength) /inSideLength * (p.x -centerPoint.x) +centerPoint.x);
pointF.y = (float) ((value *1.0 /100 *inSideLength) /inSideLength * (p.y -centerPoint.y) +centerPoint.y);
return pointF;
}
/**
* 获取两点之间的距离
*
* @param p1 第一点
* @param p2 第二点
* @return
*/
private float getDistance(PointF p1, PointF p2) {
return (float) Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
/**
* 画小标题
*
* @param canvas
*/
private void drawTitle(Canvas canvas) {
initTitlePoints();
//小标题
mTitlePaint.setColor(getContext().getResources().getColor(R.color.text_color_black));
mTitlePaint.setAlpha(mPaintAlpha);
for (int i =0; i
canvas.drawText(title[i], mTitlePoints[i].x -mTitlePaint.measureText(title[i]) /2, mTitlePoints[i].y, mTitlePaint);
}
//蓝色数值
mTitlePaint.setColor(getContext().getResources().getColor(R.color.text_color_blue));
mTitlePaint.setAlpha(mPaintAlpha);
for (int i =0; i
String text;
if (data.get(title[i]) >0) {
text = String.valueOf(data.get(title[i]));
}else {
text ="N/A";
}
canvas.drawText(text, mTitlePoints[i].x -mTitlePaint.measureText(text) /2, mTitlePoints[i].y + (fm.bottom -fm.top), mTitlePaint);
}
}
/**
* 初始化各小标题位置
*/
private void initTitlePoints() {
if (mTitlePoints ==null) {
mTitlePoints =new PointF[mPointCounts];
for (int i =0; i
mTitlePoints[i] = getTitlePoint(mBgPoints[i]);
}
}
}
/**
* 通过传入的顶点坐标 获取小标题坐标
*
* @param pointF
* @return
*/
private PointFgetTitlePoint(PointF pointF) {
return getDataPoint(pointF, 125);
}
}
首先来看看onMeasure()方法 ,这边主要是把整个视图弄成是正方形的,我们画的图形都是正多边形,视图弄成正方形的画方便下手;
再看下onSizeChanged()方法,这边主要是确定图形中心的坐标以及中心坐标到各顶点的距离,方便后面的计算;
bindData()和initAnimation()就后面再说了,init()中都是初始化一些画笔啥的;
那就来看看最重要的onDraw()方法吧,drawBackground()就是画整个背景图形用的,在该方法中的initBgPoints()中有初始化每个顶点的坐标,这个顶点的坐标计算思路就是先确定最顶部的顶点,然后这个顶点以中心坐标进行旋转,旋转角度就是360除以点的个数了,顺时针还是逆时针旋转这个就是修改加减三角函数的问题了,我这边是顺时针的。初始化各顶点后就只需要连接各个顶点就好了,当然这边的画笔的风格要设置成FILL。
接下来的方法也都比较类似,drawDeepBackground(),是画深色部分的图形,比原来图形的小一半,坐标也很好算,一样减半就好了。
//画网格 中心到各顶点线
drawMesh(canvas);
//画标题
drawTitle(canvas);
//画坐标点
drawCoordinate(canvas);
//画每个坐标点连接起来的面积区域
drawShadow(canvas);
//连接每个坐标
drawBoldLine(canvas);
这些方法就都是大同小异了,算出要画图形顶点的位置,然后连接起来。
那现在我们回来看看bindData()和initAnimation() 这两个方法,bindData传递的是一个有向的hashmap,这样能够保证每次画的图形是一样的,key为标题,value为值,这个值有效的情况下应该在0到100,initAnimation()中有用到加速的插值器,动画时间为1秒,在这一秒内更新了每个画笔的透明值,然后刷新重画,这样整个效果就做完了。
设置数据:
LinkedHashMap data =new LinkedHashMap<>();
data.put("结交人脉", 70);
data.put("维护人脉", 50);
data.put("时间投入", 30);
data.put("忙碌程度", 40);
data.put("活跃", 50);
data.put("信用", 80);
mMultidimensionalView.bindData(data);
整个控件做的还是有点粗糙,像画笔的颜色、粗细的值、动画时间可能放在自定义属性上会更好,后面再优化优化吧,还有很多改进的地方,欢迎各位指正。
项目地址:https://github.com/qiurunxing/multidimensionalView
网友评论