真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

帶你手把手講解一個復雜動效的自定義繪制

引子

自定義View是android高級UI知識體系的重要一環(huán)。也是區(qū)分中高級開發(fā)者的分水嶺。高級開發(fā)者,知識體系完善,但凡能夠語言描述出來的特效,他們總能給出解決方案。而中級開發(fā)者由于眼界受限,往往遇到復雜需求就無從下手。
一些看似復雜的特效,其實android已經(jīng)為我們提供了一套解決方案,這是中級進階高級的必學知識。
本文給出完整攻略,保證一篇入魂。= =!

十多年的信州網(wǎng)站建設經(jīng)驗,針對設計、前端、開發(fā)、售后、文案、推廣等六對一服務,響應快,48小時及時工作處理。網(wǎng)絡營銷推廣的優(yōu)勢是能夠根據(jù)用戶設備顯示端的尺寸不同,自動調(diào)整信州建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)建站從事“信州網(wǎng)站設計”,“信州網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。

順手留下GitHub鏈接,需要獲取相關面試等內(nèi)容的可以自己去找
https://github.com/xiangjiana/Android-MS

效果圖

下圖中可以看到,首先我們看到了一個心形,然后有波浪在跳動,最后綠色填滿了整個心形
帶你手把手講解一個復雜動效的自定義繪制

乍一看

誒?心形是怎么繪制的?誒?波浪是怎么畫出來的,又是如何動起來的?誒? 文字是怎么呈現(xiàn)出同一時刻的兩種顏色的?

不知道是不是有人有這樣的疑惑````請繼續(xù)往下看.

效果拆解

拿到一個復雜特效,第一件事不要慌,先仔細分析一下,這個特效里面具體有哪些細節(jié)可以拆分出來。復雜的東西都是由簡單的細節(jié) 組合而成。

開始拆解

1、繪制區(qū)域是一個心形
2、波浪從最下面開始, 逐漸用綠色填充了整個心形
3、中間有文字內(nèi)容“ 一條大灰狼”,并且在波浪增長的過程中,文字存在一段時間的上下兩部分 顏色不同的狀態(tài).

本案例用到的知識點:

1、 canvas.clipPath 畫布裁剪
2、 canvas.save 畫布狀態(tài)保存
3、 canvas.restore 恢復
4、 canvas.translate 畫布平移
5、 path.rCubicTo 構建三階貝塞爾曲線(相當于上一個點位置)
6、屬性動畫 ValueAnimator / AnimatorSet

開始擼碼
第 1步:構建一個心形區(qū)域
當一個復雜圖形擺在我們面前,而且還是不規(guī)則圖形,我們首先應該想到的,就是 android.graphics.Path 類,它可以記錄復雜圖形的全部點組成的路徑。關鍵代碼:

/**
     * 構建心形
     * 

* 注意,它這個是以 矩形區(qū)域中心點為基準的圖形,所以繪制的時候,必須先把坐標軸移動到 區(qū)域中心 */ private void initHeartPath(Path path) { List pointList = new ArrayList<>(); pointList.add(new PointF(0,Utils.dp2px(-38))); pointList.add(new PointF(Utils.dp2px(50),Utils.dp2px(-103))); pointList.add(new PointF(Utils.dp2px(112),Utils.dp2px(-61))); pointList.add(new PointF(Utils.dp2px(112),Utils.dp2px(-12))); pointList.add(new PointF(Utils.dp2px(112),Utils.dp2px(37))); pointList.add(new PointF(Utils.dp2px(51),Utils.dp2px(90))); pointList.add(new PointF(0,Utils.dp2px(129))); pointList.add(new PointF(Utils.dp2px(-51),Utils.dp2px(90))); pointList.add(new PointF(Utils.dp2px(-112),Utils.dp2px(37))); pointList.add(new PointF(Utils.dp2px(-112), Utils.dp2px(-12))); pointList.add(new PointF(Utils.dp2px(-112),Utils.dp2px(-61))); pointList.add(new PointF(Utils.dp2px(-50),Utils.dp2px(-103))); path.reset(); for(int i =0; i <4; i++) { if (i ==0) { path.moveTo(pointList.get(i *3).x, pointList.get(i *3).y); } else { path.lineTo(pointList.get(i * 3).x, pointList.get(i *3).y); } int endPointIndex; if (i ==3) { endPointIndex = 0; } else { endPointIndex = i *3+3; } path.cubicTo(pointList.get(i *3+1).x, pointList.get(i *3+1).y, pointList.get(i *3+2).x, pointList.get(i *3+2).y, pointList.get(endPointIndex).x, pointList.get(endPointIndex).y); //你的心形就是用貝塞爾曲線來畫的嗎 } path.close(); path.computeBounds(mHeartRect,false); //把path所占據(jù)的最小矩形區(qū)域,返回出去 }

傳入一個 Path引用,然后在方法內(nèi)部對 path進行各種 api調(diào)用改變其屬性. 這里需要提及一個重點:最后一行代碼 path.computeBounds(mHeartRect,false);意思是,無論什么樣的 path,它都會占據(jù)一個最小矩形區(qū)域, computeBounds方法可以獲取這個矩形區(qū)域,設置給入?yún)?mHeartRect.

第 2步:將心形區(qū)域裁剪出來, 裁剪之后,后續(xù)的繪制都只會顯示在這個區(qū)域之內(nèi)
(為了作圖方便,我們通常先把坐標軸原點移動到 繪制區(qū)域的正中央)

 @Override
  protected void onDraw(Canvas canvas) {

        int width = getWidth();
        int height = getHeight();
        canvas.translate(width / 2, height /2);
        //為了作圖方便,我們通常先把坐標軸原點移動到 繪制區(qū)域的正中央
        ...省略無關代碼

        canvas.clipPath(mMainPath);
        //裁剪心形區(qū)域
        canvas.save();
       //保存畫布狀態(tài)

        ...省略無關代碼

    }

第 3步:繪制波浪區(qū)域
這里有兩點細節(jié)

1)波浪區(qū)域分為兩塊, top和 bottom 上下兩塊
2) 整個波浪區(qū)域的長度為 心形矩形范圍寬度的 2倍 ( ?為什么是2倍?因為上面的波浪動畫,其實是整個波浪區(qū)域平移造成的視覺效果,為了讓這個動畫可以無限執(zhí)行,設計兩倍寬度,當一半的寬度向右移動剛好觸及心形矩形區(qū)域的右邊框的時候,讓它還原到原始位置,這樣就能無縫銜接。)

關鍵代碼1 - 波浪path的構建

 /**
     * @param ifTop   是否是上部分; 上下部分的封口位置不一樣
     * @param r       心形的矩形區(qū)域
     * @param process 當前進度值
     */
    private void resetWavePath(boolean ifTop,RectF r,float process,Pathpath) {
        final float width = r.width();
        final float height = r.width();

        path.reset();

        if( ifTop) {
            path.moveTo(r.left - width, r.top);
        } else {
            path.moveTo(r.left - width, r.bottom);
            //下部,初始位置點在 下
       }

        float waveHeight = height /8f;//波動的最大幅度

        //找到矩形區(qū)域的左邊線中點
        path.lineTo(r.left - width,r.bottom - height * process);

        //做兩個周期的貝塞爾曲線
        for (int i =0; i < 2; i++) {
            float px1, py1, px2, py2, px3, py3;

            px1 = width /4;
            py1 = -waveHeight;

            px2 = width /4*3;
            py2 = waveHeight;

            px3 = width;
            py3 = 0;

            path.rCubicTo(px1, py1, px2, py2, px3, py3);
        }
        if (ifTop) {
            path.lineTo(r.right, r.top);
        } else {
            path.lineTo(r.right, r.bottom);
        }
        path.close();

    }

關鍵代碼2- 屬性動畫改變兩個全局變量波浪的向上增長系數(shù)以及橫向波浪動畫系數(shù):

    AnimatorSet animatorSet;
    // 動起來
    public void startAnimator() {

        if(animatorSet == null) {
            animatorSet = new AnimatorSet();
            ValueAnimator growAnimator = ValueAnimator.ofFloat(0f, 1f);
            growAnimator.addUpdateListener(animation -> growProcess =(float) animation.getAnimatedValue());
            growAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    animatorSet.cancel();
                }
            });
            growAnimator.setInterpolator(new DecelerateInterpolator());
            growAnimator.setDuration((long)(4000/ animatorSpeedCoefficient));

            ValueAnimator waveAnimator = ValueAnimator.ofFloat(0f,1f);
            waveAnimator.setRepeatCount(ValueAnimator.INFINITE);
            waveAnimator.setRepeatMode(ValueAnimator.RESTART);
            waveAnimator.addUpdateListener(animation -> {
                waveProcess = (float) animation.getAnimatedValue();
                invalidate();
            });
            waveAnimator.setInterpolator(new LinearInterpolator());
            waveAnimator.setDuration((long)(1000/ animatorSpeedCoefficient));

            animatorSet.playTogether(growAnimator, waveAnimator);
            animatorSet.start();
        } else {
            animatorSet.cancel();
            animatorSet.start();
        }
    }

關鍵代碼3- 利用屬性動畫改變的全局變量,構建動態(tài)效果

  @Override
   protected void onDraw(Canvas canvas) {

        int width = getWidth();
        int height = getHeight();
        canvas.translate(width /2, height /2);
        //為了作圖方便,我們通常先把坐標軸原點移動到 繪制區(qū)域的正中央
        curXOffset = waveProcess * mHeartRect.width();
       //當前X軸方向上 波浪偏移量

        canvas.clipPath(mMainPath);
        canvas.save();

        mainRect = new Rect();
        ...省略無關代碼

        // 上波浪區(qū)域
        resetWavePath(true, mHeartRect, growProcess, topWavePath);
        canvas.translate(curXOffset,0);
        canvas.clipPath(topWavePath);
        canvas.drawPath(topWavePath, mTopPaint);
        ...省略無關代碼

        //下波浪區(qū)域
        resetWavePath(false, mHeartRect, growProcess, bottomWavePath);
        canvas.restore();
        canvas.translate(curXOffset,0);
        canvas.clipPath(bottomWavePath);
        canvas.drawPath(bottomWavePath, mBottomPaint);
       ...省略無關代碼

    }

第 4步:繪制“一條大灰狼” 到心形中央,并且達成雙色效果
這里有兩個細節(jié):

  1. canvas.drawText, 就算你把paint 設置了 .setTextAlign(Paint.Align.CENTER); 它也未必會在你給的 x,y為中心 繪制。原因就不解釋了,谷歌大佬就是這么設計的。解決方法:利用 paint.getTextBounds,獲得文字的矩形區(qū)域。然后在真正 canvas.drawText,計算y的時候考慮這個矩形區(qū)域,就像下面這樣如下
    mainRect = new Rect();
    textBottomPaint.getTextBounds(text,0, text.length(), mainRect);
  2. 由于之前波浪的橫向移動,坐標軸產(chǎn)生了平移,所以我繪制文字,要將平移的距離減去,再繪制,保證居中,且文字位置不隨著波浪的橫向移動而變化。

完整代碼如下(此步驟的關鍵代碼已經(jīng)標紅):
帶你手把手講解一個復雜動效的自定義繪制

結語

來解答 乍一看里面提出的3個問題:

誒?心形是怎么繪制的?答:構建Path,然后 canvas.clipPath裁剪畫布,裁剪之后,所有的作圖效果就只在這個心形區(qū)域內(nèi)可見

誒?波浪是怎么畫出來的,又是如何動起來的?答:波浪,或者說波浪區(qū)域,也是 Path構建,主要由一根波浪線以及三根直線組成,是一個封閉區(qū)域. 讓波浪動起來,其實就是 canvas平移操作,利用屬性動畫+雙倍寬度的波浪區(qū)域,形成無縫無限循環(huán)動畫.

誒? 文字是怎么呈現(xiàn)出同一時刻的兩種顏色的?答:在兩個相鄰的波浪區(qū)域,使用不一樣的顏色繪制兩次文字。視覺效果上還是一串文字,但是實際上是兩次繪制的組合效果。神奇嗎?神奇?zhèn)€屁,其實就是 同一位置繪制兩次文字,后面的覆蓋前面的......話粗理不粗- -!

話題延伸

要想隨心所欲地掌控自定義View,需要有完整的知識體系。

view的樹形結構概念
測量,布局,繪制流程
事件分發(fā)/滑動沖突核心原理
CanvasPaintPath繪制常用api
Bitmap位圖
屬性動畫
如果與 系統(tǒng)的某些View發(fā)生交互,還有可能需要你了解 系統(tǒng)源碼
但是要想隨心所欲地使用 自定義View,僅僅如此還不夠,還需要:良好的數(shù)學基礎

因為大部分的不規(guī)則圖形,可能都需要數(shù)學公式思想的輔助,像是:

心形path的構建
無限波浪的設計思路
后續(xù)文章將會 提到的 貝塞爾曲線的使用

都離不開多年前數(shù)學課上的時候養(yǎng)成的數(shù)學思維,如果數(shù)學基礎比較糟糕,做起這些特效,往往會比較困難.

順手留下GitHub鏈接,需要獲取相關面試等內(nèi)容的可以自己去找
https://github.com/xiangjiana/Android-MS


本文題目:帶你手把手講解一個復雜動效的自定義繪制
網(wǎng)頁地址:http://weahome.cn/article/gcsdeo.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部