实现刮刮卡我们可以Get到哪些技能?
* 圆形圆角图片的实现原理
* 双缓冲技术绘图
* Bitmap获取像素值数据
* 获取绘制文本的长宽
* 自定义View的掌握
* 获取屏幕密度
* TypeValue.applyDemension
* Canvas的一些绘制方法
* Paint的一些常用的属性
* Path的一些方法
刮刮卡的实现原理图
这里用到了13中模式中的DstOut这种模式。
对于这幅图而言,首先绘制Dst,设置xfermode,再绘制Src。
刮刮卡的实现原理步骤
1.绘制显示中奖的文本
2.绘制图层区/前景图片
3.设置xfermode
4.绘制Path
5.清除所有图层区方案
6.自定义属性
代码讲解
第一部分:绘制中奖信息
public class GuaGuaLe extends View{ /**中奖文本变量*/ private String mText;//显示文本 private Paint mTextPaint;//绘制文本的画笔 private Rect mTextBounds;//文本的边界 public GuaGuaLe(Context context) { this(context, null); } public GuaGuaLe(Context context, AttributeSet attrs) { super(context, attrs); initView(); } /**初始化一些实例变量*/ private void initView() { setTextMessage();//实例化文本的一些变量 } private void setTextMessage() { mText = "谢谢惠顾"; mTextBounds = new Rect(); mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); mTextPaint.setColor(Color.DKGRAY); mTextPaint.setTextSize(22); mTextPaint.getTextBounds(mText,0,mText.length(),mTextBounds);//获取到绘制文本的长宽 } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { //绘制文本 canvas.drawText(mText,getWidth()/2 - mTextBounds.width()/2,getHeight()/2 + mTextBounds.height()/2,mTextPaint); } }
如下图:
第二部分:绘制图层区/前景图片
/**画板变量*/ private Canvas mCanvas;//缓冲画板 private Bitmap mLayerBimtap;//图层图片 private Bitmap mBitmap;//画板上的画纸 private Paint mPaint;//画笔 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setCanvasMessage(getMeasuredWidth(),getMeasuredHeight());//实例化缓存画板的一些变量 } /*** * 双缓冲技术 * 1.创建一个与屏幕绘制区域一致的Canvas对象 * 2.先将图形绘制到内存中 * 3.再一次性将对象上的图形拷贝到屏幕上 * @param width * @param height */ private void setCanvasMessage(int width,int height) { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(3); mPaint.setColor(Color.parseColor("#c0c0c0")); mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建一个空白的位图 mCanvas = new Canvas(mBitmap);//在此Bitmap上作画 //mCanvas.drawColor(Color.parseColor("#c0c0c0"));//绘制背景颜色 mCanvas.drawRoundRect(new RectF(0, 0, width, height), 20, 20, mPaint);//绘制圆角矩形 mLayerBimtap = BitmapFactory.decodeResource(getResources(), R.mipmap.fg_guaguaka); mCanvas.drawBitmap(mLayerBimtap, null, new RectF(0, 0, width, height), null);//绘制图层图片 @Override protected void onDraw(Canvas canvas) { //绘制文本 canvas.drawText(mText,getWidth()/2 - mTextBounds.width()/2,getHeight()/2 + mTextBounds.height()/2,mTextPaint); //绘制图层 //canvas.drawBitmap(mBitmap,0,0,null); //绘制图层图片 canvas.drawBitmap(mBitmap,0,0,null); } }
如下图(图层颜色):
如下图(图层图片):
第三部分:设置xfermode以及绘制Path
/**路径变量*/ private Path mPath; private Paint mPathPaint; private int mLastX;//记录上一次X坐标 private int mLastY;//记录上一次Y坐标 /**初始化一些实例变量*/ private void initView() { setTextMessage();//实例化文本的一些变量 setPathMessage();//实例化路径的一些变量 } @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: break; default: break; } invalidate();//主线程中刷新 return true;//截取事件向下分发 } @Override protected void onDraw(Canvas canvas) { //绘制文本 canvas.drawText(mText,getWidth()/2 - mTextBounds.width()/2,getHeight()/2 + mTextBounds.height()/2,mTextPaint); //绘制路径 drawPath(); //绘制图片【图层背景颜色】 //canvas.drawBitmap(mBitmap,0,0,null); //绘制图片【图层图片】 canvas.drawBitmap(mBitmap, 0, 0, null); } //返回给系统的canvas是一幅带有路径的位图 private void drawPath() { //这之前是设置了图层图片 mPathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); mCanvas.drawPath(mPath,mPathPaint); }
如下图
第三部分:清除所有图层区方案
/**擦除是否完成*/ private volatile boolean isComplete; /**回调接口*/ private OnCompleteListener mOnCompleteListener; public interface OnCompleteListener{ void complete(); } //对外设置调用的公开方法 public void setmOnCompleteListener(OnCompleteListener mOnCompleteListener){ this.mOnCompleteListener = mOnCompleteListener; } case MotionEvent.ACTION_UP: //开启一个线程去计算擦除的面积 new Thread(new Runnable() { @Override public void run() { int width = getWidth(); int height = getHeight(); int area = 0;//记录被擦数的范围大小 int percent ; for(int i=0;i<width;i++) for(int j = 0;j<height;j++){ if(mBitmap.getPixel(i,j) == 0){ area ++; } } if(area > 0){ percent = area *100/(width*height); if(percent > 20){ isComplete = true; postInvalidate();//在子线程中刷新,可能比较耗时操作 } } } }).start(); @Override protected void onDraw(Canvas canvas) { //绘制文本 canvas.drawText(mText, getWidth() / 2 - mTextBounds.width() / 2, getHeight() / 2 + mTextBounds.height() / 2, mTextPaint); if(isComplete){ if(mOnCompleteListener != null){ mOnCompleteListener.complete();//接口回调,在主线程中 } } if(!isComplete){ //绘制路径 drawPath(); //绘制图片【图层背景颜色】 //canvas.drawBitmap(mBitmap,0,0,null); //绘制图片【图层图片】 canvas.drawBitmap(mBitmap, 0, 0, null); } } public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); GuaGuaLe guaguaLeView = (GuaGuaLe) findViewById(R.id.guaguale); //注册了一个观察者 guaguaLeView.setmOnCompleteListener(new GuaGuaLe.OnCompleteListener() { @Override public void complete() { Toast.makeText(MainActivity.this, "用户绘制了大部分", Toast.LENGTH_SHORT).show(); } }); } }
如下图:
第五部分:自定义View的属性
在values/attrs.xml中定义 <declare-styleable name="GuaGuaLe"> <attr name="text" format="string"></attr> <attr name="textSize" format="dimension"></attr> <attr name="textColor" format="color"></attr> </declare-styleable> 在布局文件中定义属性 <zlll.bg.com.example.cn.myapplication.view.GuaGuaLe android:id="@+id/guaguale" android:layout_width="350dp" android:layout_height="200dp" app:text="$5,000" app:textColor="#c02040" app:textSize="22sp" android:layout_centerInParent="true"/> 在主界面中定义Text文本 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); GuaGuaLe guaguaLeView = (GuaGuaLe) findViewById(R.id.guaguale); //注册了一个观察者 guaguaLeView.setmOnCompleteListener(new GuaGuaLe.OnCompleteListener() { @Override public void complete() { Toast.makeText(MainActivity.this, "用户绘制了大部分", Toast.LENGTH_SHORT).show(); } }); guaguaLeView.setTextByUser("Android 新技能Get"); } } /**中奖文本变量*/ private String mText;//显示文本 private Paint mTextPaint;//绘制文本的画笔 private Rect mTextBounds;//文本的边界 //以下两个是新增的 private int mTextSize;//文本大小 private int mTextColor;//文本颜色 //对外设置调用的设置文本公开方法 public void setTextByUser(String text){ mText = text; mTextPaint.getTextBounds(mText,0,mText.length(),mTextBounds);//重新计算文本的范围 } 在构造函数中定义 public GuaGuaLe(Context context, AttributeSet attrs) { super(context, attrs); //获取自定义属性的值 TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.GuaGuaLe); mText = a.getString(R.styleable.GuaGuaLe_text); mTextColor = a.getColor(R.styleable.GuaGuaLe_textColor, Color.parseColor("#c0c0c0")); //TypedValue.applyDimension内部就是将sp,dp转化为px mTextSize = a.getDimensionPixelSize(R.styleable.GuaGuaLe_textSize, (int) TypedValue.applyDimension (TypedValue.COMPLEX_UNIT_SP,22,getResources().getDisplayMetrics())); a.recycle(); initView(); }
如下图:
下图是用户自行设置的文本
经过上面几个步骤,就可以完成呱呱卡的实现效果了。
总结一下:
(1)圆形圆角图片的原理
1.绘制图片
2.paint.setXfermode(DstIn); – 取交集以先绘制的图层为主 Dst是背景图片
3.绘制圆形图片
1.绘制圆形图片
2.paint.setXfermode(SrcIn); – 取交集以后绘制的图层为主 Src是结构图片
3.绘制图片
(2)双缓冲技术绘图【在内存中绘制,再一次性位图拷贝到屏幕上】
1.在内存中创建与画布一致宽高的canvas
2.在缓冲区画图
3.将缓冲区位图拷贝到当前画布上
4.释放缓冲区
自己的理解是:在内存中定义一个画板,然后在上面绘图,将绘图结果放在一个Bitmap中,最后让系统的Canvas绘制这个Bitmap
(3)Bimtap获取像素数据(方式一)
bm.getPixels(x,y) 直接获取x,y坐标处的像素点数据
(4)获取绘制文本的长宽的(方式一)
1.计算文字所在的矩形,获取宽高[Rect]
Rect rect = new Rect();
paint.getTextBounds(str,0,str,length,rect);
int w = rect.width;
int h = rect.height;
(5)自定义View
1.在values目录下新建attrs.xml文件
2.设置属性有text,textColor,textSize
3.在布局文件中引用
4.在构造函数中获取自定义属性
5.对外提供方法,让用户动态的设置中奖信息
(6)获取屏幕密度(方式一)
DisplayMetrics dm = getResource().getDisplayMetrics();
float density = dm.density; – 像素比例(0.75/1.0/1.5/2.0/3.0/4.0)显示的就是px/dp的比例
int densityDpi = dm.densityDpi; – 每寸像素(120/160/240/320/480/640)
int screenWidth = dm.widthPixels; – 宽像素
int screenHeight = dm.heightPixels; – 高像素
(7) Some methods of Canva
drawColor draws the background color
drawText draws the text
drawBitmap draws the picture
[drawBitmap(bitmap,x,y,paint) – Move the picture to the specified coordinates]
[drawBitma(bitmap,srcRect,dstRect,paint) – Specify the area of the image whose parameters are the coordinates of the four vertices, upper left, lower right]
drawRoundRect draws a rounded rectangle
[drawRoundRect(rectf,rx, ry,paint) – rx (corner radius in x direction) ry (corner radius in y direction)】
(8) Some common attributes of Paint
setColor/setARGB Set the drawing color
setAlpha Sets the drawing transparency
setAntiAlias Sets smoothing
setDither Sets image jitter
setStyle Sets the style of the brush
setStrokeWidth Sets the width of the brush
setXfermode Sets the processing method when graphics overlap
setStrokeJoin setting Graphics combination methods
setTextSize sets the size of the text,
(9) Path (connect N points on the View into a "path", and then calls the drawPath of Canvas to draw graphics)
reset Clear path
moveTo (x,y) Move the starting point to the x,y coordinate point
lineTo draw a straight line
quadTo draw a curve
The above is the entire content of this article, you win the prize Yet? I hope it will be helpful to everyone's study.
For more related articles on Android scratch card implementation principles and code explanations, please pay attention to the PHP Chinese website!