自定義View之WiperSwitch改進版

近日使用了一個滑動開關,使用的是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=0, nowX=0;

    /**
     * 記錄用戶是否在滑動
     */
    private boolean onSlip = false;

    /**
     * 當前的狀態
     */
    private boolean nowStatus = false;

    /**
     * 監聽接口
     */
    private OnChangedListener listener;

    Paint paint = null;//畫筆
    float x = 0;//滑動塊的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 = 0;
        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 > 3){
                ratio = 3;
            } else if(ratio < 1/3){
                ratio = 1/3;
            }
            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 = 0;

        if(isInEditMode()){
            return;
        }

        //根據nowStatus設置背景,開或者關狀態        
        if(nowStatus){
            canvas.drawBitmap(bg_on, 0, 0, paint);          
        }else{
            canvas.drawBitmap(bg_off, 0, 0, paint);         
        }

        if (onSlip) {//是否是在滑動狀態,
            if(nowX >= bg_on.getWidth())//是否劃出指定範圍,不能讓滑塊跑到外頭,必須做這個判斷  
                x = bg_on.getWidth() - slipper_btn.getWidth()/2;//減去滑塊1/2的長度  
            else
                x = nowX - slipper_btn.getWidth()/2;
        }else {
            if(nowStatus){//根據當前的狀態設置滑塊的x值,若不在滑動狀態,非左即右!
                x = bg_on.getWidth() - slipper_btn.getWidth();
            }else{
                x = 0;
            }
        }

        //對滑塊滑動進行異常處理,不能讓滑塊出界
        if (x < 0 ){
            x = 0;
        }
        else if(x > bg_on.getWidth() - slipper_btn.getWidth()){
            x = bg_on.getWidth() - slipper_btn.getWidth();
        }

        //畫出滑塊 
        canvas.drawBitmap(slipper_btn, x , 0, 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()/2)){
                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()/2)){
                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 = 0;
            }
            //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 = 0;
        }
        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);

以及監控開關的狀態變化:

switch1.setOnChangedListener(new OnChangedListener() {

具體實現如下:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章