Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義View實戰---圓盤溫度計

Android自定義View實戰---圓盤溫度計

編輯:關於Android編程

了解了基本的自定義view基礎後,現在我們就來實踐下自定義view,也是看到我華為手機上自帶的天氣預報軟件後,想著模仿做一個,於是,我自己嘗試了下,雖然不算太像,但是還算能看,期待後期的改進。

通過本文你可以用到以下技術:

1)view的測量

2)canvas繪圖技巧

3)接口回調

4)觸摸事件的處理

最終效果如下所示

\

可以通過輸入框自己設定最低、最高溫度和當前溫度(這是為我天氣預報軟件做的方法)

\

1.繪制流程

1)其中最主要的是中間的圓環的繪制,采用的是

canvas的drawArc(RectF oval,float startAngle, float sweepAngle, boolean useCenter, Paint paint)方法。

其中參數意義為

oval:圓外接的矩形
startAngle:開始角度
sweepAngle:掃描角度
useCenter:是否和圓心連線
paint:畫筆

角度大小,以此圖為標准

\

當然中間的圓環可能看起來是兩個圓之間的區域,開始我以為是要畫2個園,沒想到只要把畫筆設置一下就好了

 

circlePaint.setStrokeWidth(60.0f);
上面就是兩個圓與圓之間(圓環)的區域。

 

值得一提的是canvas的這個畫圓的方法設計的很強大,還可以畫橢圓的,就看你圓外接的矩形的寬高了。

 

2)而中間的那個刻度線,采用的是

 

Canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint)方法
其中參數的意義為
startX:線起始點的x軸位置
startY:線起始點的y軸位置
stopX:線結束點的x軸位置
stopY:線結束點的y軸位置
paint:畫線的畫筆

 

畫了一根刻度線後,采用旋轉的是
Canvas.rotate(float degrees, float px, float py)方法
其中參數意義為
degrees: 每次要旋轉的角度
px: 旋轉的圓心x軸坐標
py: 旋轉的圓心y軸坐標

 

3)圓盤中間的文字的采用的是
canvas的Canvas.drawText(String text, float x, float y, Paint paint)方法
參數意義這裡就不說了,應該猜的出的。


4)中間的指示針采用的是一個小實心圓,采用的是

drawCircle(float cx, float cy, float radius,Paint paint)方法

參數意義

cx: 中心點的x軸

cy: 中心點的y軸

radius: 半徑

paint: Paint畫筆對象


5)中間的顏色漸變,采用的是SweepGradient
SweepGradient(float cx, float cy, int[] colors, float[] positions)
參數意義
cx:漸變圓心的x軸坐標
cy:漸變圓心的y軸坐標
colors[]: 顏色數組
positions: 顏色分隔的位置數組,可以為null,系統自己分
 

2. 繪制的計算過程
好了,基本的繪制內容就是這麼多了,其他的就是計算過程了,這些都是在onDraw()內完成的。
先說圓環的繪制的計算過程吧。
0)在繪制之前,你首先得准備好筆
工欲善其事必先利其器。初始化畫筆的工作是在兩個參數的構造方法裡的,因為只需初始化一次,而onDraw()方法會隨著繪制過程很可能會不斷的調用,所以初始化的工作放在構造方法裡

 public MyCircleView(Context context, AttributeSet attrs) {
		super(context, attrs);
		screenWidth=MeasureUtil.getScreenWidth(context);
		screenHeight=MeasureUtil.getScreenHeight(context);
		Log.e("My----->", "2  "+screenWidth+"  "+screenHeight);
		initPaint();
	}

	private void initPaint() {
		linePaint = new Paint();
		linePaint.setColor(Color.CYAN);
		linePaint.setStyle(Style.FILL);
		linePaint.setAntiAlias(true);
		linePaint.setStrokeWidth(1.0f);

		textPaint = new Paint();
		textPaint.setColor(Color.BLACK);
		textPaint.setTextAlign(Paint.Align.CENTER);
		textPaint.setAntiAlias(true);
		textPaint.setTextSize(30);

		centerTextPaint = new Paint();
		centerTextPaint.setColor(Color.BLUE);
		centerTextPaint.setTextAlign(Paint.Align.CENTER);
		centerTextPaint.setAntiAlias(true);
		centerTextPaint.setTextSize(80);
		
		circlePaint = new Paint();
		circlePaint.setColor(Color.WHITE);
		circlePaint.setAntiAlias(true);
		circlePaint.setStyle(Paint.Style.STROKE);
		circlePaint.setStrokeCap(Cap.ROUND);//實現末端圓弧
		circlePaint.setStrokeWidth(60.0f);
		
		indicatorPaint=new Paint();
		indicatorPaint.setColor(0xFFF7F709);
		indicatorPaint.setAntiAlias(true);
		indicatorPaint.setStyle(Paint.Style.FILL);
		
		// 著色的共有270度,這裡設置了12個顏色均分360度s
		int[] colors = { 0xFFD52B2B, 0xFFf70101, 0xFFFFFFFF, 0xFFFFFFFF,
				0xFF6AE2FD, 0xFF8CD0E5, 0xFFA3CBCB, 0xFFD1C299, 0xFFE5BD7D,
				0xFFAA5555, 0xFFBB4444, 0xFFC43C3C };
		
		mCenter = screenWidth / 2;
		mRadius = screenWidth/ 2 - 100;
		// 漸變色
		mSweepGradient = new SweepGradient(mCenter, mCenter, colors, null);
		// 構建圓的外切矩形
		mRectF = new RectF(mCenter - mRadius, mCenter - mRadius, mCenter
				+ mRadius, mCenter + mRadius);
	}

 

1)圓環設計的寬度為wrap_content,故需要重寫onMeasure方法,告訴畫筆它的外接圓矩形的寬度

 

// 因為自定義的空間的高度設置的是wrap_content,所以我們必須要重寫onMeasure方法去測量高度,否則布局界面看不到
	// 其他控件(被覆蓋)
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(measureWidth(widthMeasureSpec),
				measureHeight(heightMeasureSpec));
	}
測量方法

 

 

/**
	 * 測量寬度
	 *
	 * @param widthMeasureSpec
	 * @return
	 */
	private int measureWidth(int widthMeasureSpec) {
		int mode = MeasureSpec.getMode(widthMeasureSpec);
		int size = MeasureSpec.getSize(widthMeasureSpec);
		// 默認寬高;
		defaultValue=screenWidth;
		
		switch (mode) {
		case MeasureSpec.AT_MOST:
			// 最大值模式 當控件的layout_Width或layout_height屬性指定為wrap_content時
			Log.e("cmos---->", "size " + size + " screenWidth " + screenWidth);
			size = Math.min(defaultValue, size);
			break;
		case MeasureSpec.EXACTLY:
			// 精確值模式
			// 當控件的android:layout_width=”100dp”或android:layout_height=”match_parent”時

			break;
		default:
			size = defaultValue;
			break;
		}
		defaultValue = size;
		return size;
	}

圓環的外接矩形

 

 

                mCenter = screenWidth / 2;//圓心坐標
		mRadius = screenWidth/ 2 - 100;//圓的半徑  留了100dp,是為了給繪制文字留空間
	
		// 構建圓的外切矩形
		mRectF = new RectF(mCenter - mRadius, mCenter - mRadius, mCenter+ mRadius, mCenter + mRadius);
	        canvas.drawArc(mRectF, 135, 270, false, circlePaint);
這樣,圓環就繪制計算完成。

 

 

2)刻度值的計算繪制

因為整個圓盤是270度,設置每3度畫一刻度線,故要繪制90根,這裡選擇的是以圓盤正上方的刻度線為基准刻度,將其繪制好後,然後進行旋轉繪制,這樣繪制過程就Ok了

 

                for (int i = 0; i < 120; i++) {
			if (i <= 45 || i >= 75) {//空白部分不用繪制刻度
				canvas.drawLine(mCenter, mCenter - mRadius - 30, mCenter,
						mCenter - mRadius + 30, linePaint);//30是因為設置的填充圓環的寬度為60的原因
			}
			canvas.rotate(3, mCenter, mCenter);
		}

 

 

3)圓盤刻度值計算繪制

這個就稍微復雜一點了,不過也還好,計算過程無非就是高中三角值公式的使用過程,在此之前,你先要了解角度的起始值和象限。

\

其中黃線的長度即是圓環的半徑

 

              
		// x代表文字的x軸距離圓心x軸的距離 因為剛好是45度,所以文字x軸值和y值相等
		int x = 0;
		// 三角形的斜邊
		int c = mRadius + 60 / 2 + 40;// 40代表這個字距離圓外邊的距離
		// 因為是每45度寫一次文字,故根據到圓心的位置,利用三角形的公式,可以算出文字的坐標值
		x = (int) Math.sqrt((c * c / 2));
		canvas.drawText("10", mCenter - x, mCenter + x, textPaint);
		canvas.drawText("15", mCenter - c, mCenter,
				textPaint);
		canvas.drawText("20", mCenter - x, mCenter - x,
				textPaint);
		canvas.drawText("25", mCenter, mCenter - c,
				textPaint);
		canvas.drawText( "30", mCenter + x, mCenter - x,
				textPaint);
		canvas.drawText( "35", mCenter + c, mCenter,
				textPaint);
		canvas.drawText( "40", mCenter + x, mCenter + x,
				textPaint);

可以看出,這裡刻度值我把它寫死了,當然,你也可以暴露一個方法,讓調用者去設置起始值和結束值。

 

 

4)中間的小圓點指示器

了解了文字的計算過程後,再來算這個的畫,就相對容易了很多。
 

           currentScanDegree=(getCurrentDegree()-10)*3;
	int insideIndicator=mRadius-60;//離圓環的距離
        if (currentScanDegree<=45) {//第三象限
        	canvas.drawCircle((float)(mCenter-insideIndicator*Math.sin(Math.PI*(currentScanDegree+45)/180)),(float)(mCenter+insideIndicator*Math.cos(Math.PI*(currentScanDegree+45)/180)), 10, indicatorPaint);
		}else if(45<currentscandegree&&#164;tscandegree<=135) else="" if="" pre="">

過程也就不解釋了,同3的過程一致

5)中間文字的具體溫度值

這個是根據掃描的角度值進行換算的

canvas.drawText((currentScanDegree/9+10)+"℃", mCenter, mCenter, centerTextPaint);

相信了解了上面的過程,這個就很簡單了吧。

到此整個計算過程就全部完成了。

那麼,怎麼讓我們的view動起來呢,有點動態感,對此,我們只要不斷的改變掃描的度數就好,對此我設定了3個值,最低溫度值(起始)、最高溫度值(結束)、當前溫度。

點擊按鈕讓其重繪。具體代碼如下

  private void showDegree(final int minDegree, final int maxDegree, final int currentDegree) {
    	myCircleView.setMinDegree(minDegree);
    	new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = minDegree; i < (maxDegree-minDegree)*3+minDegree; i++) {
					try {
						Thread.sleep(30);
					} catch (InterruptedException e) {
						// TODO 自動生成的 catch 塊
						e.printStackTrace();
					}
					
					myCircleView.setMaxDegree(i);
					myCircleView.postInvalidate();
				}
				for (int j = 10; j<=(currentDegree-10)*3+10; j++) {
					if (j<=(currentDegree-10)*3+10) {//23*3+10=79
						myCircleView.setCurrentDegree(j);
					}
					myCircleView.postInvalidate();
				}
			}
		}).start();
}
上面要注意的地方是要注意使用postInvalidata()方法,因為我們是在子線程操控UI線程,所以不能單純的用invalidata()方法。

3. view添加滑動觸摸事件

最後作為擴展,我為此控件添加了滑動觸摸事件

代碼如下:

@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO 自動生成的方法存根
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			isCanMove = true;
			break;
		case MotionEvent.ACTION_MOVE:
			float x = event.getX();
			float y = event.getY();
			float StartX = event.getX();
			float StartY = event.getY();
			// 判斷當前手指距離圓心的距離 如果大於mCenter代表在圓心的右側
			if (x > mCenter) {
				x = x - mCenter;
			} else {
				x = mCenter - x;
			}
			if (y > mCenter) {
				y = y - mCenter;
			} else {
				y = mCenter - y;
			}
			// 判斷當前手指是否在圓環上的(30+10多加了10個像素)
			if ((mRadius + 40) < Math.sqrt(x * x + y * y)
					|| Math.sqrt(x * x + y * y) < (mRadius - 40)) {
				Log.e("cmos---->", "終止滑動");
				isCanMove = false;
				return false;
			}
			float cosValue = x / (float) Math.sqrt(x * x + y * y);
			// 根據cosValue求角度值
			double acos = Math.acos(cosValue);// 弧度值
			acos = Math.toDegrees(acos);// 角度值

			if (StartX > mCenter && StartY < mCenter) {
				acos = 360 - acos;// 第一象限
				Log.e("象限---->", "第一象限");
			} else if (StartX < mCenter && StartY < mCenter) {
				acos = 180 + acos;// 第二象限
				Log.e("象限---->", "第二象限");
			} else if (StartX < mCenter && StartY > mCenter) {
				acos = 180 - acos;// 第三象限
				Log.e("象限---->", "第三象限");
			} else {
				// acos=acos;
				Log.e("象限---->", "第四象限");
			}
			Log.e("旋轉的角度---->", acos + "");
			scanDegree = (int) acos;
			if (scanDegree >= 135 && scanDegree < 360) {
				scanDegree = scanDegree - 135;
				int actualDegree = (int) (scanDegree / 9);
				if (mGetDegreeInterface != null) {
					mGetDegreeInterface.getActualDegree(actualDegree
							+ minDegrees);
				}
			} else if (scanDegree <= 45) {
				scanDegree = (int) (180 + 45 + acos);
				int actualDegree = (int) (scanDegree / 9);
				if (mGetDegreeInterface != null) {
					mGetDegreeInterface.getActualDegree(actualDegree
							+ minDegrees);
				}
			} else {
				return false;
			}
			postInvalidate();
			return true;
		}
		return true;
	}
其中上面用到了接口回調事件

if (mGetDegreeInterface != null) {
					mGetDegreeInterface.getActualDegree(actualDegree
							+ minDegrees);
				}
假如我們主界面需要當前滑動到的溫度值,可以利用此方法

/**
	 * 獲取當前溫度值接口
	 * 
	 */
	public interface GetDegreeInterface {
		void getActualDegree(int degree);
	}

	public void setGetDegreeInterface(GetDegreeInterface arg) {
		this.mGetDegreeInterface = arg;
	}

好的,全文到此結束,對文章內容有疑問的、或者有錯的地方、或者代碼可以有優化的方法的,歡迎留言探討,共同進步!!!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved