天天看點

自定義View之開關按鈕:SwitchButton

1.自定義屬性:res/values/attrs.xml

<declare-styleable name="SwitchButton">
    	<attr name="bordeWidth" format="dimension"/>
    	<attr name="areaColor" format="reference|color"/>
    	<attr name="offColor" format="reference|color"/>
    	<attr name="onColor" format="reference|color"/>
    	<attr name="handlerColor" format="reference|color"/>
    	<attr name="animate" format="reference|boolean"/>
	    <attr name="defaultOn" format="reference|boolean"/>

		<attr name="matchStyle" format="enum">
			<enum name="whole" value="0"/>
			<enum name="handler" value="1"/>
		</attr>>
    </declare-styleable>
           

2.SwitchButton.java源代碼

package com.dandy.widget;

import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.content.Context;
import android.util.AttributeSet;
import com.lingyun.switchbutton.R;

/**
 * Created by dandy on 2016/4/12.
 */
public class SwitchButton extends View{

    private static final String TAG = "SwitchButton";

    /**預設寬**/
    private static  final float DEFAULT_WIDTH = 60;

    /**預設高**/
    private static  final float DEFAULT_HEIGHT = 30;

    private static  final long DELAYDURATION = 10;

    /**開啟顔色**/
    private int onColor = Color.parseColor("#4ebb7f");
    /**關閉顔色**/
    private int offColor = Color.parseColor("#dadbda");
    /**灰色帶顔色**/
    private int areaColor = Color.parseColor("#dadbda");
    /**搖桿顔色**/
    private int handlerColor = Color.parseColor("#ffffff");
    /**邊框顔色**/
    private int borderColor = offColor;
    /**開關狀态**/
    private boolean toggleOn = false;
    /**邊框寬**/
    private int borderWidth = 2;
    /**縱軸中心**/
    private float centerY;
    /**按鈕水準方向開始、結束的位置**/
    private float startX,endX;
    /**搖桿x軸方向最小、最大值**/
    private float handlerMinX,handlerMaxX;
    /**搖桿大小**/
    private int handlerSize;
    /**搖桿在x軸的坐标位置**/
    private float handlerX;
    /**關閉時内部灰色帶寬度**/
    private float areaWidth;
    /**是否使用動畫效果**/
    private boolean animate = true;
    /**是否預設處于打開狀态**/
    private boolean defaultOn = true;
    /**按鈕半徑**/
    private float radius;

    private RectF mRectF = new RectF();

    private Paint mPaint;

    private OnToggleChangedListener mListener;

    private Handler mHandler = new Handler();

    private double currentDelay;

    private MatchStyle matchStyle;

    private float downX = 0,downY = 0;

    public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setup(attrs);
    }
    public SwitchButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup(attrs);
    }

    /**
     * 初始化
     * @param attrs
     */
    private void setup(AttributeSet attrs){
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setAntiAlias(true);

        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SwitchButton);
        onColor = typedArray.getColor(R.styleable.SwitchButton_onColor,onColor);
        borderColor = offColor = typedArray.getColor(R.styleable.SwitchButton_offColor,offColor);
        areaColor = typedArray.getColor(R.styleable.SwitchButton_areaColor,areaColor);
        handlerColor = typedArray.getColor(R.styleable.SwitchButton_handlerColor,handlerColor);
        borderWidth = typedArray.getColor(R.styleable.SwitchButton_bordeWidth, borderWidth);
        animate = typedArray.getBoolean(R.styleable.SwitchButton_animate, animate);
        defaultOn = typedArray.getBoolean(R.styleable.SwitchButton_defaultOn,defaultOn);
        matchStyle = MatchStyle.getValue(typedArray.getInt(R.styleable.SwitchButton_matchStyle,MatchStyle.WHOLE.ordinal()));
        typedArray.recycle();

        this.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                toggle();
            }
        });

        if(defaultOn){
            currentDelay = 1;
            toggleOn();
        }else{
            currentDelay = 0;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        DisplayMetrics dm = Resources.getSystem().getDisplayMetrics();

        if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
            widthSize = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,DEFAULT_WIDTH,dm);
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize,MeasureSpec.EXACTLY);
        }

        if(heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST){
            heightSize = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,DEFAULT_HEIGHT,dm);
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize,MeasureSpec.EXACTLY);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        final int width = getWidth();
        final int height = getHeight();

        radius = Math.min(width,height) * 0.5f;
        centerY = radius;
        startX = centerY;
        endX = width - radius;
        handlerMinX = startX + borderWidth;
        handlerMaxX = endX - borderWidth;
        handlerSize = height - 4*borderWidth;
        handlerX = toggleOn?handlerMaxX:handlerMinX;
        areaWidth = 0;
    }

    @Override
    public void draw(Canvas canvas) {
        /**繪制整個按鈕**/
        mRectF.set(0, 0, getWidth(), getHeight());
        mPaint.setColor(borderColor);
        canvas.drawRoundRect(mRectF,radius,radius,mPaint);

        /**繪制關閉灰色區域**/
        if(areaWidth > 0 ){
            final float cy = areaWidth * 0.5f;
            mRectF.set(handlerX - cy,centerY - cy,endX + cy,centerY + cy);
            mPaint.setColor(offColor);
            canvas.drawRoundRect(mRectF,cy,cy,mPaint);
        }

        /**繪制搖桿**/
        final float handlerRadius = handlerSize * 0.5f;
        mRectF.set(handlerX - handlerRadius, centerY - handlerRadius, handlerX + handlerRadius, centerY + handlerRadius);
        mPaint.setColor(handlerColor);
        canvas.drawRoundRect(mRectF, handlerRadius, handlerRadius, mPaint);

        super.draw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                if(matchStyle == MatchStyle.WHOLE){
                    performClick();
                }else if(matchStyle == MatchStyle.HANDLER && mRectF.contains(downX,downY)){
                    performClick();
                }
                break;
        }
        return true;
    }

    /**
     * 開關狀态切換
     */
    public void toggle(){
        toggle(animate);
    }
    /**
     * 開關狀态切換
     * @param animate
     */
    public void toggle(boolean animate){
        toggleOn = !toggleOn;
        takeEffect(animate);
    }

    /**
     * 開啟狀态
     */
    public void toggleOn(){
        toggleOn(animate);
    }
    /**
     * 開啟狀态
     * @param animate
     */
    public void toggleOn(boolean animate){
        toggleOn = true;
        takeEffect(animate);
    }

    /**
     * 關閉狀态
     */
    public void toggleOff(){
        toggleOff(animate);
    }
    /**
     * 關閉狀态
     * @param animate
     */
    public void toggleOff(boolean animate){
        toggleOn = false;
        takeEffect(animate);
    }

    /**
     * 開始處理狀态切換
     * @param animate
     */
    private void takeEffect(boolean animate){
        if(mListener != null){
            mListener.onToggle(toggleOn);
        }
        if(animate){
            mHandler.postDelayed(toggleRunnable,DELAYDURATION);
        }else {
            caculateEffect(toggleOn ? 1 : 0);
        }
    }

    /**
     * 時時計算
     * @param value
     */
    private void caculateEffect(double value){

        handlerX = (float)mapValueFromRangeToRange(value,0,1.0,handlerMinX,handlerMaxX);

        areaWidth = (float)mapValueFromRangeToRange(1.0-value,0,1.0,10,handlerSize);

        final int fb = Color.blue(onColor);
        final int fr = Color.red(onColor);
        final int fg = Color.green(onColor);

        final int tb = Color.blue(offColor);
        final int tr = Color.red(offColor);
        final int tg = Color.green(offColor);

        int sb = (int) mapValueFromRangeToRange(1.0 - value, 0, 1.0, fb, tb);
        int sr = (int) mapValueFromRangeToRange(1.0 - value, 0, 1.0, fr, tr);
        int sg = (int) mapValueFromRangeToRange(1.0 - value, 0, 1.0, fg, tg);

        sb = clamp(sb, 0, 255);
        sr = clamp(sr, 0, 255);
        sg = clamp(sg, 0, 255);

        borderColor = Color.rgb(sr, sg, sb);

        postInvalidate();
    }

    private int clamp(int value, int low, int high) {
        return Math.min(Math.max(value, low), high);
    }
    /**
     * Map a value within a given range to another range.
     * @param value the value to map
     * @param fromLow the low end of the range the value is within
     * @param fromHigh the high end of the range the value is within
     * @param toLow the low end of the range to map to
     * @param toHigh the high end of the range to map to
     * @return the mapped value
     */
    private  double mapValueFromRangeToRange(
            double value, double fromLow, double fromHigh,
            double toLow, double toHigh) {
        double fromRangeSize = fromHigh - fromLow;
        double toRangeSize = toHigh - toLow;
        double valueScale = (value - fromLow) / fromRangeSize;
        return toLow + (valueScale * toRangeSize);
    }

    private final Runnable toggleRunnable = new Runnable() {
        @Override
        public void run() {
            mHandler.removeCallbacks(toggleRunnable);
            if(toggleOn){
                if(currentDelay <= 1){
                    caculateEffect(currentDelay);
                    mHandler.postDelayed(toggleRunnable, DELAYDURATION);
                    currentDelay = currentDelay + 0.1;
                }else{
                    currentDelay = 1;
                }
            }else{
                if(currentDelay >= 0){
                    caculateEffect(currentDelay);
                    mHandler.postDelayed(toggleRunnable, DELAYDURATION);
                    currentDelay = currentDelay - 0.1;
                }else{
                    currentDelay = 0;
                }
            }
        }
    };

    /**
     * 設定開關監聽
     */
    public void setOnToggleChangedlistener(OnToggleChangedListener listener){
        this.mListener = listener;
    }
    /**
     * 開關狀态監聽
     */
    public interface OnToggleChangedListener{
        /**
         * 是否開啟
         * @param on
         */
        void onToggle(boolean on);
    }

    /**
     * 點選或者滑動時候響應的區域
     */
    private enum MatchStyle{
        WHOLE,//整個區域響應
        HANDLER;//隻有搖桿響應
        public static MatchStyle getValue(int index){
            for(MatchStyle style:values()){
                 if(style.ordinal() == index){
                     return style;
                 }
            }
            return WHOLE;
        }
    }
}
           

繼續閱讀