閱讀本文須要上一文Android屬性動畫的基礎,這樣才能夠明白接下來要講什麼。前端
PropertyValuesHolder
是ObjectAnimation相似的一個方法,只是少了一個target,就是要執行的控件。看看正常的使用方法:會同時執行所有的Holderjava
public void doPropertyValuesHolder(){ //定義一個旋轉Holder PropertyValuesHolder rotationHolder= PropertyValuesHolder.ofFloat( "rotation", 60f,40f,100f,-60f,40f,88f,77f); //定義一個透明Holder PropertyValuesHolder alphaHolder= PropertyValuesHolder.ofFloat( "alpha", 0.01f,0.5f,1.0f,0.8f,0.2f,0.0f); //加載進ObjectAnimator ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,rotationHolder,alphaHolder); objectAnimator.setDuration(3000); objectAnimator.start(); }
能夠看看這個方法的參數:android
ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values)
PropertyValuesHolder ofFloat(String propertyName, float... values)
Object target
是要顯示動畫的控件編程
PropertyValuesHolder... values
裝載多個PropertyValuesHolder
canvas
String propertyName
表明要反射的參數,跟ObjectAnimation的參數是同樣的ide
float... values
表明是可變長參數
這樣的方法還有如下圖片這些:函數
其中ofObject(
)方法 ,也是跟ObjectAnimation的類似,也是要自定義TypeEvaluator。
佈局
看名字,就是理解爲關鍵幀的意思,在動畫中,在某幀作一些操做,從而實現對比效果比較明顯的效果。
關鍵幀表示是某個物體在哪一個時間點應該在哪一個位置上。
具體使用:post
public void doPropertyValuesHolderKeyFrame(){ //頭keyframe1,從進度0.6開始,在進度60%的時候,數值是0.1f Keyframe keyframe1=Keyframe.ofFloat(0.6f,0.1f); //中間keyframe2 Keyframe keyframe2=Keyframe.ofFloat(0.1f,0.8f); //尾部keyframe3,以50%進度做爲結束,這時候的數值爲0.2f Keyframe keyframe3=Keyframe.ofFloat(0.5f,0.2f); //裝載到Holder中,並設置要反射的方法,這是反射的是setAlpha()方法,控制透明度 PropertyValuesHolder alphaHolder=PropertyValuesHolder.ofKeyframe("alpha",keyframe1,keyframe2,keyframe3); //把裝載到Holder中裝載到ObjectAnimator或者ValueAnimation ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,alphaHolder); objectAnimator.setDuration(3000); objectAnimator.start(); }
Keyframe ofFloat(float fraction, float value)
float fraction
表示進度動畫
float value
表示在這個進度下的數值
PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)
String propertyName
要反射的set方法
Keyframe... values
傳入Keyframe
Keyframe的方法,也是和其餘的相似的。
Keyframe的set方法,設置進度,插值器,數值。
沒有設置插值器的時候,默認是線性插值器
keyframe1.setInterpolator(new LinearInterpolator()); //默認線性插值器
直接寫結論:
能夠經過串行的形式,快速定義動畫,省去一些定義,在每次界面繪製的時候,啓動動畫,比其餘的更節省消耗。
好比:
ballImageView.animate().alpha(0.5f).rotation(360f).scaleX(1.5f).translationX(100f);
能夠看到這些方法的返回值,基本都是ViewPropertyAnimator
再引用一張表格:
函數 | 含義 |
---|---|
alpha(float value) | 設置透明度 |
scaleY(float value) | 設置 Y軸方向的縮放大小 |
scaleX(float value) | 設置X軸方向的縮放大小 |
translationY(float value) | 設置Y軸方向的移動值 |
translationX(float value) | 設置X軸方向的移動值 |
rotation(float value) | 設置繞Z軸旋轉度數 |
rotationX(float value) | 設置繞x軸旋轉度數 |
rotationY(float value) | 設置繞 Y 軸旋轉度數 |
x(float value) | 相對於父容器的左上角座標在 X軸方向的最終位置 |
y(float value) | 相對於父容器的左上角座標在Y軸方向的最終位置 |
alphaBy(float value) | 設置透明度增量 |
rotationBy(float value) | 設置繞Z軸旋轉增量 |
rotationXBy(float value) | 設置繞 X 油旋轉增量 |
rotationYBy(float value) | 設置統Y軸旋轉增量 |
translationXBy(float value) | 設置X軸方向的移動值增量 |
translationYBy(float value) | 設置Y軸方向的移動值增量 |
scaleXBy(float value) | 設置X軸方向的縮放大小增量 |
scaleYBy(float value) | 設置 Y軸方向的縮放大小增量 |
xBy(float value) | 相對於父容器的左上角座標在 X軸方向的位置增量 |
yBy(float value) | 相對於父容器的左上角座標在 Y軸方向的位置增量 |
setlnterpolator(Timelnterpolator interpolator) | 設置插值器 |
setStartDelay(long startDelay) | 設置開始延時 |
setDuration(long duration) | 設置動畫時長 |
android:animateLayoutChanges="true"
在Layout加入控件,或者移除控件的時候,添加動畫,可是隻能使用默認動畫。
<LinearLayout android:animateLayoutChanges="true" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"/>
LayoutTransition能夠控制ViewGroup的動畫,可使用自定義的動畫。
具體使用:
public void doLayoutTransition(){ LinearLayout linearLayout=new LinearLayout(this); //1.建立實例 LayoutTransition transition=new LayoutTransition(); //2.建立動畫 ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(null,"rotation",0f,90f,0f); //3.動畫出現形式進行設置 transition.setAnimator(LayoutTransition.DISAPPEARING,objectAnimator); //4.將LayoutTransition設置到ViewGroup中 linearLayout.setLayoutTransition(transition); //5.開源動畫庫 NineOldAndroids }
setAnimator(int transitionType, Animator animator)
這個方法中,transitionType
有五個選項
CHANGE_APPEARING
因爲容器中要顯示一個新的元素,其餘須要變化的元素所應用的動畫(問題多,不經常使用)
_CHANGE_DISAPPEARING_
當個容器中某個元素要消失時,其餘須要變化的元素所應用的動畫(問題多,不經常使用)
_CHANGING_
容器中正在更改的元素的動畫變化
_APPEARING_
元素在容器中出現時所定義的動畫
_DISAPPEARING_
元素在容器中消失時所定義的動畫
PathMeasure
相似一個計算器,能夠計算出目標path的座標,長度等
public void doPathMeasure(){ Path path=new Path(); //初始化方法1 PathMeasure pathMeasure1=new PathMeasure(); pathMeasure1.setPath(path,true); //初始化方法2 PathMeasure pathMeasure2=new PathMeasure(path,false); }
setPath(Path path, boolean forceClosed)
path
就是表明要計算的目標Path。
forceClosed
是否閉合,true會計算閉合狀態下的Path,false會按照Path原來狀況來計算。
自定義一個view
public class PathView extends View { Path mPath; Paint mPaint; PathMeasure mPathMeasure; public PathView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mPath=new Path(); mPaint=new Paint(); mPathMeasure=new PathMeasure(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.translate(250,250); //畫布移動 mPaint.setColor(Color.BLUE); //畫筆顏色 mPaint.setStrokeWidth(5); //畫筆粗細 mPaint.setStyle(Paint.Style.STROKE); //畫筆風格 mPath.moveTo(0,0); mPath.lineTo(0,100); mPath.lineTo(100,100); mPath.lineTo(100,0); mPathMeasure.setPath(mPath,true); Log.v("showLog", "getLength()=="+mPathMeasure.getLength() +" isClosed()=="+ mPathMeasure.isClosed()); //結果400.0 true mPathMeasure.setPath(mPath,false); Log.v("showLog", "getLength()=="+mPathMeasure.getLength() +" isClosed()=="+ mPathMeasure.isClosed()); //結果300.0 false canvas.drawPath(mPath,mPaint); //繪製路徑 } }
繪製效果:
PathMeasure.getLength()
函數用於測量路徑的長度
PathMeasure.isClosed()
函數用於返回是否測量閉合狀態
mPath.addRect(-50, -50, 50, 50, Path.Direction.CW); canvas.drawPath(mPath, mPaint); mPath.addRect(-100, -100, 100, 100, Path.Direction.CW); canvas.drawPath(mPath, mPaint); mPath.addRect(-120, -120, 120, 120, Path.Direction.CW); canvas.drawPath(mPath, mPaint); mPathMeasure.setPath(mPath, false); do { float len = mPathMeasure.getLength(); Log.v("showLog", "len=" + len); } while (mPathMeasure.nextContour());
效果:
打印結果:
len=400.0 len=800.0 len=960.0
PathMeasure.nextContour()
獲得的順序與添加的Path的順序相同
PathMeasure.getLength()
只是獲得當前path的長度,不是所有的長度
使用getSegment函數須要禁用硬件加速 在構造方法中加入
setLayerType(LAYER_TYPE_SOFTWARE,null);
mPath.addRect(-50, -50, 50, 50, Path.Direction.CW); mPathMeasure.setPath(mPath,false); //計算的path mPathMeasure.getSegment(0,150,mDstPath,true); //截取並添加到mDstPath,是添加,不是其餘 canvas.drawPath(mPath, mPaint); //繪製原來的path canvas.translate(200,0); //畫布移動 mPaint.setColor(Color.RED); canvas.drawPath(mDstPath, mPaint); //繪製添加後的mDstPath
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
startD
path開始截取的點,截取的起始點,是以左上角的點開始的
stopD
截取中止的點
dst
截取後添加到的path
startWithMoveTo
是否保存原狀,true保存原樣,false則會鏈接初始點和終點,和原來的不必定相同形狀
以上代碼的效果: 截圖的方向,與原來的path的生成方向有關
代碼:
public class PathView extends View { Path mPath, mDstPath; Paint mPaint; PathMeasure mPathMeasure; float mCurAnimValue; public PathView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); setLayerType(LAYER_TYPE_SOFTWARE, null); mPath = new Path(); mDstPath = new Path(); mPaint = new Paint(); mPathMeasure = new PathMeasure(); mPaint.setColor(Color.BLUE); //畫筆顏色 mPaint.setStrokeWidth(5); //畫筆粗細 mPaint.setStyle(Paint.Style.STROKE); //畫筆風格 mPath.addCircle(100, 100, 50, Path.Direction.CW); //一個完整的圓 mPathMeasure.setPath(mPath, true); //要計算的path ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //進度 0~1 animator.setRepeatCount(ValueAnimator.INFINITE); //無限循環 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurAnimValue = (Float) animation.getAnimatedValue(); //獲得當前的進度 invalidate();//重繪,從新執行onDraw()方法 } }); animator.setDuration(5000); animator.start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.translate(100, 100); //畫布移動 float stop=mPathMeasure.getLength()*mCurAnimValue; //一個進度肯定一個截取點 mDstPath.reset(); mPathMeasure.getSegment(0,stop,mDstPath,true); //一點點添加 canvas.drawPath(mDstPath,mPaint); //每次有進度更新,就繪製一小段截取 } }
效果:
先看看函數的定義:
boolean getPosTan(float distance, float pos[], float tan[])
float distance
距離path的其實長度
float pos[]
該點的座標值。x和y pos[0]=x,pos[1]=y
float tan[]
該點的正切值。x和y pos[0]=x,pos[1]=y tan<a=y/x
代碼:
public class PathView extends View { Path mPath, mDstPath; Paint mPaint; PathMeasure mPathMeasure; float mCurAnimValue; Bitmap mArrowBmp; float[] mPos; float[] mTan; int mCenterX,mCenterY; float mRadius; public PathView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); setLayerType(LAYER_TYPE_SOFTWARE, null); mPath = new Path(); mDstPath = new Path(); mPaint = new Paint(); mPathMeasure = new PathMeasure(); mPos=new float[2]; mTan=new float[2]; //加載箭頭圖片 mArrowBmp= BitmapFactory.decodeResource(getResources(), R.drawable.arrow); mPaint.setColor(Color.BLUE); //畫筆顏色 mPaint.setStrokeWidth(5); //畫筆粗細 mPaint.setStyle(Paint.Style.STROKE); //畫筆風格 mPath.addCircle(540, 972, 486, Path.Direction.CW); //一個完整的圓 mPathMeasure.setPath(mPath, true); //要計算的path ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //進度 0~1 animator.setRepeatCount(ValueAnimator.INFINITE); //無限循環 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurAnimValue = (Float) animation.getAnimatedValue(); //獲得當前的進度 invalidate();//重繪,從新執行onDraw()方法 } }); animator.setDuration(5000); animator.start(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { /* * 獲得h,w的最小的那個值; * >> 1 移位 跟 /2 相同; * 乘以0.9f,表示佔佈局的90% * */ mRadius = (Math.min(h, w) >> 1) * 0.9f; // 中心座標 mCenterX = w / 2; mCenterY = h / 2; Log.v("showLog",mCenterX+" "+mCenterY+" "+mRadius); postInvalidate(); super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float stop=mPathMeasure.getLength()*mCurAnimValue; //一個進度肯定一個截取點 mDstPath.reset(); mPathMeasure.getSegment(0,stop,mDstPath,true); //一點點添加 canvas.drawPath(mDstPath,mPaint); //每次有進度更新,就繪製一小段截取 mPathMeasure.getPosTan(stop,mPos,mTan); //得到每點的正切值和座標 /** * Math.atan2(mTan[1],mTan[0])得到tan的弧度值 * *180.0/Math.PI將轉化爲角度值 * */ float degrees=(float)(Math.atan2(mTan[1],mTan[0])*180.0/Math.PI); Matrix matrix=new Matrix(); /** * 將圖片圍繞中心點旋轉指定角度 * postRotate(float degrees, float px, float py) * degrees是角度 (px,py)是圖片中心點 * */ matrix.postRotate(degrees,mArrowBmp.getWidth()/2,mArrowBmp.getHeight()/2); /** * 將圖片從默認的(0,0)點移動到路徑的最前端 * */ matrix.postTranslate(mPos[0]-mArrowBmp.getWidth()/2,mPos[1]-mArrowBmp.getHeight()/2); //繪製圖片 canvas.drawBitmap(mArrowBmp,matrix,mPaint); } }
效果:
參數類型:
boolean getMatrix(float distance, Matrix matrix, int flags)
使用方法:
//計算方位角 Matrix matrix = new Matrix(); //獲取位置信息 mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG); //獲取切邊信息 mPathMeasure.getMatrix(stop,matrix,PathMeasure.TANGENT_MATRIX_FLAG);
public class TickView extends View { Path mPath, mDstPath; Paint mPaint; PathMeasure mPathMeasure; float mCurAnimValue; int mCenterX, mCenterY; float mRadius; public TickView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); setLayerType(LAYER_TYPE_SOFTWARE, null); mPath = new Path(); mDstPath = new Path(); mPaint = new Paint(); mPathMeasure = new PathMeasure(); mPaint.setColor(Color.BLUE); //畫筆顏色 mPaint.setStrokeWidth(5); //畫筆粗細 mPaint.setStyle(Paint.Style.STROKE); //畫筆風格 mCenterX = 540; mCenterY = 972; mRadius = 486 / 2; /** * 圓 * */ mPath.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW); /** * 對勾 * */ mPath.moveTo(mCenterX - mRadius / 2, mCenterY); mPath.lineTo(mCenterX, mCenterY + mRadius / 2); mPath.lineTo(mCenterX + mRadius / 2, mCenterY - mRadius / 3); mPathMeasure.setPath(mPath, false); //要計算的path ValueAnimator animator = ValueAnimator.ofFloat(0, 2); //進度 0~1 是圓,1~2是對勾 animator.setRepeatCount(ValueAnimator.RESTART); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurAnimValue = (Float) animation.getAnimatedValue(); //獲得當前的進度 invalidate();//重繪,從新執行onDraw()方法 } }); animator.setDuration(5000); animator.start(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { /* * 獲得h,w的最小的那個值; * >> 1 移位 跟 /2 相同; * 乘以0.9f,表示佔佈局的90% * */ mRadius = (Math.min(h, w) >> 1) * 0.9f; // 中心座標 mCenterX = w / 2; mCenterY = h / 2; Log.v("showLog", mCenterX + " " + mCenterY + " " + mRadius); postInvalidate(); super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mCurAnimValue < 1) { float stop = mPathMeasure.getLength() * mCurAnimValue; mPathMeasure.getSegment(0, stop, mDstPath, true); } else if (mCurAnimValue == 1) { mPathMeasure.getSegment(0, mPathMeasure.getLength(), mDstPath, true); mPathMeasure.nextContour(); } else { float stop = mPathMeasure.getLength() * (mCurAnimValue - 1); mPathMeasure.getSegment(0, stop, mDstPath, true); } canvas.drawPath(mDstPath, mPaint); } }
效果:
編程中咱們會遇到多少挫折?表放棄,沙漠盡頭必是綠洲。