一、動畫效果
? 1.動效描述
? 2.關鍵點
? 3.實現(xiàn)方式 ?
二、LinearGradient簡介
三、代碼功能實現(xiàn)
?1.繪制閃光
? 2.兩道閃光順序出現(xiàn)
一、動畫效果
1.動效描述
實現(xiàn)的動畫效果主要就是,一束白光從圖片或者文字上閃過,這束光由兩道光組成,具體細節(jié)描述如下:
2.關鍵點
- 兩道閃光,傾斜-330°,透明度、寬度不一樣
- 兩道閃光按順序先后出現(xiàn)
3.實現(xiàn)方式
可以用FrameLayout,在背景圖或文字上兩個View,然后對這兩個view做動畫,控制時間、旋轉(zhuǎn)角度等。這里主要講怎么用LinearGradient來實現(xiàn)兩道光閃過的動畫效果。
二、LinearGradient簡介
講實現(xiàn)之前先認識下主角LinearGradient。LinearGradient作用是實現(xiàn)某一區(qū)域內(nèi)顏色的線性漸變效果,網(wǎng)上相關資料也很多,這里就簡單介紹下常用的構造函數(shù):
public LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions,Shader.TileMode tile)
注:Android中計算x,y坐標都是以屏幕左上角為原點,向右為x+,向下為y+
- float x0:漸變起始點x坐標
- float y0:漸變起始點y坐標
- float x1:漸變結束點x坐標
- float y1:漸變結束點y坐標
- int[] colors:顏色 的int 數(shù)組
- float[] positions: 相對位置的顏色數(shù)組,可為null,若為null,顏色沿漸變線均勻分布
- Shader.TileMode tile: 渲染器平鋪模式
Shader.TileMode有3種參數(shù)可供選擇,分別為CLAMP、REPEAT和MIRROR:
- CLAMP的作用是如果渲染器超出原始邊界范圍,則會復制邊緣顏色對超出范圍的區(qū)域進行著色
- REPEAT的作用是在橫向和縱向上以平鋪的形式重復渲染位圖
- MIRROR的作用是在橫向和縱向上以鏡像的方式重復渲染位圖
代碼功能實現(xiàn)
1.繪制閃光
閃光的繪制由LinearGradient完成,通過改變其構造函數(shù)各個參數(shù)值,就能繪制出不同的光效果
(1)閃光傾斜-330°
調(diào)節(jié)漸變閃光的傾斜角度,需用LinearGradient構造函數(shù)中的x0,y0,x1,y1參數(shù),即調(diào)節(jié)漸變的起始點,更多用法可參考Android中的LinearGradient。所以我們將這個4個參數(shù)設置成如下:
(2)兩道閃光
這里主要用到LinearGradient構造函數(shù)中的colors,positions參數(shù)。colors參數(shù)很好理解,就是一組顏色值;positions的釋義是“相對位置、權重”,看完釋義是不是還是沒有太明白(/□\*),來直接上代碼和效果圖。
LinearGradient mGradient = new LinearGradient(0, 0, mViewWidth / 2, mViewHeight,
new int[]{0x00ffffff, 0x73ffffff, 0x00ffffff, 0x99ffffff, 0x00ffffff},
new float[]{0.2f, 0.35f, 0.45f, 0.5f, 0.8f},
Shader.TileMode.CLAMP);
上面代碼可以這么理解,它定義了一組漸變的數(shù)值是{ 0x00ffffff, 0x73ffffff, 0x00ffffff, 0x99ffffff, 0x00ffffff},這組數(shù)值分別在相對應的0.2f, 0.35f, 0.45f, 0.5f, 0.8f中顯示:
- 第一道閃光顏色有效值是0.35f位置的35%白色,0.35f前后位置的顏色值都為透明,調(diào)節(jié)這兩個透明顏色的position值就可以調(diào)節(jié)第一道閃光的寬度;
- 第二道閃光顏色有效值是0.5f位置的50%白色,同理0.5f前后位置顏色為透明,調(diào)節(jié)第二道閃光寬度就可以調(diào)節(jié)這兩個position值;
- 中間0.45f位置設為透明,也就把第一道光和第二道光隔開了。
2.兩道閃光順序出現(xiàn)
現(xiàn)在兩道光用LinearGradient一起繪制出來了,要怎樣實現(xiàn)順序出現(xiàn)呢?這里配合Matrix、屬性動畫ValueAnimator來控制,先看核心代碼:
private void initGradientAnimator() {
valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float v = (Float) animation.getAnimatedValue();
//? 改變每次動畫的平移x、y值
mTranslateX = 4 * mViewWidth * v - mViewWidth * 2;
mTranslateY = mViewHeight * v;
//? mGradientMatrix為變換矩陣,設置矩陣x、y平移量
if (mGradientMatrix != null) {
mGradientMatrix.setTranslate(mTranslateX, mTranslateY);
}
//? 為線性漸變mGradient設置matrix
if (mGradient != null) {
mGradient.setLocalMatrix(mGradientMatrix);
}
//? 重繪
invalidate();
}
});
}
重點看下第?步怎么移動的,每次根據(jù)當前的動畫屬性值設置x、y平移量,x的范圍是[-2mViewWidth, 2mViewWidth],y的范圍是范圍是[0, mViewHeight],如下圖所示(x軸)。也就是兩道閃光從不可見到可見,調(diào)節(jié)valueAnimator的duration,或者更改x、y變化方式就能控制兩道閃光的出場順序了。
注:上面方式實現(xiàn)的閃光動效和文章開頭列的條件并不是百分百一樣,大致效果相同。
最后,自定義LightningView的的所有代碼:
public class LightningView extends View {
private Shader mGradient;
private Matrix mGradientMatrix;
private Paint mPaint;
private int mViewWidth = 0, mViewHeight = 0;
private float mTranslateX = 0, mTranslateY = 0;
private boolean mAnimating = false;
private Rect rect;
private ValueAnimator valueAnimator;
private boolean autoRun = true; //是否自動運行動畫
public LightningView(Context context) {
super(context);
init();
}
public LightningView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public LightningView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
rect = new Rect();
mPaint = new Paint();
initGradientAnimator();
}
public void setAutoRun(boolean autoRun) {
this.autoRun = autoRun;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
rect.set(0, 0, getWidth(), getHeight());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0) {
mViewWidth = getWidth();
mViewHeight = getHeight();
if (mViewWidth > 0) {
//亮光閃過
mGradient = new LinearGradient(0, 0, mViewWidth / 2, mViewHeight,
new int[]{0x00ffffff, 0x73ffffff, 0x00ffffff, 0x99ffffff, 0x00ffffff},
new float[]{0.2f, 0.35f, 0.5f, 0.7f, 1},
Shader.TileMode.CLAMP);
mPaint.setShader(mGradient);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));
mGradientMatrix = new Matrix();
mGradientMatrix.setTranslate(-2 * mViewWidth, mViewHeight);
mGradient.setLocalMatrix(mGradientMatrix);
rect.set(0, 0, w, h);
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mAnimating && mGradientMatrix != null) {
canvas.drawRect(rect, mPaint);
}
}
private void initGradientAnimator() {
valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float v = (Float) animation.getAnimatedValue();
//? 改變每次動畫的平移x、y值,范圍是[-2mViewWidth, 2mViewWidth]
mTranslateX = 4 * mViewWidth * v - mViewWidth * 2;
mTranslateY = mViewHeight * v;
//? 平移matrix, 設置平移量
if (mGradientMatrix != null) {
mGradientMatrix.setTranslate(mTranslateX, mTranslateY);
}
//? 設置線性變化的matrix
if (mGradient != null) {
mGradient.setLocalMatrix(mGradientMatrix);
}
//? 重繪
invalidate();
}
});
if (autoRun) {
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
mAnimating = true;
if (valueAnimator != null) {
valueAnimator.start();
}
}
});
}
}
//停止動畫
public void stopAnimation() {
if (mAnimating && valueAnimator != null) {
mAnimating = false;
valueAnimator.cancel();
invalidate();
}
}
//開始動畫
public void startAnimation() {
if (!mAnimating && valueAnimator != null) {
mAnimating = true;
valueAnimator.start();
}
}
}