自定义控件之刮刮卡

本文参考了hongyang童鞋的 http://blog.csdn.net/lmj623565791/article/details/40162163 。


自定义控件在Android中非常重要,我也是刚刚起步的菜鸟,很多东西也在探索,希望能够共同学习。微笑


这里面说的是自定义控件不是自定义布局,自定义控件是继续View,重写onDraw(),自定义布局是继承ViewGroup,重写onMeasure,onLayout等。


自定义控件步骤:

1、在attrs.xml里面写自定义属性
2、在View的构造方法里面获取我们的属性值(无论是否有自定义属性的时间,系统会调用两个参数的的构造方法,三个参数的构造方法需要我们自己手动去调用 。)
3、onMeasure因为自定义控件过程中,通过需要一些数据,可以从这获得,不是必须要调用的。
4、重写onDraw。

其实步骤是对新手有用的,在高手的心中肯定是模糊这些东西的,其实自定义控件就是: 把数据计算好、准备好,然后draw出来即可。

然后咱们通过刮刮卡这个实例讲解一下

预备知识点:
1、Xfermodes:这是一个强大的类
 //设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果
 setXfermode(Xfermode xfermode);   

可以通过修改Paint的Xfermode来影响在Canvas已有的图像上面绘制新的颜色的方式。
在正常的情况下,在已有的图像上绘图将会在其上面添加一层新的形状。如果新的Paint是完全不透明的,那么它将完全遮挡住下面的Paint;如果它是部分透明的,那么它将会被染上下面的颜色。下面的Xfermode子类可以改变这种行为:
1)AvoidXfermode  指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。
2)PixelXorXfermode  当覆盖已有的颜色时,应用一个简单的像素XOR操作。
3)PorterDuffXfermode  这是一个非常强大的转换模式,使用它,可以使用图像合成的16条Porter-Duff规则的任意一条来控制Paint如何与已有的Canvas图像进行交互。
要应用转换模式,可以使用setXferMode方法,如下所示: 

1 AvoidXfermode avoid = new AvoidXfermode(Color.BLUE, 10, AvoidXfermode.Mode. AVOID); 
2 borderPen.setXfermode(avoid);

这里可以实现完美的橡皮擦功能!代码异常简单:

1 Xfermode xFermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
2 paint.setXfermode(xFermode);

这是使用的最后一个子类,关于16条Porter-Duff规则,如下:





如何看懂这张图:
1、先绘制DST
2、设置xfermode
3、绘制src
这张图显示的就是经过上面三个步骤的显示效果

2、为什么要new Canvas(),我们知道在一个View中,只有一张画布,而这张画布也是系统在onDraw()方法里面回调给我们的,因为只有这张画布才能往界面上绘制内容,而我们new Canvas()的原因是相当于一个缓冲的效果,在自定义画布画出来的东西放到一个bitmap,然后再借助系统的bitmap去Draw出来这个内容,所以我们自己 new Canvas只是把需要的数据缓存到其对应的Bitmap上,然后再用系统的画布给画出来。


理解上面两点之后,再来看看刮刮卡思路就是更明白了。
1>画中奖信息
2>画灰色蒙板,这个蒙板就是背景为灰色的Bitmap
3>在上面的Bitmap上,设置Xfermode,绘制手指路径。
4>把第二和三步绘制出来的Bitmap最终借助系统canvas绘制出来

你会发现,自定义的控件在使用时间 ,必须要指定其具体大小,如果不指定就是全屏,其实原因很简单,是因为我们没有在自定义的onMeasure里面指定其大小 ,其实,Android中的Match_parent和wrap_content都是在onMeasure里面计算出来的动态大小,它就是根据不同的mode,计算其相应的大小,仅些而已,我们明白了这一点,就特别好理解,如果让自己的控件来实现这效果 。

EXACTLY:一般是设置了明确的值或者是MATCH_PARENT

AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

UNSPECIFIED:表示子布局想要多大就多大,很少使用



代码如下:

package com.chen.guaguaka.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class GuaguaKaView extends View {

	private Paint mOutPaint;// 外层的Paint
	private Path mPath;// 记录用户手指滑动路径
	/**
	 * 内存中创建的Canvas
	 */
	private Canvas mCanvas;
	/**
	 * 把手指路径先缓存到这个Bitmap上。
	 */
	private Bitmap mBitmap;

	private int mLastX;// 用户手指滑动的坐标值
	private int mLastY;

	/***
	 * 中奖信息
	 */
	private String mText = "¥500,0000";
	// 中奖信息画笔
	private Paint mBackPaint = new Paint();
	// 记录刮奖信息广本的宽和高
	private Rect mTextBound = new Rect();
	
	private boolean isComplete = false;
	
	private int mPadding = 100;

	public GuaguaKaView(Context context) {
		this(context, null);
	}

	public GuaguaKaView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public GuaguaKaView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		init();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);
		int widthNeed , heightNeed;
		if (widthMode == MeasureSpec.EXACTLY) {
			widthNeed = widthSize;
		}else {
			widthNeed = mTextBound.width() + mPadding;
		}
		
		if (heightMode == MeasureSpec.EXACTLY) {
			heightNeed = heightSize;
		}else {
			heightNeed = mTextBound.height() + mPadding; 
		}
		
		
		setMeasuredDimension(widthNeed, heightNeed);
		
		int width = getMeasuredWidth();
		int height = getMeasuredHeight();
		if (mBitmap == null && width > 0 && height > 0) {
			mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
		}
		if (mCanvas == null && width > 0 && height > 0) {
			mCanvas = new Canvas(mBitmap);
			mCanvas.drawColor(Color.parseColor("#c0c0c0"));
		}
	}

	@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 deltaX = Math.abs(x - mLastX);
			int delatY = Math.abs(y - mLastY);
			if (delatY > 3 || deltaX > 3) {
				mPath.lineTo(x, y);
			}
			mLastX = x;
			mLastY = y;
			break;
		case MotionEvent.ACTION_UP:
			new Thread(runnable).start();
			break;

		default:
			break;
		}

		invalidate();// 这句话会重新调用onDraw。

		return true;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// 绘制顺序是一层一层的
		// 先绘制最底层的中奖信息或者一个图片背景
		canvas.drawText(mText, getWidth() / 2 - mTextBound.width() / 2,
				getHeight() / 2 + mTextBound.height() / 2, mBackPaint );
		if (!isComplete) {
			// 把用户手指的轨迹画到mBitmap上,此时并不会显示。
			drawPath();
			// 这个是真正把mBitmap绘制到系统的画布上
			canvas.drawBitmap(mBitmap, 0, 0, null);
		}
	}

	/**
	 * 设置path画笔的属性
	 */
	private void setupOutPaint() {
		mOutPaint = new Paint();
		mOutPaint.setColor(Color.RED);
		mOutPaint.setAntiAlias(true);
		mOutPaint.setDither(true);
		mOutPaint.setStrokeJoin(Paint.Join.ROUND);// 设置绘制时各图形的结合方式,如平滑效果等  
		mOutPaint.setStrokeCap(Paint.Cap.ROUND);//当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式
		mOutPaint.setStyle(Style.STROKE);//设置画笔风格,空心或者实心。 
		mOutPaint.setStrokeWidth(20);//设置空心的边框宽度。 
	}

	private void setBkPaint() {
		mBackPaint.setStyle(Style.FILL);//设置画笔风格,空心或者实心。 
		mBackPaint.setTextScaleX(2f);
		mBackPaint.setColor(Color.DKGRAY);
		mBackPaint.setTextSize(50);
		mBackPaint.getTextBounds(mText, 0, mText.length(), mTextBound);
	}

	private void init() {
		mPath = new Path();
		setupOutPaint();
		setBkPaint();
	}

	private void drawPath() {
		mOutPaint.setStyle(Paint.Style.STROKE);
		mOutPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
		mCanvas.drawPath(mPath, mOutPaint);
	}
	/**
	 * 计算已擦除的数据
	 */
	Runnable runnable = new Runnable() {
		private int[] mPixels;
		@Override
		public void run() {
			int w = getWidth();
			int h = getHeight();
			float wipeArea = 0;
			float totalArea = w * h;
			Bitmap bitmap = mBitmap;
			mPixels = new int[w * h];
			/** 
	         * 拿到所有的像素信息 
	         */  
			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 > 50)  
	            {  
	                isComplete = true;  
	                postInvalidate();  
	            }  
	        }  
		}
	};

}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值