隨機跳動的閃屏Logo標(biāo)題-AnimLogoView

在日常開發(fā)中淮椰,經(jīng)常會遇到各種視覺效果岖是,有的效果可能一眼看去會讓人覺得很復(fù)雜帮毁,但是我們必須明確一點:所有復(fù)雜動效都是可以分解成單一的基礎(chǔ)動作,比如縮放豺撑,平移烈疚,旋轉(zhuǎn)這些基礎(chǔ)單元,然后將所有基礎(chǔ)單元動作進行組合聪轿,就會產(chǎn)生讓人眼前一亮的視覺動效爷肝。

首先看下下圖效果:

AnimLogoView.gif

按照上面我們提到的思路進行分解:

  1. Logo的名稱LitePlayer被拆分為單個文字
  2. 所有文字隨機打散在屏幕各個位置
  3. 中間的Logo被隱藏
  4. Logo文字從隨機位置平移到頁面固定位置
  5. 中間的Logo圖片逐漸顯示,并且附帶從下往上平移一小段位移
  6. Logo被打散的文字組合成名稱
  7. Logo組合成名稱后陆错,有個漸變的光暈照射效果從左往右移動
  8. 動畫結(jié)束

當(dāng)我們把動畫拆解后灯抛,就可以針對每個拆解單元去構(gòu)造實現(xiàn)方案了。

  • 首先我們先對logo文字動畫進行實現(xiàn):
  1. 首先對于數(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));
        }
    }
  1. 所有文字需要隨機打散在屏幕各個位置,因為涉及到坐標(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));
        }
    }
  1. 構(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);
            }
        }
    }
  1. 此時我們已經(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();
    }
  1. 漸變動畫是在文字移動動畫結(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);
        }
    }
  1. 到此已慢,文字動畫已經(jīng)實現(xiàn)了曲聂。剩下來就是一些自定義屬性的定義,對外提供一些屬性的settergetter方法了蛇受,同時需要考慮在頁面生命周期過程中動畫的資源釋放句葵。好了厕鹃,看下我們實現(xiàn)的效果:
    AnimLogoView2.gif
  1. 對于上面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ù)雜的動效都不足以為懼城豁。

附項目源碼地址: https://github.com/seagazer/animlogoview

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市抄课,隨后出現(xiàn)的幾起案子唱星,更是在濱河造成了極大的恐慌,老刑警劉巖跟磨,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件间聊,死亡現(xiàn)場離奇詭異,居然都是意外死亡抵拘,警方通過查閱死者的電腦和手機哎榴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來僵蛛,“玉大人尚蝌,你說我怎么就攤上這事〕湮荆” “怎么了飘言?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長驼侠。 經(jīng)常有香客問我姿鸿,道長谆吴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任苛预,我火速辦了婚禮句狼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘热某。我一直安慰自己鲜锚,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布苫拍。 她就那樣靜靜地躺著芜繁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绒极。 梳的紋絲不亂的頭發(fā)上骏令,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音垄提,去河邊找鬼榔袋。 笑死,一個胖子當(dāng)著我的面吹牛铡俐,可吹牛的內(nèi)容都是我干的凰兑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼审丘,長吁一口氣:“原來是場噩夢啊……” “哼吏够!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起滩报,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤锅知,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后脓钾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體售睹,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年可训,在試婚紗的時候發(fā)現(xiàn)自己被綠了昌妹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡握截,死狀恐怖飞崖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情川蒙,我是刑警寧澤蚜厉,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布长已,位于F島的核電站畜眨,受9級特大地震影響昼牛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜康聂,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一贰健、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恬汁,春花似錦伶椿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至约巷,卻和暖如春偎痛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背独郎。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工踩麦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人氓癌。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓谓谦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贪婉。 傳聞我的和親對象是個殘疾皇子反粥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容