<pre name="code" class="java">import java.text.SimpleDateFormat;
/**
* LastTNT
*
* @date 2014-5-6 上午10:28:53
* @version 1.0 自定义折线图VIEW
*/
public class LineChartView extends View {
private float mov_x; // 移动X坐标
private int mov_y; // 移动Y坐标
private int diff_Y; // Y轴高度
private Paint paint;// 声明画笔
private int color_xy = Color.rgb(77, 189, 235);// 设置标线颜色
private int line_color_y = Color.rgb(18, 197, 255);// 标注线的颜色
private int zhexian_color = Color.rgb(21, 139, 229);// 折线的颜色
private int Guides_line_color = Color.rgb(229, 21, 60);// 参考线的颜色
private int text_color = Color.rgb(254, 102, 1);// 折线字体颜色
private int width_xy = 4;// 设置标线宽度
private int line_width_y = 2;// 标注线的宽度
private int zhexian_width = 4;// 折线的宽度
float init_x = 0;// 初始按下的X坐标
int init_left = 0;// 左边距
private int MAX_X = 2000;// 初始化值,确定好柱子的数量、宽度和间隙后就会改变
private int MAX_X_Extend = 200;// 向右侧继续延伸100
private Double MAX_Y = 0.0;// Y轴最大值
private int lineNum = 0; // 标线份数
private int lineInterval_x = 100;// 标线间隔
private Double lineInterval_y = 0.0;// 标注线间隔
private int width = 0;// 控件宽度
private int height = 0;// 控件高度
private Bitmap bmp;// 标记点的图
private Double Guides_line = 0.0;// 参考线
private boolean isGuides = false;
private float textSize = 28;// 折线图字体大小
private float CalloutSize = 24;// 标注字体大小
private PaintFlagsDrawFilter pfd;
private Matrix matrix = new Matrix();
private int paddingBottom = 50;
private int paddingTop = 10;
private Double[] nums = null;
private String[] dataStringValue = null;
private String[] Level = null;
public static final int MARK_NUM_24 = 24;// 24小时
public static final int MARK_NUM_48 = 48;// 48小时
public static final int MARK_NUM_30 = 30;// 30天
private String itcode;
/**
* @param context
* 上下文
* @param attrs
* 暂时不用
* @param padding
* 两条标注线之间的间距
* @param numsY
* 一组Y轴的数值,如:{1,5,4,8,9}
* @param markNum
* 折线图类型,目前有MARK_NUM_24:24小时;MARK_NUM_48:48小时;MARK_NUM_30:30天
* @param isGuidesline
* 是否需要参考线
* @param GuideslineNum
* 参考线数值
* @param startDate
* 下标开始时间
* @param itcode
* 污染物的ID或者是aqi(如:air_aqi空气的aqi)
* @param level
* 等级标示
*/
public LineChartView(Context context, AttributeSet attrs, int padding,
Double[] numsY, int markNum, boolean isGuidesline,
Double GuideslineNum, String[] startDate, String itcode,
String[] level) {
super(context, attrs);
paint = new Paint();
pfd = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG);
paint.setAntiAlias(true);
matrix.postScale(1f, 1f);
this.itcode = itcode;
this.lineInterval_x = padding;
this.lineNum = markNum;
this.isGuides = isGuidesline;
this.Guides_line = GuideslineNum;
this.Level = level;
MAX_X = lineInterval_x * lineNum;
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.point);
nums = numsY;
dataStringValue = creatDate(startDate, markNum);
MAX_Y = getMax(numsY);
init(context);
}
private Double getMax(Double[] num) {
Double temp = num[0];
for (int i = 0; i < num.length; i++) {
if (num[i] > temp) {
temp = num[i];
}
}
return temp;
}
// --------------------------------惯性滑动实现-------------------------------------//
Scroller mScroller;
int mTouchSlop;
int mMinimumVelocity;
int mMaximumVelocity;
VelocityTracker mVelocityTracker;
// 初始化最小滑动速度,最大滑动速度等等参数
private void init(Context context) {
mScroller = new Scroller(getContext());
setFocusable(true);
// setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();// 获得能够进行手势滑动的距离
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();// 获得允许执行一个fling手势动作的最小速度值
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();// 获得允许执行一个fling手势动作的最大速度值
}
/**
* 设置X轴的最小移动距离为0,最大移动距离为MAX_X+paddingBottom+MAX_X_Extend-width
*
* @param velocityX
*/
public void fling(int velocityX) {
mScroller.fling(getScrollX(), getScrollY(), velocityX, 0, 0, MAX_X
+ paddingBottom + MAX_X_Extend - width, 0, 0);
awakenScrollBars(mScroller.getDuration());
invalidate();
}
public void computeScroll() {
// 动画是否停止
if (mScroller.computeScrollOffset()) {
int scrollX = getScrollX();
int scrollY = getScrollY();
int oldX = scrollX;
int oldY = scrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
scrollX = x;
scrollY = y;
scrollX = scrollX + 5;
scrollTo(scrollX, scrollY);
postInvalidate();
}
}
private void obtainVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
private void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
// --------------------------------惯性滑动实现-------------------------------------//
/**
* 返回24小时时间
*
* @param startDate
* @param markNum
* @return
*/
public String[] creatDate(String[] startDate, int markNum) {
String[] dateValue = new String[startDate.length];
try {
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH");
SimpleDateFormat dateformatday = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat format = new SimpleDateFormat("dd日HH时");
SimpleDateFormat formatday = new SimpleDateFormat("MM月dd日");
Calendar cal = Calendar.getInstance();
switch (markNum) {
case MARK_NUM_24:
// for (int i = 0; i < markNum; i++) {
// cal.setTime(startDate);
// cal.add(Calendar.HOUR, -(markNum-i-1));
// dateValue[i] = format.format(cal.getTime());
// }
break;
case MARK_NUM_48:
for (int i = 0; i < startDate.length; i++) {
if (startDate[i].equals("-")) {
dateValue[i] = startDate[i];
} else {
dateValue[i] = format.format(dateformat
.parse(startDate[i]));
}
}
break;
case MARK_NUM_30:
for (int i = 0; i < startDate.length; i++) {
if (startDate[i].equals("-")) {
dateValue[i] = startDate[i];
} else {
dateValue[i] = formatday.format(dateformatday
.parse(startDate[i]));
}
}
break;
}
return dateValue;
} catch (Exception e) {
return dateValue;
}
}
@Override
protected void onDraw(Canvas canvas) {
if (init_left == 0) {
init_left = this.getLeft();
}
if (width == 0) {
width = this.getRight() - this.getLeft();
height = this.getBottom() - this.getTop();
if (MAX_Y != 0.0) {
lineInterval_y = (height * 2 / 3) / MAX_Y;
} else {
lineInterval_y = 0.0;
}
}
diff_Y = this.getBottom() - this.getTop();
// 设置画布颜色 也就是背景颜色
canvas.drawColor(Color.TRANSPARENT);
// 绘制X坐标轴
paint.setColor(color_xy);
paint.setStrokeWidth(width_xy);
canvas.drawLine(paddingBottom, diff_Y - paddingBottom, MAX_X
+ paddingBottom + MAX_X_Extend, diff_Y - paddingBottom, paint);
// 绘制Y坐标轴
paint.setColor(color_xy);
paint.setStrokeWidth(width_xy);
canvas.drawLine(paddingBottom, diff_Y - paddingBottom, paddingBottom,
this.getTop() + paddingTop, paint);
// 绘制标注线
for (int i = 1; i <= lineNum; i++) {
paint.setColor(line_color_y);
paint.setStrokeWidth(line_width_y);
canvas.drawLine(paddingBottom + lineInterval_x * i, diff_Y
- paddingBottom, paddingBottom + lineInterval_x * i,
this.getTop() + paddingTop, paint);
}
for (int i = 0; i < dataStringValue.length; i++) {
// 绘制标注线下的字体
paint.setColor(Color.BLACK);
paint.setTextSize(CalloutSize);
paint.setTextScaleX(1.0f);// 设置文本缩放
paint.setTypeface(Typeface.SERIF);
canvas.drawText(dataStringValue[i], paddingBottom + lineInterval_x
* (i + 1) - 40, diff_Y - paddingBottom + 20, paint);
}
if (isGuides) {
// 绘制参考线
paint.setColor(Guides_line_color);
paint.setStrokeWidth(width_xy);
if (lineInterval_y * Guides_line > height) {
canvas.drawLine(paddingBottom, this.getTop(), MAX_X
+ paddingBottom + MAX_X_Extend, this.getTop(), paint);
} else {
canvas.drawLine(
paddingBottom,
diff_Y - paddingBottom
- Float.valueOf(lineInterval_y.toString())
* Float.valueOf(Guides_line.toString()),
MAX_X + paddingBottom + MAX_X_Extend,
diff_Y - paddingBottom
- Float.valueOf(lineInterval_y.toString())
* Float.valueOf(Guides_line.toString()), paint);
}
// 绘制参考线标注
paint.setColor(text_color);
paint.setTextSize(textSize);
paint.setTextScaleX(1.0f);// 设置文本缩放
paint.setTypeface(Typeface.SERIF);
if (lineInterval_y * Guides_line > height) {
canvas.drawText(Float.valueOf(Guides_line.toString()) + "",
paddingBottom - 10, this.getTop() + 25, paint);
} else {
canvas.drawText(
Float.valueOf(Guides_line.toString()) + "",
paddingBottom - 10,
diff_Y - paddingBottom
- Float.valueOf(lineInterval_y.toString())
* Float.valueOf(Guides_line.toString()) + 25,
paint);
}
}
// 绘制折线
for (int i = 1; i < nums.length; i++) {
Double temp = nums[i - 1];
Double temp1 = nums[i];
paint.setColor(zhexian_color);
paint.setStrokeWidth(zhexian_width);
canvas.drawLine(
paddingBottom + lineInterval_x * i,
(diff_Y - paddingBottom)
- (Float.valueOf(lineInterval_y.toString()) * Float
.valueOf(temp.toString())),
paddingBottom + lineInterval_x * (i + 1),
(diff_Y - paddingBottom)
- (Float.valueOf(lineInterval_y.toString()) * Float
.valueOf(temp1.toString())), paint);
}
// 绘制折线点
for (int i = 1; i <= nums.length; i++) {
Double temp = nums[i - 1];
// 绘制一个圆形
canvas.drawBitmap(
bmp,
paddingBottom + lineInterval_x * i - 8,
(diff_Y - paddingBottom)
- (Float.valueOf(lineInterval_y.toString()) * Float
.valueOf(temp.toString())) - 5, paint);
int color = Color.rgb(0, 228, 0);
if (Level != null) {
color = UtilTool.JudgeSurface(Level[i - 1]);// 根据等级判断
} else {
color = UtilTool.JudgeWRW(itcode, temp);// 根据污染物ID和值判断级别并返回相应的颜色
}
paint.setColor(color);
paint.setTextSize(textSize);
paint.setTextScaleX(1.0f);// 设置文本缩放
// 设置字体样式
// Typeface.DEFAULT:默认字体;Typeface.DEFAULT_BOLD:加粗字体;Typeface.MONOSPACE:monospace;Typeface.SANS_SERIF:sans;Typeface.SERIF:serif
paint.setTypeface(Typeface.SERIF);
String value = null;
if (itcode.equals("air_aqi")) {
int a = Integer.parseInt(new java.text.DecimalFormat("0")
.format(temp));
value = a + "";
} else {
value = temp.toString();
}
canvas.drawText(
value,
paddingBottom + lineInterval_x * i - 18,
(diff_Y - paddingBottom)
- (Float.valueOf(lineInterval_y.toString()) * Float
.valueOf(temp.toString())) - 10, paint);
}
}
// --------------------------------惯性滑动实现-------------------------------------//
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN
&& event.getEdgeFlags() != 0) {
return false;
}
mov_x = (int) event.getX();
obtainVelocityTracker(event);
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity();// 获得X轴上的速度
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {// 看看是否大于最小可滑动速度
fling(-initialVelocity);
}else if(mScroller.getCurrX()>=(MAX_X + paddingBottom + MAX_X_Extend - width)){
fling(-initialVelocity);
}else if(mScroller.getCurrX()<=0){
fling(-initialVelocity);
}
releaseVelocityTracker();
break;
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
init_x = event.getX();
break;
case MotionEvent.ACTION_MOVE:
final int diff_left = (int) (init_x - mov_x);
init_x = mov_x;
scrollBy(diff_left, 0);
break;
}
return true;
}
// --------------------------------惯性滑动实现-------------------------------------//
}
这里引用一下网上找到的别人的语言:
首先我们通过VelocityTracker、ViewConfiguration类得到一些惯性滑动所必须的变量,比如手势离开屏幕时的初始速度,允许进行手势操作的最小距离以及允许手势操作的速度边界值;
第二,创建Scroller的对象,使用它的fling方法供我们控制界面滑动使用;
第三,重写onTouchEvent方法,当我们用手指在屏幕上来回滑动时此时执行的是scrollBy方法来刷新界面,当手指离开屏幕,此时就要开始执行ACTION_UP后面的操作了;
通过对手指离开屏幕时的速度进行判断是否能够进行惯性滑动操作,
如果能够执行那么就使用Scroller类的fling方法启动滑动动画,
这时需要调用一下invalidate()方法来间接的调用computeScroll方法,
在computeScroll方法中对Scroller的动画是否执行完成做了判断,
如果动画没有完成(mScroller.computeScrollOffset() == true)那么就使用scrollTo方法对mScrollX、mScrollY的值进行重新计算刷新界面,
调用postInvalidate()方法重新绘制界面,
postInvalidate()方法会调用invalidate()方法,
invalidate()方法又会调用computeScroll方法,
就这样周而复始的相互调用,直到mScroller.computeScrollOffset() 返回false才会停止界面的重绘动作
下面就根据实际情况分析一下吧
首先是初始化
Scroller mScroller;
int mTouchSlop;
int mMinimumVelocity;
int mMaximumVelocity;
VelocityTracker mVelocityTracker;
// 初始化最小滑动速度,最大滑动速度等等参数
private void init(Context context) {
mScroller = new Scroller(getContext());
setFocusable(true);
// setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();// 获得能够进行手势滑动的距离
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();// 获得允许执行一个fling手势动作的最小速度值
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();// 获得允许执行一个fling手势动作的最大速度值
}
然后 申明一个用来处理滑动操作的方法fling(int velocityX)
/**
* 设置X轴的最小移动距离为0,最大移动距离为MAX_X+paddingBottom+MAX_X_Extend-width
* @param velocityX
*/
public void fling(int velocityX) {
mScroller.fling(getScrollX(), getScrollY(), velocityX, 0, 0, MAX_X
+ paddingBottom + MAX_X_Extend - width, 0, 0);
awakenScrollBars(mScroller.getDuration());
invalidate();
}
awakenScrollBars(int startDelay)方法根据我对注释的理解就是在这里给出动画开始的延时,当参数startDelay为0时动画将立刻开始,其实就是一个延迟的作用
再次是对VelocityTracker的初始化以及资源释放的方法
private void obtainVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
private void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
然后重写 onTouchEvent(MotionEvent event)方法
在onTouchEvent方法中,当手势执行到ACTION_UP时获得当时手势的速度值然后判断这个速度值是否大于可滑动的最小速度,如果符合条件那么就执行fling(int velocityX)方法,通过fling方法中的日志发现,在执行了invalidate()方法之后,程序便会执行computeScroll()方法,在computeScroll()方法中执行scrollTo方法主要是因为mScrollX、mScrollY这两个变量的修饰符为portected,无法在扩展类里面无法对这两个变量直接进行操作,那么就需要使用scrollTo方法对这两个变量进行操作,以刷新当前的UI控件,下面附上computeScroll()方法的代码
public void computeScroll() {
//动画是否停止
if (mScroller.computeScrollOffset()) {
int scrollX = getScrollX();
int scrollY = getScrollY();
int oldX = scrollX;
int oldY = scrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
scrollX = x;
scrollY = y;
scrollX = scrollX + 10;
scrollTo(scrollX, scrollY);
postInvalidate();
}
}
我们可以看到,当传递进来的x、y的值与控件当前的mScrollX、mScrollY的值不相同时对界面进行重新计算,根据日志打印的情况来看似乎awakenScrollBars()返回的总是true, 这样的话每执行一次computeScroll()方法,就需要执行一次postInvalidate()方法来刷新界面,而postInvalidate()方法会通过内部线程重新调用invalidate()已达到界面刷新的效果,产生手势离开屏幕之后的惯性滑动效果。
暂时就会这么多了,还希望大家多多交流~~