zoukankan      html  css  js  c++  java
  • 发红包android

    立即春节,写个应景的控件

           

    思路分析

    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


  • 相关阅读:
    between and 相关
    SQL获取所有用户名,数据库名、所有表名、所有字段名及字段类型
    性能优化探讨与分析:
    设置自动收缩数据库
    服务器注册
    多表查询及区别
    sql孤立用户解决方法
    委托、事件、观察者模式
    非托管资源
    C# 预处理器指令
  • 原文地址:https://www.cnblogs.com/zsychanpin/p/7171358.html
Copyright © 2011-2022 走看看