material design中的動畫将為使用者提供操作回報并在使用者與您的應用進行互動時提供視覺連續性。 material design将為按鈕與操作行為轉換提供一些預設動畫,而 android 5.0(api level 21)及更高版本可讓您定制這些動畫,同時也可建立新動畫:
一、觸摸回報動畫
效果圖:
material design的觸摸回報可在使用者與 ui 元素互動時,在接觸點上提供即時視覺确認。 适用于按鈕的預設觸摸動畫使用全新 rippledrawable類别,以波紋效果實作不同狀态間的轉換。
在大多數情況下,應以下列方式指定視圖背景,在您的視圖 xml 中應用此功能:
android:attr/selectableitembackground 指定有界的波紋。
android:attr/selectableitembackgroundborderless 指定越過視圖邊界的波紋。 它将由一個非空背景的視圖的最近父項所繪制和設定邊界。
任何view處于可點選狀态,都可以使用rippledrawable來達到水波紋特效,而且必須處于可點選狀态,才會出現波紋動畫效果。
在代碼中可以這樣設定:
注意:selectableitembackgroundborderless是 api level 21 中推出的新屬性。
此外,您可利用 ripple元素将 rippledrawable定義為一個 xml 資源。
您可以為 rippledrawable對象指定一種顔色。如果要改變預設觸摸回報顔色,請使用主題的 android:colorcontrolhighlight屬性。
如果要了解更多資訊,請參閱 rippledrawable類别的 api 參考文檔。
我們來看看系統自帶的觸摸回報動畫是怎麼實作的,為什麼隻需要在view的background或者foreground屬性設定成?android:attr/selectableitembackground或者?android:attr/selectableitembackgroundborderless就可以實作波紋動畫的效果?這兩個屬性點進去,可以看到在路徑sdk/platforms/android-xx/data/res/values/attrs.xml檔案中有定義這麼兩個屬性:
<!-- background drawable for bordered standalone items that need focus/pressed states. -->
<attr name="selectableitembackground" format="reference" />
<!-- background drawable for borderless standalone items that need focus/pressed states. -->
<attr name="selectableitembackgroundborderless" format="reference" />
我們想到,這兩個屬性既然是整個app中有效的,那可能會是在theme中的屬性吧,那就去androidmanifest檔案中跟這個theme一步步看下去,最後在base.v21.theme.appcompat.light這個style中看到确實是有這兩個item屬性:
<item name="selectableitembackground">?android:attr/selectableitembackground</item>
<item name="selectableitembackgroundborderless">?android:attr/selectableitembackgroundborderless</item>
但是這裡還是調用的系統的定義的屬性,繼續往下追,在android:theme.material和android:theme.material.light中,可以看到:
<item name="selectableitembackground">@drawable/item_background_material</item>
<item name="selectableitembackgroundborderless">@drawable/item_background_borderless_material</item>
然後sdk路徑下platforms\\android-xx\\data\\res\\drawable可以找到這些資源檔案如下圖:
item_background_material的内容是:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorcontrolhighlight">
<item android:id="@id/mask">
<color android:color="@color/white" />
</item>
</ripple>
item_background_borderless_material的内容是:
android:color="?attr/colorcontrolhighlight" />
系統的做法是用ripple元素将 rippledrawable定義為一個 xml 資源,而通過看view的源碼中在構造方法中是這樣擷取background屬性的:
public view(context context, @nullable attributeset attrs, int defstyleattr, int defstyleres) {
this(context);
final typedarray a = context.obtainstyledattributes(
attrs, com.android.internal.r.styleable.view, defstyleattr, defstyleres);
if (mdebugviewattributes) {
saveattributedata(attrs, a);
}
drawable background = null;
switch (attr) {
case com.android.internal.r.styleable.view_background:
background = a.getdrawable(attr);
break;
.
}
也就是說,這個background實際上就是rippledrawable類。那我們就來看看這個rippledrawable内部到底是怎麼做的吧。
首先官方文檔對rippledrawable解釋是
drawable that shows a ripple effect in response to state changes. the anchoring position of the ripple for a given state may be specified by calling sethotspot(float, float)with the corresponding state attribute identifier.
通過顯示出波紋效果來響應狀态的改變,對于給定狀态的波紋的錨定位置可以通過調用具有對應的狀态屬性辨別符的sethotspot(float,float)來指定。
rippledrawable繼承自layerdrawable,而layerdrawable是繼承drawable,rippledrawable又是為了響應view的statechange,那就看看drawable類中對點選時的狀态處理吧。
public boolean setstate(@nonnull final int[] stateset) {
if (!arrays.equals(mstateset, stateset)) {
mstateset = stateset;
return onstatechange(stateset);
}
return false;
給drawable設定狀态屬性時,會把狀态的數組傳給onstatechange方法,在rippledrawable中重寫了onstatechange。
看到setrippleactive和setbackgroundactive這兩個方法應該可以猜到是什麼意思了,接着看。
private void setrippleactive(boolean active) {
if (mrippleactive != active) {
mrippleactive = active;
if (active) {
tryrippleenter();
} else {
tryrippleexit();
如果drawable是enable=true且pressd=true時,會調用tryrippleenter方法
看到這裡,我們可以知道要開始做波紋動畫的效果了。mripple 是rippleforeground類的執行個體,然而我沒有在rippleforeground類中找到setup和enter方法,但是rippleforeground繼承自ripplecomponent類,于是,我在這個類中發現了這兩個方法。
public final void setup(float maxradius, int densitydpi) {
if (maxradius >= 0) {
mhasmaxradius = true;
mtargetradius = maxradius;
} else {
mtargetradius = gettargetradius(mbounds);
mdensityscale = densitydpi * displaymetrics.density_default_scale;
ontargetradiuschanged(mtargetradius);
setup是初始化一系列參數,enter建立一個動畫并開始動畫。
從上面建立動畫的代碼可以看到,實際上是一個組合的屬性動畫,然後自定義了三個屬性波紋半徑tween_radius、波紋中心點tween_origin和波紋的不透明度opacity。通過這三個屬性的過渡變化得到一個複合的動畫。以上就是前景波紋動畫效果的實作過程。
private void setbackgroundactive(boolean active, boolean focused) {
if (mbackgroundactive != active) {
mbackgroundactive = active;
trybackgroundenter(focused);
trybackgroundexit();
mbackground是ripplebackground類的執行個體,與rippleforeground不同的是,背景動畫隻是改變了不透明度。
@override
protected animator createsoftwareenter(boolean fast) {
// linear enter based on current opacity.
final int maxduration = fast ? opacity_enter_duration_fast : opacity_enter_duration;
final int duration = (int) ((1 - mopacity) * maxduration);
final objectanimator opacity = objectanimator.offloat(this, opacity, 1);
opacity.setautocancel(true);
opacity.setduration(duration);
opacity.setinterpolator(linear_interpolator);
return opacity;
以上分析的都是手指觸摸view時産生的enter波紋動畫,當手指擡起時state也會改變,會産生一個exit動畫,這裡就不詳細分析了。
二、使用揭露效果
當需要顯示或隐藏一組ui元素時,揭露動畫可為使用者提供視覺連續性。
/* @param view the view will be clipped to the animating circle.要隐藏或顯示的view
* @param centerx the x coordinate of the center of the animating circle, relative to <code>view</code>.動畫開始的中心點x
* @param centery the y coordinate of the center of the animating circle, relative to <code>view</code>.動畫開始的中心點y
* @param startradius the starting radius of the animating circle.動畫開始半徑
* @param endradius the ending radius of the animating circle.動畫結束半徑
*/
public static animator createcircularreveal(view view,
int centerx, int centery, float startradius, float endradius) {
return new revealanimator(view, centerx, centery, startradius, endradius);
revealanimator和之前的動畫使用沒什麼差別,同樣可以設定監聽器和加速器來實作各種各樣的特效,該動畫主要用在隐藏或者顯示一個view,改變view的大小等過渡效果。
顯示view:
final textview tv9 = (textview) findviewbyid(r.id.tv9);
findviewbyid(r.id.content_main).setonclicklistener(new view.onclicklistener() {
@override public void onclick(view v) {
// get the center for the clipping circle
int cx = (tv9.getright() - tv9.getleft()) / 2;
int cy = (tv9.getbottom() - tv9.gettop()) / 2;
// get the final radius for the clipping circle
int finalradius = math.max(tv9.getwidth(), tv9.getheight());
// create the animator for this view (the start radius is zero)
final animator anim = viewanimationutils.createcircularreveal(tv9, cx, cy, 0, finalradius);
tv9.setvisibility(view.visible);
anim.start();
});
隐藏view:
tv9.setonclicklistener(new view.onclicklistener() {
int initradius = math.max(tv9.getwidth(), tv9.getheight());
final animator anim = viewanimationutils.createcircularreveal(tv9, cx, cy, initradius, 0);
anim.addlistener(new animatorlisteneradapter() {
@override public void onanimationend(animator animation) {
super.onanimationend(animation);
// make the view visible and start the animation
tv9.setvisibility(view.invisible);
}
});
沿着中心縮小:
animator animator = viewanimationutils.createcircularreveal(view, view.getwidth() / 2, view.getheight() / 2, view.getwidth(), 0);
animator.setinterpolator(new linearinterpolator());
animator.setduration(1000);
animator.start();
從左上角擴充:
animator animator = viewanimationutils.createcircularreveal(view,0,0,0,(float) math.hypot(view.getwidth(), view.getheight()));
三、使用轉場動畫
效果圖以共享元素的轉場動畫為例:
materialdesign應用中的操作行為轉換透過通用元素之間的移動和轉換提供不同狀态之間的視覺連接配接。可為進入、退出轉換以及操作行為之間的共享元素轉換指定定制動畫。在5.0之前,我們可以在startactivity之後調用overridependingtransition來指定activity的轉場動畫。
進入轉換将決定操作行為中視圖如何進入場景。例如,在分解進入轉換中,視圖将從螢幕外進入場景并飛往螢幕中心。
退出轉換将決定操作行為中應用行為的顯示視圖如何退出場景。例如,在分解退出轉換中,視圖将從螢幕中心退出場景。
共享元素轉換将決定兩個操作行為轉換之間共享的視圖如何在這些操作行為中轉換。 例如,如果兩個操作行為擁有相同的圖像,但其位置與大小不同,changeimagetransform共享元素轉換将在這些操作行為之間平滑地轉換與縮放圖像。
android 5.0(api level 21)支援這些進入與退出轉換:(普通過渡動畫)
分解 - 從場景中心移入或移出視圖。
滑動 - 從場景邊緣移入或移出視圖。
淡入淡出 - 通過調整透明度在場景中增添或移除視圖。
也支援這些共享元素轉換:(共享元素的過渡動畫)
changebounds - 為目标視圖的大小添加動畫。
changeclipbounds - 為目标視圖的裁剪大小添加動畫。
changetransform - 為目标視圖的縮放、旋轉和位移添加動畫。
changeimagetransform - 為目标圖檔的縮放、旋轉和位移添加動畫。
指定轉場動畫
要想使用新的轉場動畫,可以繼承material design主題後在style風格中指定:
其中,change_image_transform定義如下:
<!-- res/transition/change_image_transform.xml -->
<!-- (see also shared transitions below) -->
<transitionset xmlns:android="http://schemas.android.com/apk/res/android">
<changeimagetransform/>
</transitionset>
如果要帶代碼中開啟視窗内容轉換,需要調用window.requestfeature()方法。
普通轉場動畫:
所有繼承自visibility類都可以作為進入、退出的過度動畫。如果我們想自定義進入和退出時的動畫效果,隻需要繼承visibility,重載onappear和ondisappear方法來定義進入喝退出的動畫。系統提供了三種預設方式:
explode 從螢幕中心移入或移出視圖
slide 從螢幕邊緣移入或移出視圖
fade 改變視圖的透明度
想在xml中指定自定義的進入、退出的過度動畫需要先對動畫進行定義:
<transition class="my.app.transition.customtransition"/>
注意:其中customtransition是我們自定義的動畫,它必須繼承自visibility。
想以普通轉場動畫的方式啟動一個activity,必須在startactivity函數中傳遞一個activityoptions的bundle對象:
activityoptions options = activityoptions.makescenetransitionanimation(activity);
startactivity(intent, options.tobundle());
如果想讓傳回也具備轉場效果,那麼在傳回的activity中不要再調用finish函數,而是應該使finishaftertransition來結束一個activity,該函數會等待動畫執行完畢才結束該activity。
共享轉場動畫:
如果要在兩個具有共享元素的activity之間使用轉場動畫,那麼:
1、在題中啟用視窗内容轉換。android:windowcontenttransitions
2、在theme中指定一個共享元素轉換。
3、将transitions定義為xml資源。
4、利用 android:transitionname屬性對兩個布局中的共享元素指定一個通用名稱。
5、使用 activityoptions.makescenetransitionanimation()方法。
// get the element that receives the click event
final view imgcontainerview = findviewbyid(r.id.img_container);
// get the common element for the transition in this activity
final view androidrobotview = findviewbyid(r.id.image_small);
// define a click listener
imgcontainerview.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view view) {
intent intent = new intent(this, activity2.class);
// create the transition animation - the images in the layouts
// of both activities are defined with android:transitionname="robot"
activityoptions options = activityoptions
.makescenetransitionanimation(this, androidrobotview, "robot");
// start the new activity
startactivity(intent, options.tobundle());
如果要在代碼中生成共享view,那麼需要調用view.settransitionname()方法對兩個布局中的共享元素指定一個通用名稱。
如果有多個共享元素,則可以通過pair進行包裝處理:
activityoptions options = activityoptions.makescenetransitionanimation(activity,
pair.create(view1, "name1"),//這裡view1、view2如果是textview或者imageview等,需要轉成view類型才可以
pair.create(view2, "name2"));
startactivity(intent,.tobundle());
傳回時如果需要具備轉場動畫,那麼也需要用finish函數替代finishaftertransition來結束一個activity。
使用曲線運動
因為曲線運動和屬性動畫以及貝塞爾曲線這些東西混雜在一起,是以準備把這節拿出來單獨寫。這裡就不多說了。
視圖狀态改變
android 5.0在原有的圖檔選擇器和顔色選擇器上進行了增強,不僅是控件能根據不同的狀态顯示不同的背景圖檔,還能在兩種狀态切換時指定一個動畫,來增加過渡效果,吸引使用者眼球,以突出重點内容。
statelistanimator類和圖檔選擇器,顔色選擇器類似,可以根據view的狀态改變呈現不同的動畫效果,通過xml我們可以建構對應不同狀态的動畫合集,其使用方式也非常簡單,在對應的狀态指定一個屬性動畫即可:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<set>
<objectanimator android:propertyname="translationz"
android:duration="200"
android:valueto="20dp"
android:valuetype="floattype"/>
</set>
<item android:state_enabled="true" android:state_pressed="false">
android:valueto="0"
</selector>
代碼中這樣加載即可:
textview tv11 = (textview) findviewbyid(r.id.tv11);
statelistanimator statelanim = animatorinflater.loadstatelistanimator(this,r.drawable.selector_for_button);
tv11.setstatelistanimator(statelanim);
繼承了material主題後,按鈕預設擁有了z屬性動畫。如果想取消這種預設狀态,可以把狀态動畫指定為null。
除了statelistanimator類指定狀态切換的屬性動畫外,還可以通過animatedstatelistdrawable來指定狀态切換的幀動畫:
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/pressed" android:drawable="@drawable/btn_check_15" android:state_pressed="true"/>
<item android:id="@+id/normal" android:drawable="@drawable/btn_check_0"/>
<transition android:fromid="@+id/normal" android:toid="@+id/pressed">
<animation-list>
<item android:duration="20" android:drawable="@drawable/btn_check_0"/>
<item android:duration="20" android:drawable="@drawable/btn_check_1"/>
<item android:duration="20" android:drawable="@drawable/btn_check_2"/>
</animation-list>
</transition>
</animated-selector>
幀動畫的資源檔案直接在xml中作為view的background即可。
四、矢量圖動畫
效果圖:
先在drawable中定義一張矢量圖:
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="200dp"
android:width="200dp"
android:viewportheight="400"
android:viewportwidth="400">
<group
android:name="rotationgroup"
android:pivotx="0"
android:pivoty="0">
<path
android:name="star"
android:pathdata="m 100,100 h 200 l -200 150 100 -250 100 250 z"
android:strokecolor="@color/colorprimary"
android:strokelinecap="round"
android:strokewidth="10"/>
</group>
</vector>
然後在anim中定義動畫:
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectanimator
android:propertyname="trimpathstart"
android:valuefrom="0"
android:valueto="1"
android:valuetype="floattype"
android:duration="2000"
android:repeatmode="reverse"
android:repeatcount="-1"
android:interpolator="@android:interpolator/accelerate_decelerate"/>
</set>
最後在drawable中定義一個animated-vector:将動畫資源指定給drawable屬性值的矢量圖。
<?xml version="1.0" encoding="utf-8"?><animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/vector_drawable">
<target
android:name="star"
android:animation="@anim/animation"/></animated-vector>
注意:這裡drawable屬性值是前面我們定義的矢量圖,target中name要和矢量圖中path的name一樣,animation就是前面定義的動畫資源檔案。
在view的xml中使用以及在代碼中開始動畫:
<imageview
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:srccompat="@drawable/anim_vector_drawable"
android:layout_gravity="center"/>
imageview iv = (imageview) findviewbyid(r.id.iv);
drawable drawable = iv.getdrawable();
if (drawable instanceof animatable) {
((animatable) drawable).start();
作者:shenhuniurou
來源:51cto