private float mXItemWidth;
//曲线的数据源
private List<CurveModel> mLineList;
private List<BkgModel> mBkgList;
//正常范围/异常范围背景
private Paint bkgPaint;
//顶部描述文字
//描述框的高度
private int descHeight = 100;
//X轴和Y轴保留的小数点
private double mXNum,mYNum;
public CurveTrendChartView(Context context) {
this(context, null);
}
public CurveTrendChartView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
}
public CurveTrendChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
//获取属性文件
TypedArray array = mContext.obtainStyledAttributes(attrs, R.styleable.CurveTrendChartView);
mXTextSize = array.getDimensionPixelSize(R.styleable.CurveTrendChartView_xTextSize,mTextSize);
mYTextSize = array.getDimensionPixelSize(R.styleable.CurveTrendChartView_yTextSize,mTextSize);
mXTextColor = array.getColor(R.styleable.CurveTrendChartView_xTextColor,mTextColor);
mYTextColor = array.getColor(R.styleable.CurveTrendChartView_yTextColor,mTextColor);
//TypedArray使用完一定要回收,否则会造成内存泄漏
array.recycle();
}
/**
* 初始化画笔和路径
*/
private void initPaint() {
mDottedLinePaint = new Paint();
mDottedLinePaint.setAntiAlias(true);//抗锯齿效果
mDottedLinePaint.setStyle(Paint.Style.STROKE);
mDottedLinePaint.setColor(mDottedLineColor);
mDottedLinePaint.setStrokeWidth(2);
mDottedLinePath = new Path();
mCurvePaint = new Paint();
mCurvePaint.setAntiAlias(true);
mCurvePaint.setStyle(Paint.Style.STROKE);
mPointPaint = new Paint();
mPointPaint.setAntiAlias(true);
mPointPaint.setStyle(Paint.Style.FILL);
mPointPaint.setColor(mPointColor);
mPointPaint.setStrokeWidth(pointSize);
mPointPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mTextColor);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTextSize(mTextSize);
mTextPaint.setTextAlign(Paint.Align.CENTER);
bkgPaint = new Paint();
bkgPaint.setAntiAlias(true);
bkgPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mXWidth = mWidth - mYWidth;
mXItemWidth = (mWidth - mYWidth - mXUnitHeight) / (float) mXDataList.size();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制背景和说明文字的方法,在该方法初始化了高度的一些变量,所以首先执行该方法
drawDesc(canvas);
//绘制Y坐标
drawYLineText(canvas);
//绘制X坐标
drawXLineText(canvas);
//绘制背景虚线
drawDottedLine(canvas);
//绘制每条折线
drawCurceLine(canvas);
}
/**
* 绘制描述语句部分
* @param canvas
* 初始化了全局变量@{mYHeight}
*/
private void drawDesc(Canvas canvas) {
if(null != mBkgList && mBkgList.size()>0){
//设置对齐方式为左对齐
mTextPaint.setTextAlign(Paint.Align.LEFT);
int bkgHeight = 40;
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mTextColor);
//绘制背景说明颜色和说明文字
//定义框的左边距,定义每个说明背景颜色块宽度
int left = mWidth / 2 + 20, width = 80;
//说明框的高度
descHeight = 10+bkgHeight/2;
for(BkgModel item : mBkgList){
canvas.save();
bkgPaint.setStrokeWidth(bkgHeight);
bkgPaint.setColor(item.getColor());
//画背景框
canvas.drawLine(left, descHeight+10 , left + width,descHeight+10 , bkgPaint);
//画描述语句
canvas.drawText(item.getDesc(), left + width + 20, descHeight+10+mTextSize/2, mTextPaint);
canvas.restore();
descHeight = descHeight+10+bkgHeight;
}
//绘制折线说明颜色和说明文字
left = left + width + 230;
int height =10+ bkgHeight/2;
for(CurveModel item:mLineList){
canvas.save();
mCurvePaint.setColor(item.getCurveColor());
//画折线颜色
canvas.drawLine(left, height+10, left + width, height+10, mCurvePaint);
//画描述语句
canvas.drawText(item.getCurveDesc(), left + width + 20, height+10+mTextSize/2, mTextPaint);
canvas.restore();
height = height+10+bkgHeight;
}
//画描述语句的外框
canvas.drawRect(new Rect(mWidth / 2, 10, mWidth - 10, descHeight + 10 - bkgHeight/2), mDottedLinePaint);
//初始化y轴高度和每行的高度
// descHeight -= 10;
mYHeight = mHeight - mXHeight -mYUnitHeight- descHeight;
//注意,这儿最好转为float,不然会导致高度计算不太准,我找位置不准的原因找了很久很久,终于找到了
mYItemHeight = mYHeight / (float) mYDataList.size();
//在趋势表中绘制背景色
for(BkgModel item : mBkgList){
canvas.save();
//获取最低值和最高值的下标
int bottom = mYDataList.indexOf(item.getValue().getX()),high = mYDataList.indexOf(item.getValue().getY());
//计算背景的高度
bkgPaint.setStrokeWidth((bottom - high) * mYItemHeight);
bkgPaint.setColor(item.getColor());
//计算背景的起始位置
float diastolicStart = (float) ((high +(bottom - high)/2.0 +0.5 ) * mYItemHeight + mYUnitHeight + descHeight);
canvas.drawLine(mYWidth, diastolicStart, mWidth, diastolicStart, bkgPaint);
canvas.restore();
}
}
}
/**
* 对应Y轴的虚线
* @param canvas
*/
private void drawDottedLine(Canvas canvas) {
int count = mYDataList.size();
if (count > 0) {
canvas.save();
//虚线效果:先画5的实线,再画5的空白,开始绘制的偏移值为0
mDottedLinePaint.setPathEffect(new DashPathEffect(new float[]{5, 5}, 0));
mDottedLinePaint.setStrokeWidth(1f);
float startY;
for (int i = 0; i < count; i++) {
//因为要绘制在中间,所以得加上mYItemHeight / 2,再加上mYUnitHeight + descHeight
startY = i * mYItemHeight + mYItemHeight / 2 + mYUnitHeight + descHeight;
mDottedLinePath.reset();
mDottedLinePath.moveTo(mYWidth, startY);
mDottedLinePath.lineTo(mWidth, startY);
canvas.drawPath(mDottedLinePath, mDottedLinePaint);
}
canvas.restore();
}
}
/**
* Y轴的文字
* @param canvas
*/
private void drawYLineText(Canvas canvas) {
int count = mYDataList.size();
if (count > 0) {
canvas.save();
mTextPaint.setTextSize(mYTextSize);
mTextPaint.setColor(mYTextColor);
float startY;
float baseline;
Paint.FontMetricsInt metrics = mTextPaint.getFontMetricsInt();
mTextPaint.setTextAlign(Paint.Align.CENTER);
for (int i = 0; i < count; i++) {
startY = (i + 1) * mYItemHeight;
baseline = (startY * 2 - mYItemHeight - metrics.bottom - metrics.top) / 2 + mYUnitHeight + descHeight;
canvas.drawText(String.valueOf(mYDataList.get(i)/mYNum), mYWidth / 2, baseline, mTextPaint);
}
canvas.drawText(mYDataUnit, mYWidth / 2, descHeight, mTextPaint);
canvas.restore();
}
}
/**
* X轴的文字
* @param canvas
*/
private void drawXLineText(Canvas canvas) {
int count = mXDataList.size();
if (count > 0) {
canvas.save();
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(mXTextSize);
mTextPaint.setColor(mXTextColor);
float startX;
for (int i = 0; i < count; i++) {
startX = mYWidth + i * mXItemWidth + mXItemWidth / 2;
canvas.drawText(String.valueOf((mXDataList.get(i)/mXNum)), startX, mHeight-mXHeight/2, mTextPaint);
}
canvas.restore();
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mTextColor);
canvas.drawText(mXDataUnit, mWidth - mXItemWidth / 2, mHeight-mXHeight/2, mTextPaint);
}
}
/**
* 绘制曲线
* @param canvas
*/
private void drawCurceLine(Canvas canvas) {
for(CurveModel item:mLineList) {
canvas.save();
mCurvePaint.setColor(item.getCurveColor());
List<Value> curveLineDataList = item.getCurveLineDataList();
int count = curveLineDataList.size();
if (count > 0) {
mDottedLinePath.reset();
float stopX, stopY;
float baseHeight = mYItemHeight / 2 + mYUnitHeight + descHeight;
//因为虚线是在中间绘制的,所以得减去最上虚线的上半部分和最下虚线的下半部分
float totalHeight = mYHeight - mYItemHeight;
float totalWidth = mWidth - mYWidth - mXUnitHeight - mXItemWidth;
mDottedLinePath.moveTo(mXItemWidth / 2 + mYWidth, (float) (totalHeight * ((curveLineDataList.get(0).getY() - maxData) / (minData - maxData))) + baseHeight);
canvas.drawPoint(mXItemWidth / 2 + mYWidth, (float) (totalHeight * ((curveLineDataList.get(0).getY() - maxData) / (minData - maxData))) + baseHeight, mPointPaint);
for (int i = 1; i < count; i++) {
stopX = (float) (totalWidth * (curveLineDataList.get(i).getX() - mXDataList.get(0)) / (mXDataList.get(mXDataList.size() - 1) - mXDataList.get(0)) + mYWidth + mXItemWidth / 2);
//根据比例求得点的坐标
stopY = (float) (totalHeight * ((curveLineDataList.get(i).getY() - maxData) / (minData - maxData)) + baseHeight);
mDottedLinePath.lineTo(stopX, stopY);
canvas.drawPoint(stopX, stopY, mPointPaint);
}
canvas.drawPath(mDottedLinePath, mCurvePaint);
}
canvas.restore();
}
}
/**
* 初始化全局变量
* @param context 上下文
* @param xUnit x轴的单位
* @param xDataList X轴数据源
* @param xNum x轴保留的小数点位数
* @param yUnit y轴的单位
* @param yDataList y轴的数据源
* @param yNum y轴保留的小数点位数
* @param lineList 曲线列表
* @param bkgItemList 说明背景列表
* @return
*/
private CurveTrendChartView init(Context context, String xUnit, List<Integer> xDataList, double xNum, String yUnit, List<Integer> yDataList, double yNum, List<CurveModel> lineList, List<BkgModel> bkgItemList){
this.mContext = context;
this.mXDataUnit = xUnit;
this.mXDataList = xDataList;
this.mXNum = xNum;
this.mYDataUnit = yUnit;
this.mYDataList = yDataList;
this.mYNum = yNum;
this.mLineList = lineList;
this.mBkgList = bkgItemList;
this.minData = mYDataList.get(mYDataList.size() - 1);
this.maxData = mYDataList.get(0);
initPaint();
invalidate();
return this;
}
public Builder Builder(Context context){
return new Builder(context);
}
//利用建造者模式构造一个数据表
public class Builder{
private Context mContext;
//Y轴数据
private List<Integer> mYDataList;
//Y轴单位
private String mYUnit;
//X轴数据
private List<Integer> mXDataList;
//X轴单位
private String mXUnit;
private double mXNum,mYNum;
//每条数据源集合
private List<CurveModel> mLineList = new ArrayList<>();
//每个背景集合
private List<BkgModel> mBkgItemList = new ArrayList<>();
private Builder(Context context){
this.mContext = context;
}
public Builder setX(String xUnit,List<Integer> xDataList,double xNum){
this.mXUnit = xUnit;
this.mXDataList = xDataList;
this.mXNum = xNum;
return this;
}
public Builder setY(String yUnit,List<Integer> yDataList,double yNum){
this.mYUnit = yUnit;
this.mYDataList = yDataList;
this.mYNum = yNum;
return this;
}
public Builder addLine(CurveModel item){
this.mLineList.add(item);
return this;
}
public Builder addBKG(BkgModel item){
mBkgItemList.add(item);
return this;
}
public CurveTrendChartView build(){
总结
【Android 详细知识点思维脑图(技能树)】
我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
最后,赠与大家一句话,共勉!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
[外链图片转存中…(img-h610O4G1-1714212736669)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
最后,赠与大家一句话,共勉!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!