Android uses SurfaceView to realize floating praise animation

  • 2021-08-31 09:05:51
  • OfStack

Recently, when I did a live broadcast project, I needed to realize praise animation. When I mentioned animation, I thought of using View attribute animation. Later, when I thought about it, so many users praised it, which would lead to a lot of View on the screen. The overhead was too high, and 1 would be very stuck, so I saw what solution the mainstream anchor software used to solve it.

Therefore, Yingke apk was decompiled, and after about one look, only one SurfaceView was used for its praise, and every heart was painted on the canvas in real time, so the efficiency was really high, and no amount of hearts were afraid. I have a train of thought, but it is troublesome to write it from beginning to end. Later, I checked whether other people have already done it on the Internet. Sure enough, there are ready-made ideas, which are very clear and concise. It is good to change 1 according to your own needs.

I said a pile in front, mainly trying to explain that although I have not done some effects, I can refer to how other mature products are done, so as to avoid detours. Imagine if I only use view attribute animation and realize it, wouldn't it be a card to die, and finally I have to tear it down and redo it.

Look at the effect first:

ZanBean class, each ZanBean is responsible for updating its own location, transparency and other data in real time


import android.animation.TypeEvaluator; 
import android.animation.ValueAnimator; 
import android.annotation.TargetApi; 
import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.Canvas; 
import android.graphics.Matrix; 
import android.graphics.Paint; 
import android.graphics.Point; 
import android.os.Build; 
 
import java.util.Random; 
 
public class ZanBean { 
 
 /** 
  *  Current coordinates of the center  
  */ 
 public Point point; 
 /** 
  *  Moving animation  
  */ 
 private ValueAnimator moveAnim; 
 /** 
  *  Zoom in animation  
  */ 
 private ValueAnimator zoomAnim; 
 /** 
  *  Transparency  
  */ 
 public int alpha = 255;// 
 /** 
  *  Cardiac diagram  
  */ 
 private Bitmap bitmap; 
 /** 
  *  Draw bitmap Matrix of   Used for scaling and moving  
  */ 
 private Matrix matrix = new Matrix(); 
 /** 
  *  Scale factor  
  */ 
 private float sf = 0; 
 /** 
  *  Generate random numbers  
  */ 
 private Random random; 
 public boolean isEnd = false;// End or not  
 
 public ZanBean(Context context, int resId, ZanView zanView) { 
  random = new Random(); 
  bitmap = BitmapFactory.decodeResource(context.getResources(), resId); 
  init(new Point(zanView.getWidth() / 2, zanView.getHeight()- bitmap.getHeight() / 2), new Point((random.nextInt(zanView.getWidth())), 0)); 
 } 
 
 
 public ZanBean(Bitmap bitmap, ZanView zanView) { 
  random = new Random(); 
  this.bitmap = bitmap; 
  // In order to make the display complete at the starting coordinate point,   Need to be subtracted bitmap.getHeight()/2 
  init(new Point(zanView.getWidth() / 2, zanView.getHeight() - bitmap.getHeight() / 2), new Point((random.nextInt(zanView.getWidth())), 0)); 
 } 
 
 @TargetApi(Build.VERSION_CODES.HONEYCOMB) 
 private void init(final Point startPoint, Point endPoint) { 
  moveAnim = ValueAnimator.ofObject(new BezierEvaluator(new Point(random.nextInt(startPoint.x * 2), Math.abs(endPoint.y - startPoint.y) / 2)), startPoint, endPoint); 
  moveAnim.setDuration(1500); 
  moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
   @Override 
   public void onAnimationUpdate(ValueAnimator animation) { 
    point = (Point) animation.getAnimatedValue(); 
    alpha = (int) ((float) point.y / (float) startPoint.y * 255); 
   } 
  }); 
  moveAnim.start(); 
  zoomAnim = ValueAnimator.ofFloat(0, 1f).setDuration(700); 
  zoomAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
   @Override 
   public void onAnimationUpdate(ValueAnimator animation) { 
    Float f = (Float) animation.getAnimatedValue(); 
    sf = f.floatValue(); 
   } 
  }); 
  zoomAnim.start(); 
 } 
 
// public void pause(){ 
//  if(moveAnim !=null&& moveAnim.isRunning()){ 
//   moveAnim.pause(); 
//  } 
//  if(zoomAnim !=null&& zoomAnim.isRunning()){ 
//   zoomAnim.pause(); 
//  } 
// } 
// 
// public void resume(){ 
//  if(moveAnim !=null&& moveAnim.isPaused()){ 
//   moveAnim.resume(); 
//  } 
//  if(zoomAnim !=null&& zoomAnim.isPaused()){ 
//   zoomAnim.resume(); 
//  } 
// } 
 
 public void stop() { 
  if (moveAnim != null) { 
   moveAnim.cancel(); 
   moveAnim = null; 
  } 
  if (zoomAnim != null) { 
   zoomAnim.cancel(); 
   zoomAnim = null; 
  } 
 } 
 
 /** 
  *  Main drawing function  
  */ 
 public void draw(Canvas canvas, Paint p) { 
  if (bitmap != null && alpha > 0) { 
   p.setAlpha(alpha); 
   matrix.setScale(sf, sf, bitmap.getWidth() / 2, bitmap.getHeight() / 2); 
   matrix.postTranslate(point.x - bitmap.getWidth() / 2, point.y - bitmap.getHeight() / 2); 
   canvas.drawBitmap(bitmap, matrix, p); 
  } else { 
   isEnd = true; 
  } 
 } 
 
 /** 
  * 2 Sub-Bezier curve  
  */ 
 @TargetApi(Build.VERSION_CODES.HONEYCOMB) 
 private class BezierEvaluator implements TypeEvaluator<Point> { 
 
  private Point centerPoint; 
 
  public BezierEvaluator(Point centerPoint) { 
   this.centerPoint = centerPoint; 
  } 
 
  @Override 
  public Point evaluate(float t, Point startValue, Point endValue) { 
   int x = (int) ((1 - t) * (1 - t) * startValue.x + 2 * t * (1 - t) * centerPoint.x + t * t * endValue.x); 
   int y = (int) ((1 - t) * (1 - t) * startValue.y + 2 * t * (1 - t) * centerPoint.y + t * t * endValue.y); 
   return new Point(x, y); 
  } 
 } 
} 

The code for ZanView is as follows: SurfaceView, constantly drawing ZanBean onto your canvas.


import android.content.Context; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.graphics.PixelFormat; 
import android.graphics.PorterDuff; 
import android.util.AttributeSet; 
import android.view.SurfaceHolder; 
import android.view.SurfaceView; 
 
import java.util.ArrayList; 
 
public class ZanView extends SurfaceView implements SurfaceHolder.Callback { 
 
 private SurfaceHolder surfaceHolder; 
 
 /** 
  *  The number of hearts  
  */ 
 private ArrayList<ZanBean> zanBeen = new ArrayList<>(); 
 private Paint p; 
 /** 
  *  Worker thread responsible for drawing  
  */ 
 private DrawThread drawThread; 
 
 public ZanView(Context context) { 
  this(context, null); 
 } 
 
 public ZanView(Context context, AttributeSet attrs) { 
  this(context, attrs, 0); 
 } 
 
 public ZanView(Context context, AttributeSet attrs, int defStyleAttr) { 
  super(context, attrs, defStyleAttr); 
  this.setZOrderOnTop(true); 
  /** Set canvas   Background transparency */ 
  this.getHolder().setFormat(PixelFormat.TRANSLUCENT); 
  surfaceHolder = getHolder(); 
  surfaceHolder.addCallback(this); 
  p = new Paint(); 
  p.setAntiAlias(true); 
  drawThread = new DrawThread(); 
 } 
 
 /** 
  *  Like the action   Function of adding heart   Control the number of the maximum center of the picture  
  */ 
 public void addZanXin(ZanBean zanBean) { 
  zanBeen.add(zanBean); 
  if (zanBeen.size() > 40) { 
   zanBeen.remove(0); 
  } 
  start(); 
 } 
 
 @Override 
 public void surfaceCreated(SurfaceHolder holder) { 
  if (drawThread == null) { 
   drawThread = new DrawThread(); 
  } 
  drawThread.start(); 
 } 
 
 @Override 
 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 
 
 } 
 
 @Override 
 public void surfaceDestroyed(SurfaceHolder holder) { 
  if (drawThread != null) { 
   drawThread.isRun = false; 
   drawThread = null; 
  } 
 } 
 
 class DrawThread extends Thread { 
  boolean isRun = true; 
 
  @Override 
  public void run() { 
   super.run(); 
   /** Drawing thread   Infinite loop   Constant running */ 
   while (isRun) { 
    Canvas canvas = null; 
    try { 
     synchronized (surfaceHolder) { 
      canvas = surfaceHolder.lockCanvas(); 
      /** Clear screen */ 
      canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); 
      boolean isEnd = true; 
 
      /** Traversal drawing of all centers */ 
      for (int i = 0; i < zanBeen.size(); i++) { 
       isEnd = zanBeen.get(i).isEnd; 
       zanBeen.get(i).draw(canvas, p); 
      } 
      /** Do it here 1 Performance optimization actions, because threads are infinite loops   End the thread when there is no drawing needed by the heart */ 
      if (isEnd) { 
       isRun = false; 
       drawThread = null; 
      } 
     } 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } finally { 
     if (canvas != null) { 
      surfaceHolder.unlockCanvasAndPost(canvas); 
     } 
    } 
    try { 
     /** Used to control the rendering frame rate */ 
     Thread.sleep(10); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 
   } 
  } 
 } 
 
 public void stop() { 
  if (drawThread != null) { 
 
//   for (int i = 0; i < zanBeen.size(); i++) { 
//    zanBeen.get(i).pause(); 
//   } 
   for (int i = 0; i < zanBeen.size(); i++) { 
    zanBeen.get(i).stop(); 
   } 
 
   drawThread.isRun = false; 
   drawThread = null; 
  } 
 
 } 
 
 public void start() { 
  if (drawThread == null) { 
//   for (int i = 0; i < zanBeen.size(); i++) { 
//    zanBeen.get(i).resume(); 
//   } 
   drawThread = new DrawThread(); 
   drawThread.start(); 
  } 
 } 
} 

Call mode:


public class TestActivity extends BaseActivity { 
 @Override 
 protected void onCreate(Bundle savedInstanceState) { 
  super.onCreate(savedInstanceState); 
  setContentView(R.layout.test_zan); 
  final ZanView zan = (ZanView) findViewById(R.id.zan_view); 
  zan.start(); 
  findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { 
   @Override 
   public void onClick(View v) { 
    ZanBean zanBean = new ZanBean(BitmapFactory.decodeResource(getResources(), R.drawable.ic_default_avatar), zan); 
    zan.addZanXin(zanBean); 
   } 
  }); 
 } 
} 

Related articles: