This is a Tutorial about android - like Gauge View or Seek Circle, edit by civetcat.
if you need to do something like meter on UI / Speed meter or anything like that ,
you can just download the zip file,watch the code.
Download Link: 
SeekCircle-Thread-version  // 
SeekCircle-Normal-version
Original Code : 
https://github.com/Necat0r/SeekCircle
------------------------------------------------------------------------------------------------------------
以上為給國外朋友參考用。
正文開始:
  因為有需要,所以研究了一下如何在Android上面做出一個儀表板型的UI,一開始看了很多國
內的文章幾乎都沒有寫,只有大陸那邊網站稍稍有提到一些,但下載回來都是亂碼,好不容易找到
了一個國外的open-source,於是就下載回來自己修改。
  過程當中刪除了一些我覺得不是很必要的code,如果要的話也是可以去原網址
這裡下載來
看看,使用方法為用手觸控來調整百分比(要滑動那個圓),看起來滿炫的!但我需要的不是用觸控
型的,所以這邊修改了一下改為你餵數值進去才會動。
第一個版本跟第二個版本其實沒有差很多,只是一個把餵數值的code另外獨立出來做一個thread
而已。
Code這邊可以分為三部分: MainActivity / SeekCircle / ProgressCircle
MainActivity當然就是主程式
/**
 * MainActivity.java [V 1.0.0]
 * classes :﹛com.caryfish.circleprogress.MainActivity
 * 剢豌  create at 2014-8-18 狟敁4:20:40
 */
package com.civetcat.circleprogress;
import com.caryfish.circleprogress.R;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends Activity {
 public SeekCircle s_circle;
 public static Handler handler;
 public sending_thread send_thread;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  SeekCircle seekCircle = (SeekCircle) findViewById(R.id.seekCircle);// 建一個seekCircle物件,找id
  Log.d("MainActivity", "start...");
  
  
  try {
   handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
     super.handleMessage(msg);
     switch (msg.what) {
     case 1:
      int[] receive_obj = (int[]) msg.obj; // object無法傳單一int
                // 所以用int[]來傳送
      updateText(receive_obj[0]);
      break;
     }
    }
   };
  } catch (Exception e) {
   Log.d("Handler exception", e.toString());
  }
  
  send_thread = new sending_thread();
  send_thread.start();
  // **這邊改用Thread.start() 讓他跑,updateText()則用handler去接收來更新資料
  
 }
 private void updateText(int input_progress) {
  SeekCircle seekCircle = (SeekCircle) findViewById(R.id.seekCircle);
  TextView textProgress = (TextView) findViewById(R.id.textProgress);
  if (textProgress != null && seekCircle != null) {
   seekCircle.setProgress(input_progress);
   int progress = seekCircle.getProgress();// 會去ProgressCircle找getProgress()拿資料
   textProgress.setText(Integer.toString(progress) + "%");
  }
 }
}
這邊因為要接收從另個thread來的數據,所以使用handler(請不要使用setText之類的,保證會有錯誤),updateText的部分則是會跑去setProgress(ProgressCircle.java)然後再把set好的數值用getProgress(同樣為ProgressCircle.java)取出來放在text裡面。
這部分應該是最簡單的部分,接下來兩個就是關鍵核心了!
雖然SeekCircle.java看似無用,但也是要他的建構子,所以不能把該檔案刪掉。
package com.civetcat.circleprogress;
import android.content.Context;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
public class SeekCircle extends ProgressCircle
{
 /**
  * A callback that notifies clients when the progress level has been
  * changed. This includes changes that were initiated by the user through a
  * touch gesture or arrow key/trackball as well as changes that were
  * initiated programmatically.
  */
 
 //SeekCircle建構子,勿刪
 public SeekCircle(Context context, AttributeSet attrs, int defStyle)
 {
  super(context, attrs, defStyle);
  Log.d("SeekCircle","in Seek Circle constructor");
  
 }
 
 public SeekCircle(Context context, AttributeSet attrs)
 {
  super(context, attrs);
  Log.d("SeekCircle","in Seek Circle constructor");
  
 }
 
 public SeekCircle(Context context)
 {
  super(context);
  Log.d("SeekCircle","in Seek Circle constructor");
  
 }
 //SeekCircle建構子,勿刪 end
  
}
這邊能夠調整整個圖形介面的就屬ProgressCircle.java了(MainActivity也是拉...)
package com.civetcat.circleprogress;
import com.caryfish.circleprogress.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
//import com.seekcircle.library.R;
public class ProgressCircle extends View
{
 private float mRingBias = 0.15f;
 private float mSectionRatio = 5.0f;
 private RectF mSectionRect = new RectF();
 protected float mSectionHeight;
 
 protected float mRadius;
 
 protected int mMaxProgress;
 protected int mProgress;
 
 protected float mCenterX;
 protected float mCenterY;
 
 private Paint mPaint;
 private int mColor1;
 private int mColor2;
 private int mInactiveColor;
 
 {
  mMaxProgress = 100;
  mProgress = 0;
  
  mPaint = new Paint();
  mPaint.setAntiAlias(true);
  mPaint.setStyle(Paint.Style.FILL);
  
  mColor1 = Color.parseColor("#ffff5900"); //頭端color = 紅
  mColor2 = Color.parseColor("#ff33b5e5"); //尾端color = 藍
  mInactiveColor = Color.parseColor("#ff404040");
  
  mPaint.setColor(mColor1); // Set default
 }
 //------建構子
 
 public ProgressCircle(Context context, AttributeSet attrs, int defStyle)
 {
  super(context, attrs, defStyle);
  
  initAttributes(context, attrs);
 }
 
 public ProgressCircle(Context context, AttributeSet attrs)
 {
  super(context, attrs);
  
  initAttributes(context, attrs);
 }
 
 public ProgressCircle(Context context)
 {
  super(context);
 }
 
 //建構子----end
 
 private void initAttributes(Context context, AttributeSet attrs)
 {
  TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SeekCircle, 0, 0);
  try
  {
   // Read and clamp max
   int max = attributes.getInteger(R.styleable.SeekCircle_max, 100);
   mMaxProgress = Math.max(max, 1);
   
   // Read and clamp progress
   int progress = attributes.getInteger(R.styleable.SeekCircle_progress, 0);
   mProgress = Math.max(Math.min(progress, mMaxProgress), 0);
  }
  finally
  {
   attributes.recycle();
  }
 }
 
 private void updateDimensions(int width, int height)
 {
  // Update center position
  mCenterX = width / 2.0f;
  mCenterY = height / 2.0f;
  
  // Find shortest dimension
  int diameter = Math.min(width, height);
  
  float outerRadius = diameter / 2;
  float sectionHeight = outerRadius * mRingBias;
  float sectionWidth = sectionHeight / mSectionRatio;
  
  mRadius = outerRadius - sectionHeight / 2;
  mSectionRect.set(-sectionWidth / 2, -sectionHeight / 2, sectionWidth / 2, sectionHeight / 2);
  mSectionHeight = sectionHeight;
 }
 
 @Override
 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
 {
  int width = MeasureSpec.getSize(widthMeasureSpec);
  int height = MeasureSpec.getSize(heightMeasureSpec);
  
  if (width > height)
   super.onMeasure(heightMeasureSpec, widthMeasureSpec);
  else
   super.onMeasure(widthMeasureSpec, widthMeasureSpec);
  
  updateDimensions(getWidth(), getHeight());
 }
 
 @Override //改變size大小
 protected void onSizeChanged(int w, int h, int oldw, int oldh)
 {
  super.onSizeChanged(w, h, oldw, oldh);
  
  updateDimensions(w, h);
 }
 
 @Override
 protected void onDraw(Canvas canvas)
 {
  // Center our canvas
  canvas.translate(mCenterX, mCenterY);//偏移原本原點多少x,多少y ex:100,100 -> 101,101
  
  float rotation = 360.0f / (float) mMaxProgress; //rotation 旋轉 360/100
  for (int i = 0; i < mMaxProgress; ++i)
  {
   canvas.save();
   
   canvas.rotate((float) i * rotation);
   canvas.translate(0, -mRadius);
   
   if (i < mProgress)
   {
    float bias = (float) i / (float) (mMaxProgress - 1);
    int color = interpolateColor(mColor1, mColor2, bias);//漸層顏色
    mPaint.setColor(color);//塗上顏色,在這一段
   }
   else
   {
    canvas.scale(0.7f, 0.7f);
    mPaint.setColor(mInactiveColor);
   }
   
   canvas.drawRect(mSectionRect, mPaint);
   canvas.restore();
  }
  
  super.onDraw(canvas);
 }
 
 private float interpolate(float a, float b, float bias) //interpolate = 插值,bias = 偏移,偏差
 {
  return (a + ((b - a) * bias));
 }
 private int interpolateColor(int colorA, int colorB, float bias)
 {
  float[] hsvColorA = new float[3];
  Color.colorToHSV(colorA, hsvColorA);
  float[] hsvColorB = new float[3];
  Color.colorToHSV(colorB, hsvColorB);
  hsvColorB[0] = interpolate(hsvColorA[0], hsvColorB[0], bias);
  hsvColorB[1] = interpolate(hsvColorA[1], hsvColorB[1], bias);
  hsvColorB[2] = interpolate(hsvColorA[2], hsvColorB[2], bias);
  
  // NOTE For some reason the method HSVToColor fail in edit mode. Just use the start color for now
  if (isInEditMode())
   return colorA;
  return Color.HSVToColor(hsvColorB);
 }
 
 /**
  * Get max progress
  *
  * @return Max progress
  */
 public float getMax()
 {
  return mMaxProgress;
 }
 
 /**
  * Set max progress
  *
  * @param max
  */
 public void setMax(int max)
 {
  int newMax = Math.max(max, 1);
  if (newMax != mMaxProgress)
   mMaxProgress = newMax;
  
  updateProgress(mProgress);
  invalidate();//重新繪製View
 }
 
 /**
  * Get Progress
  *
  * @return progress
  */
 public int getProgress()
 {
  Log.d("ProgressCircle","getProgress");
  Log.d("ProgressCircle on getProgress,mProgress = ",Integer.toString(mProgress));
  return mProgress;
 }
 
 /**
  * Set progress
  *
  * @param progress
  */
 public void setProgress(int progress)
 {
  Log.d("ProgressCircle","setProgress");
  
  updateProgress(progress);//設定progress
 }
 
 /**
  * Update progress internally. Clamp it to a valid range and invalidate the view if necessary
  *
  * @param progress
  * @return true if progress was changed and the view needs an update
  */
 protected boolean updateProgress(int progress)
 {
  Log.d("ProgressCircle","update Progress");
 
  Log.d("ProgressCircle Update Progress,progress = ",Integer.toString(progress));
  Log.d("ProgressCircle Update Progress,mProgress = ",Integer.toString(mProgress));
  // Clamp progress , clamp = 抓
  progress = Math.max(0, Math.min(mMaxProgress, progress));//先跟最大比 如果>100 就取100,再跟0比,最小到0 也就是0~100之間
  
  if (progress != mProgress) //跟目前狀態(mProgress)不同的話
  {
   mProgress = progress;
   invalidate();//重新繪製View
   Log.d("ProgressCircle Update Progress,progress = ",Integer.toString(progress));
   Log.d("ProgressCircle Update Progress,mProgress = ",Integer.toString(mProgress));
   return true;
  }
  
  return false;
 }
}
原始資料顏色部份是對調的,但我因為讓電池100%時看起來是綠色的,0%時為紅色的,所以就把
數據對調了一下。
值得特別注意的是  progress = Math.max(0, Math.min(mMaxProgress, progress));
                                  //先跟最大比 如果>100 就取100,再跟0比,最小到0 也就是0~100之間
這邊如果要調整最大最小值,可以透過這一行+mMaxProgress來調整。
然後invalidate();則是強制重新繪製view。
最後一塊則是Thread的部分,但thread的部分之前有寫過了,所以就不多加說明了。
其他的部分能研究的都在程式碼裡面有寫註解了,祝大家都可以順利寫出來!
附上
載點