自定义登陆动画

[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地址

传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容