activity跳转切换 拖拽旋转动画的实现
2016-12-23 13:25 阅读(237)

前言:

        最初是在酷狗音乐上看见这个效果,看不到酷狗的源码,于是根据自己的思路写了一个。希望对大家有用,当然更希望有高人能指点指点帮助改进,因为确实还有些地方存在问题有待优化。

      效果:

        这个是在activity切换时的一种动画方式的实现,可以用手触摸页面进行拖拽并实现页面跳转。

        如下效果:


      思路及实现过程:

        有两种方法,各有优劣。

        先看第一种:

        思路:获取整个activity的contentView,然后在触摸事件中对它进行动画处理,简明快捷!但是有两个很不足的地方:

第一:你不能用布局中的子view来监听touch事件,原因很简单,动画触发后整个activity的view都在移动,监听这个事件本身的view也在移动,坐标采集数据就发生混乱了,动画各种抖动,我已经实践过了,大家可以自己去试一下。

第二,排除了子view监听touch事件后就只能重写onTouchEvent(MotionEvent event)方法了,让整个窗口来监听触摸事件,不干扰到activity中的各个控件。但是使用这个方法必须保证页面中的控件都没有拦截和消费触摸事件,否则事件传不到这里来。这样一来里面的控件处理onClick()等等就变得很麻烦,并且想要抽出一个工具类也变得很难很难了。所以除非你单纯想实现这个效果,否则不推荐这个思路。

        当然,这是我局限在自己的水平得出的结论,我也希望有大神指点指点,点出一条光明的路。因为下一种思路虽然弥补了上面两个不足,但是确实绕了个小圈子,也有瑕疵啊!

        第二种思路:既然直接操作activity的contentView不理想,那我就搞一个副本,我操作你的副本嘛,这样总可以吧。于是开始绕这个小圈子:获取contentView,获取它的bitmap,构造一个ImageView放入bitmap,用WindowManager添加这个ImageView,选定指定控件监听touch事件,使contentView不可见,对ImageView处理动画。。。是不是绕了一小圈?是不是有点麻烦?没办法,我当前只能想到这个办法处理这个问题,凑合还能用,再次恳求有大神看见帮忙指点迷津!多谢多谢!


思路大概就是这样,下面来看实现过程吧,基本就是代码了。

        建一个工具类,职责1:获取activity图像内容,放入bitmap。


/** 
     * 获取activity图像内容的bitmap 
     * 
     * @return 
     */  
    public Bitmap getRootViewBitmap() {  
  
        rootView = activity.getWindow().getDecorView().findViewById(android.R.id.content);  
        Log.d(TAG, "root = " + rootView + "  ,width = " + rootView.getWidth() + "  ,height = " + rootView.getHeight());  
        rootView.setDrawingCacheEnabled(true);  
        Bitmap rootViewBitmap = rootView.getDrawingCache();  
  
        return rootViewBitmap;  
    }

职责2:创建ImageView,设置相关参数,添加到窗口上。

/** 
     * 增加覆盖在上层、与页面内容一致的ImageView 
     */  
    private void addImage() {  
        rotateImageView = new ImageView(activity);  
        rotateImageView.setImageBitmap(rootBitmap);  
  
        rotateImageParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;  
        rotateImageParams.format = PixelFormat.TRANSLUCENT;        //背景透明  
        rotateImageParams.width = rootView.getWidth();  
        rotateImageParams.height = rootView.getHeight();  
        rotateImageParams.verticalMargin = stationBarHeigth;    //距离顶部高度为一个状态栏的高度  
  
        rotateImageView.setRotation(0);  
        rotateImageView.setPivotX(rootView.getWidth() * 1f / 2f);  
        rotateImageView.setPivotY(rootView.getHeight() * 5f / 4f);  
  
        activity.getWindow().getWindowManager().addView(rotateImageView, rotateImageParams);  
    }

       职责3:处理touch事件。


        这个多说两句,touch事件处理,根据水平方向的move距离来计算旋转角度(0到90度),抬起手指后,根据检测当前角度来决定执行哪种动画。角度小,就旋转回原状态。角度大,则往外旋转直至消失,然后finish页面。

/** 
    * 需要在相应activity中设置此触摸滑动监听,touch事件才会生效 
    */  
   public View.OnTouchListener onTouchListener = new View.OnTouchListener() {  
       @Override  
       public boolean onTouch(View v, MotionEvent event) {  
               switch (event.getAction()) {  
                   case MotionEvent.ACTION_DOWN:  
                       touchStartX = event.getX();  
                       touchStartY = event.getY();  
                       addImage();  
                       new Thread(rootViewInvisibleDelay).start();  
                       Log.i(TAG, "ACTION_DOWN   touchStartX = " + touchStartX + "  ,touchStartY = " + touchStartY);  
                       break;  
  
                   case MotionEvent.ACTION_MOVE:  
                       touchMoveX = event.getX();  
                       touchMoveY = event.getY();  
                       Log.i(TAG, "ACTION_MOVE  touchStartX = " + touchStartX + "  ,touchMoveX = " + touchMoveX + "  ,touchStartY = " + touchStartY + "  ,touchMoveY = " + touchMoveY);  
  
                       float moveLength = touchMoveX - touchStartX;  
  
                       if (!rotateOpen && moveLength > 10 && Math.abs(touchMoveY - touchStartY) < 50) {  
                           rotateOpen = true;  
                       }  
  
                       if (rotateOpen) {  
                           float rotateAngle = getRotateAngle(moveLength);  
                           rotateImageView.setRotation(rotateAngle);  
                           activity.getWindow().getWindowManager().updateViewLayout(rotateImageView, rotateImageParams);  
                       }  
  
                       break;  
                   case MotionEvent.ACTION_UP:  
                       rotateOpen = false;  
  
                       float finalRotate = rotateImageView.getRotation();  
                       ValueAnimator valueAnimator;  
                       Log.v(TAG, "ACTION_UP finalRotate = " + finalRotate);  
  
                       if (finalRotate < 20) {  
                           valueAnimator = ValueAnimator.ofFloat(finalRotate, 0);  
                           rootView.setTag(resume);     //标志,恢复原状  
                       } else {  
                           valueAnimator = ValueAnimator.ofFloat(finalRotate, 90);  
                           rootView.setTag(finish);     //标志,结束页面  
                       }  
  
                       valueAnimator.setDuration(300);  
                       valueAnimator.addUpdateListener(animatorUpdateListener);  
                       valueAnimator.addListener(animatorListener);  
                       valueAnimator.start();  
  
                       break;  
               }  
               return true;  
       }  
   };

两个动画的监听:

/** 
     * 动画进行中,监听 
     */  
    ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {  
            float rotateAngle = (float) animation.getAnimatedValue();  
            rotateImageView.setRotation(rotateAngle);  
            activity.getWindow().getWindowManager().updateViewLayout(rotateImageView, rotateImageParams);  
        }  
    };  
  
    /** 
     * 动画开始、取消、结束、重复监听 
     */  
    Animator.AnimatorListener animatorListener = new Animator.AnimatorListener() {  
        @Override  
        public void onAnimationStart(Animator animation) {  
  
        }  
  
        @Override  
        public void onAnimationEnd(Animator animation) {  
            int tag = (int) rootView.getTag();  
            if (tag == resume) {  
                rootView.setVisibility(View.VISIBLE);  
                new Thread(removeImgDelay).start();  
  
            } else if (tag == finish) {  
                activity.finish();  
            }  
        }  
  
        @Override  
        public void onAnimationCancel(Animator animation) {  
  
        }  
  
        @Override  
        public void onAnimationRepeat(Animator animation) {  
  
        }  
    };

然后使用这个类也是很简单,在activity里面,如下几行代码就设置完成了。有一点需要严重注意的是,一定不要在onCreate()方法里面构造对象,布局未初始化完成,啥尺寸也测不出来,就不会有效果的。可以在onWindowFocusChanged方法里执行。

private ActivityRotateAnimationUtil activityRotateAnimationUtil;  
   @Override  
   public void onWindowFocusChanged(boolean hasFocus) {  
       super.onWindowFocusChanged(hasFocus);  
       activityRotateAnimationUtil = new ActivityRotateAnimationUtil(this);  
       bgImage.setOnTouchListener(activityRotateAnimationUtil.onTouchListener);  
   }

然后,有一个问题就是,在动画开始和结束时,我们一边要加副本ImageView,一边要让源contentView消失不见,这个过程如果放在一起执行的话页面总是会闪一下,我的理解是副本还没加上去,原contentView的不可见就已经执行完了,即使把加副本的代码放在前面也不行,因为间隔时间太短,而加副本需要一定的时间。所以代码里这两个地方采用了两个步骤中间延时一段时间来处理,也就是那两个看起来挺多余的子线程存在的原因。我还在寻找更好解决办法。。。


        还有一个注意点,也很重要:使用这个工具,activity的主题需要设置Android:theme="@android:style/Theme.Translucent",或者你有别的办法,让activity背景透明也行,否则的话,动画执行时,背景色会挡住下面的activity页面,常见的是一片黑,效果大打折扣。


        稍稍总结一下,实现这个效果其实难度并不大,重要的在细节。而且这个效果实现好了以后,顺推一下,什么平移呀,缩放呀,渐变呀,包括一些其他的属性其实都可以同理处理了,发挥的空间还是比较大的,就看想象力够不够,能不能设计出更绚烂的效果了。


        最后,贴上工具类的源码资源:http://download.csdn.net/detail/chen_zhang_yu/9575076

        欢迎拍砖,欢迎指点啊!



作者:chen_zhang_yu