尊重原創,歡迎轉載。轉載請註明: FROM GA_studio http://blog.csdn.net/tianjian4592
html
一個真正酷炫的動效每每讓人虎軀一震,話很少說。我們先瞅瞅效果:android
這個效果咱們需要考慮下面幾個問題:canvas
1. 這是圖片仍是文字;app
2. 假設是圖片該怎樣拿到圖形的邊沿線座標,假設是文字呢?ide
3. 假設拿到了邊沿線座標,怎樣讓光線沿着路徑跑動;svg
4. 怎麼處理過程的銜接;工具
以上四個問題彷佛不是太優勢理,而這幾個問題也正好是這個效果精華所在,接下來我們一個一個進行考慮,固然這樣的考慮已經基於一些國外大神的基礎之上。post
首先這是圖片仍是文字?動畫
答案是:背景是圖片。表面的文字仍是圖片。有些同窗可能會說了,靠,這麼沒含量。一個幀動畫而已。還虎軀一震,XXXXX,固然,答案確定不會是這種,背景我就不說了,普通的jpg或png圖,但文字則是SVG格式的矢量圖。ui
有了第一個問題的答案。咱們來看第二個問題,怎樣拿到文字圖形的邊沿座標。
要回答這個問題。咱們先來簡單的瞭解一個SVG(矢量圖);
SVG 意爲可縮放矢量圖形(Scalable Vector Graphics),是使用 XML 來描寫敘述二維圖形和畫圖程序的語言;
使用 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>
咱們先看下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
不少其它介紹和使用你們可以看 W3School
好,以上內容。咱們已經知道 SVG 圖是經過 Xml 格式定義的。並且裏面用到了一些主要的指令對數據進行組裝,構成基本圖形或複雜的路徑。
而對於咱們來講 ,這個xml 怎樣拿到呢?
1.咱們依據最後要作的效果。利用PS等做圖軟件設計製做出想要的圖形;
2. 使用 GIMP 之類的矢量圖軟件導出圖片的SVG數據,方法例如如下:
先使用魔棒工具高速創建選區:
而後將選區導出爲path:
這個時候在軟件的右邊欄就可以看見生成的路徑了。而後將路徑導出:
經過以上幾步。咱們就拿到了咱們本身設計的文字或圖形SVG圖的Path數據。上面圖片的SVG信息例如如下:
<?中間段省略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;
解析的過程也無非是 遇到指令則採用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; }
@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); } }
我本人也是對SVG矢量圖剛有所瞭解,主要參考國外大神的一篇博客。連接例如如下:http://www.willowtreeapps.com/blog/muzei-esque-animated-svg-drawing-for-android/
CSDN源代碼下載地址:http://download.csdn.net/detail/tianjian4592/8548495