『Android自定義View實戰(zhàn)』自定義直播紅包雨效果

前言

如今隨著直播行業(yè)的火爆破花,直播類App數(shù)不勝數(shù)叠纹,提及直播就不得不涉及到各種交互的動效,其中挺常見的一種效果就是紅包雨嫌套,當觸發(fā)出該效果時逆屡,會從屏幕上方掉落很多的紅包,用戶通過點擊掉落中的紅包領取相對應的金額踱讨,本文將仿照這種交互定制成一個控件魏蔗,最終效果如下:


YFallingSurfaceView.gif

?

實現(xiàn)

思路

要實現(xiàn)這個效果,有多種不同的思路可供實現(xiàn)痹筛,可以采用屬性動畫+View的形式去做莺治,但要考慮View的復用問題廓鞠,畢竟如果是1000個紅包...這誰頂?shù)米 A硗庖部梢酝ㄟ^屬性動畫+Bitmap的方式去繪制谣旁,但由于這種場景的刷新頻率太高床佳,采用普通的View可能還是會容易遇到卡頓問題,所以最終考慮采用SurfaceView去實現(xiàn)這個效果蔓挖。主要步驟和實現(xiàn)方式如下:

1.包裝紅包屬性對象夕土,后續(xù)所有的動畫的值都是由這些屬性決定。
2.開啟SurfaceView線程瘟判,不斷生成新的紅包對象怨绣,直到達到最大紅包數(shù),就停止拷获。
3.不斷刷新獲取各個紅包最新的屬性值篮撑,包括旋轉角度、位移等匆瓜,并將其繪制在畫布上赢笨。
4.在手指觸摸事件中判斷是否點擊了某個紅包。

效果截圖

?

1.包裝紅包屬性對象

由于后續(xù)的關于Bitmap的一系列變幻驮吱,都是通過角度茧妒、坐標和位移去決定的,所以先將它們包裝成一個紅包對象左冬,方便后續(xù)更改和刷新:

class FallingItem {

        /**
         * 起始X坐標
         */
        private int startX;
        /**
         * 線的起始Y坐標
         */
        private int startY;
        /**
         * 墜落速度
         */
        private int speed;
        /**
         * 旋轉的度數(shù)
         */
        private int rotate;

        public int getRotate() {
            return rotate;
        }

        public void setRotate(int rotate) {
            this.rotate = rotate;
        }

        public int getSpeed() {
            return speed;
        }

        public FallingItem setSpeed(int speed) {
            this.speed = speed;
            return this;
        }

        public int getStartX() {
            return startX;
        }

        public void setStartX(int startX) {
            this.startX = startX;
        }

        public int getStartY() {
            return startY;
        }

        public void setStartY(int startY) {
            this.startY = startY;
        }
}

可以看到有4個屬性值桐筏,x和y坐標就不用講了,決定了紅包在屏幕中的位置拇砰,rotate決定了紅包旋轉的角度梅忌,speed則代表紅包下落的速度,也就是每次刷新除破,都會將其原來的Y坐標加上這個speed牧氮,作為新的Y坐標,從而實現(xiàn)下落的效果瑰枫。

?

2.紅包的產生和停止

上一步我們已經封裝好了紅包對象踱葛,因此紅包的生成其實就是生成一個FallingItem類對象,在生成之前首先要判斷一下當前的數(shù)量是否已經達到紅包總數(shù):

/**
 * 掉落對象的集合
 */
private List<FallingItem> fallingItems;

private void addItem() {
        //超過紅包總數(shù)躁垛,攔截
        if(curGenerateCount >= maxCount) {
            return;
        }
        FallingItem item = new FallingItem();
        fallingItems.add(item);
        curGenerateCount++;
}

生成紅包對象后剖毯,需要為每一個紅包對象的每一個屬性進行初始化,由于要形成隨機掉落的效果教馆,所以紅包的初始橫坐標需要通過隨機數(shù)來生成:

private void addItem() {
        //超過紅包總數(shù),攔截
        if(curGenerateCount >= maxCount) {
            return;
        }
        FallingItem item = new FallingItem();
        int startInLeft = 0;
        if(lastStartX > bitmapWidth) {
            startInLeft = random.nextInt(lastStartX - bitmapWidth);
        }
        int startInRight = 0;
        if(lastStartX < mCanvasWidth - bitmapWidth + 1){
            startInRight = random.nextInt(mCanvasWidth - lastStartX - bitmapWidth + 1) + lastStartX;
        }
        if(startInLeft > 0 && startInRight > 0){
            item.startX = random.nextBoolean() ? startInLeft : startInRight;
        }else{
            if(startInLeft == 0){
                item.startX = startInRight;
            }
            if(startInRight == 0){
                item.startX = startInLeft;
            }
        }
        //int startInRight = random.nextInt(mCanvasWidth - bitmapWidth - lastStartX) + lastStartX + bitmapWidth;
        if(item.startX > mCanvasWidth - bitmapWidth){
            item.startX = mCanvasWidth - bitmapWidth;
        }
        fallingItems.add(item);
        curGenerateCount++;
}

首先為了盡量避免連續(xù)好多次都是同一位置掉落擂达,因此記錄了上一次的橫坐標 lastStartX 土铺,由于生成的位置有可能在上一次的左邊胶滋,也有可能在右邊,因此左右兩邊先各自生成一個隨機值悲敷,最后再在這兩個值中隨機挑選一個究恤。

生成范圍示意圖.png

左邊的隨機值:

int startInLeft = 0;
if(lastStartX > bitmapWidth) {
    startInLeft = random.nextInt(lastStartX - bitmapWidth);
}

也就是以0為起點,以上一個紅包的左邊緣偏移一個位圖的位置為終點后德,這個范圍內隨機一個值部宿。

右邊的隨機值:

int startInRight = 0;
if(lastStartX < mCanvasWidth - bitmapWidth + 1){
    startInRight = random.nextInt(mCanvasWidth - lastStartX - bitmapWidth + 1) + lastStartX;
}

右邊區(qū)域是以上一個紅包的左邊緣偏移一個像素為起點,畫布右邊緣減去一個紅包寬度為終點瓢湃,這個范圍內隨機一個值理张,那么就是(lastStartX, mCanvasWidth - bitmapWidth),從而可以根據(jù)random.nextInt(mCanvasWidth - lastStartX - bitmapWidth + 1) + lastStartX來獲取這個范圍的隨機值绵患。在計算之前判斷lastStartX < mCanvasWidth - bitmapWidth + 1是因為random參數(shù)不能小于等于0

兩邊的值都計算完之后雾叭,如果只有一邊滿足條件,則取滿足的那個值落蝙,如果兩邊都有滿足條件的值织狐,則隨機取兩者中的一個:

if(startInLeft > 0 && startInRight > 0){
     item.startX = random.nextBoolean() ? startInLeft : startInRight;
}else{
     if(startInLeft == 0){
          item.startX = startInRight;
     }
     if(startInRight == 0){
          item.startX = startInLeft;
     }
}

得到起始橫坐標之后,還有起始縱坐標筏勒、速度移迫、角度等屬性需要初始化:

item.startY = -60;
item.speed = (random.nextInt(3)+2)*5;
item.rotate = random.nextInt(360);
lastStartX = item.startX;

-60是讓紅包從屏幕外開始,速度和角度也給了個隨機值管行,讓整個效果更為豐富病瞳。

?

3.不斷刷新獲取各個紅包最新的屬性值亲善,包括旋轉角度、位移等,并將其繪制在畫布上。

在SurfaceView的方法里骚烧,不斷循環(huán)得去生成新紅包并修改其屬性值赃绊,最后繪制在畫布上,實現(xiàn)動畫效果:

@Override
public void run() {
        Canvas canvas = null;
        FallingItem item = null;
        while (mFlag) {
            try {
                canvas = surfaceHolder.lockCanvas();
                if(mCanvasHeight == 0) {
                    mCanvasHeight = canvas.getHeight();
                    mCanvasWidth = canvas.getWidth();
                }
                //清空畫布
                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            } catch (Exception e) {
                break;
            }

            for (int i = 0; i < fallingItems.size(); i++) {
                item = fallingItems.get(i);
                mMatrix.setRotate(item.rotate, (float) bitmapWidth / 2, (float) bitmapHeight / 2);
                mMatrix.postTranslate(item.startX, item.startY);
                canvas.drawBitmap(mBitmap, mMatrix, paint);
                item.setStartY(item.getStartY() + item.speed);
            }

             //解鎖畫布
             surfaceHolder.unlockCanvasAndPost(canvas);

            //添加墜落對象
            addItem();

            if (fallingItems.size() > 50) {
                fallingItems.remove(0);
            }
        }
}

獲取集合里面存儲的紅包對象档痪,通過Matrix遍歷更改它們的屬性值,然后調用canvas.drawBitmap將其繪制在畫布上衬廷,并在原來縱坐標的基礎上加上每次降落的距離(speed)宁昭,從而不斷降落疆拘。
?

4.紅包點擊事件

點擊事件,自然是重寫其onTouchEvent方法漱挚,在ACTION_DOWN事件里面去檢測觸摸區(qū)域是否屬于紅包范圍:

@Override
public boolean onTouchEvent(MotionEvent event) {
        int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                checkInRect((int) event.getX(), (int) event.getY());
                break;
        }
        return true;
}

紅包的x、y坐標均能獲取到颊糜,紅包的寬高也能獲取到衬鱼,那么就可以得到其范圍消别,然后將手指觸摸的點的橫縱坐標與每個紅包的范圍做對比蛇券,檢測是否包含其中:

/**
 * 是否點擊在紅包區(qū)域
 * @param x
 * @param y
 */
private void checkInRect(int x, int y) {
        Log.d("Falling", "checkInRect");
        int length = fallingItems.size();
        for (int i = 0; i < length; i++) {
            FallingItem moveModel = fallingItems.get(i);
            Rect rect = new Rect((int) moveModel.startX, (int) moveModel.startY, (int) moveModel.startX + bitmapWidth, (int) moveModel.startY + bitmapHeight);
            if (rect.contains(x, y)) {
                count++;
                resetMoveModel(moveModel);
                Log.d("Falling", "count: " + count);
                break;
            }
        }
}

如果點擊到了某個紅包,則將其屬性值重置并從紅包集合中移除掉:

private void resetMoveModel(FallingItem moveModel) {
        moveModel.startX = 0;
        moveModel.startY = -100;
        if(fallingItems.contains(moveModel)){
            fallingItems.remove(moveModel);
        }
}

?

結語

雖然基本效果實現(xiàn)了纠亚,但還有一些可以優(yōu)化的地方塘慕,例如紅包對象緩存的管理、避免大數(shù)量時內存消耗蒂胞,完整代碼已上傳到 一個集合酷炫效果的自定義組件庫图呢,歡迎Issue。
?

歡迎關注 Android小Y 的簡書骗随,更多Android精選自定義View

『Android自定義View實戰(zhàn)』實現(xiàn)一個小清新的彈出式圓環(huán)菜單
『Android自定義View實戰(zhàn)』玩轉PathMeasure之自定義支付結果動畫
『Android自定義View實戰(zhàn)』自定義弧形旋轉菜單欄——衛(wèi)星菜單
『Android自定義View實戰(zhàn)』自定義帶入場動畫的弧形百分比進度條

GitHubGitHub-ZJYWidget
CSDN博客IT_ZJYANG
簡 書Android小Y
GitHub 上建了一個集合炫酷自定義View的項目蛤织,里面有很多實用的自定義View源碼及demo,會長期維護鸿染,歡迎Star~ 如有不足之處或建議還望指正指蚜,相互學習,相互進步牡昆,如果覺得不錯動動小手點個喜歡姚炕, 謝謝~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市丢烘,隨后出現(xiàn)的幾起案子柱宦,更是在濱河造成了極大的恐慌,老刑警劉巖播瞳,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掸刊,死亡現(xiàn)場離奇詭異,居然都是意外死亡赢乓,警方通過查閱死者的電腦和手機忧侧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門石窑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蚓炬,你說我怎么就攤上這事松逊。” “怎么了肯夏?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵经宏,是天一觀的道長。 經常有香客問我驯击,道長烁兰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任徊都,我火速辦了婚禮沪斟,結果婚禮上,老公的妹妹穿的比我還像新娘暇矫。我一直安慰自己主之,他們只是感情好,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布袱耽。 她就那樣靜靜地躺著杀餐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪朱巨。 梳的紋絲不亂的頭發(fā)上史翘,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音冀续,去河邊找鬼琼讽。 笑死,一個胖子當著我的面吹牛洪唐,可吹牛的內容都是我干的钻蹬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼凭需,長吁一口氣:“原來是場噩夢啊……” “哼问欠!你這毒婦竟也來了?” 一聲冷哼從身側響起粒蜈,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤顺献,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后枯怖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體注整,經...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肿轨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寿冕。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖椒袍,靈堂內的尸體忽然破棺而出驼唱,到底是詐尸還是另有隱情,我是刑警寧澤槐沼,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布曙蒸,位于F島的核電站,受9級特大地震影響岗钩,放射性物質發(fā)生泄漏。R本人自食惡果不足惜肖油,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一兼吓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧森枪,春花似錦视搏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至式散,卻和暖如春筋遭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背暴拄。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工漓滔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乖篷。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓响驴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親撕蔼。 傳聞我的和親對象是個殘疾皇子豁鲤,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內容

  • 【Android 動畫】 動畫分類補間動畫(Tween動畫)幀動畫(Frame 動畫)屬性動畫(Property ...
    Rtia閱讀 6,178評論 1 38
  • 本文整理自: Google 官方文檔之自定義 View,筆者省略了對自己幫助不大的章節(jié)鲸沮,拜讀原文請點鏈接琳骡。 一、繼...
    程序員K哥閱讀 417評論 0 3
  • 1 背景 不能只分析源碼呀诉探,分析的同時也要整理歸納基礎知識日熬,剛好有人微博私信讓全面說說Android的動畫,所以今...
    未聞椛洺閱讀 2,716評論 0 10
  • 轉自:http://www.reibang.com/p/e9d8420b1b9c 自定義View是Android...
    CQ_TYL閱讀 1,842評論 0 26
  • 目錄 1. 自定義View基礎 1.1 分類 自定義View的實現(xiàn)方式有以下幾種 類型定義自定義組合控件多個控件組...
    銀灬楓閱讀 189,079評論 21 535