Android自定義水波紋動畫Layout實例代碼
話不多說,我們先來看看效果:
Hi前輩搜索預(yù)覽
這一張是《Hi前輩》的搜索預(yù)覽圖,你可以在這里下載這個APP查看更多效果:
http://www.wandoujia.com/apps/com.superlity.hiqianbei
LSearchView
這是一個MD風(fēng)格的搜索框,集成了ripple動畫以及search時的loading,使用很簡單,如果你也需要這樣的搜索控件不妨來試試:https://github.com/onlynight/LSearchView
RippleEverywhere
女友的照片:
女友的照片:
這是一個水波紋動畫支持庫,由于使用暫時只支持Android4.0以上版本。https://github.com/onlynight/RippleEverywhere
實現(xiàn)原理
使用屬性動畫完成該動畫的實現(xiàn),由于android2.3以下已經(jīng)不是主流機型,故只兼容4.0以上系統(tǒng)。
關(guān)于屬性動畫,如果還有童鞋不了解可以去看看hongyang大神的這篇文章:
http://www.dbjr.com.cn/article/82668.htm
在我看來屬性動畫實際上就類似于定時器,所謂定時器就是獨立在主線程之外的另外一個用于計時的線程,每當(dāng)?shù)竭_你設(shè)定時間的時候這個線程就會通知你;屬性動畫也不光是另外一個線程,他能夠操作主線程UI元素屬性就說明了它內(nèi)部已經(jīng)做了線程同步。
基本原理
我們先來看下關(guān)鍵代碼:
@Override protected void onDraw(Canvas canvas) { if (running) { // get canvas current state final int state = canvas.save(); // add circle to path to crate ripple animation // attention: you must reset the path first, // otherwise the animation will run wrong way. ripplePath.reset(); ripplePath.addCircle(centerX, centerY, radius, Path.Direction.CW); canvas.clipPath(ripplePath); // the {@link View#onDraw} method must be called before // {@link Canvas#restoreToCount}, or the change will not appear. super.onDraw(canvas); canvas.restoreToCount(state); return; } // in a normal condition, you should call the // super.onDraw the draw the normal situation. super.onDraw(canvas); } Canvas#save()和Canvas#restoreToCount()
這個兩個方法用于繪制狀態(tài)的保存與恢復(fù)。繪制之前先保存上一次的狀態(tài);繪制完成后恢復(fù)前一次的狀態(tài);以此類推直到running成為false,中間的這個過程就是動畫的過程。
Path#addCircle()和Canvas#clipPath()
addCircle用于在path上繪制一個圈;clipPath繪制剪切后的path(只繪制path內(nèi)的區(qū)域,其他區(qū)域不繪制)。
radiusAnimator = ObjectAnimator.ofFloat(this, "animValue", 0, 1); /** * This method will be called by {@link this#radiusAnimator} * reflection calls. * * @param value animation current value */ public void setAnimValue(float value) { this.radius = value * maxRadius; System.out.println("radius = " + this.radius); invalidate(); }
這一段是動畫的動效關(guān)鍵,首先要有一個隨著時間推移而變化的值,當(dāng)每次這個值變化的時候我們需要跟新界面讓view重新繪制調(diào)用onDraw方法,我們不能手動調(diào)用onDraw方法,系統(tǒng)給我們提供的invalidate會強制view重繪進而調(diào)用onDraw方法。
以上就是這個動畫的全部關(guān)鍵原理了,下面我們來一份完整的源碼:
import android.animation.Animator; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; import android.graphics.Path; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.ImageView; /** * Created by lion on 2016/11/11. * <p> * RippleImageView use the {@link Path#addCircle} function * to draw the view when {@link RippleImageView#onDraw} called. * <p> * When you call {@link View#invalidate()} function,then the * {@link View#onDraw(Canvas)} will be called. In that way you * can use {@link Path#addCircle} to draw every frame, you will * see the ripple animation. */ public class RippleImageView extends ImageView { // view center x private int centerX = 0; // view center y private int centerY = 0; // ripple animation current radius private float radius = 0; // the max radius that ripple animation need private float maxRadius = 0; // record the ripple animation is running private boolean running = false; private ObjectAnimator radiusAnimator; private Path ripplePath; public RippleImageView(Context context) { super(context); init(); } public RippleImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public RippleImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(21) public RippleImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { ripplePath = new Path(); // initial the animator, when animValue change, // radiusAnimator will call {@link this#setAnimValue} method. radiusAnimator = ObjectAnimator.ofFloat(this, "animValue", 0, 1); radiusAnimator.setDuration(1000); radiusAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); radiusAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { running = true; } @Override public void onAnimationEnd(Animator animator) { running = false; } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); centerX = (right - left) / 2; centerY = (bottom - top) / 2; maxRadius = maxRadius(left, top, right, bottom); } /** * Calculate the max ripple animation radius. * * @param left view left * @param top view top * @param right view right * @param bottom view bottom * @return */ private float maxRadius(int left, int top, int right, int bottom) { return (float) Math.sqrt(Math.pow(right - left, 2) + Math.pow(bottom - top, 2) / 2); } /** * This method will be called by {@link this#radiusAnimator} * reflection calls. * * @param value animation current value */ public void setAnimValue(float value) { this.radius = value * maxRadius; System.out.println("radius = " + this.radius); invalidate(); } @Override protected void onDraw(Canvas canvas) { if (running) { // get canvas current state final int state = canvas.save(); // add circle to path to crate ripple animation // attention: you must reset the path first, // otherwise the animation will run wrong way. ripplePath.reset(); ripplePath.addCircle(centerX, centerY, radius, Path.Direction.CW); canvas.clipPath(ripplePath); // the {@link View#onDraw} method must be called before // {@link Canvas#restoreToCount}, or the change will not appear. super.onDraw(canvas); canvas.restoreToCount(state); return; } // in a normal condition, you should call the // super.onDraw the draw the normal situation. super.onDraw(canvas); } /** * call the {@link Animator#start()} function to start the animation. */ public void startAnimation() { if (radiusAnimator.isRunning()) { radiusAnimator.cancel(); } radiusAnimator.start(); } }
以上所述是小編給大家介紹的Android自定義水波紋動畫Layout實例代碼,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
基于Android6.0實現(xiàn)彈出Window提示框
這篇文章主要為大家詳細介紹了基于Android6.0實現(xiàn)彈出Window提示框,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10Android studio有關(guān)側(cè)滑的實現(xiàn)代碼
這篇文章主要介紹了Android studio有關(guān)側(cè)滑的實現(xiàn)代碼,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06Android 6.0 藍牙搜索不到設(shè)備原因,MIUI權(quán)限申請機制方法
今天小編就為大家分享一篇Android6.0 藍牙搜索不到設(shè)備原因,MIUI權(quán)限申請機制方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07native.js獲取手機硬件基本信息實例代碼android版
本文為大家分享了native.js獲取手機硬件基本信息實例代碼android版包括手機MAC地址,手機內(nèi)存大小,手機存儲空間大小,手機CPU信息等手機硬件基本信息2018-09-09Ubuntu中為Android實現(xiàn)Application Frameworks層增加硬件訪問服務(wù)
本文主要介紹Android實現(xiàn) Application Frameworks層增加硬件訪問服務(wù),這里對實現(xiàn)增加硬件訪問服務(wù)的功能做出了詳細的工作流程,并提供示例代碼,有需要的小伙伴參考下2016-08-08Android下Activity間通信序列化過程中的深淺拷貝淺析
這篇文章主要給大家介紹了關(guān)于Android下Activity間通信序列化過程中深淺拷貝的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10Android studio 下JNI編程實例并生成so庫的實現(xiàn)代碼
這篇文章主要介紹了Android studio 下JNI編程實例并生成so庫,需要的朋友可以參考下2017-09-09