公司最近一个需求就是需要每年的每个月的消费概况的统计图 ,因为涉及到一些乱七八糟的需求 。先看图
其中要求本年未到的年份不一样的颜色,且数据不用画出来当然也没有数据。折线下面有个阴影。所以决定自己撸个图。看布局:
再看attr这个就不用解释了吧,不知道请到宏洋大神的博客里搜zidingyiview
然后看三部曲了,onMeasure,onLayout,onDraw
第一步当然是测量长宽了
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = widthSize * 1 / 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = heightSize * 1 / 2;
}
setMeasuredDimension(width, height);
}
然后onlayout
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (ischangeFirst) {
ischangeFirst =false;
mHeight = getHeight();
mSize = getWidth() /valueSize * 2;
mChartWidth = getWidth() / valueSize * 2;
textStartplace = getWidth() / valueSize * 2;
textWidth = getWidth() / valueSize * 2;
linearStartX = mSize / 2;
xOri = 0;
yOri = getHeight()/3*2;
minXInit = getWidth() - mSize * (valueSize - 1) - mSize / 2;//计算最小的长度
minZxInit = getWidth() - mSize * (valueSize);
maxXInit = linearStartX;
zhuStartX = 0;
maxZxInit = mSize;
initData();
}
super.onLayout(changed, left, top, right, bottom);
}
最后ondraw
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// mPaint.setColor(xyColor);
//渐变
gradient = new LinearGradient(0,0,getWidth(),mHeight/3*2,context.getResources().getColor(R.color.color_7c98ff),context.getResources().getColor(R.color.color_77b0ff),Shader.TileMode.REPEAT);
mbackgroundPaint.setShader(gradient);
canvas.drawRect(0, 0, getMeasuredWidth(), mHeight/3*2, mbackgroundPaint);
//画柱状图
drawRect(canvas);
//划地下月份
drawMonth(canvas);
//画折线图和圆点
drawLine(canvas);
Log.e("234","linearStartX===="+linearStartX);
}
标注已经写的很清楚了,还不知道的欢迎评论。
最后加上全部的代码
package com.py.ysl.view.myview;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.py.ysl.R;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author lizhijun 2018.8.1
* 自定义柱状图
*/
public class BarGraphView extends View{
private int bargrapColor;//柱状图的颜色
private int selectRightColor;
private int xyColor;//
private int textSize;//月份字体大小
private int textColor;//月份字体颜色
private Paint mPaint, mChartPaint,mbackgroundPaint;//横轴画笔、柱状图画笔、背景图画笔
private Paint lineapaint;//折线的画笔
private Paint dotPain;//圆点的画笔
private Paint zxbPaint;//折线下面的背景色
private Rect mBound;
private float mHeight, mChartWidth, mSize;//屏幕宽度高度、柱状图起始位置、柱状图宽度
private float textStartplace,textWidth;//月份起始位置、月份的间隔宽度
//x轴的原点坐标
private int xOri;
//y轴的原点坐标
private int yOri;
//第一个点对应的最大Y坐标
private float maxXInit;
//第一个点对应的最小X坐标
private float minXInit;
//第一个柱形的最大Y坐标
private float maxZxInit;
//第一个柱形对应的最小X坐标
private float minZxInit;
private LinearGradient gradient;//背景渐变色
private float linearStartX;//折线第一个点
private float zhuStartX;//柱形第一个位置
private Context context;
//是否在ACTION_UP时,根据速度进行自滑动,没有要求,建议关闭,过于占用GPU
private boolean isScroll = false;
//是否正在滑动
private boolean isScrolling = false;
//点击的点对应的X轴的第几个点,默认1
private int selectIndex = 1;
//数据源
//x轴坐标对应的数据
private List xValue = new ArrayList<>();
//y轴坐标对应的数据
private int yValue =0;
//折线对应的数据
private Map value = new HashMap<>();
private int posMonth = 12;//当前月份,今年若超过当前月份的则不画
private int posPlace = 0;//一开始的位移 用于显示到最新的月份
private boolean isMonthFirst = false;//用于位移时是否第一次改变数据
private boolean isRectFirst = false;
private boolean ischangeFirst = true;//防止onlayout两次改变数据
private int valueSize = 12;//正常分割为12份
//速度检测器
private VelocityTracker velocityTracker;
public BarGraphView(Context context){
this(context,null);
}
public BarGraphView(Context context,AttributeSet attrs){
this(context,attrs,0);
}
public BarGraphView(Context context,AttributeSet attrs,int defStyleAttr){
super(context,attrs,defStyleAttr);
this.context = context;
initView(context,attrs,defStyleAttr);
}
private void initView(Context context,AttributeSet attrs,int defStyleAttr){
TypedArray array =context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView,defStyleAttr,0);
int count = array.getIndexCount();
for (int i=0;i maxXInit) {
linearStartX = maxXInit;
zhuStartX = 0;
} else {
linearStartX = linearStartX + offX;
zhuStartX += offX;
}
invalidate();
break;
case MotionEvent.ACTION_UP:
this.getParent().requestDisallowInterceptTouchEvent(false);//当该view获得点击事件,就请求父控件不拦截事件
clickAction(event);
scrollAfterActionUp();
recycleVelocityTracker();
break;
}
return true;
}
/**
* 绘制显示Y值的浮动框
*
* @param canvas
* @param x
* @param y
* @param text
* @param pos 第几个
*/
private void drawFloatTextBox(Canvas canvas, float x, float y, String text,int pos) {
int dp6 = dpToPx(6);
int dp7 = dpToPx(7);
int dp35 = dpToPx(35);
int dp40 = dpToPx(40);
//p1
Path path = new Path();
lineapaint.setColor(Color.parseColor("#ffffff"));
lineapaint.setStrokeWidth(dpToPx(1));
lineapaint.setAntiAlias(true);
lineapaint.setStyle(Paint.Style.FILL);
float left = x-dp40;
float top = y-dp35;
float right =x+dp40;
float bottom =y-dp6;
if (pos==0){
left=x-mSize/3;
right=dp40*2+x-mSize/3;
}
if (pos==value.size()-1){
left=x+mSize/3-dp40*2;
right=x+mSize/3;
}
RectF oval3 = new RectF(left, top, right,bottom);// 设置个新的长方形
canvas.drawRoundRect(oval3, dpToPx(7), dpToPx(7), lineapaint);//第二个参数是x半径,第三个参数是y半径
path.moveTo(x,y);
path.lineTo(x-dp7,y-dp7);
path.lineTo(x+dp7,y-dp7);
path.close();
canvas.drawPath(path, lineapaint);
mPaint.setTextSize(textSize);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setColor(textColor);//要写在draw里面不然画不出来
mPaint.getTextBounds(text,0,text.length(),mBound);
if (pos==value.size()-1){
canvas.drawText(text, x-mSize/3, y - mBound.height()/3*2-dp7, mPaint);
}else if (pos==0){
canvas.drawText(text, x+mSize/3, y - mBound.height()/3*2-dp7, mPaint);
}else {
canvas.drawText(text, x , y - mBound.height()/3*2-dp7, mPaint);
}
}
/**
* 获取丈量文本的矩形
*
* @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;
}
/**
* 点击X轴坐标或者折线节点
*
* @param event
*/
private void clickAction(MotionEvent event) {
if (value==null||xValue==null||value.size()==0||xValue.size()==0)
return;
int dp8 = dpToPx(18);
float eventX = event.getX();
float eventY = event.getY();
for (int i = 0; i < xValue.size(); i++) {
//节点
float x = linearStartX + mSize * i;
float y = yOri - yOri * value.get(xValue.get(i)) / yValue;
if (eventX >= x - dp8 && eventX <= x + dp8 &&
eventY >= y - dp8 && eventY <= y + dp8 && selectIndex != i + 1) {//每个节点周围8dp都是可点击区域
if (i+1<=posMonth) {
selectIndex = i + 1;
invalidate();
}
return;
}
}
}
/**
* 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));
}
/**
* 获取速度跟踪器
*
* @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;
}
}
/**
* 获取速度
*
* @return
*/
private float getVelocity() {
if (velocityTracker != null) {
velocityTracker.computeCurrentVelocity(1000);
return velocityTracker.getXVelocity();
}
return 0;
}
/**
* 手指抬起后的滑动处理
*/
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 && linearStartX > minXInit) {//向左滑动
if (linearStartX - value <= minXInit)
linearStartX = minXInit;
else
linearStartX = linearStartX - value;
} else if (velocity > 0 && linearStartX < maxXInit) {//向右滑动
if (linearStartX + value >= maxXInit)
linearStartX = maxXInit;
else
linearStartX = linearStartX + 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();
}
/**
* 设置初始值
* @param value
* @param xValue
* @param yValue 纵坐标的最大值
*/
public void setValue(Map value, List xValue,int yValue) {
this.value = value;
this.xValue = xValue;
this.yValue = yValue;
invalidate();
}
/**
* 设置当前月份
* @param posMonth
*/
public void setCurrentMonth(int posMonth){
this.posMonth = posMonth;
selectIndex = posMonth;
getDisPlace(posMonth);
invalidate();
}
/**
* 设置一开始位移
* @param posPlace
*/
public void setCurrentDisplace(int posPlace){
this.posPlace = posPlace;
isRectFirst = true;
isMonthFirst = true;
}
private void getDisPlace(int posMonth){
if (posMonth<=6){
setCurrentDisplace(0);
}else {
setCurrentDisplace(posMonth-6);
}
}
}
最后就是在activity里面调用了
ListStrList = new ArrayList<>();
ListintList = new ArrayList<>();
//折线对应的数据
Map value = new HashMap<>();
for (int i = 0; i < 12; i++) {
StrList.add((i + 1) + "月");
}
value.put( "1月", 600);//60--240
value.put( "2月", 500);//60--240
value.put( "3月", 800);//60--240
value.put( "4月", 900);//60--240
value.put( "5月", 400);//60--240
value.put( "6月", 900);//60--240
value.put( "7月", 1100);//60--240
value.put( "8月", 1300);//60--240
value.put( "9月", 1800);//60--240
value.put( "10月", 1400);//60--240
value.put( "11月", 1600);//60--240
value.put( "12月", 1000);//60--240 value.put( "1月", 200);//60--240
bar_view.setValue(value,StrList,1800*3/2);
bar_view.setCurrentMonth(9);//当没有满一年的时候需要用到
就此一个可滑动的就完成了。最后感谢网上的一些大神,看了一些别人的例子才最后画出来的大神连接
https://blog.csdn.net/u014544193/article/details/54313257