顯示具有 UI 標籤的文章。 顯示所有文章
顯示具有 UI 標籤的文章。 顯示所有文章

2014年9月5日 星期五

[Android] 儀表板式顯示數值,類Gauge View / 使用Seek Circle 實作



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的部分之前有寫過了,所以就不多加說明了。

其他的部分能研究的都在程式碼裡面有寫註解了,祝大家都可以順利寫出來!

附上載點



2014年9月2日 星期二

[Android] 如何開啟後隱藏Activty UI ?

在寫App的過程當中遇到兩個問題

1. 開機時自動啟動
2. 開啟後隱藏UI (用service在背景執行)

第一個問題可能之後幾篇會講到,不過大概就是android 會廣播一則開機訊息,你只要去抓那個訊

息就知道開機了,然後利用intent去啟動class...

第二個問題網路上比較少講到,這邊也沒有做到很完全


大概歸納為三個解決方案:

1.修改Theme 改為Theme.No_Display,但這經過實測,不行。

2.使用finish()函式,經過實測,可行。

使用方法:  在onCreate()裡面都做完事情以後新增一行 finish();

3.使用 moveTaskToBack()

public boolean moveTaskToBack (boolean nonRoot)

Added in API level 1
Move the task containing this activity to the back of the activity stack. The activity's order within the task is unchanged.
Parameters
nonRootIf false then this only works if the activity is the root of a task; if true it will work for any activity in a task.
Returns
  • If the task was moved (or it was already at the back) true is returned, else false.

Google API上面的形容,會回傳值true就是已經移到背景,false則否。

有人會使用moveTaskToBack(true),也有人說也可以改成false,但經過實際測試,兩者皆可。

但API上寫是說true是無論如何都會work,false只有activity在task的root的時候才會作用 。