<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; } // --------------------------------惯性滑动实现-------------------------------------// }
这里引用一下网上找到的别人的语言:
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(); }awakenScrollBars(int startDelay)方法根据我对注释的理解就是在这里给出动画开始的延时,当参数startDelay为0时动画将立刻开始,其实就是一个延迟的作用
private void obtainVelocityTracker(MotionEvent event) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } private void releaseVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } }
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()已达到界面刷新的效果,产生手势离开屏幕之后的惯性滑动效果。