Android动画全解析(四)

     上一节课,我们用一个将Animation动画综合在一起的例子分析了一下Animation动画框架的实现,这节课也是我们Android动画全解析的最后一课了,讲完这节课,动画分析的章节就完全结束了,下面我们还要继续研究View的实现,我们会以ListView、ViewPager等系统组件为原型,展开我们View全解析的课程。

     好了,这节课我们使用的例子是从github上找到的一个例子,写的也是非常好,从这个例子的效果就很明显可以看出Animator框架是比Animation框架更细致的,Animator能实现对动画细节更好的控制,甚至有些绚丽的动画使用Animator能够实现,而使用Animation还无法实现。那么我们就来看一下Animator到底是如何实现一个动画的,这节课我们使用的Demo是,源码下载地址:

     animator动画material-ripple

     我们先来看一下这个动画的效果,点击Overlay with hover按钮,会产生一个类似涟漪的效果,放开之后,涟漪不断扩大,最后消失,下面的Ripples static init按钮的实现原理是一样的,我们就不重复了,只分析一下Overlay with hover的涟漪效果的实现过程就可以了。这个动画的触发是通过onTouch事件来触发的,当我们按下按钮时,会调用MaterialRippleLayout类的onTouchEvent方法,我们来看一下它的实现:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean superOnTouchEvent = super.onTouchEvent(event);

        if (!isEnabled() || !childView.isEnabled()) return superOnTouchEvent;

        boolean isEventInBounds = bounds.contains((int) event.getX(), (int) event.getY());

        if (isEventInBounds) {
            previousCoords.set(currentCoords.x, currentCoords.y);
            currentCoords.set((int) event.getX(), (int) event.getY());
        }
        Log.i(TAG, "ontouchevent, " + event.getAction() + ", " + childView.isEnabled() + ", "
                + hasPerformedLongPress + ", " + isInScrollingContainer());

        boolean gestureResult = gestureDetector.onTouchEvent(event);
        if (gestureResult || hasPerformedLongPress) {
            Log.i(TAG, "if case, " + gestureResult + ", " + hasPerformedLongPress);
            return true;
        } else {
            int action = event.getActionMasked();
            switch (action) {
                case MotionEvent.ACTION_UP:
                    pendingClickEvent = new PerformClickEvent();

                    if (prepressed) {
                        childView.setPressed(true);
                        postDelayed(
                                new Runnable() {
                                    @Override
                                    public void run() {
                                        childView.setPressed(false);
                                    }
                                }, ViewConfiguration.getPressedStateDuration());
                    }

                    if (isEventInBounds) {
                        startRipple(pendingClickEvent);
                    } else if (!rippleHover) {
                        setRadius(0);
                    }
                    if (!rippleDelayClick && isEventInBounds) {
                        pendingClickEvent.run();
                    }
                    cancelPressedEvent();
                    break;
                case MotionEvent.ACTION_DOWN:
                    setPositionInAdapter();
                    eventCancelled = false;
                    pendingPressEvent = new PressedEvent(event);
                    if (isInScrollingContainer()) {
                        cancelPressedEvent();
                        prepressed = true;
                        postDelayed(pendingPressEvent, ViewConfiguration.getTapTimeout());
                    } else {
                        pendingPressEvent.run();
                    }
                    break;
                case MotionEvent.ACTION_CANCEL:
                    if (rippleInAdapter) {
                        // dont use current coords in adapter since they tend to jump drastically on scroll
                        currentCoords.set(previousCoords.x, previousCoords.y);
                        previousCoords = new Point();
                    }
                    childView.onTouchEvent(event);
                    if (rippleHover) {
                        if (!prepressed) {
                            startRipple(null);
                        }
                    } else {
                        childView.setPressed(false);
                    }
                    cancelPressedEvent();
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.i(TAG, "action move, " + rippleHover + ", " + isEventInBounds + ", " + eventCancelled);
                    if (rippleHover) {
                        if (isEventInBounds && !eventCancelled) {
//                            invalidate();
                        } else if (!isEventInBounds) {
                            startRipple(null);
                        }
                    }

                    if (!isEventInBounds) {
                        cancelPressedEvent();
                        if (hoverAnimator != null) {
                            hoverAnimator.cancel();
                        }
                        childView.onTouchEvent(event);
                        eventCancelled = true;
                    }
                    break;
            }
            return true;
        }
    }

      这里需要说一下,MaterialRippleLayout类是别人封装好的一个view,是继承FrameLayout,Overlay with hover文字是一个Button,是添加到MaterialRippleLayout当中的,这里有父布局,所以重写了一下onInterceptTouchEvent方法,将事件截获下来,因为后边的动画效果就是针对MaterialRippleLayout产生的,如果不截获触摸事件,onInterceptTouchEvent方法默认是返回false的,那么默认就分发到Button上去了,那么onTouchEvent事件无法执行,动画效果也就产生不了了,关于onInterceptTouchEvent方法,可以参考上一篇博客:

     Android-onInterceptTouchEvent()和onTouchEvent()总结

     在onTouchEvent方法中,我们的触摸会产生三种效果:开始按下MotionEvent.ACTION_DOWN、移动手指或者不动MotionEvent.ACTION_MOVE、松开手指MotionEvent.ACTION_UP,一定要搞清楚,MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP只会执行一次,哪怕你手指一直按着,DOWN事件也只产生一次。那么在当前动画中,分别使用PressedEvent、PerformClickEvent来产生按下的触摸动画和Click点一直的动画,接收到ACTION_DOWN事件,就构造一个PressedEvent对象,然后直接调用它的run方法,run方法当中是调用startHover()为生产动画的,startHover()方法的实现如下:

    private void startHover() {
        if (eventCancelled) return;

        if (hoverAnimator != null) {
            hoverAnimator.cancel();
        }
//        final float radius = (float) (Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2)) * 1.2f);
        final float radius = getHeight();
        hoverAnimator = ObjectAnimator.ofFloat(this, radiusProperty, rippleDiameter, radius)
                .setDuration(HOVER_DURATION);
        hoverAnimator.setInterpolator(new LinearInterpolator());
        hoverAnimator.start();
        Log.i(TAG, "start hover, " + radius);
    }
     我们的重点目标来了,先调用ObjectAnimator.ofFloat构建好hoverAnimator对象,然后给它设置插值器,最后就调用start开启动画了。那么我们一步步来看一下执行过程。

     1、ObjectAnimator.ofFloat(this, radiusProperty, rippleDiameter, radius).setDuration(HOVER_DURATION)

     先来看一下传入的几个参数,第一个this就代表当前的MaterialRippleLayout了,radiusProperty是作者自己定义的一个Property<MaterialRippleLayout, Float>对象,rippleDiameter和radius是两个整数。我们具体来看一下radiusProperty,它是一个Property对象,Property的类代码在frameworks/base/core/java/android/util包下面:

public abstract class Property
    
    
     
      {

    private final String mName;
    private final Class
     
     
      
       mType;

    /**
     * This factory method creates and returns a Property given the 
      
      class and
     * 
      
      name parameters, where the 
      
      "name" paramete
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

红-旺永福

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值