美文网首页Android
酷炫的Switcher

酷炫的Switcher

作者: leaflying | 来源:发表于2020-04-01 15:18 被阅读0次

看见别人用kotlin写的Switcher,我刚好想了解一下Android上的动画怎么玩,于是自己就慢慢跟着大神们写的代码用Java实现重新实现了,学习过程中,如果有什么需要改进的地方,希望有人可以提出,我会加以改正。


微信图片_20200401151723.jpg

Java源码

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.RectF;
import android.os.Build;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewOutlineProvider;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;


public class Switcher extends View {
    private int onColor = 0;

    private int offColor = 0;

    private int iconColor = 0;

    private float  switcherCornerRadius = 0f;

    private int defHeight = 0;
    private int defWidth = 0;

    //背景
    private RectF switcherRect = new RectF(0f, 0f, 0f, 0f);
    private Paint switcherPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    //图标
    private RectF iconRect = new RectF(0f, 0f, 0f, 0f);
    private RectF iconClipRect = new RectF(0f, 0f, 0f, 0f);
    private Paint iconPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private float switchElevation = 0f;

    private float shadowOffset = 0f;
    private float iconTranslateX = 0f;
    private boolean isChecked = true;

    private float iconCollapsedWidth = 0f;
    private float iconRadius = 0f;
    private float iconClipRadius = 0f;
    private float iconHeight = 0f;

    private Paint iconClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private AnimatorSet animatorSet =new AnimatorSet();

    private int currentColor = 0;

    private Long COLOR_ANIMATION_DURATION = 300L;

    private Bitmap shadow = null;

    private float iconProgress = 0f;

    private float onClickOffset = 0f;

    private Paint shadowPaint =new Paint(Paint.ANTI_ALIAS_FLAG);

    private void setOnClickOffset(float onClickOffset){
        this.onClickOffset = onClickOffset;
        switcherRect.left = onClickOffset + shadowOffset;
        switcherRect.top = onClickOffset + shadowOffset / 2;
        switcherRect.right = getWidth() - onClickOffset - shadowOffset;
        switcherRect.bottom = getHeight() - onClickOffset - shadowOffset - shadowOffset / 2;
        if (!isLollipopAndAbove()) generateShadow();
        invalidate();
    }

    private float lerp(float a,float b,float t){
        return a + (b - a) * t;
    }


    private void setIconProgress(float iconProgress){
        if(iconProgress!=this.iconProgress){
            this.iconProgress = iconProgress;
            float iconOffset = lerp(0f, iconRadius - iconCollapsedWidth / 2, iconProgress);
            iconRect.left = getWidth() - switcherCornerRadius - iconCollapsedWidth / 2 - iconOffset;
            iconRect.right = getWidth() - switcherCornerRadius + iconCollapsedWidth / 2 + iconOffset;

            float clipOffset = lerp(0f, iconClipRadius, iconProgress);
            iconClipRect.set(
                    iconRect.centerX() - clipOffset,
                    iconRect.centerY() - clipOffset,
                    iconRect.centerX() + clipOffset,
                    iconRect.centerY() + clipOffset
            );
            if (!isLollipopAndAbove()) generateShadow();
            postInvalidateOnAnimation();
        }
    }

    private boolean isLollipopAndAbove(){
        return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP;
    }

    public Switcher(Context context) {
        this(context,null);
    }

    public Switcher(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public Switcher(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Switcher);
        onColor = typedArray.getColor(R.styleable.Switcher_switcher_on_color, 0);
        offColor = typedArray.getColor(R.styleable.Switcher_switcher_off_color, 0);
        iconColor = typedArray.getColor(R.styleable.Switcher_switcher_icon_color, 0);

        switchElevation = typedArray.getDimension(R.styleable.Switcher_elevation, 0f);

        defHeight = typedArray.getDimensionPixelOffset(R.styleable.Switcher_switcher_height, 0);
        defWidth = typedArray.getDimensionPixelOffset(R.styleable.Switcher_switcher_width, 0);

        isChecked = typedArray.getBoolean(R.styleable.Switcher_android_checked, true);

       if (isChecked) setCurrentColor(onColor); else setCurrentColor(offColor);

        iconPaint.setColor(iconColor);

        typedArray.recycle();

        if (!isLollipopAndAbove() && switchElevation > 0f) {
            shadowPaint.setColorFilter(new PorterDuffColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN));
            shadowPaint.setAlpha(51);
            setShadowBlurRadius(switchElevation);
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }

        this.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                setChecked(!isChecked);
            }
        });
    }
    private float toPx(float value){
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getContext().getResources().getDisplayMetrics());
    }

    private void setShadowBlurRadius(float elevation) {
        float maxElevation = toPx(24f);
        switchElevation = Math.min(25f * (elevation / maxElevation), 25f);
    }

    private void setChecked(Boolean checked){
        this.setChecked(checked,true);
    }

    private void setChecked(Boolean checked, Boolean withAnimation){

        if (this.isChecked != checked) {

            this.isChecked = checked;

            if (withAnimation && getWidth() != 0) {
                animateSwitch();
            }else {
                animatorSet.cancel();
                if (!checked) {
                    currentColor = offColor;
                    iconProgress = 1f;
                    iconTranslateX = -(getWidth() - shadowOffset - switcherCornerRadius * 2);
                } else {
                    currentColor = onColor;
                    iconProgress = 0f;
                    iconTranslateX = -shadowOffset;
                }
            }
        }
    }

    private void animateSwitch(){

        animatorSet.cancel();
        animatorSet = new AnimatorSet();

        float newProgress = 1f;
        float iconTranslateA = 0f;
        float iconTranslateB = -(getWidth() - shadowOffset - switcherCornerRadius * 2);

        if (isChecked) {
            iconTranslateA = iconTranslateB;
            iconTranslateB = -shadowOffset;
            newProgress = 0f;
        }

        ValueAnimator switcherAnimator = ValueAnimator.ofFloat(iconProgress, newProgress);

        switcherAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                setIconProgress((float)valueAnimator.getAnimatedValue());
            }
        });

        ValueAnimator translateAnimator = ValueAnimator.ofFloat(0f, 1f);
        final float finalIconTranslateA = iconTranslateA;
        final float finalIconTranslateB = iconTranslateB;
        translateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float)valueAnimator.getAnimatedValue();
                iconTranslateX = lerp(finalIconTranslateA, finalIconTranslateB, value);
            }
        });

        translateAnimator.addListener(new Animator.AnimatorListener(){

            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                setOnClickOffset(0f);
            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });


        int toColor = isChecked?onColor:offColor;
        iconClipPaint.setColor(toColor);
        ValueAnimator colorAnimator =ValueAnimator.ofInt(currentColor, toColor);
        colorAnimator.setEvaluator(new ArgbEvaluator());
        colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                setCurrentColor((int) valueAnimator.getAnimatedValue());
            }
        });



        animatorSet.playTogether(switcherAnimator,translateAnimator,colorAnimator);
        animatorSet.setDuration(500);
        animatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                if(listenner!=null){
                    listenner.onClick(isChecked);
                }
            }

            @Override
            public void onAnimationEnd(Animator animator) {

            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        animatorSet.start();

    }

    private void generateShadow() {
        if (switchElevation == 0f) return;
        if (!isInEditMode()) {
            if (shadow == null) {
                shadow = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ALPHA_8);
            } else {
                shadow.eraseColor(Color.TRANSPARENT);
            }
            Canvas c = new Canvas(shadow);

            c.drawRoundRect(switcherRect, switcherCornerRadius, switcherCornerRadius, shadowPaint);

            RenderScript rs = RenderScript.create(getContext());
            ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, Element.U8(rs));
            Allocation input = Allocation.createFromBitmap(rs, shadow);
            Allocation output = Allocation.createTyped(rs, input.getType());
            blur.setRadius(switchElevation);
            blur.setInput(input);
            blur.forEach(output);
            output.copyTo(shadow);
            input.destroy();
            output.destroy();
            blur.destroy();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!isLollipopAndAbove() && switchElevation > 0f && !isInEditMode()) {
            canvas.drawBitmap(shadow, 0f, shadowOffset, null);
        }

        canvas.drawRoundRect(switcherRect, switcherCornerRadius, switcherCornerRadius, switcherPaint);

        canvas.translate(iconTranslateX,0);
        canvas.drawRoundRect(iconRect, switcherCornerRadius, switcherCornerRadius, iconPaint);

        if (iconClipRect.width() > iconCollapsedWidth)
            canvas.drawRoundRect(iconClipRect, iconRadius, iconRadius, iconClipPaint);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
            width = defWidth;
            height = defHeight;
        }

        if (!isLollipopAndAbove()) {
            width += ((int)switchElevation) * 2;
            height += ((int)switchElevation) * 2;
        }

        setMeasuredDimension(width, height);
    }

    private void setCurrentColor(int color){
        currentColor = color;
        switcherPaint.setColor(color);
        iconClipPaint.setColor(color);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setOutlineProvider(new SwitchOutline(w, h));
            setElevation(switchElevation);
        } else {
            shadowOffset = switchElevation;
            iconTranslateX = -shadowOffset;
        }

        switcherRect.left = shadowOffset;
        switcherRect.top = shadowOffset / 2;
        switcherRect.right = getWidth() - shadowOffset;
        switcherRect.bottom = getHeight() - shadowOffset - shadowOffset / 2;

        switcherCornerRadius = (getHeight() - shadowOffset * 2) / 2f;

        iconRadius = switcherCornerRadius * 0.6f;
        iconClipRadius = iconRadius / 2.25f;
        iconCollapsedWidth = iconRadius - iconClipRadius;

        iconHeight = iconRadius * 2f;

        iconRect.set(
                getWidth() - switcherCornerRadius - iconCollapsedWidth / 2,
                ((getHeight() - iconHeight) / 2f) - shadowOffset / 2,
                getWidth() - switcherCornerRadius + iconCollapsedWidth / 2,
                (getHeight() - (getHeight() - iconHeight) / 2f) - shadowOffset / 2
        );

        if (!isChecked) {
            iconRect.left = getWidth() - switcherCornerRadius - iconCollapsedWidth / 2 - (iconRadius - iconCollapsedWidth / 2);
            iconRect.right = getWidth() - switcherCornerRadius + iconCollapsedWidth / 2 + (iconRadius - iconCollapsedWidth / 2);

            iconClipRect.set(
                    iconRect.centerX() - iconClipRadius,
                    iconRect.centerY() - iconClipRadius,
                    iconRect.centerX() + iconClipRadius,
                    iconRect.centerY() + iconClipRadius
            );

            iconTranslateX = -(getWidth() - shadowOffset - switcherCornerRadius * 2);
        }
        if (!isLollipopAndAbove()) generateShadow();

    }

    private Listenner listenner = null;

    public void setListenner(Listenner listenner) {
        this.listenner = listenner;
    }

    private interface Listenner{
        public void onClick(boolean isCheck);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private class  SwitchOutline extends ViewOutlineProvider {
        private int width,height;

        public SwitchOutline(int width, int height) {
            this.width = width;
            this.height = height;
        }

        @Override
        public void getOutline(View view, Outline outline) {
            outline.setRoundRect(0, 0, width, height, switcherCornerRadius);
        }
    }
}

attrs:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Switcher">
        <attr name="switcher_on_color" format="color"/>
        <attr name="switcher_off_color"  format="color"/>
        <attr name="switcher_icon_color"  format="color"/>
        <attr name="elevation" format="dimension"/>
        <attr name="switcher_height" format="dimension"/>
        <attr name="switcher_width" format="dimension"/>
    </declare-styleable>
</resources>

调用方法:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">



    <com.example.qmuitest.TestView
        android:id="@+id/switcher"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:switcher_height="60dp"
        app:switcher_width="120dp"
        app:layout_constraintRight_toRightOf="parent"
        app:switcher_on_color="@android:color/holo_blue_bright"
        app:switcher_off_color="@android:color/holo_red_light"
        app:switcher_icon_color="@android:color/white" />

</androidx.constraintlayout.widget.ConstraintLayout>
微信图片_20200401151733.jpg
参考:
炫酷!从未见过如此Q弹的Switcher: https://blog.csdn.net/zwluoyuxi/article/details/104824809

相关文章

网友评论

    本文标题:酷炫的Switcher

    本文链接:https://www.haomeiwen.com/subject/xqhauhtx.html