本文來自zyyoona7,zyyoona7的blog連接爲:http://www.jianshu.com/p/6d2cc30e4687。本文主要是直播界面中點贊效果,固然也能夠用OpenGL去作。
java
zyyonna7簡介:本人是一名Android開發者,從大學開始接觸Android,如今工做恰好滿兩年。在這兩年中本身以爲成長了不少不少,過去的兩年中作了不少公司項目,每一個項目中都充當比較重要的角色。堅持用blog在不斷提高本身。android
先來展現下效果圖:canvas
你們看到效果應該都不陌生,網上已經有不少相同的效果,可是網上大可能是經過動畫來實現,而我這個是經過自定義 SurfaceView 來實現。這個想法主要來自於反編譯映客 App,雖然看不到源碼,但給我提供了思路。接下來進入正題~app
自定義 SurfaceView 須要三點:繼承 SurfaceView、實現SurfaceHolder.Callback、提供渲染線程。dom
繼承 SurfaceView不須要多說,說一下 SurfaceHolder.Callback 須要實現的三個方法:ide
public void surfaceCreated(SurfaceHolder holder) : 當 Surface 第一次建立後會當即調用該函數。程序能夠在該函數中作些和繪製界面相關的初始化工做,通常狀況下都是在另外的線程來繪製界面,因此不要在這個函數中繪製 Surface。函數
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) : 當 Surface 的狀態(大小和格式)發生變化的時候會調用該函數,在 surfaceCreated() 調用後該函數至少會被調用一次。post
public void surfaceDestroyed(SurfaceHolder holder) : 當 Surface 被銷燬前會調用該函數,該函數被調用後就不能繼續使用 Surface 了,通常在該函數中來清理使用的資源。動畫
下面提供一個自定義 SurfaceView 的一個簡單模板:this
public class SimpleSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { // 子線程標誌位 private boolean isRunning; //畫筆 private Paint mPaint; public SimpleSurfaceView(Context context) { super(context, null); } public SimpleSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mPaint = new Paint(); mPaint.setAntiAlias(true); //... getHolder().addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); } @Override public void surfaceCreated(SurfaceHolder holder) { isRunning = true; //啓動渲染線程 new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { isRunning = false; } @Override public void run() { while (isRunning) { Canvas canvas = null; try { canvas = getHolder().lockCanvas(); if (canvas != null) { // draw something drawSomething(canvas); } } catch (Exception e) { e.printStackTrace(); } finally { if (canvas != null) { getHolder().unlockCanvasAndPost(canvas); } } } } /** * draw something * * @param canvas */ private void drawSomething(Canvas canvas) { }}
HeartView 實現主要分爲3部分:
初始化值,向集合中添加 Heart 對象
經過三階貝塞爾曲線實時計算每一個 Heart 對象的座標
在渲染線程遍歷集合,畫出 bitmap
首先說下三階貝塞爾曲線的幾個主要參數:起始點、結束點、控制點一、控制點二、時間(從 0 到 1 )。對貝塞爾曲線不瞭解的或者想更詳細的瞭解的能夠看一下 Path 之貝塞爾曲線 這邊文章。
接着來看一下 Heart 類中的主要屬性:
public class Heart { //實時座標 private float x; private float y; //起始點座標 private float startX; private float startY; //結束點座標 private float endX; private float endY; //三階貝塞爾曲線(兩個控制點) //控制點1座標 private float control1X; private float control1Y; //控制點2座標 private float control2X; private float control2Y; //實時的時間 private float t=0; //速率 private float speed;}
經過三階貝塞爾曲線函數來計算實時座標的公式以下:
//三階貝塞爾曲線函數float x = (float) (Math.pow((1 - t), 3) * start.x + 3 * t * Math.pow((1 - t), 2) * control1.x + 3 * Math.pow(t, 2) * (1 - t) * control2.x + Math.pow(t, 3) * end.x);float y = (float) (Math.pow((1 - t), 3) * start.y + 3 * t * Math.pow((1 - t), 2) * control1.y + 3 * Math.pow(t, 2) * (1 - t) * control2.y + Math.pow(t, 3) * end.y);
有了公式,有了 Heart 類,咱們還須要在 Heart 初始化的時候,給它的屬性隨機設置初始值,代碼以下:
//Heart.java /** * 重置下x,y座標 * 位置在最底部的中間 * * @param x * @param y */ public void initXY(float x, float y) { this.x = x; this.y = y; } /** * 重置起始點和結束點 * * @param width * @param height */ public void initStartAndEnd(float width, float height) { //起始點和結束點爲view的正下方和正上方 this.startX = width / 2; this.startY = height; this.endX = width / 2; this.endY = 0; initXY(startX,startY); } /** * 重置控制點座標 * * @param width * @param height */ public void initControl(float width, float height) { //隨機生成控制點1 this.control1X = (float) (Math.random() * width); this.control1Y = (float) (Math.random() * height); //隨機生成控制點2 this.control2X = (float) (Math.random() * width); this.control2Y = (float) (Math.random() * height); //若是兩個點重合,從新生成控制點 if (this.control1X == this.control2X && this.control1Y == this.control2Y) { initControl(width, height); } } /** * 重置速率 */ public void initSpeed() { //隨機速率 this.speed = (float) (Math.random() * 0.01 + 0.003); }//HeartView.java /** * 添加heart */ public void addHeart() { Heart heart = new Heart(); initHeart(heart); mHearts.add(heart); } /** * 重置 Heart 屬性 * * @param heart */ private void initHeart(Heart heart) { //mWidth、mHeight 分別爲 view 的寬、高 heart.initStartAndEnd(mWidth, mHeight); heart.initControl(mWidth, mHeight); heart.initSpeed(); }
萬事具有,只欠東風。屬性都已經準備就緒,接下來就開始畫了:
//HeartView.java @Override public void run() { while (isRunning) { Canvas canvas = null; try { canvas = getHolder().lockCanvas(); if (canvas != null) { //開始畫 drawHeart(canvas); } } catch (Exception e) { Log.e(TAG, "run: " + e.getMessage()); } finally { if (canvas != null) { getHolder().unlockCanvasAndPost(canvas); } } } } /** * 畫集合內的心形 * @param canvas */ private void drawHeart(Canvas canvas) { //清屏~ canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); for (Heart heart : mHearts) { if (mBitmapSparseArray.get(heart.getType()) == null) { continue; } //會覆蓋掉以前的x,y數值 mMatrix.setTranslate(0, 0); //位移到x,y mMatrix.postTranslate(heart.getX(), heart.getY()); //縮放 //mMatrix.postScale(); //旋轉 //mMatrix.postRotate(); //畫bitmap canvas.drawBitmap(mBitmapSparseArray.get( heart.getType()), mMatrix, mPaint); //計算時間 if (heart.getT() < 1) { heart.setT(heart.getT() + heart.getSpeed()); //計算下次畫的時候,x,y座標 handleBezierXY(heart); } else { removeHeart(heart); } } } /** * 計算實時的點座標 * * @param heart */ private void handleBezierXY(Heart heart) { float x = (float) (Math.pow((1 - heart.getT()), 3) * heart.getStartX() + 3 * heart.getT() * Math.pow((1 - heart.getT()), 2) * heart.getControl1X() + 3 * Math.pow(heart.getT(), 2) * (1 - heart.getT()) * heart.getControl2X() + Math.pow(heart.getT(), 3) * heart.getEndX()); float y = (float) (Math.pow((1 - heart.getT()), 3) * heart.getStartY() + 3 * heart.getT() * Math.pow((1 - heart.getT()), 2) * heart.getControl1Y() + 3 * Math.pow(heart.getT(), 2) * (1 - heart.getT()) * heart.getControl2Y() + Math.pow(heart.getT(), 3) * heart.getEndY()); heart.setX(x); heart.setY(y); }
畫完了,然咱們寫在 demo 裏欣賞一下效果吧,使用代碼以下:
//xml<com.zyyoona7.heartlib.HeartView android:id="@+id/heart_view" android:layout_width="250dp" android:layout_height="250dp" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:layout_marginBottom="40dp"/>//javamHeartView = (HeartView) findViewById(R.id.heart_view);mHeartView.addHeart();
大功告成,效果圖就回到頂部查看吧~須要查看完整代碼請點擊 Github 地址:HeartView