这个自定义控件难度很小,但是效果很好,相信大家都见过,也都有自己的实现方法,今天就来介绍下我的思路。
先看下效果
1. 我先定义一个小礼物对象
/**
* Created by 李静,
* 我们定义的小礼物对象
*/
public class GiftBean {
public float alpha = 1f;//小礼物会慢慢的变透明,0<=alpha<=1
public Point pointStart;//开始点
public Point pointEnd;//结束
public Point pointFirst;//我么采用三阶贝塞尔曲线,这是第一个参考点
public Point pointSecond;//这是第二个参考点
public float a;//加速度,我们并不希望小礼物慢吞吞的运动,更不希望小礼物一闪而过,大家也可以用一下插值器,
//在这我就用大家最熟悉的加速度了,毕竟大家的物理都不是白学的
public int time;
public float t;//贝塞尔曲线的变量 并且 0<=t<=1
public GiftBean(Point[] randomPoint) {
//随机生成的加速度,毕竟偶们不希望每个小礼物都有相同的速度,看起来像部队一样,不够调皮
float random = (float) Math.random();
this.a = Math.min(0.1f,random);
this.a = Math.max(0.075f,a);//我调的比较靠谱的加速度,大家也可以改改试试
this.pointStart = randomPoint[0];
this.pointFirst = randomPoint[1];
this.pointSecond = randomPoint[2];
this.pointEnd = randomPoint[3];
}
}
- 对象定义出来了,咱们就来让这些小家伙动起来吧,我用了SurfaceView来实现,毕竟有优势;
/**
* Created by 李静.
*/
public class GiftView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private ArrayList<GiftBean> mDatas;
private ArrayList<GiftBean> newest;
private int mWidth, mHeight;
private MyThread myThread;
public GiftView(Context context) {
super(context);
}
public GiftView(Context context, AttributeSet attrs) {
super(context, attrs);
mHolder = this.getHolder();
setZOrderOnTop(true); // necessary
mHolder.setFormat(PixelFormat.TRANSPARENT);
mHolder.addCallback(this);
myThread = new MyThread();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = w;
mHeight = h;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mDatas = new ArrayList<>();
newest = new ArrayList<>();
Collections.synchronizedList(newest);//这样就可以多线程访问了
//刚进来就让他先刷一波
for (int i = 0; i < 10; i++) {
Point[] randomPoint = createRandomPoint();
GiftBean bean = new GiftBean(randomPoint);
mDatas.add(bean);
}
if (!myThread.isAlive()) {
myThread = new MyThread();
myThread.start();
}
}
//添加新的小礼物
public void addGifts() {
for (int i = 0; i < Math.random() * 5; i++) {
Point[] randomPoint = createRandomPoint();
GiftBean bean = new GiftBean(randomPoint);
newest.add(bean);
}
if (!myThread.isAlive()) {
myThread = new MyThread();
myThread.start();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.i("lijing", "surfaceChanged");
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i("lijing", "surfaceDestroyed");
}
class MyThread extends Thread {
private Bitmap mBitmap;
private Paint paint;
public MyThread() {
paint = new Paint();
//这个很重要,要把之前的清空
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
//小礼物就长这样
mBitmap = getBitmapFromDrawable(R.mipmap.ic_launcher);
}
@Override
public void run() {
while (mDatas.size() > 0 || newest.size() > 0) {
Canvas canvas = mHolder.lockCanvas();
if (canvas == null) {
mDatas.clear();
newest.clear();
return;
}
//需要把爬出去的小礼物给清空
List<GiftBean> delete = new ArrayList<>();
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas_temp = new Canvas(bitmap);
//这里新创建一个canvas起到了双缓冲的作用,在这里其实作用不大,但是这个用法值得学习
//双缓冲是一种在内存中保留屏幕的副本或缓存的技术,先将所有图形都加载在内存中,然后一起绘制到屏幕上,避免了直接在屏幕上绘图时出现的明显闪烁。
//先看看有没有刚加进来的小礼物
mDatas.addAll(newest);
newest.clear();
for (GiftBean giftSimple : mDatas) {
giftSimple.time += 1;
float x = 0.5f * giftSimple.a * giftSimple.time * giftSimple.time;
giftSimple.t = x / mHeight;
giftSimple.alpha = 1f - giftSimple.t;
if (giftSimple.t >= 1f) {
delete.add(giftSimple);
continue;
}
Point point = evaluate(giftSimple.t, giftSimple.pointStart, giftSimple.pointFirst, giftSimple.pointSecond, giftSimple.pointEnd);
Paint paint_temp = new Paint();
paint_temp.setAlpha((int) (giftSimple.alpha * 255));
canvas_temp.drawBitmap(mBitmap, point.x, point.y, paint_temp);
}
canvas.drawBitmap(bitmap, 0, 0, paint);
SystemClock.sleep(10);
mHolder.unlockCanvasAndPost(canvas);
mDatas.removeAll(delete);//把爬出去的小礼物都给清空
}
}
}
protected Bitmap getBitmapFromDrawable(@DrawableRes int resId) {
BitmapDrawable drawable = (BitmapDrawable) getContext().getResources().getDrawable(resId);
Bitmap bitmap = drawable.getBitmap();
return Bitmap.createScaledBitmap(bitmap, 50, 50, false);
}
//利用三阶贝塞尔曲线公式算出中间点坐标,不知道公式的可以上网了解下,
public Point evaluate(float t, Point startValue, Point first, Point second, Point endValue) { //利用三阶贝塞尔曲线公式算出中间点坐标
int x = (int) (startValue.x * Math.pow((1 - t), 3) + 3 * first.x * t * Math.pow((1 - t), 2) + 3 * second.x * Math.pow(t, 2) * (1 - t) + endValue.x * Math.pow(t, 3));
int y = (int) (startValue.y * Math.pow((1 - t), 3) + 3 * first.y * t * Math.pow((1 - t), 2) + 3 * second.y * Math.pow(t, 2) * (1 - t) + endValue.y * Math.pow(t, 3));
return new Point(x, y);
}
//随机生成小礼物的四个点
private Point[] createRandomPoint() {
Point point_start = new Point(mWidth / 2, mHeight);//我希望小礼物都从控件的底部中间出来,都在相同的地方出生,然后人生就瞎跑吧。
Point point_end = new Point((int) (Math.random() * mWidth), 0);//这个没什么好说,从哪出去都行,反正都在控件的最顶部
//随机生成的两个控制点
Point point_first = new Point((int) (Math.random() * (mWidth / 2)), (int) (Math.random() * (mHeight / 2) + mHeight / 2));
Point point_second = new Point((int) (Math.random() * (mWidth / 2) + mWidth / 2), (int) (Math.random() * (mHeight / 2)));
//下面的代码我就想表达一个意思,小礼物出生后我不想让所有的都先往左跑,或者都往又跑,而是随机的,这个方法有点烂,大家轻喷
double leftOrRight = Math.random()-0.5d;
if (leftOrRight>0){
point_first.x = point_first.x+mWidth/2;
point_second.x = point_second.x-mWidth/2;
}
return new Point[]{point_start, point_first, point_second, point_end};
}
}