高级UI- 属性动画炫酷动画案例+淘宝动画+源码解析+策略模式使用

属性动画源码:

   ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(ivTest, "translationX", 0f, 200f);//平移
//        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(ivTest,"backgroundColor", red,green);//颜色
        objectAnimator.setDuration(500);
        objectAnimator.start();
    }

几个关键的点:

  1. ObjectAnimator 继承ValueAnimator
  2. 调用父类>anim.setFloatValues
  3. 调用PropertyValueHodler 的 valuesHolder.setFloatValues(values);
  4. PropertyValueHodler mKeyframes = KeyframeSet.ofFloat(values);//解析每一帧到KeyFrames集合List
  5. 第二个参数 是属性 利用反射获取方法. 具体操作

PropertyValueHodler

PropertyValueHodler.setAnimatedValue(){
mSetter.invoke(target, mTmpValueArray);//PropertyValueHodler类里面第1166行,最终将上面反射的Method调用了—即调用了setTranslationX(x)等方法达到了动画的目的。

第五点 的代码

动态传递进来的参数

	static String getMethodName(String prefix, String propertyName) {
        if (propertyName == null || propertyName.length() == 0) {
            // shouldn't get here
            return prefix;
        }
        char firstLetter = Character.toUpperCase(propertyName.charAt(0));
        String theRest = propertyName.substring(1);
        return prefix + firstLetter + theRest;
    }

案例1

刷鲜花效果

思路:两种。

  1. 自绘控件,如何让鲜花按照贝塞尔曲线运动----绘制的花(可以是SVG or 很小的图片)按照曲线路径绘制
    Path+PathMeasure (小船的案例。)
  2. 控制View的动画,每一朵鲜花都是一个ImageView,可以通过属性动画控制曲线运动ObjectAnimator.贝塞尔曲线

https://blog.csdn.net/qq_27495349/article/details/53008975?locationNum=2&fps=1

案例2

最终效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UUrgInw8-1604476455877)(http://ws1.sinaimg.cn/large/958c5b69ly1fwnrpu3et9g206j0bnu11.gif)]

淘宝底部弹出框效果

思路 :

两个view 一个在底部一个在上面 底部是隐藏的 . 后面那个 是沿着X轴旋转 加上透明度变换.
底部开始是隐藏的,开始动画时显示,并且从-h(自身的高度) 到0 的位移动画.

看效果图 背景的动画有这个几个:
显示first_view:1.透明度动画;2.缩放动画;3.翻转动画

  1. 透明度动画
  2. 旋转动画 旋转过去
  3. 旋转动画 旋转回来
  4. 缩放动画
  5. 由于缩放造成离顶部有一个距离,需要平移

第二个布局的动画 就很简单了 仅仅是从底部上来的动画

  1. 平移动画

先实现 第一个view的

  1. 透明度动画
  2. 旋转动画 旋转过去
  3. 旋转动画 旋转回来

代码

 //背景透明度变化
        ObjectAnimator firstAlphaAnim = ObjectAnimator.ofFloat(first,"alpha",1.0f,0.7f);
        firstAlphaAnim.setDuration(300);

        //旋转动画 开始
        ObjectAnimator firstRotationXanim =ObjectAnimator.ofFloat(first,"rotationX",0f,20f);
        firstRotationXanim.setDuration(300);

        //旋转动画 回来
        ObjectAnimator firstResumeRotationXanim =ObjectAnimator.ofFloat(first,"rotationX",20f,0f);
        firstRotationXanim.setDuration(300);

        AnimatorSet set = new AnimatorSet();
        set.playTogether(firstAlphaAnim,firstRotationXanim,firstResumeRotationXanim);
        set.start();

效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hgA3S7Ra-1604476455878)(http://ws3.sinaimg.cn/large/958c5b69ly1fwnrq3l4v8g206j0bnkjm.gif)]

可以看到进行旋转了 但没有缩放 现在加上缩放的动画和底部上来的动画

 //背景透明度变化
        ObjectAnimator firstAlphaAnim = ObjectAnimator.ofFloat(first,"alpha",1.0f,0.7f);
        firstAlphaAnim.setDuration(300);

        //旋转动画 开始
        ObjectAnimator firstRotationXanim =ObjectAnimator.ofFloat(first,"rotationX",0f,20f);
        firstRotationXanim.setDuration(300);

        //旋转动画 回来
        ObjectAnimator firstResumeRotationXanim =ObjectAnimator.ofFloat(first,"rotationX",20f,0f);
        firstRotationXanim.setDuration(300);

        //缩放动画
        ObjectAnimator firstScaleXanim =ObjectAnimator.ofFloat(first,"scaleX",1.0f,0.8f);
        ObjectAnimator firstScaleYanim =ObjectAnimator.ofFloat(first,"scaleY",1.0f,0.8f);
        firstScaleXanim.setDuration(300);
        firstScaleYanim.setDuration(300);

        //缩放会导致顶部距离顶部有个缩放的距离 ,所以需要向上进行平移-  为什么是-0.1f  因为Y轴 缩放为原来的0.8f 那么 相当于 上下两边各减少了0.1f   又因为是向上平移 所以是负的
        ObjectAnimator firstTranslation = ObjectAnimator.ofFloat(first,"translationY",0f,-0.1f*first.getHeight());
        firstAlphaAnim.setDuration(300);


        //第二个动画 从下往上300毫秒  需要第二个动画和第一个动画同时进行
        final ObjectAnimator secondTranslation = ObjectAnimator.ofFloat(second,"translationY",second.getHeight(),0f);
        secondTranslation.setDuration(300);
        //延时一下 要不动画不好看
        secondTranslation.setStartDelay(200);
        secondTranslation.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                //开始的时候进行动画
                second.setVisibility(View.VISIBLE);
            }
        });

        AnimatorSet set = new AnimatorSet();
        set.playTogether(firstAlphaAnim,firstRotationXanim,firstResumeRotationXanim,firstScaleXanim,firstScaleYanim,firstTranslation,secondTranslation);
        set.start();

最终效果

image

TODU

另外:

华为某些机型 翻转动画无效…
https://cn.club.vmall.com/thread-12925994-1-1.html

我自己尝试用Rotate3dAnimation 但是 效果不行. 好多机型 适配不好. 如果谁有好的解决方案 可以交流一下 感谢

案例3 加载的炫酷动画. 以及策略模式的使用

效果图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TF0hJJxf-1604476455882)(http://ws2.sinaimg.cn/large/958c5b69ly1fwnrqljar4g20a10lbtv1.gif)]

思路

view的层级 应该是两个界面:

  1. Splash界面View;
  2. 主界面ContentView
    SplashView用于预加载----小圆旋转的动画时间是不确定的。
    SplashView盖在了ContentView上面

动画分析

小圆的动画共有三个,最后是水波纹动画:

  1. 小圆的旋转动画;
  2. 小圆向外扩散动画;小圆的聚合动画 .做成一个动画
  3. 水波纹的扩散动画
先实现小圆的旋转动画,

创建两个自定义view, 一个是前面小圆旋转缩放的,另一个是做背景

第一种状态,背景是白色白色

SplashView extends View{
	onDraw(){
		//绘制动画----计算和控制坐标
		//1.背景--擦黑板,涂成白色
		//2.绘制小圆
	}
}

先声明一个观点 大圆和小圆

大圆 是几个小圆组合成的一个圆

小圆 就是在外面的那几个圆

开始在ondraw里面写动画

思考: 三个动画都有一些共性, 一个动画执行之后 下一个开启, 并且都做了绘制的逻辑

策略模式

一个工作做完了 做下一个工作 下一个工作做完了 继续交接给下一个工作.

适合使用策略模式

创建一个抽象类 里面抽象方法用于画状态
   
    private SplashState mState = null;
    
    //策略模式--- 三种动画状态 
    private abstract class SplashState{
        public abstract  void drawState(Canvas canvas);
    }
    
    
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        if(mState == null){
             //开启第一个旋转动画
            mState = new RotateState();
        }
        //调用绘制方法
        mState.drawState(canvas);
    }
    
 
    //旋转动画
    private class RotateState extends SplashState{

        @Override
        public void drawState(Canvas canvas) {
            
        }
    }
    //扩散后聚合动画
    private class MerginState extends SplashState{

        @Override
        public void drawState(Canvas canvas) {
            
        }
    }


    //扩散动画 
    //TODO 尝试使用5.0之后的那个扩散动画 
     private class ExpandState extends SplashState{

        @Override
        public void drawState(Canvas canvas) {

        }
    }
旋转动画

下面考虑应该怎么绘制.

  • 首先需要绘制背景,背景是白色,那么就画一个白色背景
  • 然后需要绘制小圆

先把逻辑理出来. 具体实现等下再看

 //旋转动画
    private class RotateState extends SplashState {

        public RotateState() {
            //1.动画的初始工作. 2 开启动画
            //花1200ms,计算某个时刻当前的角度是多少? 0~2π   计算公式 c = 2πr = πd
            mAnimator = ValueAnimator.ofFloat(0f, (float) (Math.PI * 2));//这里其实是弧度
            mAnimator.setInterpolator(new LinearInterpolator());//设置插值器
            mAnimator.setDuration(mRotationDuration);//设置持续时间
            mAnimator.setRepeatCount(ValueAnimator.INFINITE);//循环模式 无限循环
            //监听变化
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //计算某个时刻的当前角度是多少
                    mCurrentRotationAngle = (float) animation.getAnimatedValue();
                    invalidate();//刷新会调用onDraw方法
                }
            });
        }

        @Override
        public void drawState(Canvas canvas) {
            //1.背景绘制  绘制成白色
            drawBackground(canvas);
            //2.绘制小圆
            drawCircles(canvas);

        }
    }

    private void drawCircles(Canvas canvas) {
         
    }

    private void drawBackground(Canvas canvas) {
        
    }

  • 然后需要考虑动画怎么切换

当MainActivity 5秒后 调用 SplishView 的 splashDisappear()方法 在这里做判断如果当前动画是第一种动画, 那么切换成第二个动画,

    /**
     * 让旋转动画消失, 开启后面两个动画 向外扩散然后收缩 最后水波纹形式消失
     */
    public void splashDisappear() {
        if (mState != null && mState instanceof RotateState) {
            ((RotateState) mState).cancle();

            //开启下一个动画
            post(() -> mState = new MerginState());
        }
    }

  • 下面考虑如何画小圆.

先假设坐标系在大圆的中心点, 现在需要计算第一个小球的位置 : 见下图

mark

根据三角公式:

a 是当前旋转的角度 之前已经求出来了

第一个小圆的坐标X: 半径 * cos(a)

第一个小圆的坐标y: 半径 * sin(a)

但是这是坐标在大圆中心. 手机屏幕的坐标系并不是这样 所以需要平移手机的坐标系.

x轴和y轴 都需要平移,距离分别为屏幕的宽的一半,和高的一半,那么需要加上去

所以最后应该是

第一个小圆的坐标X: 半径 * cos(a) + 屏幕宽度的一半

第一个小圆的坐标y: 半径 * sin(a) + 屏幕高度的一半

这是第一个小圆,其他五个小圆只需要增加角度即可 也就是a的值 相应修改下就是下一个小圆的坐标.

    private void drawCircles(Canvas canvas) {
        //计算每个小圆之间的间隔角度 公式 2π/小球的个数
        float rotationAngle = (float) (2 * Math.PI / mCircleColors.length);
        //小圆的个数根据颜色数组来的
        for (int i = 0; i < mCircleColors.length; i++) {
            /**
             * x = r*cos(a) +centerX
             y=  r*sin(a) + centerY
             每个小圆i*间隔角度 + 旋转的角度 = 当前小圆的真实角度
             */
            //计算出每个小球旋转的角度
            double angle = i * rotationAngle + mCurrentRotationAngle;
            //计算出每个小球的坐标
            float cx = (float) (mCurrentRotationRadius * Math.cos(angle) + mCenterX);
            float cy = (float) (mCurrentRotationRadius * Math.sin(angle) + mCenterY);
            //设置每个小球的画笔颜色
            mPaint.setColor(mCircleColors[i]);
            //绘制小球
            canvas.drawCircle(cx, cy, mCircleRadius, mPaint);
        }
    }

    private void drawBackground(Canvas canvas) {
        canvas.drawColor(mSplashBgColor);//背景白色

    }

效果.

mark

聚合动画

聚合动画应该有个向外扩散的动画 使用弹性插值器

   mAnimator.setInterpolator(new OvershootInterpolator(10f));`

这个插值器会在初始的时候加上

水波纹扩散动画
  • 然后是下一个动画 扩散动画是在上一个聚合动画结束后

那么就需要给聚合动画加上监听

  • 思考水波纹扩散动画怎么做

方法

  1. 一个大圆里面画小圆 小圆不断变大
  2. 画一个圆 半径从零开始不断变大. 画笔的粗细不断变小.

如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BR6BZJBF-1604476455887)(http://ws2.sinaimg.cn/large/958c5b69ly1fwnrrjz2nfj20zm0kft8o.jpg)]

使用第二种 开启一个属性动画,用来计算空心圆的半径

  //水波纹扩散动画 可以使用5.0之后的那个动画
    private class ExpandState extends SplashState {

        //构造方法里面开启动画
        public ExpandState() {
            //计算空心圆  计算某个时刻当前的空心圆的半径是多少  0~r 中的某个值  //这个是从0 到 对角线的数值
            mAnimator = ValueAnimator.ofFloat(0, mDiagonalDist);
            mAnimator.setDuration(mRotationDuration);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //当前空心圆的半径是多少
                    mHoleRadius = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            mAnimator.start();
        }

        @Override
        public void drawState(Canvas canvas) {
            drawBackground(canvas);
        }
    }

需要得到画笔的宽度

画笔的宽度 = 对角线/2 - 空心圆的半径

对角线 怎么求 勾股定理

 mDiagonalDist = (float) Math.sqrt((w * w + h * h)) / 2f;//勾股定律
 

然后需要画背景: 当空心圆半径大于0的时候 才开始绘制扩散动画

private void drawBackground(Canvas canvas) {
        if (mHoleRadius > 0) {
//            得到画笔的宽度 = 对角线/2 - 空心圆的半径
            float strokeWidth = mDiagonalDist - mHoleRadius;
            mPaintBackground.setStrokeWidth(strokeWidth);
            //画圆的半径 = 空心圆的半径 + 画笔的宽度/2
            float radius = mHoleRadius + strokeWidth / 2;
            canvas.drawCircle(mCenterX, mCenterY, radius, mPaintBackground);
        } else {
            canvas.drawColor(mSplashBgColor);//背景白色
        }

    }

//有时间尝试使用 CircularReveal


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值