一、PropertyValuesHolder 閱讀本文需要上一文Android屬性動畫的基礎,這樣才可以明白接下來要講什麼。 1.理解和使用 是ObjectAnimation類似的一個方法,只是少了一個target,就是要執行的控制項。看看正常的使用方法:會同時執行全部的Holder 2.方法和參數 ...
一、PropertyValuesHolder
閱讀本文需要上一文Android屬性動畫的基礎,這樣才可以明白接下來要講什麼。
1.理解和使用
PropertyValuesHolder
是ObjectAnimation類似的一個方法,只是少了一個target,就是要執行的控制項。看看正常的使用方法:會同時執行全部的Holder
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();
}
2.方法和參數
可以看看這個方法的參數:
ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values)
PropertyValuesHolder ofFloat(String propertyName, float... values)
Object target
是要顯示動畫的控制項
PropertyValuesHolder... values
裝載多個PropertyValuesHolder
String propertyName
代表要反射的參數,跟ObjectAnimation的參數是一樣的
float... values
代表是可變長參數
這樣的方法還有以下圖片這些:
其中ofObject(
)方法 ,也是跟ObjectAnimation的相似,也是要自定義TypeEvaluator。
二、Keyframe
1.理解和使用
看名字,就是理解為關鍵幀的意思,在動畫中,在某幀做一些操作,從而實現對比效果比較明顯的效果。
關鍵幀表示是某個物體在哪個時間點應該在哪個位置上。
具體使用:
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();
}
2.方法和參數
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()); //預設線性插值器
3.幀的操作
直接寫結論:
- 如果去掉0幀,則以第一個關鍵幀為起始位置
- 如果去掉結束幀(進度為1),則以最後一個關鍵幀為結束位置
- 使用keyframe來構建動畫,至少需要2幀
三、ViewPropertyAnimator
1.理解和使用
可以通過串列的形式,快速定義動畫,省去一些定義,在每次界面繪製的時候,啟動動畫,比其他的更節省消耗。
比如:
ballImageView.animate().alpha(0.5f).rotation(360f).scaleX(1.5f).translationX(100f);
2.參數和方法
可以看到這些方法的返回值,基本都是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) | 設置動畫時長 |
四、animateLayoutChanges
android:animateLayoutChanges="true"
在Layout加入控制項,或者移除控制項的時候,添加動畫,但是只能使用預設動畫。
<LinearLayout
android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"/>
五、LayoutTransition
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
PathMeasure
類似一個計算器,可以計算出目標path的坐標,長度等
1.初始化
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原來情況來計算。
2.函數調用
自定義一個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); //繪製路徑
}
}
繪製效果:
2.1 PathMeasure.getLength()
PathMeasure.getLength()
函數用於測量路徑的長度
2.2 PathMeasure.isClosed()
PathMeasure.isClosed()
函數用於返回是否測量閉合狀態
2.3 PathMeasure.nextContour()
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的長度,不是全部的長度
2.3 getSegment()
使用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的生成方向有關
2.4 動態畫圓的例子
代碼:
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); //每次有進度更新,就繪製一小段截取
}
}
效果:
2.5 getPosTan()
先看看函數的定義:
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
2.6 箭頭畫圓的例子
代碼:
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);
}
}
效果:
2.7 getMatrix()
參數類型:
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);
2.8 支付成功例子
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);
}
}
效果:
編程中我們會遇到多少挫折?表放棄,沙漠盡頭必是綠洲。