实现Android5.0过渡动画兼容库

Android5.0之后为我们提供了许多炫酷的界面过渡效果,其中共享元素过渡也是很有亮点的一个效果,但这个效果只能在Android5.0之后使用,那今天我们就来将共享元素过渡效果兼容到Android4.0,让5.0之前的手机也可以体验这么炫酷的效果吧。

A transition animation compatible Library.

兼容Android5.0之后转场动画至Android4.0。

github地址:https://github.com/zhangke3016/TranslationCompat

依惯例,首先来说下本文的行文思路吧:
一、页面过渡兼容库的使用
二、页面过渡兼容库实现原理浅析
三、用兼容库将开源项目MaterialLogin动画效果兼容至Android4.0

MaterialLogin

原项目地址:MaterialLogin
将动画效果兼容至Android4.0

Translation

Translation

一、页面过渡兼容库的使用

使用这个兼容库也很简单,首先,在要控制跳转的页面调用TransitionController.getInstance().startActivity方法来实现跳转,在其中主要是传入当前界面要过渡到另一页面的过渡元素View,以及另一个页面对应共享元素的View id值。
然后,在跳转到的第二个页面调用TransitionController.getInstance().show方法来实现元素的过渡,传入参数也很简单。
最后呢,在页面返回的时候,调用TransitionController.getInstance().exitActivity方法即可。
这样一个完整的界面过渡动画基本就可以使用了,当然,为了让实现的效果更炫酷,加入了对过渡动画状态的监听,可以在动画结束时加入自己的操作,为方便起见,兼容库包含圆形元素过渡:调用ViewAnimationCompatUtils.createCircularReveal方法既可实现元素以圆形展开和收起,使用方式和ViewAnimationUtils类一致,以及矩形元素过渡:调用:ViewAnimationCompatUtils.createRectReveal方法既可实现元素以矩形方式以左、上、右、下四个方向展开。
具体代码如下:

//参数一:当前Activity
//参数二:跳转意图
//参数三:当前页面跳转至下一页面的View
//参数四:下一页面关联的View id
TransitionController.getInstance().startActivity(this,new Intent(this, RegisterActivity.class),fab,R.id.fab);

//跳转后页面调用:
TransitionController.getInstance().show(this,getIntent());
可在show方法调用之前设置监听:
 TransitionController.getInstance().setEnterListener(new TransitionCustomListener() {
            @Override
            public void onTransitionStart(Animator animator) {
            }
            @Override
            public void onTransitionEnd(Animator animator) {
            }
            @Override
            public void onTransitionCancel(Animator animator) {
            }
        });
//界面退出的时候调用
TransitionController.getInstance().exitActivity(PageDetailActivity.this);

//增加界面圆形转换动画 
// 用法及参数和ViewAnimationUtils一致
ViewAnimationCompatUtils.createCircularReveal(cvAdd, cvAdd.getWidth()/2,0, fab.getWidth() / 2, cvAdd.getHeight());

//增加界面矩形转换动画 
Animator mAnimator = ViewAnimationCompatUtils.createRectReveal( nsv, 0, nsv.getHeight(),ViewAnimationCompatUtils.RECT_TOP);

二、页面过渡兼容库实现原理浅析

先讲了这个兼容库的用法,现在来聊聊它是怎么实现的,可以把主要实现细分六步:

1、获取跳转页面过渡元素的位置
2、将跳转过渡元素的位置传给下一个页面
3、在跳转到的页面获取位置信息并创建相同宽高大小的元素和其覆盖屏幕的父容器,并将新创建的元素添加到父容器中,而父容器添加至根视图中
4、获取跳转到的页面元素截图并将其设为创建元素的背景
5、将当前新元素位置与跳转到页面对比获取缩放比例与移动距离并开始动画,结束后将父容器隐藏
6、界面返回时将创建的父容器重新添加至下一个页面动画实现,将创建的元素以动画形式返回初始位置,结束后移除父容器

1、获取跳转页面过渡元素的位置

//rect 来存储共享元素位置信息
Rect rect = new Rect();
// 获取元素位置信息
view.getGlobalVisibleRect(rect);

2、将跳转过渡元素的位置传给下一个页面

// 将位置信息附加到 intent 上
intent.setSourceBounds(rect);
intent.putExtra(TRANSITION_NEXT_ID, nextShowViewId);

3、在跳转到的页面获取位置信息并创建相同宽高大小的元素和其覆盖屏幕的父容器,并将新创建的元素添加到父容器中,而父容器添加至根视图中

View virtalView = new View(activity);
                Bitmap cacheBitmap = BitmapUtil.getCacheBitmapFromView(next_view);


                // 获取上一个界面中,元素的宽度和高度
                final int mOriginWidth = mRect.right - mRect.left;
                final int mOriginHeight = mRect.bottom - mRect.top;

                getBundleInfo(next_view,mOriginWidth,mOriginHeight,mRect);
                //创建覆盖屏幕的父容器
                mContainer = new FrameLayout(activity);
                FrameLayout.LayoutParams mContainerParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
                //父容器添加至根视图中
                parent.addView(mContainer,mContainerParams);
                if (mBgColor!=-1)
                     mContainer.setBackgroundColor(ContextCompat.getColor(activity, mBgColor));

                FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(mOriginWidth, mOriginHeight);
                params.setMargins(mRect.left, mRect.top - BarUtils.getActionBarHeight(activity) -getStatusBarHeight(activity), mRect.right, mRect.bottom);

                virtalView.setBackgroundDrawable(new BitmapDrawable(activity.getResources(), cacheBitmap));
                //创建相同宽高大小的元素
                virtalView.setLayoutParams(params);
                //将新创建的元素添加到父容器中
                mContainer.addView(virtalView);

4、获取跳转到的页面元素截图并将其设为创建元素的背景

//获取跳转到的页面元素截图
Bitmap cacheBitmap = BitmapUtil.getCacheBitmapFromView(next_view);
//将其设为创建元素的背景
virtalView.setBackgroundDrawable(new BitmapDrawable(activity.getResources(), cacheBitmap));
/**
     * 获取一个 View 的缓存视图
     *
     * @param view
     * @return
     */
    public static Bitmap getCacheBitmapFromView(View view) {
        final boolean drawingCacheEnabled = true;
        view.setDrawingCacheEnabled(drawingCacheEnabled);
        view.buildDrawingCache(drawingCacheEnabled);
        final Bitmap drawingCache = view.getDrawingCache();
        Bitmap bitmap;
        if (drawingCache != null) {
            bitmap = Bitmap.createBitmap(drawingCache);
            view.setDrawingCacheEnabled(false);
        } else {
            bitmap = null;
        }
        return bitmap;
    }

5、将当前新元素位置与跳转到页面对比获取缩放比例与移动距离并开始动画,结束后将父容器隐藏

      //将当前新元素位置与跳转到页面对比获取缩放比例与移动距离
      getBundleInfo(next_view,mOriginWidth,mOriginHeight,mRect);
      //开始动画
      runEnterAnim(virtalView,next_view,mContainer);

       /**
     * 计算缩放比例,以及位移距离
     *
     * @param
     */
    private void getBundleInfo(View mView,int mOriginWidth,int mOriginHeight,Rect mRect) {
        // 计算缩放比例
        mScaleBundle.putFloat(SCALE_WIDTH, (float) mView.getWidth() / mOriginWidth);
        mScaleBundle.putFloat(SCALE_HEIGHT, (float) mView.getHeight() / mOriginHeight);

        Rect rect = new Rect();
        mView.getGlobalVisibleRect(rect);
        // 计算位移距离
        mTransitionBundle.putFloat(TRANSITION_X, (rect.left+(rect.right - rect.left) / 2) - (mRect.left + (mRect.right - mRect.left) / 2));
        mTransitionBundle.putFloat(TRANSITION_Y, (rect.top + (rect.bottom - rect.top) / 2) - (mRect.top + (mRect.bottom - mRect.top) / 2));

    }

/**
     * 模拟入场动画
     */
    private void runEnterAnim(View next_view,final View realNextView,final FrameLayout mContainer) {

        next_view.animate()
                .setInterpolator(new LinearInterpolator())
                .setDuration(300)
                .scaleX(mScaleBundle.getFloat(SCALE_WIDTH))
                .scaleY(mScaleBundle.getFloat(SCALE_HEIGHT))
                .translationX(mTransitionBundle.getFloat(TRANSITION_X))
                .translationY(mTransitionBundle.getFloat(TRANSITION_Y))
        .setListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                realNextView.setVisibility(View.GONE);
                if (mTransitionCustomListener!=null){
                    mTransitionCustomListener.onTransitionStart(animation);
                }
            }
            @Override
            public void onAnimationEnd(Animator animation) {
                mContainer.setVisibility(View.GONE);
                realNextView.setVisibility(View.VISIBLE);
                if (mTransitionCustomListener!=null){
                    mTransitionCustomListener.onTransitionEnd(animation);
                }
            }
            @Override
            public void onAnimationCancel(Animator animation) {
                if (mTransitionCustomListener!=null){
                    mTransitionCustomListener.onTransitionCancel(animation);
                }
            }
            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
    }

6、界面返回时将创建的父容器重新添加至下一个页面动画实现,将创建的元素以动画形式返回初始位置,结束后移除父容器

/**
     * 模拟退场动画
     */
    public void exitActivity(final Activity activity) {
        if (nResId!=-1 && mContainer!=null){
//先将创建的父容器从上一个页面移除
            ((ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT)).removeView(mContainer);
            activity.finish();
            activity.overridePendingTransition(0,0);
            //将创建的父容器重新添加至下一个页面
            ((ViewGroup) mFirstActivity.findViewById(Window.ID_ANDROID_CONTENT)).addView(mContainer);
            mContainer.setVisibility(View.VISIBLE);
            //开始动画
            mContainer.getChildAt(0).animate()
                    .setInterpolator(new LinearInterpolator())
                    .setDuration(300)
                    .scaleX(1)
                    .scaleY(1)
                    .translationX(0)
                    .translationY(0)
                    .setListener(new Animator.AnimatorListener() {
                        @Override
                        public void onAnimationStart(Animator animation) {
                            mFirstView.setVisibility(View.INVISIBLE);
                        }
                        @Override
                        public void onAnimationEnd(Animator animation) {

                            mFirstView.setVisibility(View.VISIBLE);
                            mContainer.setVisibility(View.GONE);
                            //结束后移除父容器
                            ((ViewGroup) mFirstActivity.findViewById(Window.ID_ANDROID_CONTENT)).removeView(mContainer);
                            mContainer.removeAllViews();
                            mContainer = null;
                            mFirstView = null;
                            mFirstActivity =null;
                        }
                        @Override
                        public void onAnimationCancel(Animator animation) {
                        }
                        @Override
                        public void onAnimationRepeat(Animator animation) {
                        }
                    });

        }else{
            activity.finish();
            activity.overridePendingTransition(0,0);
        }
    }

三、用兼容库将开源项目MaterialLogin动画效果兼容至Android4.0

这里就简单说下兼容MaterialLogin的实现,
首先,界面跳转,调用TransitionController.getInstance().startActivity(this,new Intent(this, RegisterActivity.class),fab,R.id.fab);方法既可,
之后,跳转至注册页面,调用TransitionController.getInstance().setEnterListener设置动画监听,在过渡动画结束时,调用ViewAnimationCompatUtils.createCircularReveal显示圆形展开效果,最后返回调用TransitionController.getInstance().exitActivity(RegisterActivity.this);,炫酷的登录页面就实现啦。

项目github地址:https://github.com/zhangke3016/TranslationCompat 欢迎star、fork。

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

推荐阅读更多精彩内容