背景:
近日瀏覽國外的網(wǎng)站發(fā)現(xiàn)了一個(gè)酷炫的按鈕動畫,想著自己造輪子實(shí)現(xiàn)汉嗽,同時(shí)也記錄下實(shí)現(xiàn)的過程欲逃。由于是第一次寫技術(shù)文章,不足之處請?jiān)谠u論區(qū)多多賜教饼暑。不勝感激稳析。
先上效果圖弓叛。
這個(gè)是在國外網(wǎng)站看到的。想自己實(shí)現(xiàn)按鈕動畫與跳轉(zhuǎn)撰筷。
步驟:
乍一看挺復(fù)雜,細(xì)細(xì)分析應(yīng)該是多種動畫效果的組合毕籽。剛開始點(diǎn)擊按鈕,按鈕由長條變?yōu)閳A形关筒,圓形按鈕中間有個(gè)白色的圓弧在不斷旋轉(zhuǎn),然后按鈕突然放大跳轉(zhuǎn)界面蒸播。整理一下。
1.按鈕由長條變圓形
2.白色的圓弧不斷旋轉(zhuǎn)
3.按鈕放大袍榆,跳轉(zhuǎn)界面
實(shí)現(xiàn):
首先定義一個(gè)類取名叫NbButton胀屿,讓其繼承Button,創(chuàng)建構(gòu)造方法,在init()方法中實(shí)現(xiàn)一些準(zhǔn)備工作包雀。
public NbButton(Context context) {
super(context);
init(context);
}
public NbButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public NbButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
上圖中按鈕是粉色的宿崭,帶有圓角,所以這次在init()中直接定義顏色與圓角才写。
backDrawable=new GradientDrawable();
int colorDrawable=context.getColor(R.color.cutePink);
backDrawable.setColor(colorDrawable);
backDrawable.setCornerRadius(120);
setBackground(backDrawable);
setText("登陸");
GradientDrawable這個(gè)類用來定義shape屬性劳曹,關(guān)于GradientDrawable不作詳細(xì)介紹奴愉。backDrawable定義了按鈕的顏色,按鈕圓角半徑铁孵,并設(shè)置了字體。
然后該考慮動畫的事情了房资。
步驟1:
點(diǎn)擊按鈕后蜕劝,按鈕由長條變圓形。這個(gè)效果的原理是修改backDrawable屬性轰异,很多類似的自定義控件其形狀或顏色的變化原理都是修改Drawable屬性來實(shí)現(xiàn)岖沛。那么backDrawable發(fā)生了哪些變化?第一backDrawable的繪制范圍搭独,也就是bound變了婴削,由最初的長條范圍變化為可繪制圓的范圍。第二cornerRadius由最初的半徑變?yōu)锽utton高度的一半牙肝。通過ValueAnimator與ObjectAnimator來實(shí)現(xiàn)這些變化唉俗。
首先是bound的改變。
ValueAnimator valueAnimator=ValueAnimator.ofInt(width,heigh);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value= (int) animation.getAnimatedValue();
int leftOffset=(width-value)/2;
int rightOffset=width-leftOffset;
backDrawable.setBounds(leftOffset,0,rightOffset,heigh);
}
});
ValueAnimator是個(gè)數(shù)值變化器配椭,只是改變值的大小虫溜,并不作用于動畫中屬性的改變,屬性的變化仍然需要我們手動設(shè)置股缸。width和heigh對應(yīng)繪制按鈕的寬和高衡楞。添加UpdateListener監(jiān)聽可以檢測到每次值的變化情況,
畫的不好請見諒敦姻。每次UpdateListener檢測到值變化時(shí)通過backDrawable.setBounds重新繪制backDrawable的范圍瘾境。
bound改變的同時(shí),cornerRadius也在改變镰惦。
cornerRadius從最初設(shè)置的120變?yōu)閔eigh的一半稠诲。
ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(backDrawable,"cornerRadius",120,heigh/2);
ObjectAnimator是ValueAnimator的子類,可以直接改變相關(guān)的屬性完成動畫效果赡盘,相比較ValueAnimator捡遍,使用起來更加方便急膀。這里通過改變backDrawable的cornerRadius的屬性實(shí)現(xiàn)了圓角半徑的改變龄捡。
既然bound和cornerRadius同時(shí)改變聘殖,是不是兩個(gè)Animator一起start才可以實(shí)現(xiàn)呢行瑞?有一個(gè)類叫AnimatorSet血久,顧名思義就是Animator的集合帮非,通過它就可以使多個(gè)動畫協(xié)調(diào)工作末盔。
AnimatorSet animatorSet=new AnimatorSet();
animatorSet.setDuration(500);
animatorSet.playTogether(valueAnimator,objectAnimator);
animatorSet.start();
playTogether的方法中可以放入多個(gè)Animator,可以使多個(gè)動畫協(xié)調(diào)工作。調(diào)用start后翠拣,多個(gè)動畫開始工作误墓,步驟1就實(shí)現(xiàn)了优烧。
步驟2:
按鈕形狀改變的同時(shí)链峭,中間會出現(xiàn)個(gè)白色的圓弧弊仪,不斷旋轉(zhuǎn)。
咱們先畫一個(gè)白色的圓弧驳癌,利用canvas.drawArc這個(gè)方法就可以畫弧颓鲜。
圖中寬度的5/12.甜滨、7/12/瘤袖、1/7這些都不是真實(shí)計(jì)算得出的捂敌,只是感覺差不多是這樣既琴,請不要糾結(jié)為什么是寬度的5/12這些甫恩,請?jiān)徫业牟粐?yán)謹(jǐn)填物。霎终。莱褒。
在onDraw里面繪制白色的圓弧广凸,調(diào)用invalidate()方法重繪就有了效果谅海。
RectF rectF=new RectF(getWidth()*5/12,getHeight()/7,getWidth()*7/12,getHeight()getHeight()/7);
canvas.drawArc(rectF,0,270,false,paint);
關(guān)于paint畫筆的設(shè)置
paint=new Paint();
paint.setColor(getResources().getColor(R.color.white));
paint.setStrokeWidth(4);
paint.setStyle(Paint.Style.STROKE);
paint.setTextSize(2);
ps:如果drawArc的第三個(gè)參數(shù)設(shè)置為true扭吁,圓弧就會過圓心盲镶。
圓弧算是畫出來了,那么如何實(shí)現(xiàn)圓弧不斷旋轉(zhuǎn)呢枫吧?熟悉drawArc這個(gè)方法的話很容易就能想到九杂,drawArc的第二個(gè)參數(shù)為圓弧的起始角度例隆,只需要不斷改變這個(gè)角度植影,就會形成旋轉(zhuǎn)的動畫思币。
arcValueAnimator=ValueAnimator.ofInt(0,360);
arcValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
startAngle= (int) animation.getAnimatedValue();
invalidate();
}
});
arcValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
arcValueAnimator.setDuration(1000);
arcValueAnimator.start();
把drawArc中第二個(gè)參數(shù)換成startAngle,在每次值更新后都重繪界面惶我。這樣圓弧的起始點(diǎn)就一直變化,形成了旋
轉(zhuǎn)效果盯蝴。設(shè)置animator無限循環(huán)捧挺,這樣效果就實(shí)現(xiàn)了闽烙,
canvas.drawArc(rectF,startAngle,270,false,paint);
但是有個(gè)小問題声搁,圓弧的旋轉(zhuǎn)并不是勻速轉(zhuǎn),而是先加速后減速很魂,為什么呢遏匆?ValueAnimatro可以設(shè)置插值器Interpolator拉岁,插值器系統(tǒng)定義了好幾種惰爬,有accelerated(加速),decelerated(減速),repeated(重復(fù)),bounced(彈跳)LinearInterpolator(勻速)等陵叽。如果不設(shè)置巩掺,系統(tǒng)會默認(rèn)先加速后減速胖替。所以還需要設(shè)置插值器為勻速。
arcValueAnimator.setInterpolator(new LinearInterpolator());
這樣步驟2的效果也實(shí)現(xiàn)了端朵。
步驟3:
按鈕放大冲呢,以圓的形式向外擴(kuò)展招狸。這個(gè)動畫當(dāng)時(shí)也查了好久才發(fā)現(xiàn)了一個(gè)方法ViewAnimationUtils.createCircularReveal(View view裙戏,int centerX,int centerY翰意,float startRadius,float endRadius)醒第。第一個(gè)參數(shù)是當(dāng)前承載你動畫的view,就是這個(gè)按鈕無限放大形病,可支持他效果的view漠吻,這里我選根布局途乃,第二個(gè)參數(shù)是動畫中心x軸坐標(biāo)扔傅,就是按鈕的中心x軸坐標(biāo),第三個(gè)參數(shù)是動畫中心y軸坐標(biāo)试读,是按鈕的中心y坐標(biāo)钩骇,第四個(gè)參數(shù)是開始動畫開始的半徑,第五個(gè)是動畫結(jié)束的半徑银亲。
int xc=(button.getLeft()+button.getRight())/2;
int yc=(button.getTop()+button.getBottom())/2;
animator= ViewAnimationUtils.createCircularReveal(rlContent,xc,yc,0,1111);
rlcontent是button的根布局群凶,寬度和高度都是占滿屏幕请梢,設(shè)置背景為按鈕放大的顏色毅弧,
<RelativeLayout
android:id="@+id/rl_content"
android:background="@color/cutePink"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.tongzhang.nbbutton.ui.NbButton
android:id="@+id/button_test"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="100dp"
android:textColor="#fff"
android:text="登陸"
android:layout_width="220dp"
android:layout_height="50dp" />
</RelativeLayout>
設(shè)置根布局背景顏色為紅色后够坐,在oncreate中設(shè)置背景透明度為0元咙,這樣一進(jìn)入activity庶香,就不會出現(xiàn)滿屏幕紅色的情況简识。
rlContent.getBackground().setAlpha(0);
當(dāng)按鈕開始無限放大后,再設(shè)置透明度為不透明即可顯示支持button的紅色區(qū)域奢赂。
animator.setDuration(300);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
startActivity(intent);
overridePendingTransition(R.anim.anim_in,R.anim.anim_out);
}
},200);
}
});
animator.start();
rlContent.getBackground().setAlpha(255);
當(dāng)動畫開始后膳灶,使用handler準(zhǔn)備入場跳轉(zhuǎn)動畫立由。為什么不等待動畫結(jié)束后再進(jìn)行入場動畫呢?其實(shí)這種動畫執(zhí)行到一半就開始入場動畫效果比較好聋迎,大家可以自己試試霉晕。
至于入場動畫,貼上代碼牺堰。
anim.in.xml:
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha android:fromAlpha="0" android:toAlpha="1"
android:duration="1000"/>
</set>
anim.out.xml:
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha android:fromAlpha="1" android:toAlpha="0"
android:duration="1000"/>
</set>
至此伟葫,整個(gè)動畫的思路就結(jié)束了筏养。
附上源碼:https://github.com/TongZhang1314/ButtonAnimator
很簡單的一個(gè)實(shí)現(xiàn),記錄下實(shí)現(xiàn)過程渐溶,權(quán)當(dāng)鞏固一下自定義view方面的知識茎辐。