最近要做一個比較有趣的效果,就是将android手機設定的條形的SeekBar換成圓形的SeekBar,這裡我不講怎麼将定制系統的設定rom,但如果有人想了解設定裡面的顯示可以看看這篇部落格,隻講怎麼自定義圓形SeekBar和自定義Dialog,雖然網上有很多這方面的資料,但不系統,是以在這裡總結下,方面以後觀看。
自定義圓形SeekBar
關于圓形SeekBar我是在GitHub上下載下傳的一個開源項目,項目位址,裡面有詳細的使用說明和參數含義。在這裡,我也貼一下代碼和使用步驟。
首先,我們從網站下載下傳代碼,看看解壓包裡面有CircularSeekBar.java和attrs.xml檔案,一眼看下去就知道attrs.xml是屬性的定義,CircularSeekBar.java是自定義圓形SeekBar的實作。下面貼一下attrs.xml看下有那些屬性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircularSeekBar">
<attr name="progress" format="integer"></attr>
<attr name="max" format="integer"></attr>
<attr name="move_outside_circle" format="boolean"></attr>
<attr name="maintain_equal_circle" format="boolean"></attr>
<attr name="use_custom_radii" format="boolean"></attr>
<attr name="lock_enabled" format="boolean"></attr>
<attr name="circle_x_radius" format="dimension" />
<attr name="circle_y_radius" format="dimension" />
<attr name="circle_stroke_width" format="dimension" />
<attr name="pointer_radius" format="dimension" />
<attr name="pointer_halo_width" format="dimension" />
<attr name="pointer_halo_border_width" format="dimension"></attr>
<attr name="circle_color" format="color"></attr>
<attr name="circle_progress_color" format="color"></attr>
<attr name="pointer_color" format="color"></attr>
<attr name="pointer_halo_color" format="color"></attr>
<attr name="pointer_halo_color_ontouch" format="color"></attr>
<attr name="pointer_alpha_ontouch" format="integer"></attr>
<attr name="start_angle" format="float"></attr>
<attr name="end_angle" format="float"></attr>
<attr name="circle_fill" format="color"></attr>
</declare-styleable>
</resources>
要了解具體的屬性看了,可以去GitHub上看下,這裡就不累叙了。
之後,我們将自定義的CircularSeekBar.java導入我們的項目,當然attrs.xml也要導入,至于怎麼自定義的CircularSeekBar大家可以自己看看源碼。
最後,我們來寫一個對這個CircularSeekBar的測試。先來看看UI的代碼:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.yzx.circularseekbar.MainActivity" >
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="click"
android:layout_centerHorizontal="true"
android:text="彈出對話框"/>
</RelativeLayout>
這段代碼沒什好講的,我們再來看看MainActivity的代碼:
public class MainActivity extends Activity{
private SharedPreferences sp;
private AlertDialog dialog;
private Timer mTimer;
private TimerTask mTimerTask;
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1:
dialog.dismiss();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
//Toast.makeText(this, "haha", Toast.LENGTH_LONG).show();
switch (v.getId()) {
case R.id.btn1:
sp = getSharedPreferences("brightness", MODE_PRIVATE);
showBrightnessDialog();
break;
default:
break;
}
}
private void showBrightnessDialog() {
//使用Timer來實作3秒自動關閉dialog
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
//開始一個定時任務
mTimer.schedule(mTimerTask, 3000);
AlertDialog.Builder builder = new Builder(this);
View contentView = View.inflate(this, R.layout.brightness_dialog, null);
CircularSeekBar seekbar = (CircularSeekBar) contentView.findViewById(R.id.circularSeekBar1);
int brightnessValue = sp.getInt("brightnessValue", 45);
seekbar.setProgress(brightnessValue);
//注冊監聽事件
seekbar.setOnSeekBarChangeListener(new OnCircularSeekBarChangeListener() {
@Override
public void onProgressChanged(CircularSeekBar circularSeekBar,
int progress, boolean fromUser) {
//在此處修改亮度
System.out.println("circularSeekBar:"+progress);
}
@Override
public void onStopTrackingTouch(CircularSeekBar seekBar) {
// TODO Auto-generated method stub
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
//開始一個定時任務
mTimer.schedule(mTimerTask, 3000);
Editor editor = sp.edit();
editor.putInt("brightnessValue", seekBar.getProgress());
editor.commit();
}
@Override
public void onStartTrackingTouch(CircularSeekBar seekBar) {
// TODO Auto-generated method stub
mTimer.cancel();
}
});
dialog = builder.create();
dialog.setView(contentView,0,0,0,0);
dialog.show();
//Handle實作過3秒自動關閉dialog。自動關閉對話框的功能主要使用Handler對象來實作,該對象的postDelayed方法用來實作延時多少秒去執行某個任務。
//有Bug,無論是否操作,Dialog過三秒都會關閉
/*Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
dialog.dismiss();
}
}, 3000);*/
}
}
我們先貼出brightness_dialog.xml的代碼,再來分析下代碼:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res/com.yzx.circularseekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:background="#000000"
tools:context="com.yzx.circularseekbar.MainActivity" >
<TextView
android:id="@+id/tv_brightness"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="20sp"
android:textColor="#0174DF"
android:text="亮度"/>
<com.yzx.circularseekbar.CircularSeekBar
android:id="@+id/circularSeekBar1"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_below="@id/tv_brightness"
app:circle_x_radius="100dp"
app:circle_y_radius="100dp"
app:use_custom_radii="true"
app:progress="25"
app:max="100"
app:pointer_alpha_ontouch="100"
app:pointer_color="#0174DF"
app:pointer_halo_color="#880174DF" />
</RelativeLayout>
上面代碼達到的效果是,當你點選“彈出對話框”按鈕時,彈出圓形SeekBar。如果你不動時,彈出的AlertDialog過三秒就會自動消失,當你拖動圓形SeekBar時,就不會消失,不拖動,過3秒又自動消失。這裡面也記錄的你的進度條拖動到什麼位置了。
要注意,在brightness_dialog.xml中不要忘了一行代碼:
xmlns:app="http://schemas.android.com/apk/res/com.yzx.circularseekbar"
實作自動關閉時通過使用Timer來實作的,本來想使用Handle裡面的postDelayed來實作,但有一個不管你操不操作,過3秒就會關閉的Bug。Timer的主體代碼如下:
//使用Timer來實作3秒自動關閉dialog
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
//開始一個定時任務
mTimer.schedule(mTimerTask, 3000);
通過在單擊“彈出對話框”按鈕、onStartTrackingTouch和onStopTrackingTouch的結合使用來實作自動關閉的效果。
運作效果如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISMyYTMyATNyIDMzcDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
自定義Dialog
在上面的AlertDialog中,有一個隐藏的Bug,當我們單擊按鈕讓彈窗彈出時馬上關閉,之後又點選按鈕讓彈窗彈出,循環幾次,就會發現彈窗不可控的情況出現,其根本原因就是我們開啟了多個Timer,而在關閉(dismiss)時沒有終止Timer造成的。
對與以上的Bug,我們需要自定義Dialog,重寫自定義的Dialog中的dismiss方法,讓其在每次退出時就終止Timer。
我們現在主UI中添加一個按鈕,來調用自定義的Dialog。添加後的完整代碼如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.yzx.circularseekbar.MainActivity" >
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="click"
android:layout_centerHorizontal="true"
android:text="彈出對話框"/>
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/btn1"
android:onClick="click"
android:layout_centerInParent="true"
android:text="彈出對話框_自定義" />
</RelativeLayout>
brightness_dialog.xml中的代碼不變,而MainActivity的完整代碼如下:
public class MainActivity extends Activity{
private SharedPreferences sp;
private AlertDialog dialog;
private MyAlertDialog myDialog;
private Timer mTimer;
private TimerTask mTimerTask;
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1:
dialog.dismiss();
break;
case 2:
myDialog.dismiss();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
//Toast.makeText(this, "haha", Toast.LENGTH_LONG).show();
switch (v.getId()) {
case R.id.btn1:
sp = getSharedPreferences("brightness", MODE_PRIVATE);
showBrightnessDialog();
break;
case R.id.btn2:
sp = getSharedPreferences("brightness", MODE_PRIVATE);
showMyBrightnessDialog();
break;
default:
break;
}
}
private void showMyBrightnessDialog() {
myDialog = new MyAlertDialog(MainActivity.this);
//設定為沒有title
myDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
myDialog.show();
//使用Timer來實作3秒自動關閉dialog
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 2;
handler.sendMessage(message);
}
};
//開始一個定時任務
mTimer.schedule(mTimerTask, 3000);
}
//重寫Dialog,實作每次dismiss時終止定時器
class MyAlertDialog extends Dialog{
protected MyAlertDialog(Context context) {
super(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.brightness_dialog);
CircularSeekBar seekbar = (CircularSeekBar) findViewById(R.id.circularSeekBar1);
int brightnessValue = sp.getInt("customBrightnessValue", 45);
seekbar.setProgress(brightnessValue);
//注冊監聽事件
seekbar.setOnSeekBarChangeListener(new OnCircularSeekBarChangeListener() {
@Override
public void onProgressChanged(CircularSeekBar circularSeekBar,
int progress, boolean fromUser) {
//在此處修改亮度
System.out.println("circularSeekBar's progress is :"+progress);
}
@Override
public void onStopTrackingTouch(CircularSeekBar seekBar) {
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 2;
handler.sendMessage(message);
}
};
//開始一個定時任務
mTimer.schedule(mTimerTask, 3000);
Editor editor = sp.edit();
editor.putInt("customBrightnessValue", seekBar.getProgress());
editor.commit();
}
@Override
public void onStartTrackingTouch(CircularSeekBar seekBar) {
mTimer.cancel();
}
});
}
@Override
public void dismiss() {
// 終止所有的定時器
mTimer.cancel();
System.out.println("circularSeekBar is auto close.");
super.dismiss();
}
}
private void showBrightnessDialog() {
//使用Timer來實作3秒自動關閉dialog
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
//開始一個定時任務
mTimer.schedule(mTimerTask, 3000);
AlertDialog.Builder builder = new Builder(this);
View contentView = View.inflate(this, R.layout.brightness_dialog, null);
CircularSeekBar seekbar = (CircularSeekBar) contentView.findViewById(R.id.circularSeekBar1);
int brightnessValue = sp.getInt("brightnessValue", 45);
seekbar.setProgress(brightnessValue);
//注冊監聽事件
seekbar.setOnSeekBarChangeListener(new OnCircularSeekBarChangeListener() {
@Override
public void onProgressChanged(CircularSeekBar circularSeekBar,
int progress, boolean fromUser) {
//在此處修改亮度
System.out.println("circularSeekBar:"+progress);
}
@Override
public void onStopTrackingTouch(CircularSeekBar seekBar) {
// TODO Auto-generated method stub
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
//開始一個定時任務
mTimer.schedule(mTimerTask, 3000);
Editor editor = sp.edit();
editor.putInt("brightnessValue", seekBar.getProgress());
editor.commit();
}
@Override
public void onStartTrackingTouch(CircularSeekBar seekBar) {
// TODO Auto-generated method stub
mTimer.cancel();
}
});
dialog = builder.create();
dialog.setView(contentView,0,0,0,0);
dialog.show();
//Handle實作過3秒自動關閉dialog。自動關閉對話框的功能主要使用Handler對象來實作,該對象的postDelayed方法用來實作延時多少秒去執行某個任務。
//有Bug,無論是否操作,Dialog過三秒都會關閉
/*Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
dialog.dismiss();
}
}, 3000);*/
}
}
上面的代碼主要的部分就是:
@Override
public void dismiss() {
// 終止所有的定時器
mTimer.cancel();
System.out.println("circularSeekBar is auto close.");
super.dismiss();
}
就是每次調用dismiss時就終止Timer,這樣就解決的彈窗的不可控。
源碼下載下傳位址:https://github.com/yzhixiang/CircularSeekBar-CustomDialog