JOJO是我看過腦洞最大的動漫(沒有之一),每季必追
最近打算作簡歷,想自定義個能力分析圖,首先就想到這裏:
廢話很少說,走起,噢啦,噢啦,噢啦,噢啦...git
爲了減小變量值,讓尺寸具備很好的聯動性(等比擴縮),小黑條的長寬將取決於最大半徑
mRadius
則:小黑條長:mRadius*0.08
小黑條寬:mRadius*0.05
因此r2=mRadius-mRadius*0.08
github
public class AbilityView extends View {
private float mRadius = dp(100);//外圓半徑
private float mLineWidth = dp(1);//線寬
private Paint mLinePaint;//線畫筆
private Paint mFillPaint;//填充畫筆
public AbilityView(Context context) {
this(context, null);
}
public AbilityView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AbilityView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setStrokeWidth(mLineWidth);
mLinePaint.setStyle(Paint.Style.STROKE);
mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFillPaint.setStrokeWidth(0.05f * mRadius);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(mRadius, mRadius);//移動座標系
drawOutCircle(canvas);
}
/**
* 繪製外圈
* @param canvas 畫布
*/
private void drawOutCircle(Canvas canvas) {
canvas.save();
canvas.drawCircle(0, 0, mRadius, mLinePaint);
float r2 = mRadius - 0.08f * mRadius;//下圓半徑
canvas.drawCircle(0, 0, r2, mLinePaint);
for (int i = 0; i < 22; i++) {//循環畫出小黑條
canvas.save();
canvas.rotate(360 / 22f * i);
canvas.drawLine(0, -mRadius, 0, -r2, mFillPaint);
canvas.restore();
}
canvas.restore();
}
protected float dp(float dp) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
複製代碼
一樣尺寸和最外圓看齊,這裏繪製有一丟丟複雜,你須要瞭解canvas和path的使用
看不懂的可轉到canvas和path,若是看了這兩篇還問繪製有什麼技巧的,可轉到這裏,會告訴你技巧是什麼編程
/**
* 繪製內圈圓
* @param canvas 畫布
*/
private void drawInnerCircle(Canvas canvas) {
canvas.save();
float innerRadius = 0.6f * mRadius;
canvas.drawCircle(0, 0, innerRadius, mLinePaint);
canvas.save();
for (int i = 0; i < 6; i++) {//遍歷6條線
canvas.save();
canvas.rotate(60 * i);//每次旋轉60°
mPath.moveTo(0, -innerRadius);
mPath.rLineTo(0, innerRadius);//線的路徑
for (int j = 1; j < 6; j++) {
mPath.moveTo(-mRadius * 0.02f, innerRadius / 6 * j);
mPath.rLineTo(mRadius * 0.02f * 2, 0);
}//加5條小線
canvas.drawPath(mPath, mLinePaint);//繪製線
canvas.restore();
}
canvas.restore();
}
複製代碼
文字的方向同向,感受這樣看着好些,無論怎麼轉均可以canvas
//定義測試數據
mAbilityInfo = new String[]{"破壞力", "速度", "射程距離", "持久力", "精密度", "成長性"};
mAbilityMark = new int[]{100, 100, 60, 100, 100, 100};
mMarkMapper = new String[]{"A", "B", "C", "D", "E"};
複製代碼
/**
* 繪製文字
*
* @param canvas 畫布
*/
private void drawInfoText(Canvas canvas) {
float r2 = mRadius - 0.08f * mRadius;//下圓半徑
for (int i = 0; i < 6; i++) {
canvas.save();
canvas.rotate(60 * i + 180);
mTextPaint.setTextSize(mRadius * 0.1f);
canvas.drawText(mAbilityInfo[i], 0, r2 - 0.06f * mRadius, mTextPaint);
mTextPaint.setTextSize(mRadius * 0.15f);
canvas.drawText(abilityMark2Str(mAbilityMark[i]), 0, r2 - 0.18f * mRadius, mTextPaint);
canvas.restore();
}
mTextPaint.setTextSize(mRadius * 0.07f);
for (int k = 0; k < 5; k++) {
canvas.drawText(mMarkMapper[k], mRadius * 0.06f, mInnerRadius / 6 * (k + 1) + mRadius * 0.02f - mInnerRadius, mTextPaint);
}
}
/**
* 將分數映射成字符串
* @param mark 分數100~0
* @return
*/
private String abilityMark2Str(int mark) {
if (mark <= 100 && mark > 80) {
return mMarkMapper[0];
} else if (mark <= 80 && mark > 60) {
return mMarkMapper[1];
} else if (mark <= 60 && mark > 40) {
return mMarkMapper[2];
} else if (mark <= 40 && mark > 20) {
return mMarkMapper[3];
} else if (mark <= 20 && mark > 0) {
return mMarkMapper[4];
}
return "∞";
}
複製代碼
本覺得就連個點的事,沒想到...打了我半頁草稿紙(手動表情--可怕)
展示在你眼前的就是個for循環而已,實際上都是經過一點點分析,測試與發現規律算出來的
有什麼技巧?草稿紙拿出來畫圖,計算+分析...,只靠眼睛是不行的設計模式
//我不喜歡弄髒畫筆,再準備一支吧
mAbilityPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mAbilityPaint.setColor(0x8897C5FE);
mAbilityPath = new Path();
複製代碼
/**
* 繪製能力面
* @param canvas
*/
private void drawAbility(Canvas canvas) {
float step = mInnerRadius / 6;//每小段的長度
mAbilityPath.moveTo(0, -mAbilityMark[0] / 20.f * step);//起點
for (int i = 1; i < 6; i++) {
float mark = mAbilityMark[i] / 20.f;
mAbilityPath.lineTo(
(float) (mark * step * Math.cos(Math.PI/180*(-30+60*(i-1)))),
(float) (mark * step * Math.sin(Math.PI/180*(-30+60*(i-1)))));
}
mAbilityPath.close();
canvas.drawPath(mAbilityPath, mAbilityPaint);
}
複製代碼
這樣就完成了,你覺得這樣就結束了?這纔剛開始呢!bash
剛纔用的是測試數據,都寫死在View中,這確定是不行的
如今將數據封裝一下,再暴露接口方法,打開View和外界的通路微信
使用寬度做爲直徑,無視高度,尺寸爲圓形區域
以下所示:可看出全部的尺寸都是和按照mRadius來肯定的,因此縮放時也會等比app
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mRadius = MeasureSpec.getSize(widthMeasureSpec) / 2;
mInnerRadius = 0.6f * mRadius;
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(widthMeasureSpec));
}
複製代碼
爲了方便查看數據間關係,使用Map將能力與數值裝一下ide
private HashMap<String, Integer> mData;//核心數據
//數據的剛纔的對接
mData = new HashMap<>();
mData.put("破壞力", 100);
mData.put("速度", 100);
mData.put("射程距離", 60);
mData.put("持久力", 100);
mData.put("精密度", 100);
mData.put("成長性", 100);
mAbilityInfo = mData.keySet().toArray(new String[mData.size()]);
mAbilityMark = mData.values().toArray(new Integer[mData.size()]);
複製代碼
DataMapper
也就是100~80之間的表明字符串能夠自定義,好比"1" 、 "I" 、"☆"隨你便
這也是我剛悟到的一種解耦方式,應該算是策略設計模式吧(只能分五個等級)
若是自定義分類狀況重寫abilityMark2Str方法就好了post
/**
* 做者:張風捷特烈<br/>
* 時間:2018/12/28 0028:12:21<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:數據映射抽象類
*/
public class DataMapper {
protected String[] mapper;
public DataMapper(String[] mapper) {
if (mapper.length != 5) {
throw new IllegalArgumentException("the length of mapper must be 5");
}
this.mapper = mapper;
}
public String[] getMapper() {
return mapper;
}
/**
* 數值與字符串的映射關係
*
* @param mark 數值
* @return 字符串
*/
public String abilityMark2Str(int mark) {
if (mark <= 100 && mark > 80) {
return mapper[0];
} else if (mark <= 80 && mark > 60) {
return mapper[1];
} else if (mark <= 60 && mark > 40) {
return mapper[2];
} else if (mark <= 40 && mark > 20) {
return mapper[3];
} else if (mark <= 20 && mark > 0) {
return mapper[4];
}
return "∞";
}
}
複製代碼
給一個默認的映射類:
WordMapper
也就是剛纔在View裏寫的那個方法
/**
* 做者:張風捷特烈<br/>
* 時間:2018/12/28 0028:12:24<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:單詞映射
*/
public class WordMapper extends DataMapper {
public WordMapper() {
super(new String[]{"A", "B", "C", "D", "E"});
}
複製代碼
View裏如何修改呢?
//定義成員變量
private DataMapper mDataMapper;//數據與字符串映射規則
//init裏
mDataMapper = new WordMapper();//初始化DataMapper--默認WordMapper
//繪製文字的時候由mDataMapper提供數據
private void drawInfoText(Canvas canvas) {
float r2 = mRadius - 0.08f * mRadius;//下圓半徑
for (int i = 0; i < 6; i++) {
canvas.save();
canvas.rotate(60 * i + 180);
mTextPaint.setTextSize(mRadius * 0.1f);
canvas.drawText(mAbilityInfo[i], 0, r2 - 0.06f * mRadius, mTextPaint);
mTextPaint.setTextSize(mRadius * 0.15f);
canvas.drawText(
mDataMapper.abilityMark2Str(mAbilityMark[i]), 0, r2 - 0.18f * mRadius, mTextPaint);
canvas.restore();
}
mTextPaint.setTextSize(mRadius * 0.07f);
for (int k = 0; k < 5; k++) {
canvas.drawText(mDataMapper.getMapper()[k], mRadius * 0.06f, mInnerRadius / 6 * (k + 1) + mRadius * 0.02f - mInnerRadius, mTextPaint);
}
}
//暴漏get、set方法---提供外界設置
public DataMapper getDataMapper() {
return mDataMapper;
}
public void setDataMapper(DataMapper dataMapper) {
mDataMapper = dataMapper;
}
//暴漏設置數據方法給外部
public HashMap<String, Integer> getData() {
return mData;
}
public void setData(HashMap<String, Integer> data) {
mData = data;
mAbilityInfo = mData.keySet().toArray(new String[mData.size()]);
mAbilityMark = mData.values().toArray(new Integer[mData.size()]);
invalidate();
}
複製代碼
使用DataMapper將字符串抽離出來,而且還能夠根據數值來主要以返回字符串
AbilityView abilityView = findViewById(R.id.id_ability_view);
mData = new HashMap<>();
mData.put("Java", 100);
mData.put("Kotlin", 70);
mData.put("JavaScript", 100);
mData.put("Python", 60);
mData.put("Dart", 50);
mData.put("C++", 60);
abilityView.setDataMapper(new DataMapper(new String[]{"神", "高", "普", "新", "入"}));
abilityView.setData(mData);
複製代碼
ok,搞定,你覺得完了?No,精彩繼續
搞了個6個,不得了了嗎?可見其中還有一個死的東西,那就是數據條數
這個就麻煩了,若是剛纔是0->1的創造
,填充數據是1->2的積累
,那接下來就是2->n的生命
好吧,我又打了半張草稿紙,終於算完了!View一共不到200行代碼,感受很優雅了
有興趣的本身研究(畫畫圖,打打草稿),沒興趣的直接拿去用,
/**
* 做者:張風捷特烈<br/>
* 時間:2018/12/28 0028:7:40<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:能力對比圖
*/
public class AbilityView extends View {
private static final String TAG = "AbilityView";
private float mRadius = dp(100);//外圓半徑
private float mLineWidth = dp(1);//線寬
private Paint mLinePaint;//線畫筆
private Paint mFillPaint;//填充畫筆
private Path mPath;
private HashMap<String, Integer> mData;//核心數據
private Paint mTextPaint;
String[] mAbilityInfo;
Integer[] mAbilityMark;
private float mInnerRadius;
private Path mAbilityPath;
private Paint mAbilityPaint;
private DataMapper mDataMapper;//數據與字符串映射規則
public AbilityView(Context context) {
this(context, null);
}
public AbilityView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AbilityView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setStrokeWidth(mLineWidth);
mLinePaint.setStyle(Paint.Style.STROKE);
mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFillPaint.setStrokeWidth(0.05f * mRadius);
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextSize(mRadius * 0.1f);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mAbilityPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mAbilityPaint.setColor(0x8897C5FE);
mAbilityPath = new Path();
mPath = new Path();
mData = new HashMap<>();
mDataMapper = new WordMapper();//初始化DataMapper--默認WordMapper
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mRadius = MeasureSpec.getSize(widthMeasureSpec) / 2;
mInnerRadius = 0.6f * mRadius;
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(widthMeasureSpec));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mAbilityInfo == null) {
return;
}
canvas.translate(mRadius, mRadius);//移動座標系
drawOutCircle(canvas);
drawInnerCircle(canvas);
drawInfoText(canvas);
drawAbility(canvas);
}
/**
* 繪製能力面
*
* @param canvas
*/
private void drawAbility(Canvas canvas) {
float step = mInnerRadius / (mDataMapper.getMapper().length + 1);//每小段的長度
mAbilityPath.moveTo(0, -mAbilityMark[0] / 20.f * step);//起點
for (int i = 1; i < mData.size(); i++) {
float mark = mAbilityMark[i] / 20.f;
mAbilityPath.lineTo(
(float) (mark * step * Math.cos(Math.PI / 180 * (360.f / mData.size() * i - 90))),
(float) (mark * step * Math.sin(Math.PI / 180 * (360.f / mData.size() * i - 90))));
}
mAbilityPath.close();
canvas.drawPath(mAbilityPath, mAbilityPaint);
}
/**
* 繪製文字
*
* @param canvas 畫布
*/
private void drawInfoText(Canvas canvas) {
float r2 = mRadius - 0.08f * mRadius;//下圓半徑
for (int i = 0; i < mData.size(); i++) {
canvas.save();
canvas.rotate(360.f / mData.size() * i + 180);
mTextPaint.setTextSize(mRadius * 0.1f);
canvas.drawText(mAbilityInfo[i], 0, r2 - 0.06f * mRadius, mTextPaint);
mTextPaint.setTextSize(mRadius * 0.15f);
canvas.drawText(
mDataMapper.abilityMark2Str(mAbilityMark[i]), 0, r2 - 0.18f * mRadius, mTextPaint);
canvas.restore();
}
mTextPaint.setTextSize(mRadius * 0.07f);
for (int k = 0; k < mDataMapper.getMapper().length; k++) {
canvas.drawText(mDataMapper.getMapper()[k], mRadius * 0.06f,
mInnerRadius / (mDataMapper.getMapper().length + 1) * (k + 1) + mRadius * 0.02f - mInnerRadius, mTextPaint);
}
}
/**
* 繪製內圈圓
*
* @param canvas 畫布
*/
private void drawInnerCircle(Canvas canvas) {
canvas.save();
canvas.drawCircle(0, 0, mInnerRadius, mLinePaint);
canvas.save();
for (int i = 0; i < mData.size(); i++) {//遍歷6條線
canvas.save();
canvas.rotate(360.f / mData.size() * i);//每次旋轉60°
mPath.moveTo(0, -mInnerRadius);
mPath.rLineTo(0, mInnerRadius);//線的路徑
for (int j = 1; j <= mDataMapper.getMapper().length; j++) {
mPath.moveTo(-mRadius * 0.02f, -mInnerRadius / (mDataMapper.getMapper().length + 1) * j);
mPath.rLineTo(mRadius * 0.02f * 2, 0);
}//加5條小線
canvas.drawPath(mPath, mLinePaint);//繪製線
canvas.restore();
}
canvas.restore();
}
/**
* 繪製外圈
*
* @param canvas 畫布
*/
private void drawOutCircle(Canvas canvas) {
canvas.save();
canvas.drawCircle(0, 0, mRadius, mLinePaint);
float r2 = mRadius - 0.08f * mRadius;//下圓半徑
canvas.drawCircle(0, 0, r2, mLinePaint);
for (int i = 0; i < 22; i++) {//循環畫出小黑條
canvas.save();
canvas.rotate(360 / 22f * i);
canvas.drawLine(0, -mRadius, 0, -r2, mFillPaint);
canvas.restore();
}
canvas.restore();
}
protected float dp(float dp) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
/////////////////////////////---------------------
public float getRadius() {
return mRadius;
}
public void setRadius(float radius) {
mRadius = radius;
}
public DataMapper getDataMapper() {
return mDataMapper;
}
public void setDataMapper(DataMapper dataMapper) {
mDataMapper = dataMapper;
}
public HashMap<String, Integer> getData() {
return mData;
}
public void setData(HashMap<String, Integer> data) {
mData = data;
mAbilityInfo = mData.keySet().toArray(new String[mData.size()]);
mAbilityMark = mData.values().toArray(new Integer[mData.size()]);
invalidate();
}
}
複製代碼
好了,這下真的結束了
項目源碼 | 日期 | 備註 |
---|---|---|
V0.1--github | 2018-12-28 | Android自定義控件(高手級)--JOJO同款能力分析圖 |
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
個人github | 個人簡書 | 個人掘金 | 我的網站 |
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持