自定义登陆动画

[TOC]

效果图

loading.gif

一个自用的登陆动画,可以自定义背景颜色、文本、选中颜色、动画时间等。需要android21以上版本。

实现步骤

自定义登陆按钮

登陆按钮的绘制主要有以下几点:

  • 绘制按钮的静态背景及文本:这个没什么好说的,就是canvas的使用。
  • 按钮的焦点状态:通过onFocusChanged方法来判断按钮是否获得焦点,不再tv上使用可以无视这个。
  • 按钮的缩短动画:通过属性动画(值动画)获取一个不断改变的状态值,同时不断刷新view实现动画效果。
  • 按钮的旋转动画:就是一个普通的旋转补间动画RotateAnimation。
  • 登陆失败时的回置动画:同缩短动画。

文笔比较渣,还是直接看代码吧,比较简单,也加了注解。

public class LoginButton extends View {
    private final int duration = 300;//按钮缩短动画的持续时间
    private Paint paintBg;
    private Paint paintText;
    private Paint paintCircle;
    private int startWidth;//初始宽度
    private int startHeight;//初始高度
    private int marginLength;//缩进距离
    private boolean drawCircle = false;//绘制圆还是文本

    private int bgColor;
    private int bgColorFocused;
    private int textColor;
    private String text;
    private int textSize;

    private int textWidth;
    private int textHeight;

    private Loading loading;

    public void setLoading(Loading loading) {
        this.loading = loading;
    }

    public LoginButton(Context context) {
        super(context, null);
    }

    public LoginButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        //自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoginButton);

        bgColor = array.getColor(R.styleable.LoginButton_bgColor, Color.RED);
        bgColorFocused = array.getColor(R.styleable.LoginButton_bgColorFocused, 0);
        textColor = array.getColor(R.styleable.LoginButton_textColor, Color.WHITE);
        text = array.getString(R.styleable.LoginButton_text);
        textSize = array.getDimensionPixelSize(R.styleable.LoginButton_textSize, 24);

        array.recycle();
        init();
    }

    private void init() {
        //设置按钮可点击,可获得焦点
        setClickable(true);
        setFocusable(true);

        paintBg = new Paint();
        paintBg.setColor(bgColor);
        paintBg.setStyle(Paint.Style.FILL);
        paintBg.setAntiAlias(true);


        paintText = new Paint();
        paintText.setColor(textColor);
        paintText.setTextSize(textSize);
        Rect rect = new Rect();
        paintText.getTextBounds(text, 0, text.length(), rect);
        textWidth = rect.width();
        textHeight = rect.height();


        paintCircle = new Paint();
        paintCircle.setColor(textColor);
        paintCircle.setStrokeWidth(3);
        paintCircle.setStyle(Paint.Style.STROKE);
        paintCircle.setAntiAlias(true);
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (hasWindowFocus) {
            startWidth = getMeasuredWidth();
            startHeight = getMeasuredHeight();
            invalidate();
        }
    }

    //如果不在tv上使用,可以不加这段
    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
        if (gainFocus && bgColorFocused != 0)
            paintBg.setColor(bgColorFocused);
        else
            paintBg.setColor(bgColor);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawBg(canvas);
        if (drawCircle)
            drawCircle(canvas);
        else
            drawText(canvas);
    }

    //绘制圆,动画
    private void drawCircle(Canvas canvas) {
        RectF oval = new RectF();
        oval.left = startWidth / 2 - startHeight / 2 + startHeight / 5;
        oval.right = oval.left + startHeight / 5 * 3;
        oval.top = startHeight / 5;
        oval.bottom = oval.top + startHeight / 5 * 3;
        canvas.drawArc(oval, 0, 120, false, paintCircle);
    }

    //绘制文本
    private void drawText(Canvas canvas) {
        canvas.drawText(text, startWidth / 2 - textWidth / 2, startHeight / 2 + textHeight / 2, paintText);
    }

    //绘制背景
    private void drawBg(Canvas canvas) {
        int left = marginLength;
        int top = 0;
        int right = startWidth - marginLength;
        int bottom = startHeight;
        RectF rectF = new RectF(left, top, right, bottom);
        //绘制圆角矩形  1.范围  2.x方向的圆角半径  3.y方向的圆角半径  4.画笔
        canvas.drawRoundRect(rectF, startHeight / 2, startHeight / 2, paintBg);
    }

    /**
     * 点击按钮后启动动画
     */
    public void click() {
        setClickable(false);

        float scale = startHeight / (float) startWidth;
        ValueAnimator animator = ValueAnimator.ofFloat(1, scale);
        animator.setDuration(duration);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float flag = (float) animation.getAnimatedValue();
                marginLength = (int) (startWidth * (1 - flag)) / 2;

                if ((startWidth - marginLength * 2) < textWidth * 1.5)
                    drawCircle = true;

                invalidate();
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {

                startCircleAnim();

                if (loading != null)
                    loading.startLoading();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        animator.start();
    }

    /**
     * 登陆失败后回置
     */
    public void reset() {
        clearAnimation();

        float scale = startHeight / (float) startWidth;
        ValueAnimator animator = ValueAnimator.ofFloat(scale, 1);
        animator.setDuration(duration);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float flag = (float) animation.getAnimatedValue();
                marginLength = (int) (startWidth * (1 - flag)) / 2;
                invalidate();
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                drawCircle = false;
                invalidate();
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                setClickable(true);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        animator.start();
    }

    //开始按钮的旋转动画,表示加载中
    private void startCircleAnim() {
        RotateAnimation ra = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        ra.setDuration(duration * 2);
        ra.setInterpolator(new LinearInterpolator());
        ra.setRepeatCount(-1);
        ra.setFillAfter(true);
        startAnimation(ra);
    }

    public interface Loading {
        //添加我们自己的逻辑
        void startLoading();
    }
}

调用

如果要使用揭露动画展示下一个界面的话,可以把该按钮在屏幕中的位置信息传递过去,以此作为目标界面揭露动画的起点。

<lxf.widget.LoginButton
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:layout_marginTop="20dp"
            android:onClick="login"
            app:bgColor="@color/btn_normal"
            app:bgColorFocused="@color/btn_focused"
            app:text="登 陆"
            app:textColor="@color/white"
            app:textSize="18sp" />
            
            
public void login(final View view) {
        LoginButton lb = (LoginButton) view;
        final int[] location = new int[2];
        lb.getLocationInWindow(location);
        location[0] = location[0] + lb.getWidth() / 2;
        location[1] = location[1] + lb.getHeight() / 2;
        lb.setLoading(new LoginButton.Loading() {
            @Override
            public void startLoading() {
                loading(view, location);
            }
        });
        lb.click();
    }

    private void loading(final View view, final int[] location) {
        Observable.timer(1000, TimeUnit.MILLISECONDS)
                .subscribe(new Observer<Long>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(Long value) {
                        Intent intent = new Intent(view.getContext(), LoginTargetActivity.class);
                        intent.putExtra("location", location);
                        view.getContext().startActivity(intent);
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            ((Activity) view.getContext()).overridePendingTransition(0, 0);
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        Toast.makeText(getApplicationContext(),"登录失败",Toast.LENGTH_SHORT).show();
                        ((LoginButton) view).reset();
                    }

                    @Override
                    public void onComplete() {
                        finish();
                    }
                });
    }

使用揭露动画展示第二个页面

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login_target);
        
        WindowManager wm = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        screenW =  outMetrics.widthPixels;
        screenH =  outMetrics.heightPixels;

        rootView = (LinearLayout) findViewById(R.id.rootView);

        rootView.post(new Runnable() {
            @Override
            public void run() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    int[] location = getIntent().getIntArrayExtra("location");
                    Animator animator = ViewAnimationUtils.createCircularReveal(rootView, location[0], location[1], 0,
                            (float) Math.hypot(Math.max(location[0], screenW - location[0]), Math.max(location[1], screenH - location[1])));
                    animator.addListener(new Animator.AnimatorListener() {
                        @Override
                        public void onAnimationStart(Animator animation) {

                        }

                        @Override
                        public void onAnimationEnd(Animator animation) {
                            //// TODO: 业务逻辑 
                        }

                        @Override
                        public void onAnimationCancel(Animator animation) {

                        }

                        @Override
                        public void onAnimationRepeat(Animator animation) {

                        }
                    });
                    animator.setDuration(500).start();
                } 
            }
        });
    }

Demo地址

传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容