Android自定義水波紋動(dòng)畫(huà)Layout實(shí)例代碼
話(huà)不多說(shuō),我們先來(lái)看看效果:

Hi前輩搜索預(yù)覽
這一張是《Hi前輩》的搜索預(yù)覽圖,你可以在這里下載這個(gè)APP查看更多效果:
http://www.wandoujia.com/apps/com.superlity.hiqianbei
LSearchView

這是一個(gè)MD風(fēng)格的搜索框,集成了ripple動(dòng)畫(huà)以及search時(shí)的loading,使用很簡(jiǎn)單,如果你也需要這樣的搜索控件不妨來(lái)試試:https://github.com/onlynight/LSearchView
RippleEverywhere
女友的照片:

女友的照片:

這是一個(gè)水波紋動(dòng)畫(huà)支持庫(kù),由于使用暫時(shí)只支持Android4.0以上版本。https://github.com/onlynight/RippleEverywhere
實(shí)現(xiàn)原理
使用屬性動(dòng)畫(huà)完成該動(dòng)畫(huà)的實(shí)現(xiàn),由于android2.3以下已經(jīng)不是主流機(jī)型,故只兼容4.0以上系統(tǒng)。
關(guān)于屬性動(dòng)畫(huà),如果還有童鞋不了解可以去看看hongyang大神的這篇文章:
http://www.dhdzp.com/article/82668.htm
在我看來(lái)屬性動(dòng)畫(huà)實(shí)際上就類(lèi)似于定時(shí)器,所謂定時(shí)器就是獨(dú)立在主線(xiàn)程之外的另外一個(gè)用于計(jì)時(shí)的線(xiàn)程,每當(dāng)?shù)竭_(dá)你設(shè)定時(shí)間的時(shí)候這個(gè)線(xiàn)程就會(huì)通知你;屬性動(dòng)畫(huà)也不光是另外一個(gè)線(xiàn)程,他能夠操作主線(xiàn)程UI元素屬性就說(shuō)明了它內(nèi)部已經(jīng)做了線(xiàn)程同步。
基本原理
我們先來(lái)看下關(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()
這個(gè)兩個(gè)方法用于繪制狀態(tài)的保存與恢復(fù)。繪制之前先保存上一次的狀態(tài);繪制完成后恢復(fù)前一次的狀態(tài);以此類(lèi)推直到running成為false,中間的這個(gè)過(guò)程就是動(dòng)畫(huà)的過(guò)程。
Path#addCircle()和Canvas#clipPath()
addCircle用于在path上繪制一個(gè)圈;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();
}
這一段是動(dòng)畫(huà)的動(dòng)效關(guān)鍵,首先要有一個(gè)隨著時(shí)間推移而變化的值,當(dāng)每次這個(gè)值變化的時(shí)候我們需要跟新界面讓view重新繪制調(diào)用onDraw方法,我們不能手動(dòng)調(diào)用onDraw方法,系統(tǒng)給我們提供的invalidate會(huì)強(qiáng)制view重繪進(jìn)而調(diào)用onDraw方法。
以上就是這個(gè)動(dòng)畫(huà)的全部關(guān)鍵原理了,下面我們來(lái)一份完整的源碼:
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自定義水波紋動(dòng)畫(huà)Layout實(shí)例代碼,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Android 自定義view實(shí)現(xiàn)水波紋動(dòng)畫(huà)效果
- Android自定義View 實(shí)現(xiàn)水波紋動(dòng)畫(huà)引導(dǎo)效果
- Android實(shí)現(xiàn)水波紋效果
- Android實(shí)現(xiàn)自定義華麗的水波紋效果
- Android自定義view實(shí)現(xiàn)水波紋進(jìn)度球效果
- Android實(shí)現(xiàn)兼容的水波紋效果
- Android特效之水波紋的實(shí)現(xiàn)
- Android仿水波紋流量球進(jìn)度條控制器
- Android項(xiàng)目實(shí)戰(zhàn)手把手教你畫(huà)圓形水波紋loadingview
- Android實(shí)現(xiàn)水波紋點(diǎn)擊效果
相關(guān)文章
Android11及以上文件讀寫(xiě)權(quán)限申請(qǐng)?jiān)敿?xì)介紹
安卓11改變了此前安卓系統(tǒng)對(duì)于文件管理的規(guī)則,在安卓 11 上,文件讀寫(xiě)變成了特殊權(quán)限,下面這篇文章主要給大家介紹了關(guān)于A(yíng)ndroid11及以上文件讀寫(xiě)權(quán)限申請(qǐng)的相關(guān)資料,需要的朋友可以參考下2022-08-08
Android getReadableDatabase() 和 getWritableDatabase()分析對(duì)比
這篇文章主要介紹了Android getReadableDatabase() 和 getWritableDatabase()分析對(duì)比的相關(guān)資料,需要的朋友可以參考下2017-06-06
android水平循環(huán)滾動(dòng)控件使用詳解
這篇文章主要為大家詳細(xì)介紹了android水平循環(huán)滾動(dòng)控件的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android App中使用ViewPager+Fragment實(shí)現(xiàn)滑動(dòng)切換效果
這篇文章主要介紹了Android App中使用ViewPager+Fragment實(shí)現(xiàn)滑動(dòng)切換效果的方法,借助Fragment可以使Activity的內(nèi)部管理邏輯更加清晰,需要的朋友可以參考下2016-03-03
Android開(kāi)發(fā)中Dialog半透明背景消失
這篇文章主要介紹了Android開(kāi)發(fā)中Dialog半透明背景消失的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-11-11
ListView異步加載圖片實(shí)現(xiàn)思路(優(yōu)化篇)
關(guān)于listview的異步加載,網(wǎng)上其實(shí)很多示例了,中心思想都差不多,不過(guò)很多版本或是有bug,或是有性能問(wèn)題有待優(yōu)化,下面就讓在下闡述其原理以探索個(gè)中奧秘2013-04-04
Android實(shí)現(xiàn)與Apache Tomcat服務(wù)器數(shù)據(jù)交互(MySql數(shù)據(jù)庫(kù))
本篇文章主要介紹了Android實(shí)現(xiàn)與Apache Tomcat服務(wù)器數(shù)據(jù)交互(MySql數(shù)據(jù)庫(kù)),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06

