繪製曲線圖2(完善平滑移動)

<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;
	}
	// --------------------------------慣性滑動實現-------------------------------------//
}


這裏引用一下網上找到的別人的語言:

首先我們通過VelocityTrackerViewConfiguration類得到一些慣性滑動所必須的變量,比如手勢離開屏幕時的初始速度,允許進行手勢操作的最小距離以及允許手勢操作的速度邊界值;
第二,創建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()已達到界面刷新的效果,產生手勢離開屏幕之後的慣性滑動效果。

暫時就會這麼多了,還希望大家多多交流~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章