在日常開發(fā)中淮椰,經(jīng)常會遇到各種視覺效果岖是,有的效果可能一眼看去會讓人覺得很復(fù)雜帮毁,但是我們必須明確一點:所有復(fù)雜動效都是可以分解成單一的基礎(chǔ)動作,比如縮放豺撑,平移烈疚,旋轉(zhuǎn)這些基礎(chǔ)單元,然后將所有基礎(chǔ)單元動作進行組合聪轿,就會產(chǎn)生讓人眼前一亮的視覺動效爷肝。
首先看下下圖效果:
按照上面我們提到的思路進行分解:
- Logo的名稱LitePlayer被拆分為單個文字
- 所有文字隨機打散在屏幕各個位置
- 中間的Logo被隱藏
- Logo文字從隨機位置平移到頁面固定位置
- 中間的Logo圖片逐漸顯示,并且附帶從下往上平移一小段位移
- Logo被打散的文字組合成名稱
- Logo組合成名稱后陆错,有個漸變的光暈照射效果從左往右移動
- 動畫結(jié)束
當(dāng)我們把動畫拆解后灯抛,就可以針對每個拆解單元去構(gòu)造實現(xiàn)方案了。
- 首先我們先對
logo
文字動畫進行實現(xiàn):
- 首先對于數(shù)據(jù)來源音瓷,我們期望傳入一個
logo
的字符串对嚼,內(nèi)部將字符串拆解為單個文字?jǐn)?shù)組:
// fill the text to array
private void fillLogoTextArray(String logoName) {
if (TextUtils.isEmpty(logoName)) {
return;
}
if (mLogoTexts.size() > 0) {
mLogoTexts.clear();
}
for (int i = 0; i < logoName.length(); i++) {
char c = logoName.charAt(i);
mLogoTexts.put(i, String.valueOf(c));
}
}
- 所有文字需要隨機打散在屏幕各個位置,因為涉及到坐標(biāo)绳慎,我們可以在
onSizeChanged
中進行logo
文字隨機位置的初始化纵竖,同時我們構(gòu)建兩個集合存儲每個文字被打散和組合后的坐標(biāo)狀態(tài):
// 最終合成logo后的坐標(biāo)
private SparseArray<PointF> mQuietPoints = new SparseArray<>();
// logo被隨機打散的坐標(biāo)
private SparseArray<PointF> mRadonPoints = new SparseArray<>();
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
initLogoCoordinate();
}
private void initLogoCoordinate() {
float centerY = mHeight / 2f + mPaint.getTextSize() / 2 + mLogoOffset;
// calculate the final xy of the text
float totalLength = 0;
for (int i = 0; i < mLogoTexts.size(); i++) {
String str = mLogoTexts.get(i);
float currentLength = mPaint.measureText(str);
if (i != mLogoTexts.size() - 1) {
totalLength += currentLength + mTextPadding;
} else {
totalLength += currentLength;
}
}
// the draw width of the logo must small than the width of this AnimLogoView
if (totalLength > mWidth) {
throw new IllegalStateException("This view can not display all text of logoName, please change text size.");
}
float startX = (mWidth - totalLength) / 2;
if (mQuietPoints.size() > 0) {
mQuietPoints.clear();
}
for (int i = 0; i < mLogoTexts.size(); i++) {
String str = mLogoTexts.get(i);
float currentLength = mPaint.measureText(str);
mQuietPoints.put(i, new PointF(startX, centerY));
startX += currentLength + mTextPadding;
}
// generate random start xy of the text
if (mRadonPoints.size() > 0) {
mRadonPoints.clear();
}
// 構(gòu)建隨機初始坐標(biāo)
for (int i = 0; i < mLogoTexts.size(); i++) {
mRadonPoints.put(i, new PointF((float) Math.random() * mWidth, (float) Math.random() * mHeight));
}
}
- 構(gòu)建動畫過程,定義一個屬性動畫從0-1計算進度杏愤,在動畫過程通過重繪實現(xiàn)文字從凌亂打散的坐標(biāo)到最終組合坐標(biāo)進行移動:
// init the translation animation
private void initOffsetAnimation() {
mOffsetAnimator = ValueAnimator.ofFloat(0, 1);
mOffsetAnimator.setDuration(mOffsetDuration);
mOffsetAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (mQuietPoints.size() <= 0 || mRadonPoints.size() <= 0) {
return;
}
mOffsetAnimProgress = (float) animation.getAnimatedValue();
invalidate();
}
});
}
@Override
protected void onDraw(Canvas canvas) {
if (!isOffsetAnimEnd) {// offset animation
mPaint.setAlpha((int) Math.min(255, 255 * mOffsetAnimProgress + 100));
for (int i = 0; i < mQuietPoints.size(); i++) {
PointF quietP = mQuietPoints.get(i);
PointF radonP = mRadonPoints.get(i);
float x = radonP.x + (quietP.x - radonP.x) * mOffsetAnimProgress;
float y = radonP.y + (quietP.y - radonP.y) * mOffsetAnimProgress;
canvas.drawText(mLogoTexts.get(i), x, y, mPaint);
}
}
}
- 此時我們已經(jīng)把
logo
文字動畫實現(xiàn)了靡砌,接下來看我們拆解的第7步,還有個光照效果珊楼。對于這種光照效果通殃,首選方案是通過Gradient
+Shader
實現(xiàn)。因為繪制漸變也涉及到坐標(biāo)厕宗,所以動畫的初始化我們也放到了onSizeChanged
中進行:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
initLogoCoordinate();// 初始化坐標(biāo)動畫
initGradientAnimation(w);// 初始化漸變動畫
}
// init the gradient animation
private void initGradientAnimation(int width) {
mGradientAnimator = ValueAnimator.ofInt(0, 2 * width);
if (mGradientListener != null) {
mGradientAnimator.addListener(mGradientListener);
}
mGradientAnimator.setDuration(mGradientDuration);
mGradientAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mMatrixTranslate = (int) animation.getAnimatedValue();
invalidate();
}
});
mLinearGradient = new LinearGradient(-width, 0, 0, 0, new int[]{mTextColor, mGradientColor, mTextColor},
new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP);
mGradientMatrix = new Matrix();
}
- 漸變動畫是在文字移動動畫結(jié)束后自動播放的画舌,所以我們可以在初始化文字移動動畫時對動畫結(jié)束進行監(jiān)聽處理,同時在繪制
onDraw
中對文字進行繪制:
// init the translation animation
private void initOffsetAnimation() {
...
// 初始化移動動畫
...
mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (mGradientAnimator != null && isShowGradient) {
isOffsetAnimEnd = true;
mPaint.setShader(mLinearGradient);
mGradientAnimator.start();
}
}
});
}
@Override
protected void onDraw(Canvas canvas) {
if (!isOffsetAnimEnd) {// offset animation
...
// 文字移動動畫
...
} else {// gradient animation
for (int i = 0; i < mQuietPoints.size(); i++) {
PointF quietP = mQuietPoints.get(i);
canvas.drawText(mLogoTexts.get(i), quietP.x, quietP.y, mPaint);
}
mGradientMatrix.setTranslate(mMatrixTranslate, 0);
mLinearGradient.setLocalMatrix(mGradientMatrix);
}
}
- 到此已慢,文字動畫已經(jīng)實現(xiàn)了曲聂。剩下來就是一些自定義屬性的定義,對外提供一些屬性的
setter
和getter
方法了蛇受,同時需要考慮在頁面生命周期過程中動畫的資源釋放句葵。好了厕鹃,看下我們實現(xiàn)的效果:
- 對于上面Logo圖片的動畫可以單獨對一個
ImageView
進行平移+透明度動畫實現(xiàn)兢仰,這里就不花篇幅去描述了。
自定義View
我相信大部分同學(xué)都已經(jīng)掌握熟練剂碴,但是對于復(fù)雜動畫把将,是否能夠?qū)⑦@些熟練的能力用在刀刃上呢,也許會有部份同學(xué)看到一個華麗的效果就不知所措了忆矛。本文沒有對動畫進行深入的分析察蹲,也沒涉及到復(fù)雜的數(shù)據(jù)運算请垛,只是通過一個簡單的例子,闡述了一種通用的動效分析實現(xiàn)的方式洽议,通過這種思維方式宗收,你可以很清晰的了解自己每一步的實現(xiàn)以及目標(biāo)。
最后總結(jié)一下亚兄,對于自定義動效而言混稽,我們首先可以讓UI提供最終視覺效果,通過工具進行單幀解析审胚,觀察其中的每一幀之間的動作關(guān)系匈勋,將其拆解為一個個基礎(chǔ)單元。接著針對每個單元步驟進行實現(xiàn)膳叨,最后整合到一起洽洁,就能夠?qū)崿F(xiàn)一個連貫的效果了。這是一種思想菲嘴,當(dāng)你熟練掌握這種思想后饿自,還需要對一些數(shù)學(xué)知識有一定的了解,比如三角函數(shù)临谱,矩陣運算等等璃俗。只要培養(yǎng)好這兩方面能力,日常開發(fā)中悉默,任何復(fù)雜的動效都不足以為懼城豁。