发红包android

来源:互联网 发布:vb如何只删除一个字符 编辑:程序博客网 时间:2024/04/29 01:33

马上春节,写个应景的控件

       

思路分析

1.红包沿着不同的轨迹由上往下运动
2.当手指捕获到一个红包,红包停止原先的运动,可以随着手指的滑动做跟手操作
3.当手指动作停止后,红包放大
4.通过滑动刮开红包,看到期待已久的money 

大体知识点概况

1.属性动画,实现红包按照贝塞尔曲线运动和放大效果
2.实现一个可移动的view,可以参考我的另一篇博客http://blog.csdn.net/xuan_xiaofeng/article/details/50463595
3.图片的结合模式,主要是实现刮开红包
4.自定义控件的相关知识 

实战

1.先来做个红包。继承view,做点初始化的工作
private void init() {    mPath = new Path();    mRandom = new Random();    initPaint();    initMoneyPaint();    mText = moneys[mRandom.nextInt(moneys.length)];    //获取字体的宽高    moneyPaint.getTextBounds(mText, 0, mText.length(), mTextBound);}private void initPaint() {    mPaint = new Paint();    mPaint.setColor(Color.parseColor("#c0c0c0"));    mPaint.setStyle(Paint.Style.STROKE);    mPaint.setStrokeCap(Paint.Cap.ROUND);    /**     * 设置接合处的形态     */    mPaint.setStrokeJoin(Paint.Join.ROUND);    /**     * 抗抖动     */    mPaint.setDither(true);    mPaint.setAntiAlias(true);    mPaint.setStrokeWidth(PAINT_WIDTH);}/** * money画笔 */private void initMoneyPaint() {    moneyPaint = new Paint();    moneyPaint.setColor(Color.RED);    moneyPaint.setAntiAlias(true);    moneyPaint.setTextSize(30);    mTextBound = new Rect();    moneyPaint.getTextBounds(moneys[0], 0, moneys[0].length(), mTextBound);}

2.创建一个画布,就是一个绘制一个红包的图片,依据手指在控件上的滑动路径,除去图片的结合部分

mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);mCanvas = new Canvas(mBitmap);Bitmap bitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.red_packet));mCanvas.drawBitmap(bitmap, null, new RectF(0, 0, width, height), null);//设置图片的结合方式mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));mCanvas.drawPath(mPath, mPaint);canvas.drawBitmap(mBitmap, 0, 0, null);

3.重写onTouchEvent方法记录手指的擦除路径以及实现跟手操作
public boolean onTouchEvent(MotionEvent event) {    x = (int) event.getX();    y = (int) event.getY();    switch (event.getAction()) {        case MotionEvent.ACTION_DOWN:            //路径的初始化位置            mPath.moveTo(x, y);            break;        case MotionEvent.ACTION_MOVE:            if (movable) {                // 跟手滑效果                setX(x + getLeft() + getTranslationX() - getWidth() / 2);                setY(y + getTop() + getTranslationY() - getHeight() / 2);            } else if (Math.abs(x - mLastX) > DEFAULT_PATH_INSTANCE || Math.abs(y - mLastY) > DEFAULT_PATH_INSTANCE) {                // 记录手指擦除路径                mPath.lineTo(x, y);                invalidate();            }        case MotionEvent.ACTION_UP:            MyAsyncTask task = new MyAsyncTask();            task.execute();            break;    }    //记录上次位置    mLastX = x;    mLastY = y;    return true;}

4.附上完整的代码
public class RedPacketView extends ImageView {    private Paint mPaint, moneyPaint;    private Path mPath;    private Canvas mCanvas;    private Bitmap mBitmap;    private int x, y, mLastX, mLastY;    public boolean movable = true;    public boolean isTouch = false;    private String[] moneys = new String[]{"¥5", "¥10", "¥20", "¥50"};    private Rect mTextBound;    private String mText;    private Random mRandom;    private boolean isComplete = false;    /**     * 笔触的宽度     */    private static final float PAINT_WIDTH = 20;    /**     * 默认绘制的最小距离     */    private static final float DEFAULT_PATH_INSTANCE = 5;    public RedPacketView(Context context) {        super(context);        init();    }    public RedPacketView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public RedPacketView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    private void init() {        mPath = new Path();        mRandom = new Random();        initPaint();        initMoneyPaint();        //随机产生一个面值        mText = moneys[mRandom.nextInt(moneys.length)];        //获取字体的宽高        moneyPaint.getTextBounds(mText, 0, mText.length(), mTextBound);    }    private void initPaint() {        mPaint = new Paint();        mPaint.setColor(Color.parseColor("#c0c0c0"));        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setStrokeCap(Paint.Cap.ROUND);        /**         * 设置接合处的形态         */        mPaint.setStrokeJoin(Paint.Join.ROUND);        /**         * 抗抖动         */        mPaint.setDither(true);        mPaint.setAntiAlias(true);        mPaint.setStrokeWidth(PAINT_WIDTH);    }    /**     * money画笔     */    private void initMoneyPaint() {        moneyPaint = new Paint();        moneyPaint.setColor(Color.RED);        moneyPaint.setAntiAlias(true);        moneyPaint.setTextSize(30);        mTextBound = new Rect();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int width = getMeasuredWidth();        int height = getMeasuredHeight();        try {            mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);            mCanvas = new Canvas(mBitmap);            Bitmap bitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.red_packet));            mCanvas.drawBitmap(bitmap, null, new RectF(0, 0, width, height), null);        } catch (Exception e) {            e.printStackTrace();        }    }    @Override    protected void onDraw(Canvas canvas) {        try {            canvas.drawText(mText, getWidth() / 2 - mTextBound.width() / 2, getHeight() / 2 + mTextBound.height() / 2, moneyPaint);            if (isComplete) return;            //设置图片的结合方式            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));            mCanvas.drawPath(mPath, mPaint);            canvas.drawBitmap(mBitmap, 0, 0, null);        } catch (Exception e) {            e.printStackTrace();        }    }    @Override    public boolean onTouchEvent(MotionEvent event) {        x = (int) event.getX();        y = (int) event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                //路径的初始化位置                mPath.moveTo(x, y);                break;            case MotionEvent.ACTION_MOVE:                if (movable) {                    // 跟手滑效果                    setX(x + getLeft() + getTranslationX() - getWidth() / 2);                    setY(y + getTop() + getTranslationY() - getHeight() / 2);                } else if (Math.abs(x - mLastX) > DEFAULT_PATH_INSTANCE || Math.abs(y - mLastY) > DEFAULT_PATH_INSTANCE) {                    // 记录手指擦除路径                    mPath.lineTo(x, y);                    invalidate();                }            case MotionEvent.ACTION_UP:                MyAsyncTask task = new MyAsyncTask();                task.execute();                break;        }        //记录上次位置        mLastX = x;        mLastY = y;        return true;    }    /**     * 查看目前的红包的擦除比例,实现完全擦除     */    class MyAsyncTask extends AsyncTask{        @Override        protected Object doInBackground(Object[] params) {            clearOverPercent();            return null;        }        private void clearOverPercent()        {            int[] mPixels;            int w = getWidth();            int h = getHeight();            float wipeArea = 0;            float totalArea = w * h;            Bitmap bitmap = Bitmap.createBitmap(mBitmap);            mPixels = new int[w * h];            //拿到所有像素信息            bitmap.getPixels(mPixels, 0, w, 0, 0, w, h);            //获取擦除部分的面积            int index = 0;            for (int i = 0; i < w; i++) {                for (int j = 0; j < h; j++) {                    if (mPixels[index] == 0) {                        wipeArea++;                    }                    index++;                }            }            int percent = (int) (wipeArea / totalArea * 100);            if (percent > 70) {                isComplete = true;                postInvalidate();            }        }    };}

5.实现发红包的父容器LaunchRedPacketLayout。重点说下贝塞尔曲线动画部分的实现。实现的过程用到四个点,分别是起点,随机点1,随机点2,终点。起点为控件的底部中点,终点为控件顶部的任意点即(x=n, y=0)。随机点为控件内部任意点,当然为了更好的效果,点位分布均匀为佳。

6.有了四个点后,根据贝塞尔曲线的公式新建一个估值器,以便于计算红包当前的位置
/** * 估值器 */static class BSEEvaluator implements TypeEvaluator<PointF> {    private PointF pointF1;    private PointF pointF2;    public BSEEvaluator(PointF pointF1, PointF pointF2) {        this.pointF1 = pointF1;        this.pointF2 = pointF2;    }    @Override    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {        PointF pointF = new PointF();        float lFraction = 1 - fraction;        pointF.x = (float) (startValue.x * Math.pow(lFraction, 3) +                3 * pointF1.x * fraction * Math.pow(lFraction, 2) +                3 * pointF2.x * Math.pow(lFraction, 2) * fraction +                endValue.x * Math.pow(fraction, 3));        pointF.y = (float) (startValue.y * Math.pow(lFraction, 3) +                3 * pointF1.y * fraction * Math.pow(lFraction, 2) +                3 * pointF2.y * Math.pow(fraction, 2) * lFraction +                endValue.y * Math.pow(fraction, 3));        return pointF;    }}

7.设置属性动画的监听器,不断将新的位置设置给红包,让红包动起来

private ValueAnimator getBSEValueAnimator(View target) {    //贝赛尔估值器    BSEEvaluator evaluator = new BSEEvaluator(getPoint(), getPoint());    ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) / 2, mHeight - dHeight), new PointF(random.nextInt(mWidth), 0));    animator.addUpdateListener(new BSEListenr(target));    animator.setTarget(target);    animator.setDuration(3000);    return animator;}private class BSEListenr implements ValueAnimator.AnimatorUpdateListener {    private View target;    public BSEListenr(View target) {        this.target = target;    }    @Override    public void onAnimationUpdate(ValueAnimator animation) {        //这里获取到贝塞尔曲线计算出来的的xy值        PointF pointF = (PointF) animation.getAnimatedValue();        target.setX(pointF.x);        target.setY(pointF.y);    }}

8.提供发射红包的入口方法

/** * 发射多个红包 * * @param numb */public void launch(int numb) throws Exception {    for (int i = 0; i < numb; i++)        launch();}/** * 发射红包 */public void launch() throws Exception {    final RedPacketView imageView = new RedPacketView(getContext());    imageView.setImageDrawable(drawable);    //设置位置    LayoutParams layoutParams = new LayoutParams(dWidth, dHeight);    layoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE);    layoutParams.addRule(CENTER_HORIZONTAL, TRUE);    imageView.setLayoutParams(layoutParams);    final Animator set = addAnimatior(imageView);    imageView.setOnTouchListener(new OnTouchListener() {        public boolean onTouch(View v, MotionEvent event) {            x = (int) imageView.getX();            y = (int) imageView.getY();            if (!imageView.isTouch) {                imageView.isTouch = true;                set.end();            }            if (MotionEvent.ACTION_UP == event.getAction()) {                if (imageView.movable) {                    ObjectAnimator.ofFloat(imageView, View.ALPHA, 1f).start();                    AnimatorSet setDown = new AnimatorSet();                    setDown.playTogether(                            ObjectAnimator.ofFloat(imageView, "scaleX", 0.8f, 1.5f),                            ObjectAnimator.ofFloat(imageView, "scaleY", 0.8f, 1.5f)                    );                    setDown.start();                    imageView.movable = false;                }            }            return false;        }    });    addView(imageView);    set.addListener(new AnimatorListenerAdapter() {        @Override        public void onAnimationEnd(Animator animation) {            super.onAnimationEnd(animation);            // 动画结束移除view            if (imageView.isTouch) {                imageView.setX(x);                imageView.setY(y);            } else {                removeView(imageView);            }        }    });    set.start();}


9.附上完整代码

public class LaunchRedPacketLayout extends RelativeLayout {    private Drawable drawable;    private int dWidth;    private int dHeight;    private int mWidth;    private int mHeight;    int x, y;    /**     * 插值器组     */    private Interpolator[] interpolatorsArray;    private Random random;    public LaunchRedPacketLayout(Context context) {        super(context);        init();    }    public LaunchRedPacketLayout(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    private void init() {        drawable = getResources().getDrawable(R.drawable.red_packet);        dWidth = drawable.getIntrinsicWidth();        dHeight = drawable.getIntrinsicHeight();        random = new Random();        interpolatorsArray = new Interpolator[4];        interpolatorsArray[0] = new LinearInterpolator();        interpolatorsArray[1] = new AccelerateInterpolator();        interpolatorsArray[2] = new DecelerateInterpolator();        interpolatorsArray[3] = new AccelerateDecelerateInterpolator();        post(new Runnable() {            @Override            public void run() {                mHeight = getMeasuredHeight();                mWidth = getMeasuredWidth();                int curWidth = dWidth;                dWidth = mWidth / 5;                dHeight = dHeight * dWidth / curWidth;            }        });    }    /**     * 发射多个红包     *     * @param numb     */    public void launch(int numb) throws Exception {        for (int i = 0; i < numb; i++)            launch();    }    /**     * 发射红包     */    public void launch() throws Exception {        final RedPacketView imageView = new RedPacketView(getContext());        imageView.setImageDrawable(drawable);        //设置位置        LayoutParams layoutParams = new LayoutParams(dWidth, dHeight);        layoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE);        layoutParams.addRule(CENTER_HORIZONTAL, TRUE);        imageView.setLayoutParams(layoutParams);        final Animator set = addAnimatior(imageView);        imageView.setOnTouchListener(new OnTouchListener() {            public boolean onTouch(View v, MotionEvent event) {                x = (int) imageView.getX();                y = (int) imageView.getY();                if (!imageView.isTouch) {                    imageView.isTouch = true;                    set.end();                }                if (MotionEvent.ACTION_UP == event.getAction()) {                    if (imageView.movable) {                        ObjectAnimator.ofFloat(imageView, View.ALPHA, 1f).start();                        AnimatorSet setDown = new AnimatorSet();                        setDown.playTogether(                                ObjectAnimator.ofFloat(imageView, "scaleX", 0.8f, 1.5f),                                ObjectAnimator.ofFloat(imageView, "scaleY", 0.8f, 1.5f)                        );                        setDown.start();                        imageView.movable = false;                    }                }                return false;            }        });        addView(imageView);        set.addListener(new AnimatorListenerAdapter() {            @Override            public void onAnimationEnd(Animator animation) {                super.onAnimationEnd(animation);                // 动画结束移除view                if (imageView.isTouch) {                    imageView.setX(x);                    imageView.setY(y);                } else {                    removeView(imageView);                }            }        });        set.start();    }    /**     * 设置动画     *     * @param target     */    private Animator addAnimatior(View target) throws Exception {        AnimatorSet set = new AnimatorSet();        AnimatorSet enterSet = getEnterSet(target);        ValueAnimator bezierValueAnimator = getBSEValueAnimator(target);        set.playSequentially(enterSet, bezierValueAnimator);        set.setInterpolator(interpolatorsArray[random.nextInt(4)]);        set.setTarget(target);        return set;    }    private class BSEListenr implements ValueAnimator.AnimatorUpdateListener {        private View target;        public BSEListenr(View target) {            this.target = target;        }        @Override        public void onAnimationUpdate(ValueAnimator animation) {            //这里获取到贝塞尔曲线计算出来的的x y值            PointF pointF = (PointF) animation.getAnimatedValue();            target.setX(pointF.x);            target.setY(pointF.y);        }    }    /**     * 设置贝赛尔曲线动画     *     * @param target     * @return     */    private ValueAnimator getBSEValueAnimator(View target) {        //贝赛尔估值器        BSEEvaluator evaluator = new BSEEvaluator(getPoint(), getPoint());        ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) / 2, mHeight - dHeight), new PointF(random.nextInt(mWidth), 0));        animator.addUpdateListener(new BSEListenr(target));        animator.setTarget(target);        animator.setDuration(3000);        return animator;    }    private PointF getPoint() {        PointF pointF = new PointF();        pointF.x = random.nextInt(mWidth);        pointF.y = random.nextInt(mHeight - dHeight);        return pointF;    }    /**     * 估值器     */    static class BSEEvaluator implements TypeEvaluator<PointF> {        private PointF pointF1;        private PointF pointF2;        public BSEEvaluator(PointF pointF1, PointF pointF2) {            this.pointF1 = pointF1;            this.pointF2 = pointF2;        }        @Override        public PointF evaluate(float fraction, PointF startValue, PointF endValue) {            PointF pointF = new PointF();            float lFraction = 1 - fraction;            pointF.x = (float) (startValue.x * Math.pow(lFraction, 3) +                    3 * pointF1.x * fraction * Math.pow(lFraction, 2) +                    3 * pointF2.x * Math.pow(lFraction, 2) * fraction +                    endValue.x * Math.pow(fraction, 3));            pointF.y = (float) (startValue.y * Math.pow(lFraction, 3) +                    3 * pointF1.y * fraction * Math.pow(lFraction, 2) +                    3 * pointF2.y * Math.pow(fraction, 2) * lFraction +                    endValue.y * Math.pow(fraction, 3));            return pointF;        }    }    /**     * 入场动画     *     * @param target     * @return     */    private AnimatorSet getEnterSet(View target) {        try {            AnimatorSet enterSet = new AnimatorSet();            enterSet.playTogether(                    ObjectAnimator.ofFloat(target, View.ALPHA, 0, 1f),                    ObjectAnimator.ofFloat(target, View.SCALE_X, 0.1f, 0.8f),                    ObjectAnimator.ofFloat(target, View.SCALE_Y, 0.1f, 0.8f)            );            enterSet.setDuration(500);            enterSet.setInterpolator(new LinearInterpolator());            enterSet.setTarget(target);            return enterSet;        } catch (Exception e) {            e.printStackTrace();        }        return null;    }}

10.试下

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    app:layout_behavior="@string/appbar_scrolling_view_behavior"    tools:context="sample.MainActivity"    tools:showIn="@layout/activity_main">    <com.empty.launchredpacket.LaunchRedPacketLayout        android:id="@+id/launchRedPacket"        android:layout_width="match_parent"        android:background="#faecec"        android:layout_height="400dp"        />    <Button        android:id="@+id/launchBtn"        android:layout_width="match_parent"        android:layout_height="40dp"        android:layout_margin="5dp"        android:background="@color/colorPrimary"        android:text="发射"        android:textColor="@android:color/white" />    <Button        android:id="@+id/reStart"        android:layout_width="match_parent"        android:layout_height="40dp"        android:layout_margin="5dp"        android:background="@color/colorPrimary"        android:text="重新开始"        android:textColor="@android:color/white" /></LinearLayout>

public class MainActivity extends AppCompatActivity implements View.OnClickListener {    private LaunchRedPacketLayout launchRedPacketLayout;    private Button launchBtn, reStartBtn;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        setSupportActionBar(toolbar);        launchRedPacketLayout = (LaunchRedPacketLayout) findViewById(R.id.launchRedPacket);        launchBtn = (Button) findViewById(R.id.launchBtn);        reStartBtn = (Button) findViewById(R.id.reStart);        launchBtn.setOnClickListener(this);        reStartBtn.setOnClickListener(this);    }    @Override    public void onClick(View v) {        try {            switch (v.getId()) {                case R.id.reStart:                    startActivity(new Intent(this, MainActivity.class));                    finish();                    overridePendingTransition(0, 0);                    break;                case R.id.launchBtn:                    launchRedPacketLayout.launch(3);                    break;            }        } catch (Exception e) {            e.printStackTrace();        }    }}

总结

1.发射过多红包会引起页面卡顿,需要优化
2.代码结构还可以优化下
3.欢迎大家评论交流 

十分感谢 程序亦非猿,hongyang 大神的博客


源码地址 https://github.com/wolow3/LaunchRedPacket


6 0
原创粉丝点击