文档章节

[Material Design] MaterialButton 效果进阶 动画自动移动进行对齐效果

Qiujuer
 Qiujuer
发布于 2014/10/12 08:46
字数 2490
阅读 74
收藏 1
点赞 0
评论 2

原创作品,转载请注明出处:http://blog.csdn.net/qiujuer/article/details/39998961

做 Android 动画效果一段时间了,感觉深深喜欢上了钻研特效。在手机上显示自己的特效是一件很不错的事情。

废话不多说,前面几天我发布了:[Material Design] 教你做一个Material风格、动画的按钮(MaterialButton)

在其中我讲解了我对 Android L 中 Material 效果的按钮的动画实现方式,今天的文章将基于其上进行进阶讲解新的特效。

在 MaterialButton 中的特效原理是:用户点击时启动一个动画,该动画是在点击位置画颜色渐变同时半径变大的圆,从而实现扩散效果;具体可点击上面的链接查看一下。在按钮中的这样的特效距离谷歌的还是有很大的差距的,下面来对比一下:

官方的:


我们上个版本的:


可以看出谷歌的是有位移效果,而我们的是原地扩散的效果,当然动画速度这个与PS的设置有关,不做比较,实际速度比上面的略快。

下面咱们就来试试做做位移的特效,先画个图给大家看看:


相信大家都能看懂,第一种就是之前的实现方式,只是在原地扩散,第二种就是新的,将在扩散的同时向中心靠拢,且为了达到更加好的视觉效果,靠拢中心的XY轴速度并不是一样的,X轴的靠拢时间=整个扩散时间,向Y轴靠拢的时间~=整个扩散时间*0.3(且都是先快后慢),现在来看看成品效果:


点击中间的时候与第一种差距不大,但是点击两边的时候将会有明显的差距,能感觉到向中心靠拢的触觉。是不是和谷歌的相比起来又近了一些了?


说了这个多的理论与演示,下面来说说整个的实现:

首先我们抽取上一篇文章的成果作为这篇的开头,具体怎么新建控件就不再做介绍了,先看看上一篇的代码成果(该代码进行了一定的修改):

public class MaterialButton extends Button {
    private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();
    private static final long ANIMATION_TIME = 600;

    private Paint backgroundPaint;
    private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();
    private float paintX, paintY, radius;


    public MaterialButton(Context context) {
        super(context);
        init(null, 0);
    }

    public MaterialButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

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

    @SuppressWarnings("deprecation")
    private void init(AttributeSet attrs, int defStyle) {
        ...
    }


    @SuppressWarnings("NullableProblems")
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.drawCircle(paintX, paintY, radius, backgroundPaint);
        canvas.restore();

        super.onDraw(canvas);
    }

    @SuppressWarnings("NullableProblems")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            paintX = event.getX();
            paintY = event.getY();
            startRoundAnimator();
        }
        return super.onTouchEvent(event);
    }

    /**
     * =============================================================================================
     * The Animator methods
     * =============================================================================================
     */

    /**
     * Start Round Animator
     */
    private void startRoundAnimator() {
        float start, end, height, width;
        long time = (long) (ANIMATION_TIME * 1.85);

        //Height Width
        height = getHeight();
        width = getWidth();

        //Start End
        if (height < width) {
            start = height;
            end = width;
        } else {
            start = width;
            end = height;
        }

        float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;
        float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;

        //If The approximate square approximate square
        if (startRadius > endRadius) {
            startRadius = endRadius * 0.6f;
            endRadius = endRadius / 0.8f;
            time = (long) (time * 0.5);
        }

        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),
                ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))
        );
        // set Time
        set.setDuration((long) (time / end * endRadius));
        set.setInterpolator(ANIMATION_INTERPOLATOR);
        set.start();
    }    


    /**
     * =============================================================================================
     * The custom properties
     * =============================================================================================
     */

    
    private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {
        @Override
        public Float get(MaterialButton object) {
            return object.radius;
        }

        @Override
        public void set(MaterialButton object, Float value) {
            object.radius = value;
            invalidate();
        }
    };

    private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {
        @Override
        public Integer get(MaterialButton object) {
            return object.backgroundPaint.getColor();
        }

        @Override
        public void set(MaterialButton object, Integer value) {
            object.backgroundPaint.setColor(value);
        }
    };

}
在上述代码中我们实现了点击时进行扩散的效果,初始化控件部分由于我加入了许多的代码这里删除了,具体可以看看我的项目实现,最后会给出地址。

现在基于此开工!

首先我们建立 两个新的属性 分别X坐标与Y坐标属性:

private Property<MaterialButton, Float> mPaintXProperty = new Property<MaterialButton, Float>(Float.class, "paintX") {
        @Override
        public Float get(MaterialButton object) {
            return object.paintX;
        }

        @Override
        public void set(MaterialButton object, Float value) {
            object.paintX = value;
        }
    };

    private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class, "paintY") {
        @Override
        public Float get(MaterialButton object) {
            return object.paintY;
        }

        @Override
        public void set(MaterialButton object, Float value) {
            object.paintY = value;
        }
    };
在这两个属性中并未调用第一篇所说的 “ invalidate();”方法进行界面刷新,因为该方法应该放在持续时间最长的半径属性中调用。

之后我们获取到高宽 以及根据高和宽 计算出对应的 开始半径与结束半径:

<span style="white-space:pre">	</span>float start, end, height, width, speed = 0.3f;
        long time = ANIMATION_TIME;

        //Height Width
        height = getHeight();
        width = getWidth();

        //Start End
        if (height < width) {
            start = height;
            end = width;
        } else {
            start = width;
            end = height;
        }
        start = start / 2 > paintY ? start - paintY : paintY;
        end = end * 0.8f / 2f;

        //If The approximate square approximate square
        if (start > end) {
            start = end * 0.6f;
            end = end / 0.8f;
            time = (long) (time * 0.65);
            speed = 1f;
        }
我们首先比较了高与宽的长度 把短的赋予为开始半径 长的赋予为结束半径。

第二步,我们把开始长度除以2  得出其一半的长度 然后与 点击时的Y轴坐标比较,如果Y轴较长则取Y,如果不够则取其相减结果。这样能保证点击开始时的半径能刚好大于其高或者宽(短的一边),这样就不会出现小圆扩散的效果,看起来将会由椭圆的效果(当然以后将会直接画出椭圆)

第三步,我们运算出结束半径,同时保证结束半径为长的一边的一半的8/10 这样的效果是不会出现布满整个控件的情况。8/10 的空间刚好是个不错的选择。

第四步,判断开始长度是否大于结束长度,如果是(近似正方形情况),进行一定规则的重新运算,保证其开始半径能刚好与控件长度差不多(0.48左右),结束半径能刚刚布满控件,同时减少动画时间

当然,我现在才发现了一个BUG,在第二步的地方的BUG,大家看看,希望能提出是哪里的BUG;就当是一个互动!该BUG将会在下个版本修复。


之后我们建立每个属性的动画,并给每个属性动画设置对应的时间:

<span style="white-space:pre">	</span>//PaintX
        ObjectAnimator aPaintX = ObjectAnimator.ofFloat(this, mPaintXProperty, paintX, width / 2);
        aPaintX.setDuration(time);
        //PaintY
        ObjectAnimator aPaintY = ObjectAnimator.ofFloat(this, mPaintYProperty, paintY, height / 2);
        aPaintY.setDuration((long) (time * speed));
        //Radius
        ObjectAnimator aRadius = ObjectAnimator.ofFloat(this, mRadiusProperty, start, end);
        aRadius.setDuration(time);
        //Background
        ObjectAnimator aBackground = ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2));
        aBackground.setDuration(time);

可以看见Y轴的时间乘以了一个speed变量,该变量默认是0.3 如果是近似正方形将初始化为1以便能同时对齐到中心位置,在上一步中有对应变量。

然后咱们把所有的属性动画添加到一个动画集并设置其速度方式为:先快后慢。最后启动该动画集。

//AnimatorSet
        AnimatorSet set = new AnimatorSet();
        set.playTogether(aPaintX, aPaintY, aRadius, aBackground);
        set.setInterpolator(ANIMATION_INTERPOLATOR);
        set.start();


以上就是最新的动画效果的实现原理及代码了,当然我们可以将其合并到第一篇的代码中,并使用一个 Bool 属性来控制使用哪一种动画:

public class MaterialButton extends Button {
    private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator();
    private static final long ANIMATION_TIME = 600;

    private Paint backgroundPaint;
    private static ArgbEvaluator argbEvaluator = new ArgbEvaluator();
    private float paintX, paintY, radius;
    private Attributes attributes;

    public MaterialButton(Context context) {
        super(context);
        init(null, 0);
    }

    public MaterialButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

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

    @SuppressWarnings("deprecation")
    private void init(AttributeSet attrs, int defStyle) {
        ...
    }


    @SuppressWarnings("NullableProblems")
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.drawCircle(paintX, paintY, radius, backgroundPaint);
        canvas.restore();

        super.onDraw(canvas);
    }

    @SuppressWarnings("NullableProblems")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (attributes.isMaterial() && event.getAction() == MotionEvent.ACTION_DOWN) {
            paintX = event.getX();
            paintY = event.getY();
            if (attributes.isAutoMove())
                startMoveRoundAnimator();
            else
                startRoundAnimator();
        }
        return super.onTouchEvent(event);
    }

    /**
     * =============================================================================================
     * The Animator methods
     * =============================================================================================
     */

    /**
     * Start Round Animator
     */
    private void startRoundAnimator() {
        float start, end, height, width;
        long time = (long) (ANIMATION_TIME * 1.85);

        //Height Width
        height = getHeight();
        width = getWidth();

        //Start End
        if (height < width) {
            start = height;
            end = width;
        } else {
            start = width;
            end = height;
        }

        float startRadius = (start / 2 > paintY ? start - paintY : paintY) * 1.15f;
        float endRadius = (end / 2 > paintX ? end - paintX : paintX) * 0.85f;

        //If The approximate square approximate square
        if (startRadius > endRadius) {
            startRadius = endRadius * 0.6f;
            endRadius = endRadius / 0.8f;
            time = (long) (time * 0.5);
        }

        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(this, mRadiusProperty, startRadius, endRadius),
                ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2))
        );
        // set Time
        set.setDuration((long) (time / end * endRadius));
        set.setInterpolator(ANIMATION_INTERPOLATOR);
        set.start();
    }

    /**
     * Start Move Round Animator
     */
    private void startMoveRoundAnimator() {
        float start, end, height, width, speed = 0.3f;
        long time = ANIMATION_TIME;

        //Height Width
        height = getHeight();
        width = getWidth();

        //Start End
        if (height < width) {
            start = height;
            end = width;
        } else {
            start = width;
            end = height;
        }
        start = start / 2 > paintY ? start - paintY : paintY;
        end = end * 0.8f / 2f;

        //If The approximate square approximate square
        if (start > end) {
            start = end * 0.6f;
            end = end / 0.8f;
            time = (long) (time * 0.65);
            speed = 1f;
        }

        //PaintX
        ObjectAnimator aPaintX = ObjectAnimator.ofFloat(this, mPaintXProperty, paintX, width / 2);
        aPaintX.setDuration(time);
        //PaintY
        ObjectAnimator aPaintY = ObjectAnimator.ofFloat(this, mPaintYProperty, paintY, height / 2);
        aPaintY.setDuration((long) (time * speed));
        //Radius
        ObjectAnimator aRadius = ObjectAnimator.ofFloat(this, mRadiusProperty, start, end);
        aRadius.setDuration(time);
        //Background
        ObjectAnimator aBackground = ObjectAnimator.ofObject(this, mBackgroundColorProperty, argbEvaluator, attributes.getColor(1), attributes.getColor(2));
        aBackground.setDuration(time);

        //AnimatorSet
        AnimatorSet set = new AnimatorSet();
        set.playTogether(aPaintX, aPaintY, aRadius, aBackground);
        set.setInterpolator(ANIMATION_INTERPOLATOR);
        set.start();
    }


    /**
     * =============================================================================================
     * The custom properties
     * =============================================================================================
     */

    private Property<MaterialButton, Float> mPaintXProperty = new Property<MaterialButton, Float>(Float.class, "paintX") {
        @Override
        public Float get(MaterialButton object) {
            return object.paintX;
        }

        @Override
        public void set(MaterialButton object, Float value) {
            object.paintX = value;
        }
    };

    private Property<MaterialButton, Float> mPaintYProperty = new Property<MaterialButton, Float>(Float.class, "paintY") {
        @Override
        public Float get(MaterialButton object) {
            return object.paintY;
        }

        @Override
        public void set(MaterialButton object, Float value) {
            object.paintY = value;
        }
    };

    private Property<MaterialButton, Float> mRadiusProperty = new Property<MaterialButton, Float>(Float.class, "radius") {
        @Override
        public Float get(MaterialButton object) {
            return object.radius;
        }

        @Override
        public void set(MaterialButton object, Float value) {
            object.radius = value;
            invalidate();
        }
    };

    private Property<MaterialButton, Integer> mBackgroundColorProperty = new Property<MaterialButton, Integer>(Integer.class, "bg_color") {
        @Override
        public Integer get(MaterialButton object) {
            return object.backgroundPaint.getColor();
        }

        @Override
        public void set(MaterialButton object, Integer value) {
            object.backgroundPaint.setColor(value);
        }
    };

}


在最后附上两种方式运行后的效果对比图:




还不错吧?
要是感觉比较和你的胃口,这里有我的整个项目:

Genius-Android



后续将会上传对应的 测试APK ,当然如果大伙觉得动画不错我把动画的视频文件一起上传打包。


© 著作权归作者所有

共有 人打赏支持
Qiujuer

Qiujuer

粉丝 139
博文 25
码字总数 60206
作品 2
深圳
程序员
加载中

评论(2)

Qiujuer
Qiujuer

引用来自“-ROC-”的评论

沙发 顶。期待博主更多动画的文章

谢谢你的支持,后续会尽量的完善,加入更多的动画,同时保证性能。谢谢你的支持;有兴趣可以迁移GitHub一起开发啊。
-ROC-
-ROC-
沙发 顶。期待博主更多动画的文章
[Material Design] 教你做一个Material风格、动画的按钮(MaterialButton)

原创作品,转载请注明出处:http://blog.csdn.net/qiujuer/article/details/39831451 前段时间Android L 发布了,相信看过发布会了解过的朋友都为其中的 “Material Design” 感到由衷的惊艳...

Qiujuer ⋅ 2014/10/07 ⋅ 9

Genius-Android 开源框架发布 2.0.0 版本

Genius-Android是Android中一些常用的的方法集合,Genius提供6个基本板块: app(Ui)animation(动画)widget(Material控件)command(命令行)net tool(Ping、Dns...)util(常用方法,类)...

Qiujuer ⋅ 2015/01/07 ⋅ 29

Button 不错的点击效果

官方的: 我们上个版本的: 可以看出谷歌的是有位移效果,而我们的是原地扩散的效果,当然动画速度这个与PS的设置有关,不做比较,实际速度比上面的略快。 下面咱们就来试试做做位移的特效,...

竹草席 ⋅ 2015/06/16 ⋅ 0

Android 5.X的新特性及Material Design

导语 说来惭愧,Android8.0奥利奥都出了,我还在学5.0的新特性,看来得再努把力了,Material Design动画确实好看,查看实例戳这里。 主要内容 Android5.X UI设计初步 Palette 视图与阴影 Ti...

一个有故事的程序员 ⋅ 2017/10/24 ⋅ 0

Android Design Support Library v28 新增组件详解

1.简介 Google在近期发布了最新的Design Support Library 28.0.0-alpha3版本,其中新增了一些非常实用的组件,本篇文章将会对其进行详细的介绍,一起来看下! 如果你对Material Design还不太...

容华谢后 ⋅ 06/21 ⋅ 0

Material Design学习之 Bottom Sheets (顺便提提CoordinatorLayout)

转载请注明出处:王亟亟的大牛之路 昨天连续上了2篇介绍第三方库的文章,正直好久没提交自己写东西了,那么就补一篇之前MD系列漏的部分 Bottom Sheets Bottom Sheets–底部动作条 底部动作条(...

ddwhan0123 ⋅ 2016/03/08 ⋅ 0

彻底理解 Android 中的阴影

如果我们想创造更好的 Android App,我相信我们需要遵循 Material Design 的设计规范。一般而言,Material Design 是一个包含光线,材质和投影的三维环境。如果我们想要在 App 的开发过程中,...

丁佳辉 ⋅ 04/18 ⋅ 0

创建Material Design风格的Android应用--使用自定义动画

动画在Material Design设计中给用户反馈放用户点击时,并且在程序用户界面中提供连贯的视觉。Material主题为按钮(Button)和activity的转换提供了一些默认的动画,在android5.0(api 21)和...

码农明明 ⋅ 2014/11/13 ⋅ 0

在低版本android系统上实现Material设计应用

Material Design真的很好看,动画效果真的很实用。前面也写了一些文章介绍如何编写Material风格的程序,但是很多都是一些新的api,低版本上面没有这些api,我们没办法使用。但是不用气馁,g...

码农明明 ⋅ 2014/11/17 ⋅ 12

Android UI框架(中)

唯美清新的switch按钮 优雅的switch状态按钮 通用的引导页和轮播器 仿微信图片选择器 快捷设置沉浸式状态栏 复杂表格布局 最全的开源集合 阻尼效果的界面 炫酷的纸飞机下拉刷新控件 仿ios水滴...

Moosphon ⋅ 2017/12/31 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

ThreadPoolExecutor

ThreadPoolExecutor public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, ......

4rnold ⋅ 昨天 ⋅ 0

Java正无穷大、负无穷大以及NaN

问题来源:用Java代码写了一个计算公式,包含除法和对数和取反,在页面上出现了-infinity,不知道这是什么问题,网上找答案才明白意思是负的无穷大。 思考:为什么会出现这种情况呢?这是哪里...

young_chen ⋅ 昨天 ⋅ 0

前台对中文编码,后台解码

前台:encodeURI(sbzt) 后台:String param = URLDecoder.decode(sbzt,"UTF-8");

west_coast ⋅ 昨天 ⋅ 0

实验楼—MySQL基础课程-挑战3实验报告

按照文档要求创建数据库 sudo sercice mysql startwget http://labfile.oss.aliyuncs.com/courses/9/createdb2.sqlvim /home/shiyanlou/createdb2.sql#查看下数据库代码 代码创建了grade......

zhangjin7 ⋅ 昨天 ⋅ 0

VS2015配置并运行汇编(一步一步照图做)【vs2017的链接在最后】

x64: TITLE Add and Subtract (AddSub.asm) ; This program adds and subtracts 32-bit integers. ; Last update: 2/1/02 ;.MODEL flat,stdcall x64 not su......

simpower ⋅ 昨天 ⋅ 0

一起读书《深入浅出nodejs》-node模块机制

node 模块机制 前言 说到node,就不免得提到JavaScript。JavaScript自诞生以来,经历了工具类库、组件库、前端框架、前端应用的变迁。通过无数开发人员的努力,JavaScript不断被类聚和抽象,...

小草先森 ⋅ 昨天 ⋅ 0

Java桌球小游戏

其实算不上一个游戏,就是两张图片,不停的重画,改变ball图片的位置。一个左右直线碰撞的,一个有角度碰撞的。 左右直线碰撞 package com.bjsxt.test;import javax.swing.*;import j...

森林之下 ⋅ 昨天 ⋅ 0

你真的明白RPC 吗?一起来探究 RPC 的实质

你真的明白RPC 吗?一起来探究 RPC 的实质 不论你是科班出身还是半路转行,这么优秀的你一定上过小学语文,那么对扩句和缩句你一定不陌生。缩句就是去除各种修饰提炼出一句话的核心,而不失基...

AI9o後 ⋅ 昨天 ⋅ 0

z-index设置失效?

今天碰到了一个问题,就是在给li设置提示框的时候,有用到遮罩效果,本来想把对应的出现在最顶层,可是不管将li设置的z-index值设为多大,li都没有出现在遮罩层之上。 我在网上查了z-index设...

IrisHunag ⋅ 昨天 ⋅ 0

CyclicBarrier、CountDownLatch以及Semaphore使用及其原理分析

CyclicBarrier、CountDownLatch以及Semaphore是Java并发包中几个常用的并发组件,这几个组件特点是功能相识很容易混淆。首先我们分别介绍这几个组件的功能然后再通过实例分析和源码分析其中设...

申文波 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部