(收集转载总结)MD设计常用代码/尺寸/颜色/控件个人收集总结--动画

特别说明

当前博客平台账号已废弃,如果有使用细节问题请前往我新博客平台进行讨论交流。

个人博客平台 HuRuWo的技术小站

文章首发于个人博客HuRuWo的技术小站,如果本文非vip用户无法完全浏览或者图片无法打开,可前往个人博客文章地址查看文章并留言讨论。

个人博客文章地址(收集转载总结)MD设计常用代码/尺寸/颜色/控件个人收集总结--动画

更多技术文章访问本人博客HuRuWo的技术小站,包括 Electron从零开发 Android 逆向 app 微信数据抓取 抖音数据抓取 闲鱼数据抓取 小红书数据抓取 其他软件爬虫 等技术文章

前言

作为Android 开发者,不仅要学习功能的实现,还需要定制用户的界面。如何制作一款符合大家审美的app是个根令人头疼的问题。

不过好在google在15发布了MD设计规范,帮助程序员设计更好的app。

动画

MD设计规范对于动画的解释

有意义的动画效果

动画效果(简称动效)可以有效地暗示、指引用户。动效的设计要根据用户行为而定,能够改变整体设计的触感。

动效应当在独立的场景呈现。通过动效,让物体的变化以更连续、更平滑的方式呈现给用户,让用户能够充分知晓所发生的变化。

动效应该是有意义的、合理的,动效的目的是为了吸引用户的注意力,以及维持整个系统的连续性体验。动效反馈需细腻、清爽。转场动效需高效、明晰。

如何实现符合MD设计的动画

真实的动作

物理世界中物体拥有质量,所以只有当施加给它们力量的时候才会移动,因此物体没法在瞬间开始或者结束动作。动画突然开始或者停止,或者在运动时突兀的变化方向,都会使用户感到意外和不和谐的干扰。

主要有两点:

  1. 迅速的加速和平滑的减速会感到自然和愉快
    物理世界中物体拥有质量,所以只有当施加给它们力量的时候才会移动,因此物体没法在瞬间开始或者结束动作。动画突然开始或者停止,或者在运动时突兀的变化方向,都会使用户感到意外和不和谐的干扰。
  2. 特殊情况:进入和退出的场景
    当一个物体进入这个场景时,请确保它在最高速度下移动,这个行为模拟了自然移动:一个人进入场景的时候,并不是从场景的边缘开始走入的,而是从更远的地方。当然,一个物体退出这个场景时,需要维持它的速度,缓慢的离开场景,逐渐的进入和缓慢的离开会把用户的注意力吸引到这个动作上,在大多数情况下,这是你希望的效果。

不是所有物体的移动方式是相同的,轻的/小的物体可能会更快的加速和减速,因为它们质量比较小,所以只需要施加给他们较少的力就可以。大的/重的物体可能花需要更多的时间来到达他的最高速度或者回到停止状态。仔细琢磨如何将他们的动作应用到你的应用的UI元素中。

下面分析一下开源的两个app的动画实现:

FAB随着list滑动,下拉退出,上拉出现

简影讯

这个FAB会根据RecycleView的滑动来进入和退出界面,符合第二种动画。既要以最大速度退出和进入。下面看代码实现:
布局类似在CoordinatorLayout下添加RecycleView加上FloatingActionButton

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:title="MdView" />

        <android.support.design.widget.TabLayout
            android:id="@+id/tablayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <android.support.design.widget.FloatingActionButton
        app:layout_behavior="com.othershe.mdview.ScrollAwareFABBehavior"
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="16dp"
        android:src="@android:drawable/ic_menu_share" />

</android.support.design.widget.CoordinatorLayout>

关键代码:app:layout_behavior="com.othershe.mdview.ScrollAwareFABBehavior"这行是用来控制fab行为的,而这个行为是用户自定义的。
具体代码可以参考浮动操作按钮详解

在此之前要先了解Behavior这个类:
参考文章:深入理解CoordinatorLayout.Behavior

在android5.0之后新的嵌套滑动机制中,引入了:NestScrollChild和NestedScrollingParent两个接口,用于协调子父控件滑动状态,而CoordinatorLayout实现了NestedScrollingParent接口,在实现了NestScrollChild这个接口的子控件在滑动时会调用NestedScrollingParent接口的相关方法,将事件发给父控件,由父控件决定是否消费当前事件,在CoordinatorLayout实现的NestedScrollingParent相关方法中会调用Behavior内部的方法。

我们实现Behavior的方法,就可以嵌入整个CoordinatorLayout所构造的嵌套滑动机制中,可以获取到两个方面的内容:

1、某个view监听另一个view的状态变化,例如大小、位置、显示状态等
需要重写layoutDependsOnonDependentViewChanged方法

2、某个view监听CoordinatorLayoutNestedScrollingChild的接口实现类的滑动状态
重写onStartNestedScrollonNestedPreScroll方法。注意:是监听实现了NestedScrollingChild的接口实现类的滑动状态,这就可以解释为什么不能用ScrollView而用NestScrollView来滑动了。

下面看怎么定义这个fab行为类的:

  1. 继承FAB的行为类FloatingActionButton.Behavior
 private boolean mIsAnimatingOut = false;
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
 
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
            FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || 
            super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target,
            nestedScrollAxes);
    }}

重写了onStartNestedScroll这个方法,这个方法是用来决定是否要响应CoordinatorLayout的滚动,返回ture 表示接收。

  1. 重写onNestedScroll处理滑动事件,包括定义fab进入和退出的动画。
@Override
    public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                               final View target, final int dxConsumed, final int dyConsumed,
                               final int dxUnconsumed, final int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        if (dyConsumed > 0 && !mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
            animateOut(child);
        } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
            animateIn(child);
        }
    }
  1. 因为FloatingActionButton.Behavior的基类已经有了animateIn() 和 animateOut()方法,同时它也设置了一个私有变量mIsAnimatingOut,这些方法和变量都是私有的,所以现在我们需要重新实现这些动画方法。
   private void animateOut(final FloatingActionButton button) {
       ViewCompat.animate(button).translationY(button.getHeight() + getMarginBottom(button))
               .setInterpolator(INTERPOLATOR).withLayer()
               .setListener(new ViewPropertyAnimatorListener() {
                   public void onAnimationStart(View view) {
                       mIsAnimatingOut = true;
                   }

                   public void onAnimationCancel(View view) {
                       mIsAnimatingOut = false;
                   }

                   public void onAnimationEnd(View view) {
                       mIsAnimatingOut = false;
                       view.setVisibility(View.INVISIBLE);
                   }
               }).start();
   }
   private void animateIn(FloatingActionButton button) {
       button.setVisibility(View.VISIBLE);
       ViewCompat.animate(button).translationY(0)
               .setInterpolator(INTERPOLATOR).withLayer().setListener(null)
               .start();
   }

   private int getMarginBottom(View v) {
       int marginBottom = 0;
       final ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
       if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
           marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
       }
       return marginBottom;
   }

这里核心控制进出是否为最大速度的就是插值器
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();

  1. 最后一步就是把这个CoordinatorLayout Behavior与浮动操作按钮联系起来。我们可以在xml的自定义属性pp:layout_behavior中定义它:
<android.support.design.widget.FloatingActionButton    
    app:layout_behavior="com.codepath.floatingactionbuttontest.ScrollAwareFABBehavior" />
  1. 因为我们是在xml中静态的定义这个behavior,为了让 layout inflation顺利进行,我们必须实现一个构造函数。
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
    // ...
 
    public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
        super();
    }

响应式交互

响应式交互能让用户信任,并且吸引他们。 当用户操作一个美观且符合常理的应用时,他们会感到满意甚至很高兴。那是一种经过深思熟虑、有目的、非随机的而且可以带有轻微异想天开但不会让人分心的交互。

在 material design 中,应用是响应式的并且渴望用户操作的:

  1. 触摸,语音,键盘及鼠标作为首要考虑的输入方式。
  1. 虽然 UI 元素是有形的,但是他们被限制在屏幕里面(电脑或者移动设备的屏幕),视觉元素和动效能减少这种割裂,让用户能够立即感知自己的操作。

总结来说就是:让用户知道他点击了某个按钮
同样是剪影讯源码分析:

RecyclerView的item里面使用了cardView,使用了卡片效果。

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:layout_margin="3dp"
    android:id="@+id/cardview"
    android:foreground="?android:attr/selectableItemBackground"
    >

    <LinearLayout
        android:id="@+id/item_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        >

        <TextView
            android:id="@+id/item_tv"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:gravity="center" />

    </LinearLayout>

</android.support.v7.widget.CardView>

cardView的
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
这两行设置了水波纹的点击效果:
除了selectableItemBackground还有一种水波纹
selectableitembackgroundborderless只能在v21以上使用

2017-03-11_22_14_09_0-240.gif

要注意一点,在adapter里面设置点击事件的时候一定要设置最外层的布局的点击事件才有效的实现,否则冲突就无法实现点击的水波纹。

 holder.cardView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        });

水波纹可以加在所有可以点击的控件上,比如Button

<Button
    android:foreground="?android:attr/selectableItemBackground"
    android:layout_width="match_parent"
    android:layout_height="50dp" />

有意义的转场动画

为了让界面切换不显的突兀,android里设置了专场动画。Activity的转场动画很早就有,但是太过于单调,样式也不好看,于是Google在Android5.0之后,又推出的新的转场动画,效果还是非常炫的。

参考文章: Android5.0之Activity的转场动画

  1. 旧转场动画回顾
    首先我们还是先来看看在5.0之前如果我们想要在启动Activity时使用动画该怎么做呢?
startActivity(new Intent(this, Main3Activity.class));  
        overridePendingTransition(R.anim.in,R.anim.out);  

对应的入场和出场动画就是两个补间动画,如下:
入场动画:

<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android">  
    <translate  
        android:toYDelta="0"  
        android:fromYDelta="100%"  
        android:duration="1500"/>  
</set>  

出场动画:

<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android">  
    <translate  
        android:duration="1500"  
        android:fromYDelta="0"  
        android:toYDelta="-100%"/>  
</set>  

这种动画是针对整个Activity而言的,无法设置Activity中元素的入场/出场动画。

  1. 5.0之后的转场动画
    分为两种:
  2. 分解、滑动进入、淡入淡出
    分解
    先来看一张效果图:

    就是这样一种效果,那我们接下来看看这种效果要怎么实现。
    首先,把之前启动Activity的代码改成下面的写法:
startActivity(new Intent(this, Main2Activity.class), ActivityOptions.makeSceneTransitionAnimation(this).toBundle());  

添加完成之后,在Main2Activity中设置该Activity的进出场动画即可:

getWindow().setEnterTransition(new Explode().setDuration(2000));  
getWindow().setExitTransition(new Explode().setDuration(2000));  

大家一定要记得在styles.xml文件中添加下面一行代码,表示激活Activity中元素的过渡效果:

<item name="android:windowContentTransitions">true</item>  

滑动进入
有了上面的步骤,再设置滑动进入就很简单了,只需要修改Main2Activity中的两行代码即可:

getWindow().setEnterTransition(new Slide().setDuration(2000));  
getWindow().setExitTransition(new Slide().setDuration(2000));  

显示效果如下:


淡入淡出
Main2Activity修改代码如下:

getWindow().setEnterTransition(new Fade().setDuration(2000));  
        getWindow().setExitTransition(new Fade().setDuration(2000));  

显示效果如下:



Activity的进场和退出可以分别设置不同的动画效果,如果没有分别设置,则进场和退出的动画反过来。比如退出是变淡,则进场就是变深。<只在设置了专场动画的两个Activity里>

  1. 元素共享
    共享元素动画
    共享元素动画是一个非常神奇的东东,我们先来看看效果:



    可能这个Gif动画还不太清晰,我再来解释一下,在MainActivity和Main2Activity里边都有一个Button,只不过一个大一个小,从MainActivity跳转到Main2Activity时,我并没有感觉到Activity的跳转,只是觉得好像第一个页面的Button放大了,同理,当我从第二个页面回到第一个页面时,也好像Button变小了。OK,这就是我们的Activity共享元素。
    当两个Activity中有同一个控件的时候,我们便可以采用共享元素动画。
    使用共享元素动画的时候,我们需要首先给MainActivity和Main2Activity中的两个button分别添加Android:transitionName="mybtn"属性,并且该属性的值要相同,这样系统才知道这两个控件是共享元素。设置完成之后,接下来就是启动Activity的代码了,如下:

startActivity(new Intent(this,Main2Activity.class), ActivityOptions.makeSceneTransitionAnimation(this,view,"mybtn").toBundle());  

还是上面那种启动方式的重载方法,只不过这里多了两个参数,view表示MainActivity中的共享元素(就是那个Button),第二个参数表示布局文件中transitionAnimation属性的值。OK,就这么简单。
多元素共享:
那我如果两个页面中有多个共享元素该怎么办呢?简单,android:transitionName属性还像上面一样设置,然后在启动Activity时我们可以通过Pair.create方法来设置多个共享元素,如下:

startActivity(new Intent(this, Main2Activity.class),  
                ActivityOptions.makeSceneTransitionAnimation(this, Pair.create(((View) iv1),"myiv"), create(((View) textView),"mytv")).toBundle());\

分析简影讯里使用的过场动画:
1.主界面的RecycleView进入的过渡动画:
如下:

2017-03-12_17_01_27_0-59.gif

分析源码:

这个是RecycleView加载Item的动画,所以写在RecycleView的Adapter。
其实RecycleView内部是可以设置Item的动画的,使用这个方法:
mRecyclerView.setItemAnimator(new DefaultItemAnimator(mRecyclerView));
除了默认的动画还有其他内置的动画:

SlideInOutLeftItemAnimator : which applies a slide in/out from/to the left animation
SlideInOutRightItemAnimator : which applies a slide in/out from/to the right animation
SlideInOutTopItemAnimator : which applies a slide in/out from/to the top animation
**SlideInOutBottomItemAnimator **: which applies a slide in/out from/to the bottom animation
ScaleInOutItemAnimator : which applies a scale animation
**SlideScaleInOutRightItemAnimator **: which applies a scale animation with a slide in/out from/to the right animation

但是我们都不用,所以自己写在Adapter里面:
定义属性动画:

 protected Animator[] getAnimators(View view) {
        return new Animator[]{
                ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1f).setDuration(4000),
                ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 200, 0).setDuration(4000)
        };
    }

写一个函数设置是否加载动画:

public void setShowAnim(boolean showAnim) {
        isShowAnim = showAnim;
    }

在onBindViewHolder里为每一个item设置动画:

    private int mLastPosition = -1;

Animator[] animators = getAnimators(holder.itemView);
        if (isShowAnim && animators != null && animators.length > 0
                && holder.getAdapterPosition() > mLastPosition) {
            if (animators.length > 1) {
                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.playTogether(animators);
                animatorSet.start();
            } else {
                for (Animator animator : animators) {
                    animator.start();
                }
            }

            mLastPosition = holder.getAdapterPosition();
        }

mLastPosition 用来判断是不是最后一个,否则不设置加载动画。

2.进入详情页面的动画

2017-03-12_17_01_46_0-215.gif

过度动画:在第二个Activity里设置:

public static void navigation(Activity activity, View view, MovieModel movieModel) {
        Intent intent = new Intent(activity, MovieDetailActivity.class);
        intent.putExtra("movie_model", movieModel);

        if (Build.VERSION.SDK_INT >= 21) {
            activity.getWindow().setExitTransition(new Explode());
            ActivityCompat.startActivity(activity, intent,
                    ActivityOptions.makeSceneTransitionAnimation(activity).toBundle());
        } else {
            ActivityOptionsCompat option = ActivityOptionsCompat.makeScaleUpAnimation(view, 0, 0,
                    view.getMeasuredWidth(), view.getMeasuredHeight());
            ActivityCompat.startActivity(activity, intent, option.toBundle());
        }
    }

第一个Activity里跳转调用:

startActivity(new Intent(MainActivity.this,DetailActivity.class), ActivityOptions.makeSceneTransitionAnimation(MainActivity.this,view,"mybtn").toBundle());

打动用户的细节

这里主要是一些图标的小动画

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

推荐阅读更多精彩内容