Android 使用 Path 實現(xiàn)搜索動態(tài)加載動畫效果
今天實現(xiàn)一個搜索動態(tài)加載數(shù)據(jù)的動畫效果,還是先看效果吧,用文字描述干巴巴的,看圖說話什么都明白了,

實現(xiàn)這個就是使用Path中的getSegment()不斷的去改變它截取片段的start和stop,再結(jié)合動畫,今天就分步驟實現(xiàn)它,看完以后你也會覺的不是很難,只是沒想到這么實現(xiàn)而已,所以要多見識,所謂眼界決定你的高度,還是延續(xù)我寫博客的習慣,一步步分析,第一步就是繪制如下圖:

如果單純的繪制這個圖很簡單很簡單的,繪制一個圓,然后再繪制一根線就搞定,但是要考慮這里的效果,就不能這么干了,如果你看了上面的gif圖就知道,其實這是2個同心圓,然后前一個path的起點和后一個path的起點相連接就是形成一條直線了,但是path中的圖形內(nèi)容也就是這個圓是怎么繪制出來的呢?如果是繪制圓的話,上面的線起點和終點位置怎么去計算,這是個問題,但是我們繪制圓還可以使用繪制橢圓的形式也是可以繪制達到圓的效果,從45度開始繪制一個圓,是不是這個線的起點搞定了,分析圖如下:

那么好,根據(jù)上面的分析開始寫代碼繪制出一個靜態(tài)的搜索圖:
package com.tuya;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by admin on 2016/12/17.
*/
public class DynamicSearchView2 extends View {
private Paint paint;
private int width;//view的寬度
private int height;//view的高度
private Path searchPath;
private Path circlePath;
private float BigCircleRectWidth;//搜索圓對應的外切正方形邊長
private PathMeasure pathMeasure;
private float[] pos;
public DynamicSearchView2(Context context) {
this(context,null);
}
public DynamicSearchView2(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
initPath();
}
/**
* 初始化path
*/
private void initPath() {
searchPath = new Path();
circlePath = new Path();
if(width>height){//長方形
BigCircleRectWidth = height;
}else if(width<height){
BigCircleRectWidth = width;
}else{
BigCircleRectWidth = width;
}
float smallbordWidth =BigCircleRectWidth/8;
RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);
searchPath.addArc(searchRect,45,360);
float bigBordWidth = smallbordWidth*2;
RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
circlePath.addArc(circleRect,45,-360);
pathMeasure = new PathMeasure(circlePath,false);
pos = new float[2];
pathMeasure.getPosTan(0,pos,null);
searchPath.lineTo(pos[0],pos[1]);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(width/2,height/2);//平移畫布把這個view的中心點當做原點
canvas.drawPath(searchPath,paint);
canvas.drawPath(circlePath,paint);
}
}
效果圖:

本來這個外圓是不需要draw上去的,我在這繪制上去只是告訴你這二個圓是有一定的聯(lián)系,哪為什么這根線是這樣的呢?我們在繪制這個圓的時候是從45度開始繪制360剛好是一周,形成了一個圓,現(xiàn)在做個測試不要360,就寫個330度,效果如下:

這個時候你會發(fā)現(xiàn)這條線是對的,導致問題其實是這樣的,如圖分析:

把繪制橢圓的關(guān)鍵代碼:
searchPath.addArc(searchRect,45,358);
circlePath.addArc(circleRect,45,-358);
不要寫成360,改為358試試,效果圖:

發(fā)現(xiàn)這線是不是正常了,至于外面的圓還有點缺口,第一你可以把358改成359應該沒事了,還有就是我們其實真實的效果并不需要這個外面的圓,所以不改也沒事,那么好,第一步算是完成了,現(xiàn)在想想第二步怎么實現(xiàn),先把第二步的效果用gif展示看下,不然光想沒思路,就像你看美女,第一眼看那,是吧,就不多說了!要有畫面感,

還是畫布分析:

哪我們只要改變startD這個離起始點的位置值就ok,當然有很多種方法,但是Android中基本上都是使用值動畫,ok,根據(jù)這個思路實現(xiàn)這個第二步邏輯:
package com.tuya;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by admin on 2016/12/17.
*/
public class DynamicSearchView2 extends View {
private Paint paint;
private int width;//view的寬度
private int height;//view的高度
private Path searchPath;
private Path circlePath;
private float BigCircleRectWidth;//搜索圓對應的外切正方形邊長
private PathMeasure pathMeasure;
private float[] pos;
private float animPercent;//
private ValueAnimator serchStartAnim;
private long animDuration = 2000;//動畫時間
public DynamicSearchView2(Context context) {
this(context,null);
}
public DynamicSearchView2(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
initPaint();
initAnim();
initAnimListener();
startAnim();
}
/**
* 開始執(zhí)行動畫
*/
private void startAnim() {
serchStartAnim.start();
}
/**
* 動畫監(jiān)聽
*/
private void initAnimListener() {
serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//獲取動畫在單位時間內(nèi),每次執(zhí)行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
}
/**
* 初始化動畫
*/
private void initAnim() {
serchStartAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
}
/**
* 初始化畫筆
*/
private void initPaint() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
initPath();
}
/**
* 初始化path
*/
private void initPath() {
searchPath = new Path();
circlePath = new Path();
if(width>height){//長方形
BigCircleRectWidth = height;
}else if(width<height){
BigCircleRectWidth = width;
}else{
BigCircleRectWidth = width;
}
float smallbordWidth =BigCircleRectWidth/8;
RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);
searchPath.addArc(searchRect,45,358);
float bigBordWidth = smallbordWidth*2;
RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
circlePath.addArc(circleRect,45,-358);
pathMeasure = new PathMeasure(circlePath,false);
pos = new float[2];
pathMeasure.getPosTan(0,pos,null);
searchPath.lineTo(pos[0],pos[1]);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(width/2,height/2);//平移畫布把這個view的中心點當做原點
drawSearch(canvas);
}
private void drawSearch(Canvas canvas) {
Path dst = new Path();
pathMeasure.setPath(searchPath,false);
pathMeasure.getSegment(pathMeasure.getLength()*animPercent,pathMeasure.getLength(),dst,true);
canvas.drawPath(searchPath,paint);
}
}
效果:

現(xiàn)在還我們效果還差外圓的大圓的效果了,那么大圓是在小圓動畫執(zhí)行完畢后再去做旋轉(zhuǎn)效果的,那好,我們只要監(jiān)聽動畫就可以,畫圖:
package com.tuya;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by admin on 2016/12/17.
*/
public class DynamicSearchView2 extends View {
private static final String TAG = "DynamicSearchView2";
private Paint paint;
private int width;//view的寬度
private int height;//view的高度
private Path searchPath;
private Path circlePath;
private float BigCircleRectWidth;//搜索圓對應的外切正方形邊長
private PathMeasure pathMeasure;
private float[] pos;
private float animPercent;//
private ValueAnimator serchStartAnim;
private ValueAnimator bigCircleAnim;//外面大圓運動的動畫
private long animDuration = 2000;//動畫時間
private int drawTag = 1;//區(qū)分是繪制搜索框還是外層圓
public DynamicSearchView2(Context context) {
this(context,null);
}
public DynamicSearchView2(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
initPaint();
initAnim();
initAnimListener();
startAnim();
}
/**
* 開始執(zhí)行動畫
*/
private void startAnim() {
drawTag = 1;
serchStartAnim.start();
invalidate();
}
/**
* 開啟大圓執(zhí)行動畫
*/
public void startBigCirCleAnim(){
serchStartAnim.removeAllUpdateListeners();//把上一個動畫監(jiān)聽移除 以免總成詭異的bug
bigCircleAnim.start();
drawTag = 2;
}
/**
* 動畫監(jiān)聽
*/
private void initAnimListener() {
serchStartAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
startBigCirCleAnim();
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//獲取動畫在單位時間內(nèi),每次執(zhí)行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
bigCircleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//獲取動畫在單位時間內(nèi),每次執(zhí)行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
}
/**
* 初始化動畫
*/
private void initAnim() {
bigCircleAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
serchStartAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
}
/**
* 初始化畫筆
*/
private void initPaint() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
initPath();
}
/**
* 初始化path
*/
private void initPath() {
searchPath = new Path();
circlePath = new Path();
if(width>height){//長方形
BigCircleRectWidth = height;
}else if(width<height){
BigCircleRectWidth = width;
}else{
BigCircleRectWidth = width;
}
float smallbordWidth =BigCircleRectWidth/8;
RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);
searchPath.addArc(searchRect,45,358);
float bigBordWidth = smallbordWidth*2;
RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
circlePath.addArc(circleRect,45,-358);
pathMeasure = new PathMeasure(circlePath,false);
pos = new float[2];
pathMeasure.getPosTan(0,pos,null);
searchPath.lineTo(pos[0],pos[1]);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(width/2,height/2);//平移畫布把這個view的中心點當做原點
drawSearch(canvas);
}
private void drawSearch(Canvas canvas) {
if(drawTag==1){
drawSearchGraph(canvas);
}else if(drawTag==2){
drawBigCircleGraph(canvas);
}
}
/**
* 繪制外層大圓
* @param canvas
*/
private void drawBigCircleGraph(Canvas canvas) {
pathMeasure.setPath(circlePath, false);
Path dst2 = new Path();
float stop = pathMeasure.getLength() * animPercent;
float start = (float) (stop - ((0.5 - Math.abs(animPercent - 0.5)) * 200f));
pathMeasure.getSegment(start, stop, dst2, true);
canvas.drawPath(dst2, paint);
}
/**
* 繪制搜索框
* @param canvas
*/
private void drawSearchGraph(Canvas canvas) {
pathMeasure.setPath(searchPath,false);
Path dst = new Path();
pathMeasure.getSegment(pathMeasure.getLength()*animPercent,pathMeasure.getLength(),dst,true);
canvas.drawPath(dst,paint);
}
}
效果:

發(fā)現(xiàn)轉(zhuǎn)一圈就到頭了,如果有特定的需求肯定是要控制整個轉(zhuǎn)圈的圈數(shù),如果是網(wǎng)絡加載的話,除非網(wǎng)絡特別的好,先不管了,因為等下還要寫周報,也是很痛苦的
現(xiàn)在還差最后一步就是大圓的運動完后要繪制搜索框出來,其實這個和第一步效果剛好是相關(guān)的,
package com.tuya;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by admin on 2016/12/17.
*/
public class DynamicSearchView2 extends View {
private static final String TAG = "DynamicSearchView2";
private Paint paint;
private int width;//view的寬度
private int height;//view的高度
private Path searchPath;
private Path circlePath;
private float BigCircleRectWidth;//搜索圓對應的外切正方形邊長
private PathMeasure pathMeasure;
private float[] pos;
private float animPercent;//
private ValueAnimator serchStartAnim;
private ValueAnimator bigCircleAnim;//外面大圓運動的動畫
private ValueAnimator startDrawSearchAnim;//最后一步繪制搜索框
private long animDuration = 2000;//動畫時間
private int drawTag = 1;//區(qū)分是繪制搜索框還是外層圓
public DynamicSearchView2(Context context) {
this(context,null);
}
public DynamicSearchView2(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public DynamicSearchView2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
initPaint();
initAnim();
initAnimListener();
startAnim();
}
/**
* 開始執(zhí)行動畫
*/
private void startAnim() {
drawTag = 1;
serchStartAnim.start();
invalidate();
}
/**
* 開啟大圓執(zhí)行動畫
*/
public void startBigCirCleAnim(){
serchStartAnim.removeAllUpdateListeners();//把上一個動畫監(jiān)聽移除 以免總成詭異的bug
bigCircleAnim.start();
drawTag = 2;
}
/**
* 最后繪制搜索框的動畫
*/
public void drawSearchAanim(){
bigCircleAnim.removeAllUpdateListeners();//把上一個動畫監(jiān)聽移除 以免總成詭異的bug
startDrawSearchAnim.start();
drawTag = 3;
}
/**
* 動畫監(jiān)聽
*/
private void initAnimListener() {
bigCircleAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
drawSearchAanim();
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//獲取動畫在單位時間內(nèi),每次執(zhí)行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
serchStartAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
startBigCirCleAnim();
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
serchStartAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//獲取動畫在單位時間內(nèi),每次執(zhí)行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
bigCircleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//獲取動畫在單位時間內(nèi),每次執(zhí)行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
startDrawSearchAnim .addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//獲取動畫在單位時間內(nèi),每次執(zhí)行的值
animPercent = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
}
/**
* 初始化動畫
*/
private void initAnim() {
bigCircleAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
serchStartAnim = ValueAnimator.ofFloat(0,1).setDuration(animDuration);
startDrawSearchAnim = ValueAnimator.ofFloat(1,0).setDuration(animDuration);
}
/**
* 初始化畫筆
*/
private void initPaint() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(6);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
initPath();
}
/**
* 初始化path
*/
private void initPath() {
searchPath = new Path();
circlePath = new Path();
if(width>height){//長方形
BigCircleRectWidth = height;
}else if(width<height){
BigCircleRectWidth = width;
}else{
BigCircleRectWidth = width;
}
float smallbordWidth =BigCircleRectWidth/8;
RectF searchRect = new RectF(-smallbordWidth,-smallbordWidth,smallbordWidth,smallbordWidth);
searchPath.addArc(searchRect,45,358);
float bigBordWidth = smallbordWidth*2;
RectF circleRect = new RectF(-bigBordWidth,-bigBordWidth,bigBordWidth,bigBordWidth);
circlePath.addArc(circleRect,45,-358);
pathMeasure = new PathMeasure(circlePath,false);
pos = new float[2];
pathMeasure.getPosTan(0,pos,null);
searchPath.lineTo(pos[0],pos[1]);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(width/2,height/2);//平移畫布把這個view的中心點當做原點
drawSearch(canvas);
}
private void drawSearch(Canvas canvas) {
if(drawTag==1){
drawSearchGraph(canvas);
}else if(drawTag==2){
drawBigCircleGraph(canvas);
}else if(drawTag==3){
drawSearchBox(canvas);
}
}
/**
* 最后一步繪制搜索框 從終點到起點
* @param canvas
*/
private void drawSearchBox(Canvas canvas) {
pathMeasure.setPath(searchPath, false);
Path dst3 = new Path();
pathMeasure.getSegment(pathMeasure.getLength() * animPercent, pathMeasure.getLength(), dst3, true);
canvas.drawPath(dst3, paint);
}
/**
* 繪制外層大圓
* @param canvas
*/
private void drawBigCircleGraph(Canvas canvas) {
pathMeasure.setPath(circlePath, false);
Path dst2 = new Path();
float stop = pathMeasure.getLength() * animPercent;
float start = (float) (stop - ((0.5 - Math.abs(animPercent - 0.5)) * 200f));
pathMeasure.getSegment(start, stop, dst2, true);
canvas.drawPath(dst2, paint);
}
/**
* 繪制搜索框
* @param canvas
*/
private void drawSearchGraph(Canvas canvas) {
pathMeasure.setPath(searchPath,false);
Path dst = new Path();
pathMeasure.getSegment(pathMeasure.getLength()*animPercent,pathMeasure.getLength(),dst,true);
canvas.drawPath(dst,paint);
}
}
效果:

github: https://github.com/zhouguizhi/PathSearch
總結(jié)
以上所述是小編給大家介紹的Android 使用 Path 實現(xiàn)搜索動態(tài)加載動畫效果,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Android編程實現(xiàn)手機自帶內(nèi)部存儲路徑的獲取方法
這篇文章主要介紹了Android編程實現(xiàn)手機自帶內(nèi)部存儲路徑的獲取方法,涉及Android針對掛載點信息的獲取技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
Android中TextView實現(xiàn)分段顯示不同顏色的字符串
在做項目的時候,遇到過一行文字有兩種顏色。在菜鳥的時候直接會想到用多個TextView來實現(xiàn),所以下面這篇文章主要給大家介紹了關(guān)于Android中TextView如何實現(xiàn)分段顯示不同顏色字符串的相關(guān)資料,需要的朋友可以參考下。2017-12-12
Android?PickerScrollView滑動選擇控件使用方法詳解
這篇文章主要為大家詳細介紹了Android?PickerScrollView滑動選擇控件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04

