Android 自定義View實(shí)現(xiàn)芝麻分曲線圖效果
1.簡(jiǎn)介
其實(shí)這個(gè)效果幾天之前就寫(xiě)了,但是一直沒(méi)有更新博客,本來(lái)想著把芝麻分雷達(dá)圖也做好再發(fā)博客的,然后今天看到鴻洋的微信公眾號(hào)有朋友發(fā)了芝麻分的雷達(dá)圖,所以就算了,算是一個(gè)互補(bǔ)吧。平時(shí)文章也寫(xiě)的比較少,所以可能有點(diǎn)雜亂,有什么需要改進(jìn)的地方歡迎給出建議,不勝感激。
效果圖:

2.步驟:
初始化View的屬性
初始化畫(huà)筆
繪制代表最高分和最低分的兩根虛線
繪制文字
繪制代表月份的屬性
繪制芝麻分折線
繪制代表芝麻分的圓點(diǎn)
繪制選中分?jǐn)?shù)的懸浮文字以及背景
處理點(diǎn)擊事件
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();
}
初始化畫(huà)筆:
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);
// 實(shí)例化路徑
Path mPath = new Path();
mPath.reset();
// 定義路徑的起點(diǎn)
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坐標(biāo)
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坐標(biāo)
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);
}
繪制代表芝麻分的圓點(diǎn)
//繪制折線穿過(guò)的點(diǎn)
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);
//繪制浮動(dòng)文本背景框
drawFloatTextBackground(canvas, scorePoints.get(i).x, scorePoints.get(i).y - dipToPx(8f));
textPaint.setColor(0xffffffff);
//繪制浮動(dòng)文字
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);
}
}
繪制選中分?jǐn)?shù)的懸浮文字以及背景
//繪制顯示浮動(dòng)文字的背景
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);
//最后一個(gè)點(diǎn)連接到第一個(gè)點(diǎn)
brokenPath.lineTo(x, y);
canvas.drawPath(brokenPath, brokenPaint);
}
處理點(diǎn)擊事件
@Override
public boolean onTouchEvent(MotionEvent event)
{
this.getParent().requestDisallowInterceptTouchEvent(true);//一旦底層View收到touch的action后調(diào)用這個(gè)方法那么父層View就不會(huì)再調(diào)用onInterceptTouchEvent了,也無(wú)法截獲以后的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)
{
//曲線觸摸區(qū)域
for(int i = 0; i < scorePoints.size(); i++)
{
// dipToPx(8)乘以2為了適當(dāng)增大觸摸面積
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;
}
}
}
//月份觸摸區(qū)域
//計(jì)算每個(gè)月份X坐標(biāo)的中心點(diǎn)
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.總結(jié)
還有一些比較不夠完善的地方需要處理,比如說(shuō)可以通過(guò)XML調(diào)節(jié)的屬性太少了。平時(shí)寫(xiě)的東西還是太少了,希望以后多總結(jié)完善寫(xiě)作功底吧。需要的屬性后面需要再完善吧
GitHub地址:https://github.com/FelixLee0527/ZhiMaScoreCurve
以上所述是小編給大家介紹的Android 自定義View實(shí)現(xiàn)芝麻分曲線圖效果,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android利用碎片fragment實(shí)現(xiàn)底部標(biāo)題欄(Github模板開(kāi)源)
Fragment可以作為Activity的組成部分,一個(gè)Activity可以有多個(gè)Fragment,這篇文章主要介紹了Android利用碎片fragment實(shí)現(xiàn)底部標(biāo)題欄(Github模板開(kāi)源),需要的朋友可以參考下2019-12-12
android書(shū)架效果實(shí)現(xiàn)原理與代碼
以前也模仿者ireader實(shí)現(xiàn)了書(shū)架的效果,但是那種是使用listview實(shí)現(xiàn)的,并不好用,今天介紹android書(shū)架效果實(shí)現(xiàn)方法2013-01-01
Android Studio使用Profiler來(lái)完成內(nèi)存泄漏的定位
這篇文章主要介紹了Android Studio使用Profiler來(lái)完成內(nèi)存泄漏的定位,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03
Android編程中Tween動(dòng)畫(huà)和Frame動(dòng)畫(huà)實(shí)例分析
這篇文章主要介紹了Android編程中Tween動(dòng)畫(huà)和Frame動(dòng)畫(huà),結(jié)合實(shí)例形式較為詳細(xì)的分析了Android中Tween動(dòng)畫(huà)和Frame動(dòng)畫(huà)的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-12-12
Android Camera2實(shí)現(xiàn)最簡(jiǎn)單的預(yù)覽框顯示
這篇文章主要為大家詳細(xì)介紹了Android Camera2實(shí)現(xiàn)最簡(jiǎn)單的預(yù)覽框顯示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Android仿eleme點(diǎn)餐頁(yè)面二級(jí)聯(lián)動(dòng)列表
本站一直在點(diǎn)外賣(mài),于是心血來(lái)潮就像仿餓了么做個(gè)站,接下來(lái)通過(guò)本文給大家介紹android 二級(jí)聯(lián)動(dòng)列表,仿eleme點(diǎn)餐頁(yè)面的相關(guān)資料,需要的朋友可以參考下2016-10-10
Android應(yīng)用開(kāi)發(fā)中數(shù)據(jù)的保存方式總結(jié)
這篇文章主要介紹了Android應(yīng)用開(kāi)發(fā)中數(shù)據(jù)的保存方式總結(jié),包括對(duì)ROM、SD卡、SharedPreference這三種方式實(shí)現(xiàn)的核心代碼的精選,需要的朋友可以參考下2016-02-02
Android RecyclerView網(wǎng)格布局示例解析
這篇文章主要介紹了Android RecyclerView網(wǎng)格布局示例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12

