自定义View开关按钮

今天来介绍下如何自定义类似iOS的按钮。


switch.gif

要想自定义View,需要了解自定义View的相关步骤,一般来说自定义View需要实现以下操作:


自定义view步骤

自定义切换按钮

1.自定义属性

根据自定义View中需要用户设置的属性,来构造自定义属性,直接在xml资源文件中的resources标签下声明:

<declare-styleable name="Switch">
    <attr name="checked" format="reference|boolean"/>
    <attr name="switchOnColor" format="reference|color"/>
    <attr name="switchOffColor" format="reference|color"/>
    <attr name="spotOnColor" format="reference|color"/>
    <attr name="spotOffColor" format="reference|color"/>
    <attr name="spotPadding" format="reference|dimension"/>
    <attr name="duration" format="reference|integer" />
</declare-styleable>

并在继承View类的Switch类中声明全局变量:

private int switchOnColor;
private int switchOffColor;
private int spotOnColor;
private int spotOffColor;
private int spotPadding;
private boolean mChecked;
private int duration;
2.初始化构造函数

一般来说,如果自定义View用于在布局文件声明,则必须初始化覆盖基类的两个参数的构造函数;其他带参构造函数,根据需求进行初始化。

在构造函数中通过obtainStyledAttributes()方法获取用户设置的属性值:

public Switch(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Switch);
        switchOnColor = a.getColor(R.styleable.Switch_switchOnColor, DEFAULT_SWITCH_ON_COLOR);
        switchOffColor = a.getColor(R.styleable.Switch_switchOffColor, DEFAULT_SWITCH_OFF_COLOR);
        spotOnColor = a.getColor(R.styleable.Switch_spotOnColor, DEFAULT_SPOT_ON_COLOR);
        spotOffColor = a.getColor(R.styleable.Switch_spotOffColor, DEFAULT_SPOT_OFF_COLOR);
        spotPadding = a.getDimensionPixelSize(R.styleable.Switch_spotPadding, dp2px(DEFAULT_SPOT_PADDING));
        duration = a.getInteger(R.styleable.Switch_duration, ANIMATION_DURATION);
        mChecked = a.getBoolean(R.styleable.Switch_checked, false);
        a.recycle();

        state = mChecked ? State.SWITCH_ON : State.SWITCH_OFF;
        setClickable(true);
}
3.测量View——onMeasure

onMeasure方法是决定自定义View布局大小的关键,可通过MeasureSpec获取父布局分配给子布局的大小及布局模式。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

    int width = dp2px(DEFAULT_WIDTH) + getPaddingLeft() + getPaddingRight();
    int height = dp2px(DEFAULT_HEIGHT) + getPaddingTop() + getPaddingBottom();

    if (widthSpecMode != MeasureSpec.AT_MOST) {
        width = Math.max(width, widthSpecSize);
    }

    if (heightSpecMode != MeasureSpec.AT_MOST) {
        height = Math.max(height, heightSpecSize);
    }

    setMeasuredDimension(width, height);
}
4.给定View高宽——setMeasuredDimension

通过setMeasuredDimension方法设置自定义布局想要的View大小。代码如上所示。

5.绘制View——onDraw

最核心的方法莫过于onDraw了,onDraw是绘制View的关键,结合ValueAnimatorObjectAnimator等可实现View的动画效果。

private void animateToCheckedState() {
    valueAnimator = ValueAnimator.ofFloat(0, 1);
    valueAnimator.setDuration(duration);
    valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            currentPos = (float) animation.getAnimatedValue();
            invalidate();
        }
    });

    valueAnimator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animation) {
            super.onAnimationStart(animation);
            isMoving = true;
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            isMoving = false;
        }
    });

    if (!valueAnimator.isRunning()) {
        valueAnimator.start();
        currentPos = 0;
    }
}
6.回调监听

切换按钮的回调,主要体现在开关状态的变化:

public interface OnCheckedChangeListener {
    /**
     * Called when the checked state of a switch has changed.
     *
     * @param s The switch whose state has changed.
     * @param isChecked The new checked state of switch.
     */
    void onCheckedChanged(Switch s, boolean isChecked);
}
7.手势处理

本项目中并未使用onTouchEvent方法处理手势监听,而是覆盖了父类的performClick方法,同时保证了OnClickListener的生效。

@Override
public boolean performClick() {
    toggle();

    final boolean handled = super.performClick();
    if (!handled) {
        // View only makes a sound effect if the onClickListener was
        // called, so we'll need to make one here instead.
        playSoundEffect(SoundEffectConstants.CLICK);
    }

    return handled;
}
作者 @fynn
项目地址 github

猜你喜欢

转载自blog.csdn.net/a214024475/article/details/54380165