自定义控件—仿IOS7适用于Android的滑动开关

1 我们都知道Android4.0以上才带有滑动开关Switch,那么在4.0以下呢,很多人会选择用CheckBox,放两张图片,但是这样子只能点击,效果不太好,所以我就自定义了滑动开关SwitchButton 这么一个控件。下面是效果图 


2 下面是讲要用到的素材



3 以下是代码 

     1 继承 checkBox

    

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.widget.CheckBox;

import com.jieke.demoswitchbutton.R;

/**
 * Created by jieke on 2016/6/22.
 */
public class SwitchButton extends CheckBox {
    private Paint mPaint;
    private RectF mSaveLayerRectF;
    private float mFirstDownY;
    private float mFirstDownX;
    private int mClickTimeout;
    private int mTouchSlop;
    private final int MAX_ALPHA = 255;
    private int mAlpha = MAX_ALPHA;
    private boolean mChecked = true;
    private boolean mBroadcasting;// 标示是否正在执行监听事件中
    private boolean mTurningOn;// 标示位置是否达到开启状态
    private PerformClick mPerformClick;
    private OnCheckedChangeListener mOnCheckedChangeListener;
    private OnCheckedChangeListener mOnCheckedChangeWidgetListener;
    private boolean mAnimating;// 标示是否继续执行移动动画
    private final float VELOCITY = 350;// 定义按钮动画移动的最大长度
    private float mVelocity;// 按钮动画移动的最大像素长度
    private float mAnimationPosition;// 按钮动画移动的当前位置
    private float mAnimatedVelocity;// 按钮动画移动的实际位移(+mVelocity/-mVelocity)
    private Bitmap bmBgGreen;// 绿色背景
    private Bitmap bmBgWhite;// 白色背景
    private Bitmap bmBtnNormal;// 未按下时按钮
    private Bitmap bmBtnPressed;// 按下时按钮
    private Bitmap bmCurBtnPic;// 当前显示的按钮图片
    private Bitmap bmCurBgPic;// 当前背景图片
    private float bgWidth;// 背景宽度
    private float bgHeight;// 背景宽度
    private float btnWidth;// 按钮宽度
    private float offBtnPos;// 按钮关闭时位置
    private float onBtnPos;// 按钮开启时位置
    private float curBtnPos;// 按钮当前位置
    private float startBtnPos;// 开始按钮位置
    private final int COMMON_WIDTH_IN_PIXEL = 82;// 默认宽度
    private final int COMMON_HEIGHT_IN_PIXEL = 50;// 默认高度
    public SwitchButton(Context context) {
        this(context, null);
    }
    public SwitchButton(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.checkboxStyle);
    }

    public SwitchButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);
        Resources resources = context.getResources();

        // get attrConfiguration
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton);

        int width = (int) array.getDimensionPixelSize(R.styleable.SwitchButton_bmWidth, 0);
        int height = (int) array.getDimensionPixelSize(R.styleable.SwitchButton_bmHeight, 0);
        array.recycle();

        // size width or height
        if (width <= 0 || height <= 0) {
            width = COMMON_WIDTH_IN_PIXEL;
            height = COMMON_HEIGHT_IN_PIXEL;
        } else {
            float scale = (float) COMMON_WIDTH_IN_PIXEL
                    / COMMON_HEIGHT_IN_PIXEL;
            if ((float) width / height > scale) {
                width = (int) (height * scale);
            } else if ((float) width / height < scale) {
                height = (int) (width / scale);
            }
        }

        // get viewConfiguration
        mClickTimeout = ViewConfiguration.getPressedStateDuration()
                + ViewConfiguration.getTapTimeout();
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        // get Bitmap
        bmBgGreen = BitmapFactory.decodeResource(resources,
                R.drawable.switch_btn_bg_green);
        bmBgWhite = BitmapFactory.decodeResource(resources,
                R.drawable.switch_btn_bg_white);
        bmBtnNormal = BitmapFactory.decodeResource(resources,
                R.drawable.switch_btn_normal);
        bmBtnPressed = BitmapFactory.decodeResource(resources,
                R.drawable.switch_btn_pressed);

        // size Bitmap
        bmBgGreen = Bitmap.createScaledBitmap(bmBgGreen, width, height, true);
        bmBgWhite = Bitmap.createScaledBitmap(bmBgWhite, width, height, true);
        bmBtnNormal = Bitmap.createScaledBitmap(bmBtnNormal, height, height,
                true);
        bmBtnPressed = Bitmap.createScaledBitmap(bmBtnPressed, height, height,
                true);

        bmCurBtnPic = bmBtnNormal;// 初始按钮图片
        bmCurBgPic = mChecked ? bmBgGreen : bmBgWhite;// 初始背景图片
        bgWidth = bmBgGreen.getWidth();// 背景宽度
        bgHeight = bmBgGreen.getHeight();// 背景高度
        btnWidth = bmBtnNormal.getWidth();// 按钮宽度
        offBtnPos = 0;// 关闭时在最左边
        onBtnPos = bgWidth - btnWidth;// 开始时在右边
        curBtnPos = mChecked ? onBtnPos : offBtnPos;// 按钮当前为初始位置

        // get density
        float density = resources.getDisplayMetrics().density;
        mVelocity = (int) (VELOCITY * density + 0.5f);// 动画距离
        mSaveLayerRectF = new RectF(0, 0, bgWidth, bgHeight);
    }

    @Override
    public void setEnabled(boolean enabled) {
        mAlpha = enabled ? MAX_ALPHA : MAX_ALPHA / 3;
        super.setEnabled(enabled);
    }

    public boolean isChecked() {
        return mChecked;
    }

    public void toggle() {
        setChecked(!mChecked);
    }

    private void setCheckedDelayed(final boolean checked) {
        postDelayed(new Runnable() {
            @Override
            public void run() {
                setChecked(checked);
            }
        }, 10);
    }

    public void setChecked(boolean checked) {
        if (mChecked != checked) {
            mChecked = checked;

            // 初始化按钮位置
            curBtnPos = checked ? onBtnPos : offBtnPos;
            // 改变背景图片
            bmCurBgPic = checked ? bmBgGreen : bmBgWhite;
            invalidate();

            if (mBroadcasting) {
                // NO-OP
                return;
            }
            // 正在执行监听事件
            mBroadcasting = true;
            if (mOnCheckedChangeListener != null) {
                mOnCheckedChangeListener.onCheckedChanged(SwitchButton.this, mChecked);
            }
            if (mOnCheckedChangeWidgetListener != null) {
                mOnCheckedChangeWidgetListener.onCheckedChanged(SwitchButton.this, mChecked);
            }
            // 监听事件结束
            mBroadcasting = false;
        }
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        mOnCheckedChangeListener = listener;
    }

    void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
        mOnCheckedChangeWidgetListener = listener;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        float x = event.getX();
        float y = event.getY();
        float deltaX = Math.abs(x - mFirstDownX);
        float deltaY = Math.abs(y - mFirstDownY);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                ViewParent mParent = getParent();
                if (mParent != null) {
                    // 通知父控件不要拦截本view的触摸事件
                    mParent.requestDisallowInterceptTouchEvent(true);
                }
                mFirstDownX = x;
                mFirstDownY = y;
                bmCurBtnPic = bmBtnPressed;
                startBtnPos = mChecked ? onBtnPos : offBtnPos;
                break;
            case MotionEvent.ACTION_MOVE:
                float time = event.getEventTime() - event.getDownTime();
                curBtnPos = startBtnPos + event.getX() - mFirstDownX;
                if (curBtnPos >= onBtnPos) {
                    curBtnPos = onBtnPos;
                }
                if (curBtnPos <= offBtnPos) {
                    curBtnPos = offBtnPos;
                }
                mTurningOn = curBtnPos > bgWidth / 2 - btnWidth / 2;
                break;
            case MotionEvent.ACTION_UP:
                bmCurBtnPic = bmBtnNormal;
                time = event.getEventTime() - event.getDownTime();
                if (deltaY < mTouchSlop && deltaX < mTouchSlop
                        && time < mClickTimeout) {
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClick();
                    }
                } else {
                    startAnimation(mTurningOn);
                }
                break;
        }
        invalidate();
        return isEnabled();
    }

    private class PerformClick implements Runnable {
        public void run() {
            performClick();
        }
    }

    @Override
    public boolean performClick() {
        startAnimation(!mChecked);
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.saveLayerAlpha(mSaveLayerRectF, mAlpha, Canvas.MATRIX_SAVE_FLAG
                | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
                | Canvas.FULL_COLOR_LAYER_SAVE_FLAG
                | Canvas.CLIP_TO_LAYER_SAVE_FLAG);

        // 绘制底部图片
        canvas.drawBitmap(bmCurBgPic, 0, 0, mPaint);

        // 绘制按钮
        canvas.drawBitmap(bmCurBtnPic, curBtnPos, 0, mPaint);

        canvas.restore();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension((int) bgWidth, (int) bgHeight);
    }

    private void startAnimation(boolean turnOn) {
        mAnimating = true;
        mAnimatedVelocity = turnOn ? mVelocity : -mVelocity;
        mAnimationPosition = curBtnPos;
        new SwitchAnimation().run();
    }

    private void stopAnimation() {
        mAnimating = false;
    }

    private final class SwitchAnimation implements Runnable {
        @Override
        public void run() {
            if (!mAnimating) {
                return;
            }
            doAnimation();
            requestAnimationFrame(this);
        }
    }

    private void doAnimation() {
        mAnimationPosition += mAnimatedVelocity * ANIMATION_FRAME_DURATION
                / 1000;
        if (mAnimationPosition <= offBtnPos) {
            stopAnimation();
            mAnimationPosition = offBtnPos;
            setCheckedDelayed(false);
        } else if (mAnimationPosition >= onBtnPos) {
            stopAnimation();
            mAnimationPosition = onBtnPos;
            setCheckedDelayed(true);
        }
        curBtnPos = mAnimationPosition;
        invalidate();
    }

    private static final int MSG_ANIMATE = 1000;
    public static final int ANIMATION_FRAME_DURATION = 1000 / 60;

    public void requestAnimationFrame(Runnable runnable) {
        Message message = new Message();
        message.what = MSG_ANIMATE;
        message.obj = runnable;
        mHandler.sendMessageDelayed(message, ANIMATION_FRAME_DURATION);
    }

    private Handler mHandler = new Handler() {
        public void handleMessage(Message m) {
            switch (m.what) {
                case MSG_ANIMATE:
                    if (m.obj != null) {
                        ((Runnable) m.obj).run();
                    }
                    break;
            }
        }
    };
}

 2  values 里的attrs 文件

<declare-styleable name="SwitchButton">
        <attr name="bmWidth" format="dimension"></attr>
        <attr name="bmHeight" format="dimension"></attr>
    </declare-styleable>

3 布局文件中的适用

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <!-- 一定有id才能使用命名空间 -->
    <com.jieke.demoswitchbutton.com.jieke.view.SwitchButton
        xmlns:jieke="http://schemas.android.com/apk/res-auto"
        android:id="@+id/sb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        jieke:bmHeight="100dp"
        jieke:bmWidth="100dp"
        />
</RelativeLayout>


  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值