(收集轉(zhuǎn)載總結(jié))MD設(shè)計(jì)常用代碼/尺寸/顏色/控件個(gè)人收集總結(jié)--動畫

特別說明

當(dāng)前博客平臺賬號已廢棄,如果有使用細(xì)節(jié)問題請前往我新博客平臺進(jìn)行討論交流。

個(gè)人博客平臺 HuRuWo的技術(shù)小站

文章首發(fā)于個(gè)人博客HuRuWo的技術(shù)小站,如果本文非vip用戶無法完全瀏覽或者圖片無法打開,可前往個(gè)人博客文章地址查看文章并留言討論。

個(gè)人博客文章地址(收集轉(zhuǎn)載總結(jié))MD設(shè)計(jì)常用代碼/尺寸/顏色/控件個(gè)人收集總結(jié)--動畫

更多技術(shù)文章訪問本人博客HuRuWo的技術(shù)小站,包括 Electron從零開發(fā) Android 逆向 app 微信數(shù)據(jù)抓取 抖音數(shù)據(jù)抓取 閑魚數(shù)據(jù)抓取 小紅書數(shù)據(jù)抓取 其他軟件爬蟲 等技術(shù)文章

前言

作為Android 開發(fā)者,不僅要學(xué)習(xí)功能的實(shí)現(xiàn),還需要定制用戶的界面。如何制作一款符合大家審美的app是個(gè)根令人頭疼的問題。

不過好在google在15發(fā)布了MD設(shè)計(jì)規(guī)范,幫助程序員設(shè)計(jì)更好的app。

動畫

MD設(shè)計(jì)規(guī)范對于動畫的解釋

有意義的動畫效果

動畫效果(簡稱動效)可以有效地暗示、指引用戶。動效的設(shè)計(jì)要根據(jù)用戶行為而定,能夠改變整體設(shè)計(jì)的觸感。

動效應(yīng)當(dāng)在獨(dú)立的場景呈現(xiàn)。通過動效,讓物體的變化以更連續(xù)、更平滑的方式呈現(xiàn)給用戶,讓用戶能夠充分知曉所發(fā)生的變化。

動效應(yīng)該是有意義的、合理的,動效的目的是為了吸引用戶的注意力,以及維持整個(gè)系統(tǒng)的連續(xù)性體驗(yàn)。動效反饋需細(xì)膩、清爽。轉(zhuǎn)場動效需高效、明晰。

如何實(shí)現(xiàn)符合MD設(shè)計(jì)的動畫

真實(shí)的動作

物理世界中物體擁有質(zhì)量,所以只有當(dāng)施加給它們力量的時(shí)候才會移動,因此物體沒法在瞬間開始或者結(jié)束動作。動畫突然開始或者停止,或者在運(yùn)動時(shí)突兀的變化方向,都會使用戶感到意外和不和諧的干擾。

主要有兩點(diǎn):

  1. 迅速的加速和平滑的減速會感到自然和愉快
    物理世界中物體擁有質(zhì)量,所以只有當(dāng)施加給它們力量的時(shí)候才會移動,因此物體沒法在瞬間開始或者結(jié)束動作。動畫突然開始或者停止,或者在運(yùn)動時(shí)突兀的變化方向,都會使用戶感到意外和不和諧的干擾。
  2. 特殊情況:進(jìn)入和退出的場景
    當(dāng)一個(gè)物體進(jìn)入這個(gè)場景時(shí),請確保它在最高速度下移動,這個(gè)行為模擬了自然移動:一個(gè)人進(jìn)入場景的時(shí)候,并不是從場景的邊緣開始走入的,而是從更遠(yuǎn)的地方。當(dāng)然,一個(gè)物體退出這個(gè)場景時(shí),需要維持它的速度,緩慢的離開場景,逐漸的進(jìn)入和緩慢的離開會把用戶的注意力吸引到這個(gè)動作上,在大多數(shù)情況下,這是你希望的效果。

不是所有物體的移動方式是相同的,輕的/小的物體可能會更快的加速和減速,因?yàn)樗鼈冑|(zhì)量比較小,所以只需要施加給他們較少的力就可以。大的/重的物體可能花需要更多的時(shí)間來到達(dá)他的最高速度或者回到停止?fàn)顟B(tài)。仔細(xì)琢磨如何將他們的動作應(yīng)用到你的應(yīng)用的UI元素中。

下面分析一下開源的兩個(gè)app的動畫實(shí)現(xiàn):

FAB隨著list滑動,下拉退出,上拉出現(xiàn)

簡影訊

這個(gè)FAB會根據(jù)RecycleView的滑動來進(jìn)入和退出界面,符合第二種動畫。既要以最大速度退出和進(jìn)入。下面看代碼實(shí)現(xiàn):
布局類似在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>

關(guān)鍵代碼:app:layout_behavior="com.othershe.mdview.ScrollAwareFABBehavior"這行是用來控制fab行為的,而這個(gè)行為是用戶自定義的。
具體代碼可以參考浮動操作按鈕詳解

在此之前要先了解Behavior這個(gè)類:
參考文章:深入理解CoordinatorLayout.Behavior

在android5.0之后新的嵌套滑動機(jī)制中,引入了:NestScrollChild和NestedScrollingParent兩個(gè)接口,用于協(xié)調(diào)子父控件滑動狀態(tài),而CoordinatorLayout實(shí)現(xiàn)了NestedScrollingParent接口,在實(shí)現(xiàn)了NestScrollChild這個(gè)接口的子控件在滑動時(shí)會調(diào)用NestedScrollingParent接口的相關(guān)方法,將事件發(fā)給父控件,由父控件決定是否消費(fèi)當(dāng)前事件,在CoordinatorLayout實(shí)現(xiàn)的NestedScrollingParent相關(guān)方法中會調(diào)用Behavior內(nèi)部的方法。

我們實(shí)現(xiàn)Behavior的方法,就可以嵌入整個(gè)CoordinatorLayout所構(gòu)造的嵌套滑動機(jī)制中,可以獲取到兩個(gè)方面的內(nèi)容:

1、某個(gè)view監(jiān)聽另一個(gè)view的狀態(tài)變化,例如大小、位置、顯示狀態(tài)等
需要重寫layoutDependsOnonDependentViewChanged方法

2、某個(gè)view監(jiān)聽CoordinatorLayout內(nèi)NestedScrollingChild的接口實(shí)現(xiàn)類的滑動狀態(tài)
重寫onStartNestedScrollonNestedPreScroll方法。注意:是監(jiān)聽實(shí)現(xiàn)了NestedScrollingChild的接口實(shí)現(xiàn)類的滑動狀態(tài),這就可以解釋為什么不能用ScrollView而用NestScrollView來滑動了。

下面看怎么定義這個(gè)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這個(gè)方法,這個(gè)方法是用來決定是否要響應(yīng)CoordinatorLayout的滾動,返回ture 表示接收。

  1. 重寫onNestedScroll處理滑動事件,包括定義fab進(jìn)入和退出的動畫。
@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. 因?yàn)镕loatingActionButton.Behavior的基類已經(jīng)有了animateIn() 和 animateOut()方法,同時(shí)它也設(shè)置了一個(gè)私有變量mIsAnimatingOut,這些方法和變量都是私有的,所以現(xiàn)在我們需要重新實(shí)現(xiàn)這些動畫方法。
   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;
   }

這里核心控制進(jìn)出是否為最大速度的就是插值器
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();

  1. 最后一步就是把這個(gè)CoordinatorLayout Behavior與浮動操作按鈕聯(lián)系起來。我們可以在xml的自定義屬性pp:layout_behavior中定義它:
<android.support.design.widget.FloatingActionButton    
    app:layout_behavior="com.codepath.floatingactionbuttontest.ScrollAwareFABBehavior" />
  1. 因?yàn)槲覀兪窃趚ml中靜態(tài)的定義這個(gè)behavior,為了讓 layout inflation順利進(jìn)行,我們必須實(shí)現(xiàn)一個(gè)構(gòu)造函數(shù)。
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
    // ...
 
    public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
        super();
    }

響應(yīng)式交互

響應(yīng)式交互能讓用戶信任,并且吸引他們。 當(dāng)用戶操作一個(gè)美觀且符合常理的應(yīng)用時(shí),他們會感到滿意甚至很高興。那是一種經(jīng)過深思熟慮、有目的、非隨機(jī)的而且可以帶有輕微異想天開但不會讓人分心的交互。

在 material design 中,應(yīng)用是響應(yīng)式的并且渴望用戶操作的:

  1. 觸摸,語音,鍵盤及鼠標(biāo)作為首要考慮的輸入方式。
  1. 雖然 UI 元素是有形的,但是他們被限制在屏幕里面(電腦或者移動設(shè)備的屏幕),視覺元素和動效能減少這種割裂,讓用戶能夠立即感知自己的操作。

總結(jié)來說就是:讓用戶知道他點(diǎn)擊了某個(gè)按鈕
同樣是剪影訊源碼分析:

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"
這兩行設(shè)置了水波紋的點(diǎn)擊效果:
除了selectableItemBackground還有一種水波紋
selectableitembackgroundborderless只能在v21以上使用

2017-03-11_22_14_09_0-240.gif

要注意一點(diǎn),在adapter里面設(shè)置點(diǎn)擊事件的時(shí)候一定要設(shè)置最外層的布局的點(diǎn)擊事件才有效的實(shí)現(xiàn),否則沖突就無法實(shí)現(xiàn)點(diǎn)擊的水波紋。

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

            }
        });

水波紋可以加在所有可以點(diǎn)擊的控件上,比如Button

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

有意義的轉(zhuǎn)場動畫

為了讓界面切換不顯的突兀,android里設(shè)置了專場動畫。Activity的轉(zhuǎn)場動畫很早就有,但是太過于單調(diào),樣式也不好看,于是Google在Android5.0之后,又推出的新的轉(zhuǎn)場動畫,效果還是非常炫的。

參考文章: Android5.0之Activity的轉(zhuǎn)場動畫

  1. 舊轉(zhuǎn)場動畫回顧
    首先我們還是先來看看在5.0之前如果我們想要在啟動Activity時(shí)使用動畫該怎么做呢?
startActivity(new Intent(this, Main3Activity.class));  
        overridePendingTransition(R.anim.in,R.anim.out);  

對應(yīng)的入場和出場動畫就是兩個(gè)補(bǔ)間動畫,如下:
入場動畫:

<?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>  

這種動畫是針對整個(gè)Activity而言的,無法設(shè)置Activity中元素的入場/出場動畫。

  1. 5.0之后的轉(zhuǎn)場動畫
    分為兩種:
  2. 分解、滑動進(jìn)入、淡入淡出
    分解
    先來看一張效果圖:

    就是這樣一種效果,那我們接下來看看這種效果要怎么實(shí)現(xiàn)。
    首先,把之前啟動Activity的代碼改成下面的寫法:
startActivity(new Intent(this, Main2Activity.class), ActivityOptions.makeSceneTransitionAnimation(this).toBundle());  

添加完成之后,在Main2Activity中設(shè)置該Activity的進(jìn)出場動畫即可:

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

大家一定要記得在styles.xml文件中添加下面一行代碼,表示激活A(yù)ctivity中元素的過渡效果:

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

滑動進(jìn)入
有了上面的步驟,再設(shè)置滑動進(jìn)入就很簡單了,只需要修改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的進(jìn)場和退出可以分別設(shè)置不同的動畫效果,如果沒有分別設(shè)置,則進(jìn)場和退出的動畫反過來。比如退出是變淡,則進(jìn)場就是變深。<只在設(shè)置了專場動畫的兩個(gè)Activity里>

  1. 元素共享
    共享元素動畫
    共享元素動畫是一個(gè)非常神奇的東東,我們先來看看效果:



    可能這個(gè)Gif動畫還不太清晰,我再來解釋一下,在MainActivity和Main2Activity里邊都有一個(gè)Button,只不過一個(gè)大一個(gè)小,從MainActivity跳轉(zhuǎn)到Main2Activity時(shí),我并沒有感覺到Activity的跳轉(zhuǎn),只是覺得好像第一個(gè)頁面的Button放大了,同理,當(dāng)我從第二個(gè)頁面回到第一個(gè)頁面時(shí),也好像Button變小了。OK,這就是我們的Activity共享元素。
    當(dāng)兩個(gè)Activity中有同一個(gè)控件的時(shí)候,我們便可以采用共享元素動畫。
    使用共享元素動畫的時(shí)候,我們需要首先給MainActivity和Main2Activity中的兩個(gè)button分別添加Android:transitionName="mybtn"屬性,并且該屬性的值要相同,這樣系統(tǒng)才知道這兩個(gè)控件是共享元素。設(shè)置完成之后,接下來就是啟動Activity的代碼了,如下:

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

還是上面那種啟動方式的重載方法,只不過這里多了兩個(gè)參數(shù),view表示MainActivity中的共享元素(就是那個(gè)Button),第二個(gè)參數(shù)表示布局文件中transitionAnimation屬性的值。OK,就這么簡單。
多元素共享:
那我如果兩個(gè)頁面中有多個(gè)共享元素該怎么辦呢?簡單,android:transitionName屬性還像上面一樣設(shè)置,然后在啟動Activity時(shí)我們可以通過Pair.create方法來設(shè)置多個(gè)共享元素,如下:

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

分析簡影訊里使用的過場動畫:
1.主界面的RecycleView進(jìn)入的過渡動畫:
如下:

2017-03-12_17_01_27_0-59.gif

分析源碼:

這個(gè)是RecycleView加載Item的動畫,所以寫在RecycleView的Adapter。
其實(shí)RecycleView內(nèi)部是可以設(shè)置Item的動畫的,使用這個(gè)方法:
mRecyclerView.setItemAnimator(new DefaultItemAnimator(mRecyclerView));
除了默認(rèn)的動畫還有其他內(nèi)置的動畫:

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)
        };
    }

寫一個(gè)函數(shù)設(shè)置是否加載動畫:

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

在onBindViewHolder里為每一個(gè)item設(shè)置動畫:

    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 用來判斷是不是最后一個(gè),否則不設(shè)置加載動畫。

2.進(jìn)入詳情頁面的動畫

2017-03-12_17_01_46_0-215.gif

過度動畫:在第二個(gè)Activity里設(shè)置:

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());
        }
    }

第一個(gè)Activity里跳轉(zhuǎn)調(diào)用:

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

打動用戶的細(xì)節(jié)

這里主要是一些圖標(biāo)的小動畫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容