当前位置: 首页 > 编程笔记 >

Android 自定义View实现芝麻分曲线图效果

诸彬郁
2023-03-14
本文向大家介绍Android 自定义View实现芝麻分曲线图效果,包括了Android 自定义View实现芝麻分曲线图效果的使用技巧和注意事项,需要的朋友参考一下

1.简介

其实这个效果几天之前就写了,但是一直没有更新博客,本来想着把芝麻分雷达图也做好再发博客的,然后今天看到鸿洋的微信公众号有朋友发了芝麻分的雷达图,所以就算了,算是一个互补吧。平时文章也写的比较少,所以可能有点杂乱,有什么需要改进的地方欢迎给出建议,不胜感激。

效果图:

2.步骤:

初始化View的属性
初始化画笔
绘制代表最高分和最低分的两根虚线
绘制文字
绘制代表月份的属性
绘制芝麻分折线
绘制代表芝麻分的圆点
绘制选中分数的悬浮文字以及背景
处理点击事件

3.编码:

初始化View属性

/**
* 初始化布局配置
*
* @param context
* @param attrs
*/
private void initConfig(Context context, AttributeSet attrs)
{
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ScoreTrend);
maxScore=a.getInt(R.styleable.ScoreTrend_max_score,700);
minScore=a.getInt(R.styleable.ScoreTrend_min_score,650);
brokenLineColor=a.getColor(R.styleable.ScoreTrend_broken_line_color,brokenLineColor);
a.recycle();
}

初始化画笔:

private void init()
{
brokenPath = new Path();
brokenPaint = new Paint();
brokenPaint.setAntiAlias(true);
brokenPaint.setStyle(Paint.Style.STROKE);
brokenPaint.setStrokeWidth(dipToPx(brokenLineWith));
brokenPaint.setStrokeCap(Paint.Cap.ROUND);
straightPaint = new Paint();
straightPaint.setAntiAlias(true);
straightPaint.setStyle(Paint.Style.STROKE);
straightPaint.setStrokeWidth(brokenLineWith);
straightPaint.setColor((straightLineColor));
straightPaint.setStrokeCap(Paint.Cap.ROUND);
dottedPaint = new Paint();
dottedPaint.setAntiAlias(true);
dottedPaint.setStyle(Paint.Style.STROKE);
dottedPaint.setStrokeWidth(brokenLineWith);
dottedPaint.setColor((straightLineColor));
dottedPaint.setStrokeCap(Paint.Cap.ROUND);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor((textNormalColor));
textPaint.setTextSize(dipToPx(15));
}

绘制代表最高分和最低分虚线

//绘制虚线
private void drawDottedLine(Canvas canvas, float startX, float startY, float stopX, float stopY)
{
dottedPaint.setPathEffect(new DashPathEffect(new float[]{20, 10}, 4));
dottedPaint.setStrokeWidth(1);
// 实例化路径
Path mPath = new Path();
mPath.reset();
// 定义路径的起点
mPath.moveTo(startX, startY);
mPath.lineTo(stopX, stopY);
canvas.drawPath(mPath, dottedPaint);
}

绘制文本

//绘制文本
private void drawText(Canvas canvas)
{
textPaint.setTextSize(dipToPx(12));
textPaint.setColor(textNormalColor);
canvas.drawText(String.valueOf(maxScore), viewWith * 0.1f - dipToPx(10), viewHeight * 0.15f + textSize * 0.25f, textPaint);
canvas.drawText(String.valueOf(minScore), viewWith * 0.1f - dipToPx(10), viewHeight * 0.4f + textSize * 0.25f, textPaint);
textPaint.setColor(0xff7c7c7c);
float newWith = viewWith - (viewWith * 0.15f) * 2;//分隔线距离最左边和最右边的距离是0.15倍的viewWith
float coordinateX;//分隔线X坐标
textPaint.setTextSize(dipToPx(12));
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(textNormalColor);
textSize = (int) textPaint.getTextSize();
for(int i = 0; i < monthText.length; i++)
{
coordinateX = newWith * ((float) (i) / (monthCount - 1)) + (viewWith * 0.15f);
if(i == selectMonth - 1)
{
textPaint.setStyle(Paint.Style.STROKE);
textPaint.setColor(brokenLineColor);
RectF r2 = new RectF();
r2.left = coordinateX - textSize - dipToPx(4);
r2.top = viewHeight * 0.7f + dipToPx(4) + textSize / 2;
r2.right = coordinateX + textSize + dipToPx(4);
r2.bottom = viewHeight * 0.7f + dipToPx(4) + textSize + dipToPx(8);
canvas.drawRoundRect(r2, 10, 10, textPaint);
}
//绘制月份
canvas.drawText(monthText[i], coordinateX, viewHeight * 0.7f + dipToPx(4) + textSize + dipToPx(5), textPaint);
textPaint.setColor(textNormalColor);
}
}

绘制代表月份的属性

//绘制月份的直线(包括刻度)
private void drawMonthLine(Canvas canvas)
{
straightPaint.setStrokeWidth(dipToPx(1));
canvas.drawLine(0, viewHeight * 0.7f, viewWith, viewHeight * 0.7f, straightPaint);
float newWith = viewWith - (viewWith * 0.15f) * 2;//分隔线距离最左边和最右边的距离是0.15倍的viewWith
float coordinateX;//分隔线X坐标
for(int i = 0; i < monthCount; i++)
{
coordinateX = newWith * ((float) (i) / (monthCount - 1)) + (viewWith * 0.15f);
canvas.drawLine(coordinateX, viewHeight * 0.7f, coordinateX, viewHeight * 0.7f + dipToPx(4), straightPaint);
}
}

绘制芝麻分折线

//绘制折线
private void drawBrokenLine(Canvas canvas)
{
brokenPath.reset();
brokenPaint.setColor(brokenLineColor);
brokenPaint.setStyle(Paint.Style.STROKE);
if(score.length == 0)
{
return;
}
Log.v("ScoreTrend", "drawBrokenLine: " + scorePoints.get(0));
brokenPath.moveTo(scorePoints.get(0).x, scorePoints.get(0).y);
for(int i = 0; i < scorePoints.size(); i++)
{
brokenPath.lineTo(scorePoints.get(i).x, scorePoints.get(i).y);
}
canvas.drawPath(brokenPath, brokenPaint);
}

绘制代表芝麻分的圆点

//绘制折线穿过的点
private void drawPoint(Canvas canvas)
{
if(scorePoints == null)
{
return;
}
brokenPaint.setStrokeWidth(dipToPx(1));
for(int i = 0; i < scorePoints.size(); i++)
{
brokenPaint.setColor(brokenLineColor);
brokenPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(3), brokenPaint);
brokenPaint.setColor(Color.WHITE);
brokenPaint.setStyle(Paint.Style.FILL);
if(i == selectMonth - 1)
{
brokenPaint.setColor(0xffd0f3f2);
canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(8f), brokenPaint);
brokenPaint.setColor(0xff81dddb);
canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(5f), brokenPaint);
//绘制浮动文本背景框
drawFloatTextBackground(canvas, scorePoints.get(i).x, scorePoints.get(i).y - dipToPx(8f));
textPaint.setColor(0xffffffff);
//绘制浮动文字
canvas.drawText(String.valueOf(score[i]), scorePoints.get(i).x, scorePoints.get(i).y - dipToPx(5f) - textSize, textPaint);
}
brokenPaint.setColor(0xffffffff);
canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(1.5f), brokenPaint);
brokenPaint.setStyle(Paint.Style.STROKE);
brokenPaint.setColor(brokenLineColor);
canvas.drawCircle(scorePoints.get(i).x, scorePoints.get(i).y, dipToPx(2.5f), brokenPaint);
}
}

绘制选中分数的悬浮文字以及背景

//绘制显示浮动文字的背景
private void drawFloatTextBackground(Canvas canvas, int x, int y)
{
brokenPath.reset();
brokenPaint.setColor(brokenLineColor);
brokenPaint.setStyle(Paint.Style.FILL);
//P1
Point point = new Point(x, y);
brokenPath.moveTo(point.x, point.y);
//P2
point.x = point.x + dipToPx(5);
point.y = point.y - dipToPx(5);
brokenPath.lineTo(point.x, point.y);
//P3
point.x = point.x + dipToPx(12);
brokenPath.lineTo(point.x, point.y);
//P4
point.y = point.y - dipToPx(17);
brokenPath.lineTo(point.x, point.y);
//P5
point.x = point.x - dipToPx(34);
brokenPath.lineTo(point.x, point.y);
//P6
point.y = point.y + dipToPx(17);
brokenPath.lineTo(point.x, point.y);
//P7
point.x = point.x + dipToPx(12);
brokenPath.lineTo(point.x, point.y);
//最后一个点连接到第一个点
brokenPath.lineTo(x, y);
canvas.drawPath(brokenPath, brokenPaint);
}

处理点击事件

@Override
public boolean onTouchEvent(MotionEvent event)
{
this.getParent().requestDisallowInterceptTouchEvent(true);//一旦底层View收到touch的action后调用这个方法那么父层View就不会再调用onInterceptTouchEvent了,也无法截获以后的action
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
onActionUpEvent(event);
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
case MotionEvent.ACTION_CANCEL:
this.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return true;
}
private void onActionUpEvent(MotionEvent event)
{
boolean isValidTouch = validateTouch(event.getX(), event.getY());
if(isValidTouch)
{
invalidate();
}
}
//是否是有效的触摸范围
private boolean validateTouch(float x, float y)
{
//曲线触摸区域
for(int i = 0; i < scorePoints.size(); i++)
{
// dipToPx(8)乘以2为了适当增大触摸面积
if(x > (scorePoints.get(i).x - dipToPx(8) * 2) && x < (scorePoints.get(i).x + dipToPx(8) * 2))
{
if(y > (scorePoints.get(i).y - dipToPx(8) * 2) && y < (scorePoints.get(i).y + dipToPx(8) * 2))
{
selectMonth = i + 1;
return true;
}
}
}
//月份触摸区域
//计算每个月份X坐标的中心点
float monthTouchY = viewHeight * 0.7f - dipToPx(3);//减去dipToPx(3)增大触摸面积
float newWith = viewWith - (viewWith * 0.15f) * 2;//分隔线距离最左边和最右边的距离是0.15倍的viewWith
float validTouchX[] = new float[monthText.length];
for(int i = 0; i < monthText.length; i++)
{
validTouchX[i] = newWith * ((float) (i) / (monthCount - 1)) + (viewWith * 0.15f);
}
if(y > monthTouchY)
{
for(int i = 0; i < validTouchX.length; i++)
{
Log.v("ScoreTrend", "validateTouch: validTouchX:" + validTouchX[i]);
if(x < validTouchX[i] + dipToPx(8) && x > validTouchX[i] - dipToPx(8))
{
Log.v("ScoreTrend", "validateTouch: " + (i + 1));
selectMonth = i + 1;
return true;
}
}
}
return false;
}

获取控件的宽高

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
viewWith = w;
viewHeight = h;
initData();
}

4.总结

还有一些比较不够完善的地方需要处理,比如说可以通过XML调节的属性太少了。平时写的东西还是太少了,希望以后多总结完善写作功底吧。需要的属性后面需要再完善吧

GitHub地址:https://github.com/FelixLee0527/ZhiMaScoreCurve

以上所述是小编给大家介绍的Android 自定义View实现芝麻分曲线图效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对小牛知识库网站的支持!

 类似资料:
  • 本文向大家介绍Android自定义View实现折线图效果,包括了Android自定义View实现折线图效果的使用技巧和注意事项,需要的朋友参考一下 下面就是结果图(每种状态用一个表情图片表示): 一、主页面的布局文件如下: 其中linecharview就是自定义的View,而app:xx就是这个View的各种属性。 二、在values的attrs文件中加入如下xml,来定义linecharview

  • 本文向大家介绍Android自定义View仿支付宝芝麻信用分仪表盘,包括了Android自定义View仿支付宝芝麻信用分仪表盘的使用技巧和注意事项,需要的朋友参考一下 先看下iOS的芝麻信用分截图 这是我做的效果,还是有点差距的 支付宝9.9版本芝麻信用分的实现 首先初始化各种画笔,默认的size,padding,小圆点. (因为实在找不到原版芝麻信用的带点模糊效果的小圆点,所以只好用这个代替)

  • 本文向大家介绍Android 自定义view实现TopBar效果,包括了Android 自定义view实现TopBar效果的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了Android自定义view实现TopBar的具体代码,供大家参考,具体内容如下 布局文件 自定义属性attrs.xml文件 自定义View的Class类 Main方法的代码调用自定义的类和点击事件 效果图: 以上就

  • 本文向大家介绍Android实现价格走势自定义曲线图,包括了Android实现价格走势自定义曲线图的使用技巧和注意事项,需要的朋友参考一下 本文是引用开源图表库框架 MPAndroidChart的LineChart 地址:https://github.com/PhilJay/MPAndroidChart 1.需求: (1)动态添加RadioButton,点击改变下面的LineChart数据 (2)

  • 本文向大家介绍Android自定义View实现自动转圈效果,包括了Android自定义View实现自动转圈效果的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了Android实现自动转圈效果展示的具体代码,供大家参考,具体内容如下 在values文件夹下创建attrs.xml 写一个类继承view 在主页面布局中引入自定义view类 以上就是本文的全部内容,希望对大家的学习有所帮助,也

  • 本文向大家介绍Android自定义View实现饼状图带动画效果,包括了Android自定义View实现饼状图带动画效果的使用技巧和注意事项,需要的朋友参考一下 一个简单的自定义view饼状图,加入了动画效果 先看一下效果 下面就直接上代码了 已经加入了详细的注释,同样的原理,还可以将动画加入到柱形图等等。 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。