项目需求讨论 — 用Transition做一个漂亮的登录界面

前言

一次在逛Github的时候,看到一个漂亮的登录界面,用的是Transition做的。我就直接贴上地址:

MaterialLogin

当然,如果单纯的直接拿过来用,没有任何意义。主要还是来看具体如何实现的。我就来写下具体如何一步步的来实现这个效果。

我也按照相应的原理写了个Demo。最后的效果如下图所示(其中layout布局我就直接从github上面拷贝过来了):


基础

首先我们来看下什么是Transition。大家看仔细是Transition,而不是Translate。我们直接看翻译:

而Translate通常我们指的是平移的动画操作。

Transition:

所以我们知道了用的是过渡的方式来做,那什么是过渡呢?

Android 4.4:

Android对于开发者提供了越来越多的动画API支持。从API 1就存在的Drawable Animation和View Animation,以及API 11(Android 3.0)以后加入的Property Animation。而过渡动画Transition是在API 19(Android 4.4.2)中加入的。

基础知识我就不说了,直接看其他文章传送门:

Android 过渡(Transition)动画解析之基础篇

所以初步我们可以理解为(可能这么说明有不对,可以提出):

场景(scenes)和变换(transitions)。场景(scenes)定义了当前的UI状态,变换(transitions)则定义了在不同场景之间动画变化的过程。

当一个场景改变的时候,transition主要负责:

(1)捕捉每个View在开始场景和结束场景时的状态。

(2)根据两个场景(开始和结束)之间的区别创建一个Animator。

Android 5.0

Android 5.0中Transition可以被用来实现Activity或者Fragment切换时的异常复杂的动画效果。

虽然在以前的版本中,已经可以使用Activity的overridePendingTransition() 和 FragmentTransaction的setCustomAnimation()来实现Activity或者Fragment的动画切换,但是他们仅仅局限与将整个视图一起动画变换。新的Lollipop api更进了一步,让单独的view也可以在进入或者退出其布局容器中时发生动画效果,甚至还可以在不同的activity/Fragment中共享一个view。

还是上面那个图,只是变成了二个Activity界面:

我们在跳转到第二个Activity的时候,我们会有个过场动画。会第一个Activity的按钮移动到第二个Activity的按钮。效果如下所示:


所以我们再回头看下面这种效果,是不是就知道怎么实现了,用的是Activity的过渡动画了。

大家也可以看看下面的相关文章链接:

Activity和Fragment Transition介绍

深入理解Content Transition

深入理解共享元素变换(Shared Element Transition)-上


正文

我们先准备第一个Activity,界面如下:
[图片上传中...(1515553694(1).png-7b7bdf-1515572604428-0)]

第一步:fab按钮的移动:

我们让那个按钮"+"能移动到顶部:

我们由前面的demo说明已经知道了,启动第二个Activity,我们我们先让第二个Activity的界面如下所示:

我们设置第二个Activity的主题为:

<style name="Translucent" parent="Theme.AppCompat.Light.NoActionBar">
    //第一个activity的状态栏颜色为#0288D1
    <item name="colorPrimaryDark">#0288D1</item>
    //第二个activity的背景为透明,
    //这样可以看得到第一个Activity的界面
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowIsTranslucent">true</item>
</style>

没错,我们在第二个界面先写上一个按钮"X",这样我们启动第二个Activity的时候就盖在了第一个Activity的上面,同时这个fab按钮也有了动画的效果:

代码很简单,只要让第一个Activity的按钮的android:transitionName与第二个Activity的按钮的android:transitionName一样就可以。我们称这个为共享元素

FloatingActionButton btn = findViewById(R.id.fab);;

ActivityOptionsCompat optionsCompat
    = ActivityOptionsCompat.makeSceneTransitionAnimation(LoginMainActivity.this,btn,btn.getTransitionName());
startActivity(new Intent(LoginMainActivity.this,RegisterMainActivity.class),optionsCompat.toBundle());

然后通过ActivityOptionsCompat来记录当前这个Activity的这个fab按钮的状态。然后在startActivity的时候,通过optionsCompat.toBundle()把内容带到了第二个Activity中。第二个Activity就会让现在的相同trasitionName的fab按钮,以传过来的第一个Activity的按钮相同位置的为起始点,然后通过动画到了最终的地方。(所以动画是在第二个Activity中完成的,只是按钮的起始状态是以第一个Activity传过来的按钮的状态信息相同,然后到最终用户设置的位置。)

我们可以看到,共享元素变换并不是真正实现了两个activity或者Fragment之间元素的共享,实际上我们看到的几乎所有变换效果中(不管是B进入还是B返回A),共享元素都是在B中绘制出来的。Framework没有真正试图将A中的某个元素传递给B,而是采用了不同的方法来达到相同的视觉效果。A传递给B的是共享元素的状态信息。B利用这些信息来初始化共享View元素,让它们的位置、大小、外观与在A中的时候完全一致。当变换开始的时候,B中除了共享元素之外,所有的其他元素都是不可见的。随着动画的进行,framework 逐渐将B的activity窗口显示出来,当动画完成,B的窗口才完全可见。

并且其实动画是绘制在ViewOverlay上面,可以看看这篇文章:ViewOverlay与animation介绍

第二步让fab按钮通过曲线路径变化:

我们直接不做任何处理,默认是fab按钮的位置变化是直线。
我们更希望是:

我们可以设置共享元素的进入动画:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:interpolator/linear_out_slow_in"
    android:duration="3000">

    <changeBounds>
        <arcMotion
            android:maximumAngle="0"
            android:minimumHorizontalAngle="60"
            android:minimumVerticalAngle="90" />
    </changeBounds>
</transitionSet>
//获取过渡动画
Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.fabtransition);
设置共享元素的进入动画
getWindow().setSharedElementEnterTransition(transition);

这里使用的是arcMotion来做的曲线路径.

ArcMotion文档

里面的介绍我用的谷歌翻译翻译的,大致应该是这个意思:
PathMotion在包含两个点的假想圆上沿圆弧生成曲线路径。 如果点之间的水平距离小于垂直距离,则圆的中心点将与终点水平对齐。 如果垂直距离小于水平距离,则圆的中心点将与终点垂直对齐。
当两点接近水平或垂直时,运动的曲线将会变小,因为圆的中心距两点都很远。 要强制路径的曲率,可以使用setMinimumHorizontalAngle(float)和setMinimumVerticalAngle(float)来设置两点之间的弧的最小角度。

其他参考文章:

曲线运动-1

曲线运动 - 2

第三步fab按钮动画结束后出现注册界面:

我们上一步对fab按钮设置了过渡的动画。我们可以对这个过渡动画设置结束的监听,然后其他我们的注册界面的出现:

Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.fabtransition);
getWindow().setSharedElementEnterTransition(transition);
transition.addListener(new Transition.TransitionListener() {
            
    .....
    .....
            
    @Override
    public void onTransitionEnd(Transition transition) {
        transition.removeListener(this);
        
        
        /*我们可以在动画结束后,
            可以在这里写上代码,
            让注册界面出现
        */
        .....
        .....
       
    }
});

这是我们的第二个Activity的布局变成了这样:

只不过默认这个注册界面是不可见的,等到我们的fab按钮动画结束后,我们再让注册界面可见就可以了。

这里我们可以直接在上面fab按钮动画结束的时候,直接让注册界面出现(因为这个注册界面是用CardView写的,所以这里直接用cardView来指这个实例),我们可以在上面的结束监听里面直接设置:

@Override
public void onTransitionEnd(Transition transition) {
    transition.removeListener(this);
    //设置可见
    cardView.setVisibility(View.VISIBLE);
}

效果如下:

我们发现,直接突然出现,虽然功能实现了,但我们还是希望有更好看的效果,就像文章开头那样,这个注册界面是慢慢展开的。所以我们在fab按钮过渡动画结束后,不是简单的对cardView设置View.VISIBLE就可以。我们使用揭露动画来实现:

Animator mAnimator = ViewAnimationUtils.createCircularReveal(cardView,cardView.getWidth()/2
    ,0,0,cardView.getHeight());
mAnimator.setDuration(500);
mAnimator.setInterpolator(new AccelerateInterpolator());
mAnimator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationStart(Animator animation) {
        super.onAnimationStart(animation);
        cardView.setVisibility(View.VISIBLE);
    }
});
mAnimator.start();

揭露动画参考文章:

使用Circular Reveal为你的应用添加揭露动画效果

所以我们这么使用后效果变成了:


第四步返回登录界面:

这里有二种方式:

  1. 按了手机上的返回键
  2. 按了那个fab按钮返回

我们的fab键从左边移动到了上边,然后如果你按返回键,你会发现自动fab键会先执行相应的自动回去动画,然后activity再关闭。比如你直接对fab键设置了点击事件:

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
       finish();
    }
});

直接调用finish()方法的话,你就会发现,没有fab键返回的动画,而是直接第二个activity关闭,显示第一个activity的见面。这样很不友善。

我们知道默认按返回键是调用了:

@Override
public void onBackPressed() {
    super.onBackPressed();
}

说明调用onBackPressed会调用退出动画效果后再finish();

参考文章:

最常用的Activity的onBackPressed()与finish()的区别.

所以我们知道了,我们点击fab键返回的时候不能直接finish,而是最后一步是调用super.onBackPressed();

所以我们最终是先让注册界面慢慢消失,消失后调用super.onBackPressed();

//覆写返回键操作,
//执行注册界面消失动画,
//然后再执行super.onBackPressed();
@Override
public void onBackPressed() {
    animateRevealClose();
}
//fab的点击事件与上面一样
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        animateRevealClose();
    }
});
//注册界面界面慢慢消失,然后调用super.onBackPressed()
//然后fab键会执行动画回到原始位置,然后第二个Activity关闭。
//然后显示了第一个Activity
public void animateRevealClose(){
    
    Animator mAnimator = ViewAnimationUtils.createCircularReveal(cardView,cardView.getWidth()/2
            ,0,cardView.getHeight(),0);
    mAnimator.setDuration(500);
    mAnimator.setInterpolator(new AccelerateInterpolator());
    mAnimator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            cardView.setVisibility(View.GONE);
            btn.setImageResource(R.drawable.plus);
            RegisterMainActivity.super.onBackPressed();
        }
    });
    mAnimator.start();
}

最后实现就是这样子了:


结语

哪里错误了,大家留言回复哦,多谢支持。o( ̄︶ ̄)o

大佬如果能帮我解答下下面二个问题,非常感谢:

  1. 我在使用arcMotion的时候,小米5(6.0)与华为(7.0),呈现的曲线效果差别很大,(gif图是小米的,所以fab键移动的时候更像是直线,但是华为就很明显的是曲线)不知道是什么原因,知道的可以告诉我下。


网上的文章清一色都是要求app的主题设置里面这个属性要是true,但是我设成了false,为什么也是没问题的。比如activity之间的共享元素动画也是一样执行的。测试手机是小米5(6.0)与华为(7.0)。

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

推荐阅读更多精彩内容