仿直播app送礼物控件(冒气泡)

这个自定义控件难度很小,但是效果很好,相信大家都见过,也都有自己的实现方法,今天就来介绍下我的思路。

先看下效果
这里写图片描述
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];
    }
}
  1. 对象定义出来了,咱们就来让这些小家伙动起来吧,我用了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};
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值