电商活动中刮刮卡的实现
一、实现原理
Paint.setXferMode();
a、绘制我们的圆形
b、setXferMode(SrcIn)
c、绘制矩形(正方形)图片
二、主要思想:
将一个view设计成多层:背景层,含中奖信息等;
遮盖层,用于刮奖,使用关联一个Bitmap的Canvas
在该Bitmap上,使用它的canvas.drawPath的api来处理 手势滑动(类似刮奖的动作)
使用paint.setXfermode 来进行消除手势滑动区域
当刮开90%的时候会全部消失。
三、代码实现
public class GuaGuaKa extends View { private Paint mOutterPaint; private Path mPath; private Canvas mCanvas; private Bitmap mBitmap; private int mLastX; private int mLastY; private Bitmap mOutterBitmap; // ------------------------------- // private Bitmap bitmap; private String mText; private Paint mBackPaint; /** * 记录刮奖信息文本的宽和高 */ private Rect mTextBound; private int mTextSize; private int mTextColor; // 判断遮盖层区域是否消除达到阈值 private volatile boolean mComplete = false; /** * 刮刮卡刮完的回调 * * */ public interface OnGuaGuaKaCompleteListener { void complete(); } private OnGuaGuaKaCompleteListener mListener; public void setOnGuaGuaKaCompleteListener( OnGuaGuaKaCompleteListener mListener) { this.mListener = mListener; } public GuaGuaKa(Context context) { this(context, null); } public GuaGuaKa(Context context, AttributeSet attrs) { this(context, attrs, 0); } public GuaGuaKa(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); TypedArray a = null; try { a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GuaGuaKa, defStyle, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.GuaGuaKa_text: mText = a.getString(attr); break; case R.styleable.GuaGuaKa_textSize: mTextSize = (int) a.getDimension(attr, TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_SP, 22, getResources().getDisplayMetrics())); break; case R.styleable.GuaGuaKa_textColor: mTextColor = a.getColor(attr, 0x000000); break; } } } finally { if (a != null) a.recycle(); } } public void setText(String mText) { this.mText = mText; // 获得当前画笔绘制文本的宽和高 mBackPaint.getTextBounds(mText, 0, mText.length(), mTextBound); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int height = getMeasuredHeight(); // 初始化我们的bitmap mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); mCanvas = new Canvas(mBitmap); // 设置绘制path画笔的一些属性 setupOutPaint(); setUpBackPaint(); // mCanvas.drawColor(Color.parseColor("#c0c0c0")); mCanvas.drawRoundRect(new RectF(0, 0, width, height), 30, 30, mOutterPaint); mCanvas.drawBitmap(mOutterBitmap, null, new Rect(0, 0, width, height), null); } /** * 设置我们绘制获奖信息的画笔属性 */ private void setUpBackPaint() { mBackPaint.setColor(mTextColor); mBackPaint.setStyle(Style.FILL); mBackPaint.setTextSize(mTextSize); // 获得当前画笔绘制文本的宽和高 mBackPaint.getTextBounds(mText, 0, mText.length(), mTextBound); } /** * 设置绘制path画笔的一些属性 */ private void setupOutPaint() { mOutterPaint.setColor(Color.parseColor("#c0c0c0")); mOutterPaint.setAntiAlias(true); mOutterPaint.setDither(true); mOutterPaint.setStrokeJoin(Paint.Join.ROUND); mOutterPaint.setStrokeCap(Paint.Cap.ROUND); mOutterPaint.setStyle(Style.FILL); mOutterPaint.setStrokeWidth(20); } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); int x = (int) event.getX(); int y = (int) event.getY(); switch (action) { case MotionEvent.ACTION_DOWN: mLastX = x; mLastY = y; mPath.moveTo(mLastX, mLastY); break; case MotionEvent.ACTION_MOVE: int dx = Math.abs(x - mLastX); int dy = Math.abs(y - mLastY); if (dx > 3 || dy > 3) { mPath.lineTo(x, y); } mLastX = x; mLastY = y; break; case MotionEvent.ACTION_UP: if (!mComplete) new Thread(mRunnable).start(); break; } if (!mComplete) invalidate(); return true; }
</pre>我们在ACTION_UP的时候就行计算,首先我们还是给大家灌输下计算的原理,如果大家用心看了,应该知道我们所有的操作基本都在mBitmap,现在我们获得mBItmap上所有的像素点的数据,统计被清除的区域(被清除的像素为0);最后与我们图片的总像素数做个除法元算,就可以拿到我们清除的百分比了;不过,计算可能会是一个耗时的操作,具体速度跟图片大小有关,所以我们决定使用异步的方式去计算:<pre name="code" class="java">
private Runnable mRunnable = new Runnable() { @Override public void run() { int w = getWidth(); int h = getHeight(); float wipeArea = 0; float totalArea = w * h; Bitmap bitmap = mBitmap; int[] mPixels = new int[w * h]; // 获得Bitmap上所有的像素信息 bitmap.getPixels(mPixels, 0, w, 0, 0, w, h); for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { int index = i + j * w; if (mPixels[index] == 0) { wipeArea++; } } } if (wipeArea > 0 && totalArea > 0) { int percent = (int) (wipeArea * 100 / totalArea); Log.e("TAG", percent + ""); if (percent > 90) { // 清除掉图层区域 mComplete = true; postInvalidate(); } } } }; @Override protected void onDraw(Canvas canvas) { // canvas.drawBitmap(bitmap, 0 , 0, null); canvas.drawText(mText, getWidth() / 2 - mTextBound.width() / 2, getHeight() / 2 + mTextBound.height() / 2, mBackPaint); if (!mComplete) { drawPath(); canvas.drawBitmap(mBitmap, 0, 0, null); } if (mComplete) { if (mListener != null) { mListener.complete(); } } } private void drawPath() { mOutterPaint.setStyle(Style.STROKE); mOutterPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT)); mCanvas.drawPath(mPath, mOutterPaint); } /** * 进行一些初始化操作 */ private void init() { mOutterPaint = new Paint(); mPath = new Path(); mOutterBitmap = BitmapFactory.decodeResource(getResources(), cn.zhilinghui.guaguaka.R.drawable.fg_guaguaka); mText = "谢谢惠顾"; mTextBound = new Rect(); mBackPaint = new Paint(); mTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 22, getResources().getDisplayMetrics()); } }view的自定义控件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:hyman="http://schemas.android.com/apk/res/cn.zhilinghui.guaguaka" android:layout_width="match_parent" android:layout_height="match_parent" > <cn.zhilinghui.guaguaka.view.GuaGuaKa android:id="@+id/id_guaguaka" android:layout_width="300dp" android:layout_height="100dp" android:layout_centerInParent="true" hyman:text="¥500,0000" hyman:textColor="#ff00f0" hyman:textSize="30sp" /> </RelativeLayout>