近日使用了一個滑動開關,使用的是xiaanming的WiperSwitch,位址在
http://blog.csdn.net/xiaanming/article/details/8842453
确實是個好東西,很容易就移植到app中,果然是沒有版本不相容問題。
可是在使用過程中,發現有些小問題,然後進行了改進。改進有如下幾點:
1,支援控件的縮放;
2,解決有時開關會卡在中間的問題;
3,更換了圖檔;
4,解決滑動沖突問題;
5,onDraw 中不new;
先上個改進後的效果圖:
下面分别對改進點進行說明:
1、支援控件的縮放
就是重寫了onMeasure,根據設定的寬高值進行縮放。縮放時,為了美觀,保持了view的寬高的比例;
設定縮放上下限:不能太大、太小,都沒有意義,此處,我是限定上下限為 1/3 – 3倍;
2、解決有時開關會卡在中間的問題
運作時會遇到中間的滑動按鈕卡在中間的情況,後來上網一頓查找,才知道是觸摸事件onTouch()的進行中,有一種動作沒有處理到,是MotionEvent.ACTION_CANCEL:
當保持按下操作,并從滑動開關轉移到外層控件時,會觸發MotionEvent.ACTION_CANCEL。
中onTouch()中添加上,并與MotionEvent.ACTION_UP做同樣的處理,ok。
3、更換了圖檔
更換圖檔有些地方需要注意:
滑塊的高度與背景圖檔的高度保持一緻,滑塊外緣是透明部分,通過透明部分來襯托出外邊緣。
另外,說句題外話,做安卓開發,學習點PhotoShop也挺不錯的。
4、解決滑動沖突問題
我在實際項目使用中,遇到将滑塊滑動到中間部分時,突然跳轉到另一頭的情況。通過跟蹤資訊,發現竟然執行了MotionEvent.ACTION_CANCEL,原來是滑動事件被上層的布局給搶走了(我在外層使用了一個側滑菜單,進而出現了滑動沖突)。
解決方法:
在ACTION_DOWN時禁止父ViewGroup的滑動監聽:
getParent().requestDisallowInterceptTouchEvent(true);
在ACTION_CANCEL與ACTION_UP時恢複父ViewGroup的滑動監聽:
getParent().requestDisallowInterceptTouchEvent(false);
5、onDraw 中不new
根據Lint的提示,View 的 onDraw() 方法會被頻繁的調用,是以不建議在onDraw()函數體内進行對象配置設定。如果在其中有需要用到的對象,就把這些對象的配置設定放在别處。
6、下面是具體實作
package com.customview.view;
import com.customview.LogUtil;
import com.customview.R;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
/**
*
* @author xiaanming,lintax
*
*/
public class WiperSwitch extends View implements OnTouchListener{
private Bitmap bg_on, bg_off, slipper_btn;
/**
* 按下時的x和目前的x
*/
private float downX=, nowX=;
/**
* 記錄使用者是否在滑動
*/
private boolean onSlip = false;
/**
* 目前的狀态
*/
private boolean nowStatus = false;
/**
* 監聽接口
*/
private OnChangedListener listener;
Paint paint = null;//畫筆
float x = ;//滑動塊的x位置
public WiperSwitch(Context context) {
super(context);
init();
}
public WiperSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public void init(){
LogUtil.logWithMethod(new Exception(),"init");
//載入圖檔資源
bg_on = BitmapFactory.decodeResource(getResources(), R.drawable.switch_open);
bg_off = BitmapFactory.decodeResource(getResources(), R.drawable.switch_close);
slipper_btn = BitmapFactory.decodeResource(getResources(), R.drawable.switch_btn);
x = ;
paint = new Paint();
setOnTouchListener(this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height ;
boolean flagExactly=false;
float viewWidth = (float)bg_on.getWidth();
float viewHeight = (float)bg_on.getHeight();
if (widthMode == MeasureSpec.EXACTLY)
{
width = widthSize;
flagExactly = true;
} else
{
width = (int) (viewWidth );
}
if (heightMode == MeasureSpec.EXACTLY)
{
height = heightSize;
flagExactly = true;
} else
{
height = (int) ( viewHeight );
}
//如果是指定大小,且寬高值确實不一緻了,要重新調整圖檔的大小
if( flagExactly && ( (width!=viewWidth) || (height!=viewHeight) )){
//限制調整的範圍:最大3倍,比例需要保持(要調比例,換原始圖檔)
//按比例,取小值
float ratio = Math.min(width/viewWidth, height/viewHeight);
if(ratio > ){
ratio = ;
} else if(ratio < /){
ratio = /;
}
width=(int)(viewWidth*ratio);
height=(int)(viewHeight*ratio);
LogUtil.logWithMethod(new Exception(),"after change: width="+width+" height="+height);
bg_on = Bitmap.createScaledBitmap(bg_on, width, height, false);
bg_off = Bitmap.createScaledBitmap(bg_off, width, height, false);
slipper_btn = Bitmap.createScaledBitmap(slipper_btn, height, height, false);
}
setMeasuredDimension(width, height);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
x = ;
if(isInEditMode()){
return;
}
//根據nowStatus設定背景,開或者關狀态
if(nowStatus){
canvas.drawBitmap(bg_on, , , paint);
}else{
canvas.drawBitmap(bg_off, , , paint);
}
if (onSlip) {//是否是在滑動狀态,
if(nowX >= bg_on.getWidth())//是否劃出指定範圍,不能讓滑塊跑到外頭,必須做這個判斷
x = bg_on.getWidth() - slipper_btn.getWidth()/;//減去滑塊1/2的長度
else
x = nowX - slipper_btn.getWidth()/;
}else {
if(nowStatus){//根據目前的狀态設定滑塊的x值,若不在滑動狀态,非左即右!
x = bg_on.getWidth() - slipper_btn.getWidth();
}else{
x = ;
}
}
//對滑塊滑動進行異常處理,不能讓滑塊出界
if (x < ){
x = ;
}
else if(x > bg_on.getWidth() - slipper_btn.getWidth()){
x = bg_on.getWidth() - slipper_btn.getWidth();
}
//畫出滑塊
canvas.drawBitmap(slipper_btn, x , , paint);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:{
LogUtil.logWithMethod(new Exception(), "ACTION_DOWN");
if (event.getX() > bg_off.getWidth() || event.getY() > bg_off.getHeight()){
return false;
}else{
onSlip = true;
downX = event.getX();
nowX = downX;
}
//LogUtil.logWithMethod(new Exception(),"ACTION_DOWN: nowX="+nowX);
getParent().requestDisallowInterceptTouchEvent(true);//禁止父ViewGroup的滑動監聽
break;
}
case MotionEvent.ACTION_MOVE:{
nowX = event.getX();
if(event.getX() >= (bg_on.getWidth()/)){
nowStatus = true;
}else{
nowStatus = false;
}
break;
}
case MotionEvent.ACTION_CANCEL:
//LogUtil.logWithMethod(new Exception(),"ACTION_CANCEL:");
case MotionEvent.ACTION_UP:{
onSlip = false;
if(event.getX() >= (bg_on.getWidth()/)){
LogUtil.logWithMethod(new Exception(), "ACTION_UP: choseed");
nowStatus = true;
nowX = bg_on.getWidth() - slipper_btn.getWidth();
}else{
LogUtil.logWithMethod(new Exception(), "ACTION_UP: un choseed");
nowStatus = false;
nowX = ;
}
//LogUtil.logWithMethod(new Exception(),"ACTION_UP: nowX="+nowX);
if(listener != null){
listener.OnChanged(WiperSwitch.this, nowStatus);
}
getParent().requestDisallowInterceptTouchEvent(false);//使能父ViewGroup的滑動監聽
break;
}
}
//重新整理界面
invalidate();
return true;
}
/**
* 為WiperSwitch設定一個監聽,供外部調用的方法
* @param listener
*/
public void setOnChangedListener(OnChangedListener listener){
this.listener = listener;
}
/**
* 設定滑動開關的初始狀态,供外部調用
* @param checked
*/
public void setChecked(boolean checked){
if(checked){
nowX = bg_off.getWidth();
}else{
nowX = ;
}
nowStatus = checked;
invalidate();
}
/**
* 回調接口
* @author len
*
*/
public interface OnChangedListener {
public void OnChanged(WiperSwitch wiperSwitch, boolean checkState);
}
}
7、調用View的布局xml
布局中就隻是存放了3個View,示範調用方法:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/com.customview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<com.customview.view.WiperSwitch
android:id="@+id/switch1"
android:layout_width="80dp"
android:layout_height="40dp"
android:layout_marginTop="10dp"
android:layout_gravity="center_horizontal"
/>
<com.customview.view.WiperSwitch
android:id="@+id/switch2"
android:layout_width="120dp"
android:layout_height="40dp"
android:layout_marginTop="10dp"
android:layout_gravity="center_horizontal"
/>
<com.customview.view.WiperSwitch
android:id="@+id/switch3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_gravity="center_horizontal"
/>
</LinearLayout>
8、主Activity
主Activity中通路滑動開關,可以設定初始狀态:
switch1.setChecked(true);
以及監控開關的狀态變化:
具體實作如下:
package com.customview;
import android.os.Bundle;
import android.view.WindowManager;
import com.customview.view.WiperSwitch;
import com.customview.view.WiperSwitch.OnChangedListener;
import android.app.Activity;
public class MainActivity extends Activity
{
WiperSwitch switch1;
WiperSwitch switch2;
WiperSwitch switch3;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);//去掉資訊欄
setContentView(R.layout.activity_main);
LogUtil.logWithMethod(new Exception());
switch1 = (WiperSwitch) findViewById(R.id.switch1);
switch2 = (WiperSwitch) findViewById(R.id.switch2);
switch3 = (WiperSwitch) findViewById(R.id.switch3);
switch1.setChecked(true);
switch2.setChecked(true);
switch3.setChecked(false);
switch1.setOnChangedListener(new OnChangedListener() {
@Override
public void OnChanged(WiperSwitch wiperSwitch, boolean checkState) {
// TODO Auto-generated method stub
LogUtil.logWithMethod(new Exception(),"switch1 checkState="+checkState);
}
});
switch2.setOnChangedListener(new OnChangedListener() {
@Override
public void OnChanged(WiperSwitch wiperSwitch, boolean checkState) {
// TODO Auto-generated method stub
LogUtil.logWithMethod(new Exception(),"switch2 checkState="+checkState);
}
});
switch3.setOnChangedListener(new OnChangedListener() {
@Override
public void OnChanged(WiperSwitch wiperSwitch, boolean checkState) {
// TODO Auto-generated method stub
LogUtil.logWithMethod(new Exception(),"switch3 checkState="+checkState);
}
});
}
}
9、源代碼位址:
http://download.csdn.net/detail/lintax/9674388