前言
如今隨著直播行業(yè)的火爆破花,直播類App數(shù)不勝數(shù)叠纹,提及直播就不得不涉及到各種交互的動效,其中挺常見的一種效果就是紅包雨嫌套,當觸發(fā)出該效果時逆屡,會從屏幕上方掉落很多的紅包,用戶通過點擊掉落中的紅包領取相對應的金額踱讨,本文將仿照這種交互定制成一個控件魏蔗,最終效果如下:
?
實現(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
土铺,由于生成的位置有可能在上一次的左邊胶滋,也有可能在右邊,因此左右兩邊先各自生成一個隨機值悲敷,最后再在這兩個值中隨機挑選一個究恤。
左邊的隨機值:
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)』自定義帶入場動畫的弧形百分比進度條
GitHub:GitHub-ZJYWidget
CSDN博客:IT_ZJYANG
簡 書:Android小Y
在 GitHub 上建了一個集合炫酷自定義View的項目蛤织,里面有很多實用的自定義View源碼及demo,會長期維護鸿染,歡迎Star~ 如有不足之處或建議還望指正指蚜,相互學習,相互進步牡昆,如果覺得不錯動動小手點個喜歡姚炕, 謝謝~