//第一种:
//第一步 写个ChartView类继承View:
public class ChartView extends View {
//xy坐标轴颜色
private int xylinecolor = 0xffe2e2e2;
//xy坐标轴宽度
private int xylinewidth = dpToPx(1);
//xy坐标轴文字颜色
private int xytextcolor = 0xff7e7e7e;
//xy坐标轴文字大小
private int xytextsize = spToPx(12);
//折线图中折线的颜色
private int linecolor = 0xff02bbb7;
//x轴各个坐标点水平间距
private int interval = dpToPx(50);
//背景颜色
private int bgcolor = 0xffffffff;
//是否在ACTION_UP时,根据速度进行自滑动,没有要求,建议关闭,过于占用GPU
private boolean isScroll = false;
//绘制XY轴坐标对应的画笔
private Paint xyPaint;
//绘制XY轴的文本对应的画笔
private Paint xyTextPaint;
//画折线对应的画笔
private Paint linePaint;
private int width;
private int height;
//x轴的原点坐标
private int xOri;
//y轴的原点坐标
private int yOri;
//第一个点X的坐标
private float xInit;
//第一个点对应的最大Y坐标
private float maxXInit;
//第一个点对应的最小X坐标
private float minXInit;
//x轴坐标对应的数据
private List<String> xValue = new ArrayList<>();
//y轴坐标对应的数据
private List<Integer> yValue = new ArrayList<>();
//折线对应的数据
private Map<String, Integer> value = new HashMap<>();
//点击的点对应的X轴的第几个点,默认1
private int selectIndex = 1;
//X轴刻度文本对应的最大矩形,为了选中时,在x轴文本画的框框大小一致
private Rect xValueRect;
//速度检测器
private VelocityTracker velocityTracker;
public ChartView(Context context) {
this(context, null);
}
public ChartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
initPaint();
}
/**
* 初始化畫筆
*/
private void initPaint() {
xyPaint = new Paint();
xyPaint.setAntiAlias(true);
xyPaint.setStrokeWidth(xylinewidth);
xyPaint.setStrokeCap(Paint.Cap.ROUND);
xyPaint.setColor(xylinecolor);
xyTextPaint = new Paint();
xyTextPaint.setAntiAlias(true);
xyTextPaint.setTextSize(xytextsize);
xyTextPaint.setStrokeCap(Paint.Cap.ROUND);
xyTextPaint.setColor(xytextcolor);
xyTextPaint.setStyle(Paint.Style.STROKE);
linePaint = new Paint();
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth(xylinewidth);
linePaint.setStrokeCap(Paint.Cap.ROUND);
linePaint.setColor(linecolor);
linePaint.setStyle(Paint.Style.STROKE);
}
/**
* 初始化
*
* @param context
* @param attrs
* @param defStyleAttr
*/
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.chartView, defStyleAttr, 0);
int count = array.getIndexCount();
for (int i = 0; i < count; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.chartView_xylinecolor://xy坐标轴颜色
xylinecolor = array.getColor(attr, xylinecolor);
break;
case R.styleable.chartView_xylinewidth://xy坐标轴宽度
xylinewidth = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xylinewidth, getResources().getDisplayMetrics()));
break;
case R.styleable.chartView_xytextcolor://xy坐标轴文字颜色
xytextcolor = array.getColor(attr, xytextcolor);
break;
case R.styleable.chartView_xytextsize://xy坐标轴文字大小
xytextsize = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xytextsize, getResources().getDisplayMetrics()));
break;
case R.styleable.chartView_linecolor://折线图中折线的颜色
linecolor = array.getColor(attr, linecolor);
break;
case R.styleable.chartView_interval://x轴各个坐标点水平间距
interval = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, interval, getResources().getDisplayMetrics()));
break;
case R.styleable.chartView_bgcolor: //背景颜色
bgcolor = array.getColor(attr, bgcolor);
break;
case R.styleable.chartView_isScroll://是否在ACTION_UP时,根据速度进行自滑动
isScroll = array.getBoolean(attr, isScroll);
break;
}
}
array.recycle();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed) {
//这里需要确定几个基本点,只有确定了xy轴原点坐标,第一个点的X坐标值及其最大最小值
width = getWidth();
height = getHeight();
//Y轴文本最大宽度
float textYWdith = getTextBounds("000", xyTextPaint).width();
for (int i = 0; i < yValue.size(); i++) {//求取y轴文本最大的宽度
float temp = getTextBounds(yValue.get(i) + "", xyTextPaint).width();
if (temp > textYWdith)
textYWdith = temp;
}
int dp2 = dpToPx(2);
int dp3 = dpToPx(3);
xOri = (int) (dp2 + textYWdith + dp2 + xylinewidth);//dp2是y轴文本距离左边,以及距离y轴的距离
// //X轴文本最大高度
xValueRect = getTextBounds("000", xyTextPaint);
float textXHeight = xValueRect.height();
for (int i = 0; i < xValue.size(); i++) {//求取x轴文本最大的高度
Rect rect = getTextBounds(xValue.get(i) + "", xyTextPaint);
if (rect.height() > textXHeight)
textXHeight = rect.height();
if (rect.width() > xValueRect.width())
xValueRect = rect;
}
yOri = (int) (height - dp2 - textXHeight - dp3 - xylinewidth);//dp3是x轴文本距离底边,dp2是x轴文本距离x轴的距离
xInit = interval + xOri;
minXInit = width - (width - xOri) * 0.1f - interval * (xValue.size() - 1);//减去0.1f是因为最后一个X周刻度距离右边的长度为X轴可见长度的10%
maxXInit = xInit;
}
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
canvas.drawColor(bgcolor);
drawXY(canvas);
drawBrokenLineAndPoint(canvas);
}
/**
* 绘制折线和折线交点处对应的点
*
* @param canvas
*/
private void drawBrokenLineAndPoint(Canvas canvas) {
if (xValue.size() <= 0)
return;
//重新开一个图层
int layerId = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);
drawBrokenLine(canvas);
drawBrokenPoint(canvas);
// 将折线超出x轴坐标的部分截取掉
linePaint.setStyle(Paint.Style.FILL);
linePaint.setColor(bgcolor);
linePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
RectF rectF = new RectF(0, 0, xOri, height);
canvas.drawRect(rectF, linePaint);
linePaint.setXfermode(null);
//保存图层
canvas.restoreToCount(layerId);
}
/**
* 绘制折线对应的点
*
* @param canvas
*/
private void drawBrokenPoint(Canvas canvas) {
float dp2 = dpToPx(2);
float dp4 = dpToPx(4);
float dp7 = dpToPx(7);
//绘制节点对应的原点
for (int i = 0; i < xValue.size(); i++) {
float x = xInit + interval * i;
float y = yOri - yOri * (1 - 0.1f) * value.get(xValue.get(i)) / yValue.get(yValue.size() - 1);
//绘制选中的点
if (i == selectIndex - 1) {
linePaint.setStyle(Paint.Style.FILL);
linePaint.setColor(0xffd0f3f2);
canvas.drawCircle(x, y, dp7, linePaint);
linePaint.setColor(0xff81dddb);
canvas.drawCircle(x, y, dp4, linePaint);
drawFloatTextBox(canvas, x, y - dp7, value.get(xValue.get(i)));
}
//绘制普通的节点
linePaint.setStyle(Paint.Style.FILL);
linePaint.setColor(Color.WHITE);
canvas.drawCircle(x, y, dp2, linePaint);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setColor(linecolor);
canvas.drawCircle(x, y, dp2, linePaint);
}
}
/**
* 绘制显示Y值的浮动框
*
* @param canvas
* @param x
* @param y
* @param text
*/
private void drawFloatTextBox(Canvas canvas, float x, float y, int text) {
int dp6 = dpToPx(6);
int dp18 = dpToPx(18);
//p1
Path path = new Path();
path.moveTo(x, y);
//p2
path.lineTo(x - dp6, y - dp6);
//p3
path.lineTo(x - dp18, y - dp6);
//p4
path.lineTo(x - dp18, y - dp6 - dp18);
//p5
path.lineTo(x + dp18, y - dp6 - dp18);
//p6
path.lineTo(x + dp18, y - dp6);
//p7
path.lineTo(x + dp6, y - dp6);
//p1
path.lineTo(x, y);
canvas.drawPath(path, linePaint);
linePaint.setColor(Color.WHITE);
linePaint.setTextSize(spToPx(14));
Rect rect = getTextBounds(text + "", linePaint);
canvas.drawText(text + "", x - rect.width() / 2, y - dp6 - (dp18 - rect.height()) / 2, linePaint);
}
/**
* 绘制折线
*
* @param canvas
*/
private void drawBrokenLine(Canvas canvas) {
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setColor(linecolor);
//绘制折线
Path path = new Path();
float x = xInit + interval * 0;
float y = yOri - yOri * (1 - 0.1f) * value.get(xValue.get(0)) / yValue.get(yValue.size() - 1);
path.moveTo(x, y);
for (int i = 1; i < xValue.size(); i++) {
x = xInit + interval * i;
y = yOri - yOri * (1 - 0.1f) * value.get(xValue.get(i)) / yValue.get(yValue.size() - 1);
path.lineTo(x, y);
}
canvas.drawPath(path, linePaint);
}
/**
* 绘制XY坐标
*
* @param canvas
*/
private void drawXY(Canvas canvas) {
int length = dpToPx(4);//刻度的长度
//绘制Y坐标
canvas.drawLine(xOri - xylinewidth / 2, 0, xOri - xylinewidth / 2, yOri, xyPaint);
//绘制y轴箭头
xyPaint.setStyle(Paint.Style.STROKE);
Path path = new Path();
path.moveTo(xOri - xylinewidth / 2 - dpToPx(5), dpToPx(12));
path.lineTo(xOri - xylinewidth / 2, xylinewidth / 2);
path.lineTo(xOri - xylinewidth / 2 + dpToPx(5), dpToPx(12));
canvas.drawPath(path, xyPaint);
//绘制y轴刻度
int yLength = (int) (yOri * (1 - 0.1f) / (yValue.size() - 1));//y轴上面空出10%,计算出y轴刻度间距
for (int i = 0; i < yValue.size(); i++) {
//绘制Y轴刻度
//这里可以设置成这个Y轴刻度,和屏幕一样宽的效果:
// canvas.drawLine(xOri, yOri - yLength * i + xylinewidth / 2, xOri+width, yOri - yLength * i + xylinewidth / 2, xyPaint);
canvas.drawLine(xOri, yOri - yLength * i + xylinewidth / 2, xOri + length, yOri - yLength * i + xylinewidth / 2, xyPaint);
xyTextPaint.setColor(xytextcolor);
//绘制Y轴文本
String text = yValue.get(i) + "";
Rect rect = getTextBounds(text, xyTextPaint);
canvas.drawText(text, 0, text.length(), xOri - xylinewidth - dpToPx(2) - rect.width(), yOri - yLength * i + rect.height() / 2, xyTextPaint);
}
//绘制X轴坐标
canvas.drawLine(xOri, yOri + xylinewidth / 2, width, yOri + xylinewidth / 2, xyPaint);
//绘制x轴箭头
xyPaint.setStyle(Paint.Style.STROKE);
path = new Path();
//整个X轴的长度
float xLength = xInit + interval * (xValue.size() - 1) + (width - xOri) * 0.1f;
if (xLength < width)
xLength = width;
path.moveTo(xLength - dpToPx(12), yOri + xylinewidth / 2 - dpToPx(5));
path.lineTo(xLength - xylinewidth / 2, yOri + xylinewidth / 2);
path.lineTo(xLength - dpToPx(12), yOri + xylinewidth / 2 + dpToPx(5));
canvas.drawPath(path, xyPaint);
//绘制x轴刻度
for (int i = 0; i < xValue.size(); i++) {
float x = xInit + interval * i;
if (x >= xOri) {//只绘制从原点开始的区域
xyTextPaint.setColor(xytextcolor);
canvas.drawLine(x, yOri, x, yOri - length, xyPaint);
//绘制X轴文本
String text = xValue.get(i);
Rect rect = getTextBounds(text, xyTextPaint);
if (i == selectIndex - 1) {
xyTextPaint.setColor(linecolor);
canvas.drawText(text, 0, text.length(), x - rect.width() / 2, yOri + xylinewidth + dpToPx(2) + rect.height(), xyTextPaint);
canvas.drawRoundRect(x - xValueRect.width() / 2 - dpToPx(3), yOri + xylinewidth + dpToPx(1), x + xValueRect.width() / 2 + dpToPx(3), yOri + xylinewidth + dpToPx(2) + xValueRect.height() + dpToPx(2), dpToPx(2), dpToPx(2), xyTextPaint);
} else {
canvas.drawText(text, 0, text.length(), x - rect.width() / 2, yOri + xylinewidth + dpToPx(2) + rect.height(), xyTextPaint);
}
}
}
}
private float startX;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isScrolling)
return super.onTouchEvent(event);
this.getParent().requestDisallowInterceptTouchEvent(true);//当该view获得点击事件,就请求父控件不拦截事件
obtainVelocityTracker(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
if (interval * xValue.size() > width - xOri) {//当期的宽度不足以呈现全部数据
float dis = event.getX() - startX;
startX = event.getX();
if (xInit + dis < minXInit) {
xInit = minXInit;
} else if (xInit + dis > maxXInit) {
xInit = maxXInit;
} else {
xInit = xInit + dis;
}
invalidate();
}
break;
case MotionEvent.ACTION_UP:
clickAction(event);
scrollAfterActionUp();
this.getParent().requestDisallowInterceptTouchEvent(false);
recycleVelocityTracker();
break;
case MotionEvent.ACTION_CANCEL:
this.getParent().requestDisallowInterceptTouchEvent(false);
recycleVelocityTracker();
break;
}
return true;
}
//是否正在滑动
private boolean isScrolling = false;
/**
* 手指抬起后的滑动处理
*/
private void scrollAfterActionUp() {
if (!isScroll)
return;
final float velocity = getVelocity();
float scrollLength = maxXInit - minXInit;
if (Math.abs(velocity) < 10000)//10000是一个速度临界值,如果速度达到10000,最大可以滑动(maxXInit - minXInit)
scrollLength = (maxXInit - minXInit) * Math.abs(velocity) / 10000;
ValueAnimator animator = ValueAnimator.ofFloat(0, scrollLength);
animator.setDuration((long) (scrollLength / (maxXInit - minXInit) * 1000));//时间最大为1000毫秒,此处使用比例进行换算
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
if (velocity < 0 && xInit > minXInit) {//向左滑动
if (xInit - value <= minXInit)
xInit = minXInit;
else
xInit = xInit - value;
} else if (velocity > 0 && xInit < maxXInit) {//向右滑动
if (xInit + value >= maxXInit)
xInit = maxXInit;
else
xInit = xInit + value;
}
invalidate();
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
isScrolling = true;
}
@Override
public void onAnimationEnd(Animator animator) {
isScrolling = false;
}
@Override
public void onAnimationCancel(Animator animator) {
isScrolling = false;
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
/**
* 获取速度
*
* @return
*/
private float getVelocity() {
if (velocityTracker != null) {
velocityTracker.computeCurrentVelocity(1000);
return velocityTracker.getXVelocity();
}
return 0;
}
/**
* 点击X轴坐标或者折线节点
*
* @param event
*/
private void clickAction(MotionEvent event) {
int dp8 = dpToPx(8);
float eventX = event.getX();
float eventY = event.getY();
for (int i = 0; i < xValue.size(); i++) {
//节点
float x = xInit + interval * i;
float y = yOri - yOri * (1 - 0.1f) * value.get(xValue.get(i)) / yValue.get(yValue.size() - 1);
if (eventX >= x - dp8 && eventX <= x + dp8 &&
eventY >= y - dp8 && eventY <= y + dp8 && selectIndex != i + 1) {//每个节点周围8dp都是可点击区域
selectIndex = i + 1;
invalidate();
return;
}
//X轴刻度
String text = xValue.get(i);
Rect rect = getTextBounds(text, xyTextPaint);
x = xInit + interval * i;
y = yOri + xylinewidth + dpToPx(2);
if (eventX >= x - rect.width() / 2 - dp8 && eventX <= x + rect.width() + dp8 / 2 &&
eventY >= y - dp8 && eventY <= y + rect.height() + dp8 && selectIndex != i + 1) {
selectIndex = i + 1;
invalidate();
return;
}
}
}
/**
* 获取速度跟踪器
*
* @param event
*/
private void obtainVelocityTracker(MotionEvent event) {
if (!isScroll)
return;
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(event);
}
/**
* 回收速度跟踪器
*/
private void recycleVelocityTracker() {
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
}
public int getSelectIndex() {
return selectIndex;
}
public void setSelectIndex(int selectIndex) {
this.selectIndex = selectIndex;
invalidate();
}
public void setxValue(List<String> xValue) {
this.xValue = xValue;
}
public void setyValue(List<Integer> yValue) {
this.yValue = yValue;
invalidate();
}
public void setValue(Map<String, Integer> value) {
this.value = value;
invalidate();
}
public void setValue(Map<String, Integer> value, List<String> xValue, List<Integer> yValue) {
this.value = value;
this.xValue = xValue;
this.yValue = yValue;
invalidate();
}
public List<String> getxValue() {
return xValue;
}
public List<Integer> getyValue() {
return yValue;
}
public Map<String, Integer> getValue() {
return value;
}
/**
* 获取丈量文本的矩形
*
* @param text
* @param paint
* @return
*/
private Rect getTextBounds(String text, Paint paint) {
Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
return rect;
}
/**
* dp转化成为px
*
* @param dp
* @return
*/
private int dpToPx(int dp) {
float density = getContext().getResources().getDisplayMetrics().density;
return (int) (dp * density + 0.5f * (dp >= 0 ? 1 : -1));
}
/**
* sp转化为px
*
* @param sp
* @return
*/
private int spToPx(int sp) {
float scaledDensity = getContext().getResources().getDisplayMetrics().scaledDensity;
return (int) (scaledDensity * sp + 0.5f * (sp >= 0 ? 1 : -1));
}
}
//第二步:在values下写个attrs.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- xy坐标轴颜色 -->
<attr name="xylinecolor" format="color" />
<!-- xy坐标轴宽度 -->
<attr name="xylinewidth" format="dimension" />
<!-- xy坐标轴文字颜色 -->
<attr name="xytextcolor" format="color" />
<!-- xy坐标轴文字大小 -->
<attr name="xytextsize" format="dimension" />
<!-- 折线图中折线的颜色 -->
<attr name="linecolor" format="color" />
<!-- x轴各个坐标点水平间距 -->
<attr name="interval" format="dimension" />
<!-- 背景颜色 -->
<attr name="bgcolor" format="color" />
<!--是否在ACTION_UP时,根据速度进行自滑动,建议关闭,过于占用GPU-->
<attr name="isScroll" format="boolean" />
<declare-styleable name="chartView">
<attr name="xylinecolor" />
<attr name="xylinewidth" />
<attr name="xytextcolor" />
<attr name="xytextsize" />
<attr name="linecolor" />
<attr name="interval" />
<attr name="bgcolor" />
<attr name="isScroll" />
</declare-styleable>
</resources>
//第三步在Activity里使用:
public class MainActivity extends AppCompatActivity {
//x轴坐标对应的数据
private List<String> xValue = new ArrayList<>();
//y轴坐标对应的数据
private List<Integer> yValue = new ArrayList<>();
//折线对应的数据
private Map<String, Integer> value = new HashMap<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取当前月份
Time t = new Time();
t.setToNow();
int year = t.year;
int month = t.month + 1;
//获取当前月的天数
int currentMonthDay = getCurrentMonthDay();
Log.e("TAG2", currentMonthDay + "");
//数据集合
List<Integer> mList = new ArrayList<>();
mList.add(1);
mList.add(10);
mList.add(10);
mList.add(9);
mList.add(7);
mList.add(35);
mList.add(28);
// (int) (Math.random() * 181 + 60)
for (int i = 0; i < 7; i++) {
xValue.add(month + "." + (i + 1));
value.put(month + "." + (i + 1), mList.get(i));
}
//集合最大值
Integer max = Collections.max(mList);
//集合最小值
Integer min = Collections.min(mList);
yValue.add(0);//y轴坐标最小数
yValue.add(max / 2);//y轴坐标最大数/2
yValue.add(max);//y轴坐标最大数
ChartView chartView = (ChartView) findViewById(R.id.chartview);
//折线图设置数据
chartView.setValue(value, xValue, yValue);
}
//获取当月的 天数
public int getCurrentMonthDay() {
Calendar a = Calendar.getInstance();
a.set(Calendar.DATE, 1);
a.roll(Calendar.DATE, -1);
int maxDate = a.get(Calendar.DATE);
return maxDate;
}
}
//我的布局:
<com.example.hasee.a1029ceshi.ChartView
android:id="@+id/chartview"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_centerInParent="true"
app:bgcolor="#ffffff"
app:interval="40dp"
app:isScroll="false"
app:linecolor="#02bbb7"
app:xylinecolor="#e2e2e2"
app:xylinewidth="2dp"
app:xytextcolor="#7e7e7e"
app:xytextsize="15sp"
/>
//第二种 带动画的折线图效果:
//首先第一步:写个LineChartView继承View
public class LineChartView extends View {
private float xOrigin; // x轴原点坐标
private float yOrigin; // y轴原点坐标
private int mMargin10; // 10dp的间距
private int mWidth; // 控件宽度
private int mHeight; // 控件高度
private int max = 100, min = 0; // 最大值、最小值
private float yInterval; // y轴坐标间隔
private float xInterval; // x轴坐标间隔
private String startTime = "2017-03-15";
private String endTime = "2017-03-24";
private int textWidth; // 日期宽度
private List<ItemBean> mItems;// 折线数据
private int[] shadeColors; // 渐变阴影颜色
private int mAxesColor; // 坐标轴颜色
private float mAxesWidth; // 坐标轴宽度
private int mTextColor; // 字体颜色
private float mTextSize; // 字体大小
private int mLineColor; // 折线颜色
private int mBgColor; // 背景色
private Paint mPaintText; // 画文字的画笔
private Paint mPaintAxes; // 坐标轴画笔
private Paint mPaintLine; // 折线画笔
private Path mPath; // 折线路径
private Paint mPaintShader; // 渐变色画笔
private Path mPathShader; // 渐变色路径
private float mProgress; // 动画进度
private Paint mPaintXPoint;//坐标轴点画笔
private Paint mPaintPath;//折线折点画笔
public int[] getShadeColors() {
return shadeColors;
}
public void setShadeColors(int[] shadeColors) {
this.shadeColors = shadeColors;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public int getMin() {
return min;
}
public void setMin(int min) {
this.min = min;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
public List<ItemBean> getItems() {
return mItems;
}
public void setItems(List<ItemBean> items) {
mItems = items;
int max=0;
int min=0;
for (ItemBean mItem : mItems) {
int anInt=mItem.getValue();
if(max<anInt){
max=anInt;
}
if(min>anInt){
min=anInt;
}
}
setMax(max%10==0?max:(max/10+1)*10);
setMin(min<=10?0:(max/10-1)*10);
setStartTime(mItems.get(mItems.size()-1).getDate());
setEndTime(mItems.get(0).getDate());
}
public int getAxesColor() {
return mAxesColor;
}
public void setAxesColor(int axesColor) {
mAxesColor = axesColor;
}
public float getAxesWidth() {
return mAxesWidth;
}
public void setAxesWidth(float axesWidth) {
mAxesWidth = axesWidth;
}
public int getTextColor() {
return mTextColor;
}
public void setTextColor(int textColor) {
mTextColor = textColor;
}
public float getTextSize() {
return mTextSize;
}
public void setTextSize(float textSize) {
mTextSize = textSize;
}
public int getLineColor() {
return mLineColor;
}
public void setLineColor(int lineColor) {
mLineColor = lineColor;
}
public int getBgColor() {
return mBgColor;
}
public void setBgColor(int bgColor) {
mBgColor = bgColor;
}
public LineChartView(Context context) {
super(context);
init();
}
public LineChartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
init();
}
public LineChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LineChartView);
mAxesColor = typedArray.getColor(R.styleable.LineChartView_axesColor, Color.parseColor("#CCCCCC"));
mAxesWidth = typedArray.getDimension(R.styleable.LineChartView_axesWidth, 2);
mTextColor = typedArray.getColor(R.styleable.LineChartView_textColor, Color.parseColor("#ABABAB"));
mTextSize = typedArray.getDimension(R.styleable.LineChartView_textSize, 32);
mLineColor = typedArray.getColor(R.styleable.LineChartView_lineColor, Color.parseColor("#1fbaf3"));
mBgColor = typedArray.getColor(R.styleable.LineChartView_bgColor, Color.WHITE);
typedArray.recycle();
// 初始化渐变色
shadeColors = new int[]{
Color.argb(100, 31, 186, 243), Color.argb(20, 31, 186, 243),
Color.argb(0, 31, 186, 243)};
// 初始化折线数据
mItems = new ArrayList<>();
mMargin10 = ScreenUtils.dp2px(context, 10);
init();
}
private void init() {
// 初始化坐标轴画笔
mPaintAxes = new Paint();
mPaintAxes.setColor(mAxesColor);
mPaintAxes.setStrokeWidth(mAxesWidth);
// 初始化文字画笔
mPaintText = new Paint();
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setAntiAlias(true); //抗锯齿
mPaintText.setTextSize(mTextSize);
mPaintText.setColor(mTextColor);
mPaintText.setTextAlign(Paint.Align.CENTER);//对齐方式
// 初始化折线的画笔
mPaintLine = new Paint();
mPaintLine.setStyle(Paint.Style.STROKE);
mPaintLine.setAntiAlias(true);
mPaintLine.setStrokeWidth(mAxesWidth*2);
mPaintLine.setColor(mLineColor);
// 初始化折线路径
mPath = new Path();
mPathShader = new Path();
//初始化x轴坐标点画笔
mPaintXPoint = new Paint();
mPaintXPoint.setStyle(Paint.Style.FILL);
mPaintXPoint.setAntiAlias(true); //抗锯齿
mPaintXPoint.setColor(mAxesColor);
mPaintXPoint.setStrokeWidth(mAxesWidth);
//初始化线上折点画笔
mPaintPath=new Paint();
mPaintPath.setStyle(Paint.Style.FILL);
mPaintPath.setAntiAlias(true); //抗锯齿
mPaintPath.setColor(mLineColor);
mPaintPath.setStrokeWidth(mAxesWidth);
// 阴影画笔
mPaintShader = new Paint();
mPaintShader.setAntiAlias(true);
mPaintShader.setStrokeWidth(2f);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mWidth = getWidth();
mHeight = getHeight();
textWidth = (int) mPaintText.measureText(""+max);
// 初始化原点坐标
xOrigin = mMargin10;
yOrigin = (mHeight - mTextSize - mMargin10);
// 设置背景色
setBackgroundColor(mBgColor);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 画坐标轴
drawAxes(canvas);
// 画文字
drawText(canvas);
// X,Y轴间距
yInterval = (yOrigin - mMargin10)/(max - min) ;
xInterval = (mWidth - mMargin10*2) / (mItems.size() - 1);
// 画折线
drawLine(canvas);
// 设置动画
setAnim(canvas);
}
/*@Override
public boolean onTouchEvent(MotionEvent event) {
this.getParent().requestDisallowInterceptTouchEvent(true);
if(event.getAction()==MotionEvent.ACTION_UP){
for (ItemBean mItem : mItems) {
if(event.getX()<mItem.getiX()+xInterval/4&&event.getX()>mItem.getiX()-xInterval/4){
Log.d("LineChartView", "mItem.getValue():" + mItem.getValue());
}
}
}
return true;
}*/
private void setAnim(Canvas canvas) {
//测量path
PathMeasure measure = new PathMeasure(mPath, false);
float pathLength = measure.getLength();
/*冲刺路径效应
float数组,必须是偶数长度,且>=2,指定了多少长度的实线之后再画多少长度的空白。
phase 是跳过这么长的长度,从此长度之后还是绘制*/
PathEffect effect = new DashPathEffect(new float[]{pathLength, pathLength}, pathLength - pathLength * mProgress);
mPaintLine.setPathEffect(effect);
canvas.drawPath(mPath, mPaintLine);
if(mProgress==1.0){
for (ItemBean mItem : mItems) {
canvas.drawCircle(mItem.getiX(),mItem.getiY(),mAxesWidth*3,mPaintPath);
}
}
}
private void drawLine(Canvas canvas) {
// 画坐标点
ItemBean itemBean=null;
for (int i = 0; i < mItems.size(); i++) {
float x = i * xInterval + xOrigin + mAxesWidth;
float y=yOrigin - (mItems.get(i).getValue() - min) * yInterval;
itemBean=mItems.get(i);
if (i == 0) {
mPath.moveTo(x, y);
mPathShader.moveTo(x, y);
canvas.drawText(itemBean.getDate(), x+mMargin10, mHeight - mMargin10, mPaintText);
} else if (i == mItems.size() - 1) {
mPath.lineTo(x, y);
mPathShader.lineTo(x, y);
canvas.drawText(itemBean.getDate(), x-mMargin10, mHeight - mMargin10, mPaintText);
mPathShader.lineTo(x, yOrigin);
mPathShader.lineTo(xOrigin, yOrigin);
mPathShader.close();
}else{
mPath.lineTo(x, y);
mPathShader.lineTo(x, y);
if(mItems.size()>24){
if(i%6==0){
canvas.drawText(itemBean.getDate(), x-mAxesWidth, mHeight - mMargin10, mPaintText);
}
}else {
canvas.drawText(itemBean.getDate(), x-mAxesWidth, mHeight - mMargin10, mPaintText);
}
}
itemBean.setiX(x);
itemBean.setiY(y);
canvas.drawCircle(x-mAxesWidth,yOrigin,mAxesWidth*2,mPaintXPoint);
}
// 渐变阴影 创建着色器
/*线性渐变
x0表示渲染起始位置的x坐标,
y0表示渲染起始位置的y坐标,
x1表示渲染结束位置的x坐标,
y1表示渲染结束位置的y坐标,
colors表示渲染的颜色,它是一个颜色数组,数组长度必须大于等于2,
positions表示colors数组中几个颜色的相对位置,是一个float类型的数组,该数组的长度必须与colors数组的长度相同。
如果这个参数使用null也可以,这时系统会按照梯度线来均匀分配colors数组中的颜色,
tileMode表示平铺方式
1:TileMode.CLAMP 表示重复最后一种颜色直到该View结束的地方
2:TileMode.REPEAT 表示着色器在水平或者垂直方向上对控件进行重复着色,类似于windows系统桌面背景中的“平铺”
3:TileMode.MIRROR 在水平方向或者垂直方向上以镜像的方式进行渲染,这种渲染方式的一个特征就是具有翻转的效果
3种模式的区别在于起始位置到结束位置是否包含全部面积,包含全部则效果一样
*/
Shader mShader = new LinearGradient(0, 0, 0, getHeight(), shadeColors, null, Shader.TileMode.CLAMP);
mPaintShader.setShader(mShader);
// 绘制渐变阴影
canvas.drawPath(mPathShader, mPaintShader);
}
private void drawText(Canvas canvas) {
// 绘制最大值
canvas.drawText(max+"", xOrigin + textWidth/2, 2 * mMargin10, mPaintText);
canvas.drawCircle(xOrigin,mMargin10,mAxesWidth*2,mPaintXPoint);
// 绘制最小值
canvas.drawText(min+"", xOrigin + textWidth/2, yOrigin - 6, mPaintText);
canvas.drawCircle(xOrigin,yOrigin,mAxesWidth*2,mPaintXPoint);
// 绘制中间值
canvas.drawText((max+min)/2+"", xOrigin + textWidth/2, (yOrigin + mMargin10) / 2, mPaintText);
canvas.drawCircle(xOrigin,(yOrigin + mMargin10) / 2,mAxesWidth*2,mPaintXPoint);
/* // 绘制开始日期
canvas.drawText(startTime, xOrigin, mHeight - mMargin10, mPaintText);
// 绘制结束日期
canvas.drawText(endTime, mWidth - textWidth - mMargin10, mHeight - mMargin10, mPaintText);*/
}
// 画坐标轴
private void drawAxes(Canvas canvas) {
// 绘制X轴
canvas.drawLine(xOrigin, yOrigin, mWidth - mMargin10, yOrigin, mPaintAxes);
// 绘制X中轴线
canvas.drawLine(xOrigin, (yOrigin+ mMargin10) / 2, mWidth - mMargin10, (yOrigin+ mMargin10) / 2, mPaintAxes);
// 绘制X上边线
//canvas.drawLine(xOrigin, mMargin10, mWidth - mMargin10, mMargin10, mPaintAxes);
// 绘制画Y轴
canvas.drawLine(xOrigin, yOrigin, xOrigin, mMargin10, mPaintAxes);
// 绘制Y右边线
//canvas.drawLine(mWidth - mMargin10, mMargin10, mWidth - mMargin10, yOrigin, mPaintAxes);
}
// 计算动画进度
public void setPercentage(float percentage) {
if (percentage < 0.0f || percentage > 1.0f) {
throw new IllegalArgumentException("setPercentage not between 0.0f and 1.0f");
}
mProgress = percentage;
invalidate();
}
/**
* @param lineChartView
* @param duration 动画持续时间
*/
public void startAnim(LineChartView lineChartView, long duration) {
//ObjectAnimator通过反射机制,找到了ImageView中的setTranslationX()这个方法,
// 然后每个十几ms就调用这个方法,并把我们的变化的值传到里面去,从而实现动画效果。
//当我们需要用ObjectAnimator实现一个控件的动画效果时,
// 我们首先需要做的就是在这个控件中找到对应的setXXX()驼峰式写法的方法,
// 只有控件拥有相应的setXXX()方法, 我们传入的propertyName参数才起到作用
ObjectAnimator anim = ObjectAnimator.ofFloat(lineChartView, "percentage", 0.0f, 1.0f);
anim.setDuration(duration);
anim.setInterpolator(new LinearInterpolator());
anim.start();
}
public void clear(){
mPath.reset();
mPathShader.reset();
}
// 折线数据的实体类
public static class ItemBean {
private int value;
private float iX;
private float iY;
private String date;
public ItemBean(){}
public ItemBean(int value,String date) {
super();
this.value = value;
this.date = date;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public float getiX() {
return iX;
}
public void setiX(float iX) {
this.iX = iX;
}
public float getiY() {
return iY;
}
public void setiY(float iY) {
this.iY = iY;
}
@Override
public String toString() {
return "ItemBean{" +
"value=" + value +
", iX=" + iX +
", iY=" + iY +
", date='" + date + '\'' +
'}';
}
}
}
//第二步 写个ScreenUtils类,用来初始化数据时使用:
public class ScreenUtils {
private static int screenW;
private static int screenH;
private static float screenDensity;
public static int getScreenW(Context context){
if (screenW == 0){
initScreen(context);
}
return screenW;
}
public static int getScreenH(Context context){
if (screenH == 0){
initScreen(context);
}
return screenH;
}
public static float getScreenDensity(Context context){
if (screenDensity == 0){
initScreen(context);
}
return screenDensity;
}
private static void initScreen(Context context){
DisplayMetrics metric = context.getResources().getDisplayMetrics();
screenW = metric.widthPixels;
screenH = metric.heightPixels;
screenDensity = metric.density;
}
public static int dp2px(Context context, float dpValue) {
return (int) (dpValue * getScreenDensity(context) + 0.5f);
}
public static int px2dp(Context context, float pxValue) {
return (int) (pxValue / getScreenDensity(context) + 0.5f);
}
}
//第三步 在values下写个attrs.xml文件来设置风格颜色等:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LineChartView">
<attr name="axesColor" format="color"/> <!--坐标轴颜色-->
<attr name="axesWidth" format="dimension"/><!--坐标轴宽度-->
<attr name="textColor" format="color"/> <!--x轴字体颜色-->
<attr name="textSize" format="dimension"/> <!--字体大小-->
<attr name="lineColor" format="color"/> <!--折线颜色-->
<attr name="bgColor" format="color"/> <!--背景色-->
</declare-styleable>
</resources>
//第四步 在Activity里使用:
private void initData() {
record_header_linecharview = (LineChartView) findViewById(R.id.record_header_linecharview);
//数据集合
List<Integer> mList = new ArrayList<>();
for (int i = 0; i < 7; i++) {
mList.add(i);
}
//日期数据集合
ArrayList<String> mStringList = new ArrayList<>();
mStringList.add("周一");
mStringList.add("周二");
mStringList.add("周三");
mStringList.add("周四");
mStringList.add("周五");
mStringList.add("周六");
mStringList.add("周日");
List<LineChartView.ItemBean> mItems = new ArrayList<>();
for (int i = 0; i < mList.size(); i++) {
mItems.add(new LineChartView.ItemBean(mList.get(i),mStringList.get(i)));
}
// 设置折线数据
record_header_linecharview.setItems(mItems);
// // 开启动画
// record_header_linecharview.startAnim(record_header_linecharview,2000);
Button mBut = (Button) findViewById(R.id.mBut);
mBut.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 开启动画
record_header_linecharview.clear();
record_header_linecharview.startAnim(record_header_linecharview,2000);
}
});
}
//我的布局:
<com.example.hasee.a1024ceshi.LineChartView
android:id="@+id/record_header_linecharview"
android:layout_width="match_parent"
android:layout_height="200dp"
app:textColor="#ABABAB"
app:textSize="12dp"/>
<Button
android:id="@+id/mBut"
android:text="开始动画"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
//———————————————————————完—————————————————————————————–
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/118306.html