发红包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();}@Overrideprotected 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();}}@Overrideprotected 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();}}@Overridepublic 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{@Overrideprotected 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;}@Overridepublic 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;}@Overridepublic 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() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);// 動畫結束移除viewif (imageView.isTouch) {imageView.setX(x);imageView.setY(y);} else {removeView(imageView);}}});set.start(); }
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() {@Overridepublic 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() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);// 動畫結束移除viewif (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;}@Overridepublic 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;}@Overridepublic 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;@Overrideprotected 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);}@Overridepublic 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
轉載于:https://www.cnblogs.com/zsychanpin/p/7171358.html
總結
以上是生活随笔為你收集整理的发红包android的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 洛谷 P1615 西游记公司
- 下一篇: 在一台电脑上运行两个或多个tomcat