一個真正酷炫的動效每每讓人虎軀一震,話很少說,我們先瞅瞅效果:
javascript
若是你想看 GAStudio Github主頁,請戳這裏;
若是你想看 GAStudio更多技術文章,請戳這裏;
QQ技術交流羣:277582728;
github地址: github.com/Ajian-studi…java
1.這是圖片仍是文字;
2.若是是圖片該如何拿到圖形的邊沿線座標,若是是文字呢?
3.若是拿到了邊沿線座標,如何讓光線沿着路徑跑動;
4.怎麼處理過程的銜接;android
以上四個問題彷佛不是太好處理,而這幾個問題也正好是這個效果精華所在,接下來我們一個一個進行考慮,固然這種考慮已經基於一些國外大神的基礎之上;git
首先這是圖片仍是文字?github
答案是:背景是圖片,表面的文字仍是圖片,有些同窗可能會說了,靠,這麼沒含量,一個幀動畫而已,還虎軀一震,XXXXX,固然,答案確定不會是這樣的,背景我就不說了,普通的jpg或png圖,但文字則是SVG格式的矢量圖;canvas
有了第一個問題的答案,咱們來看第二個問題,如何拿到文字圖形的邊沿座標;app
要回答這個問題,咱們先來簡單的瞭解一個SVG(矢量圖);
SVG 意爲可縮放矢量圖形(Scalable Vector Graphics),是使用 XML 來描述二維圖形和繪圖程序的語言;ide
使用 SVG 的優點在於:svg
1.SVG 可被很是多的工具讀取和修改(好比記事本),因爲使用xml格式定義,因此能夠直接被看成文本文件打開,看裏面的數據;
2.SVG 與 JPEG 和 GIF 圖像比起來,尺寸更小,且可壓縮性更強,SVG 圖就至關於保存了關鍵的數據點,好比要顯示一個圓,須要知道圓心和半徑,那麼SVG 就只保存圓心座標和半徑數據,而日常咱們用的位圖都是以像素點的形式根據圖片大小保存對應個數的像素點,於是SVG尺寸更小;
3.SVG 是可伸縮的,日常使用的位圖拉伸會發虛,壓縮會變形,而SVG格式圖片保存數據進行運算展現,無論多大多少,能夠不失真顯示;
4.SVG 圖像可在任何的分辨率下被高質量地打印;
5.SVG 可在圖像質量不降低的狀況下被放大;
6.SVG 圖像中的文本是可選的,同時也是可搜索的(很適合製做地圖);
7.SVG 能夠與 Java 技術一塊兒運行;
8.SVG 是開放的標準;
9.SVG 文件是純粹的 XML;工具
看起來好厲害的樣子,仍是回到咱們的問題,從SVG圖中咱們能否拿到咱們想要的數據點呢?根據上面的介紹,答案固然是確定的,從SVG圖中咱們能夠拿到咱們想要的全部數據;
好的,拿到數據以後,怎麼讓一條線沿着路徑跑起來呢?毋庸置疑,咱們須要用到path;
最後咱們根據效果的須要,設置幾個繪製過程,進行繪製;
接下來咱們一塊兒來解決以上問題:
既然SVG是公認的xml文件格式定義的,那麼咱們則能夠經過解析xml文件拿到對應SVG圖的全部數據,咱們先看下 path 類型的SVG 數據:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path d="M250 150 L150 350 L350 350 Z" />
</svg>複製代碼
上面有一個path 標籤,裏面用到了 M 和 Z 指令,M 就至關於 android Path 裏的moveTo(),Z 則至關於 Path 裏的close();
咱們先看下SVG 裏關於path 有哪些指令:
M = moveto 至關於 android Path 裏的moveTo(),用於移動起始點
L = lineto 至關於 android Path 裏的lineTo(),用於畫線
H = horizontal lineto 用於畫水平線
V = vertical lineto 用於畫豎直線
C = curveto 至關於cubicTo(),三次貝塞爾曲線
S = smooth curveto 一樣三次貝塞爾曲線,更平滑
Q = quadratic Belzier curve quadTo(),二次貝塞爾曲線
T = smooth quadratic Belzier curveto 一樣二次貝塞爾曲線,更平滑
A = elliptical Arc 至關於arcTo(),用於畫弧
Z = closepath 至關於closeTo(),關閉path複製代碼
瞭解了以上path相關的指令,就能夠看懂path構成的SVG圖的數據了,除此以外,SVG裏還定義了一些基本的圖形和效果:
更多介紹和使用你們能夠看 W3School
好,以上內容,咱們已經知道 SVG 圖是經過 Xml 格式定義的,而且裏面用到了一些基本的指令對數據進行組裝,構成基本圖形或複雜的路徑;
而對於咱們來講 ,這個xml 如何拿到呢?
1.咱們根據最後要作的效果,利用PS等做圖軟件設計製做出想要的圖形;
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="6.95746in" height="1.82269in"
viewBox="0 0 668 175">
<path id="Selection"
fill="none" stroke="black" stroke-width="1"
d="M 530.00,34.00
C 530.00,34.00 526.08,59.00 526.08,59.00
526.08,59.00 518.00,105.00 518.00,105.00
518.00,105.00 515.42,119.00 515.42,119.00
515.42,119.00 513.26,125.01 513.26,125.01
513.26,125.01 506.00,126.00 506.00,126.00
506.00,126.00 496.00,126.00 496.00,126.00
496.00,126.00 496.00,120.00 496.00,120.00
490.87,124.16 486.71,126.42 480.00,126.91
475.71,127.22 471.06,126.94 467.00,125.44
454.13,120.68 451.86,110.19 452.00,98.00
452.22,79.34 465.14,64.55 484.00,63.18
492.14,62.59 498.96,65.71 504.00,72.00
504.00,72.00 510.00,34.00 510.00,34.00
510.00,34.00 530.00,34.00 530.00,34.00 Z
M 551.00,56.89
C 539.01,55.86 537.45,39.82 551.00,35.55
568.60,33.45 567.67,58.33 551.00,56.89 Z複製代碼
中間段省略
M 263.00,134.00
C 263.00,134.00 263.00,145.00 263.00,145.00
263.00,145.00 202.00,145.00 202.00,145.00
202.00,145.00 202.00,134.00 202.00,134.00
202.00,134.00 263.00,134.00 263.00,134.00 Z" />
</svg>複製代碼
根據圖形路徑的複雜度,生成的path數據複雜度也不同,但格式也算是很是的清楚,即採用必定的指令把數據點進行拼接;
如今有了這些數據點,咱們須要作的則是對數據進行解析,封裝成咱們要的Path;
解析的過程也無非是 遇到指令則採用android Path 裏的對應方法進行置換,解析方式以下:
public Path parsePath(String s) throws ParseException {
mCurrentPoint.set(Float.NaN, Float.NaN);
mPathString = s;
mIndex = 0;
mLength = mPathString.length();
PointF tempPoint1 = new PointF();
PointF tempPoint2 = new PointF();
PointF tempPoint3 = new PointF();
Path p = new Path();
p.setFillType(Path.FillType.WINDING);
boolean firstMove = true;
while (mIndex < mLength) {
char command = consumeCommand();
boolean relative = (mCurrentToken == TOKEN_RELATIVE_COMMAND);
switch (command) {
case 'M':
case 'm': {
// m指令,至關於android 裏的 moveTo()
boolean firstPoint = true;
while (advanceToNextToken() == TOKEN_VALUE) {
consumeAndTransformPoint(tempPoint1,
relative && mCurrentPoint.x != Float.NaN);
if (firstPoint) {
p.moveTo(tempPoint1.x, tempPoint1.y);
firstPoint = false;
if (firstMove) {
mCurrentPoint.set(tempPoint1);
firstMove = false;
}
} else {
p.lineTo(tempPoint1.x, tempPoint1.y);
}
}
mCurrentPoint.set(tempPoint1);
break;
}
case 'C':
case 'c': {
// c指令,至關於android 裏的 cubicTo()
if (mCurrentPoint.x == Float.NaN) {
throw new ParseException("Relative commands require current point", mIndex);
}
while (advanceToNextToken() == TOKEN_VALUE) {
consumeAndTransformPoint(tempPoint1, relative);
consumeAndTransformPoint(tempPoint2, relative);
consumeAndTransformPoint(tempPoint3, relative);
p.cubicTo(tempPoint1.x, tempPoint1.y, tempPoint2.x, tempPoint2.y,
tempPoint3.x, tempPoint3.y);
}
mCurrentPoint.set(tempPoint3);
break;
}
case 'L':
case 'l': {
// 至關於lineTo()進行畫直線
if (mCurrentPoint.x == Float.NaN) {
throw new ParseException("Relative commands require current point", mIndex);
}
while (advanceToNextToken() == TOKEN_VALUE) {
consumeAndTransformPoint(tempPoint1, relative);
p.lineTo(tempPoint1.x, tempPoint1.y);
}
mCurrentPoint.set(tempPoint1);
break;
}
case 'H':
case 'h': {
// 畫水平直線
if (mCurrentPoint.x == Float.NaN) {
throw new ParseException("Relative commands require current point", mIndex);
}
while (advanceToNextToken() == TOKEN_VALUE) {
float x = transformX(consumeValue());
if (relative) {
x += mCurrentPoint.x;
}
p.lineTo(x, mCurrentPoint.y);
}
mCurrentPoint.set(tempPoint1);
break;
}
case 'V':
case 'v': {
// 畫豎直直線
if (mCurrentPoint.x == Float.NaN) {
throw new ParseException("Relative commands require current point", mIndex);
}
while (advanceToNextToken() == TOKEN_VALUE) {
float y = transformY(consumeValue());
if (relative) {
y += mCurrentPoint.y;
}
p.lineTo(mCurrentPoint.x, y);
}
mCurrentPoint.set(tempPoint1);
break;
}
case 'Z':
case 'z': {
// 封閉path
p.close();
break;
}
}
}
return p;
}複製代碼
有了圖形對應的path,咱們只須要按照咱們想要的效果進行繪製便可,具體過程再也不細講,你們看代碼:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mState == STATE_NOT_STARTED || mGlyphData == null) {
return;
}
long t = System.currentTimeMillis() - mStartTime;
// 繪製出現前的邊沿線和跑動過程
for (int i = 0; i < mGlyphData.length; i++) {
float phase = MathUtil.constrain(0, 1,
(t - (mTraceTime - mTraceTimePerGlyph) * i * 1f / mGlyphData.length)
* 1f / mTraceTimePerGlyph);
float distance = INTERPOLATOR.getInterpolation(phase) * mGlyphData[i].length;
mGlyphData[i].paint.setColor(mTraceResidueColors[i]);
mGlyphData[i].paint.setPathEffect(new DashPathEffect(
new float[] {
distance, mGlyphData[i].length
}, 0));
canvas.drawPath(mGlyphData[i].path, mGlyphData[i].paint);
mGlyphData[i].paint.setColor(mTraceColors[i]);
mGlyphData[i].paint.setPathEffect(new DashPathEffect(
new float[] {
0, distance, phase > 0 ? mMarkerLength : 0,
mGlyphData[i].length
}, 0));
canvas.drawPath(mGlyphData[i].path, mGlyphData[i].paint);
}
if (t > mFillStart) {
if (mState < STATE_FILL_STARTED) {
changeState(STATE_FILL_STARTED);
}
// 繪製漸變出現的過程,即改變alpha過程
float phase = MathUtil.constrain(0, 1, (t - mFillStart) * 1f / mFillTime);
for (int i = 0; i < mGlyphData.length; i++) {
GlyphData glyphData = mGlyphData[i];
mFillPaint.setARGB((int) (phase * ((float) mFillAlphas[i] / (float) 255) * 255),
mFillReds[i],
mFillGreens[i],
mFillBlues[i]);
canvas.drawPath(glyphData.path, mFillPaint);
}
}
if (t < mFillStart + mFillTime) {
ViewCompat.postInvalidateOnAnimation(this);
} else {
changeState(STATE_FINISHED);
}
}複製代碼
好了,主要的問題和思路基本如上,有些人可能會說,你這講的跟UX分享似的,沒毛線用,其實個人目的只有一個,那就是無論你是否能看懂代碼,都能按照我上面所說作出本身想要的效果,並加以改變,靈活運用,畢竟輪子不須要重複造!
我本人也是對SVG矢量圖剛有所瞭解,主要參考國外大神的一篇博客,連接以下:www.willowtreeapps.com/blog/muzei-…
CSDN源碼下載地址:download.csdn.net/detail/tian…
最後,附上GAStudio技術交流羣和Github,喜歡的話歡迎follow和star:
若是你想看 GAStudio Github主頁,請戳這裏;
若是你想看 GAStudio更多技術文章,請戳這裏;
QQ技術交流羣:277582728;
github地址: github.com/Ajian-studi…