关于自定义View的Paint、Canvas和PorterDuffXfermode的用法
来源:互联网 发布:在线字体识别软件 编辑:程序博客网 时间:2024/05/21 00:53
转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/74729925
本文出自:【顾林海的博客】
前言
自定义View一直是初中级程序员的痛,在之前也写过很多关于自定义控件的文章,很多人也看了一些关于自定义控件的文章或是相关源码,效果不是很好,那么怎样才能学会自定义View呢,我认为基础很重要,先对自定义View相关的方法和知识点有了一定的了解,才能对产品提出的自定义效果了然于胸,本篇文章是关于Paint、Canvas和PorterDuffXfermode的相关用法,内容比较简单,后续也会撰写自定义View相关知识点的文章。
一个简单的自定义View
一开始我们定义一个继承View的类,比如下面这种,当然,如果这样定义的话IDE会报错,告诉我们要添加相应的构造器。
public class MyView extends View {}
添加四个构造器:
public class MyView extends View { public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); }}
这四个构造器的说明如下:
- 第一个构造器 :当不需要使用xml声明或者不需要使用inflate动态加载时候,实现此构造函数。
- 第二个构造器 :当需要在xml中声明此控件,则需要实现此构造函数。并且在构造函数中把自定义的属性与控件的数据成员连接起来。
- 第三个构造器: 与第二个构造器一样,并接受一个style资源
- 第四个构造器:与第三个构造器一样,当defStyleAttr属性没有赋值时,使用defStyleRes进行主题定义。
定义完MyView后,在xml中引用它:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent"> <com.glh.project20170707.view.MyView android:layout_width="match_parent" android:layout_height="match_parent" /></RelativeLayout>
并在Activity中设置布局文件:
package com.glh.project20170707;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}
运行程序,显示如下:
发现屏幕上什么都没有,这是因为我们还有没在MyView进行相关操作,不管怎么说, 至少我们已经向前迈进了一小步。在日常生活中,假如我们要画一幅图,需要准备笔和纸,在Android中自定义View也是如此,需要Paint(笔)和Canvas(纸),Paint可以通过new实例化一个,Canvas可以通过重写onDraw方法获取。
package com.glh.project20170707.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;import com.glh.project20170707.util.WindowUtil;public class MyView extends View { //画图需要的笔 private Paint mPaint; public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initPaint(); } /** * 初始化笔 */ private void initPaint() { //创建一支笔,并打开抗锯齿 this.mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //我要一支红色的笔 this.mPaint.setColor(Color.RED); //设置画笔的粗细度 this.mPaint.setStrokeWidth(5); //设置画笔样式 this.mPaint.setStyle(Paint.Style.FILL); /* * 画笔样式分三种: * 1.Paint.Style.STROKE:描边 * 2.Paint.Style.FILL_AND_STROKE:描边并填充 * 3.Paint.Style.FILL:填充 */ this.mPaint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); }}
相关工具类:
package com.glh.project20170707.util;import android.content.Context;import android.util.DisplayMetrics;import android.view.WindowManager;public class WindowUtil { public static int getWindowWidth(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metric = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(metric); return metric.widthPixels; } public static int getWindowHeight(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metric = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(metric); return metric.heightPixels; }}
上面程序中创建了一支粗细度为5(像素:px)的抗锯齿笔,样式描边,并设置成红色,这样我们在画图时就有了颜色,那么笔(Paint)和纸(Canvas)都有了,是不是该画点什么,看看Canvas都提供了哪些方法用于绘图。
方法很多,比如drawCircle方法绘制一个圆、drawArc方法绘制圆弧、drawBitmap方法绘制Bitmap、drawPoint方法绘制点等等。方法这么多,我们不需要全部去记住,当用到的什么再去了解,现在绘制一个圆试试。
package com.glh.project20170707.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;import com.glh.project20170707.util.WindowUtil;public class MyView extends View { //画图需要的笔 private Paint mPaint; private Context mContext; public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.mContext = context; this.initPaint(); } /** * 初始化笔 */ private void initPaint() { //创建一支笔,并打开抗锯齿 this.mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //我要一支红色的笔 this.mPaint.setColor(Color.RED); //设置画笔的粗细度 this.mPaint.setStrokeWidth(5); //设置画笔样式 this.mPaint.setStyle(Paint.Style.FILL); /* * 画笔样式分三种: * 1.Paint.Style.STROKE:描边 * 2.Paint.Style.FILL_AND_STROKE:描边并填充 * 3.Paint.Style.FILL:填充 */ mPaint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(WindowUtil.getWindowWidth(mContext) / 2, WindowUtil.getWindowHeight(mContext) / 2, WindowUtil.getWindowWidth(mContext) / 2, mPaint); }}
drawCircle方法的第一个和第二个参数是设定圆的中心点,这里设置成屏幕的中心,第三个参数是圆的半径,第四个参数就是之前定义的Paint。运行效果如下:
接下来修改程序如下:
package com.glh.project20170707.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;import com.glh.project20170707.util.WindowUtil;public class MyView extends View { //画图需要的笔 private Paint mPaint; //圆中心X坐标 private int mCircleX; //圆中心Y坐标 private int mCircleY; //圆半径 private int mRadius; //圆最大半径 private int mMaxRadius; //是否运行 private boolean mRunning; private MyThread mMyThread; private Context mContext; public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.mContext = context; this.initPaint(); this.initCircle(); } /** * 初始化笔 */ private void initPaint() { //创建一支笔,并打开抗锯齿 this.mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //我要一支红色的笔 this.mPaint.setColor(Color.RED); //设置画笔的粗细度 this.mPaint.setStrokeWidth(5); //设置画笔样式 this.mPaint.setStyle(Paint.Style.FILL); /* * 画笔样式分三种: * 1.Paint.Style.STROKE:描边 * 2.Paint.Style.FILL_AND_STROKE:描边并填充 * 3.Paint.Style.FILL:填充 */ mPaint.setStyle(Paint.Style.STROKE); } /** * 初始化绘制圆的相关属性 */ private void initCircle() { this.mCircleX = WindowUtil.getWindowWidth(mContext) / 2; this.mCircleY = WindowUtil.getWindowHeight(mContext) / 2; this.mMaxRadius = WindowUtil.getWindowWidth(mContext) / 2; this.mRadius = this.mMaxRadius; this.mRunning = false; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(this.mCircleX, this.mCircleY, this.mRadius, mPaint); } class MyThread extends Thread { @Override public void run() { while (mRunning) { try { sleep(100); mRadius-=20; if (mRadius <= 0) { mRadius = mMaxRadius; } postInvalidate(); } catch (InterruptedException ignored) { } } } } /** * 开启 */ public void start() { this.mRunning = true; if (null == this.mMyThread) { this.mMyThread = new MyThread(); } this.mMyThread.start(); } public void stop() { if (this.mMyThread != null) { this.mRunning = false; this.mMyThread.interrupt(); this.mMyThread = null; this.mMyThread = null; } } public boolean isRunning() { return this.mRunning; }}
程序中,定义了一个线程,用于不停的减少半径值,并通过postInvalidate方法进行重绘。运行程序如下:
到这里我们已经自定义了一个不停变小的圆环,在自定义View这条路上已经迈了一大步,总结到现在的知识点:
1. 通过继承View创建自定义控件,并重写四个构造器。
2. 重写onDraw方法进行绘制并获取画布Canvas。
3. 创建画笔Paint,并设置相关属性。
4. 通过drawCircle方法绘制圆。
5. 通过postInvalidate在非UI线程中进行重绘。
PorterDuffXfermode
在日常绘图时,如果需要将我们绘制的图像进行混合形成新的图像的,可以使用PorterDuffXfermode,通过它可以很轻松的完成多个图像间混合,方法如下:
this.mRectPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
实例化PorterDuffXfermode时,需要传入PorterDuff内部的枚举类Mode,通过Mode枚举类提供的模式来实现我们的想要的效果,下图使用PorterDuffXfermode时的各种模式。
按照上图,挑选几个进行模式来演示,先编写自定义MyView,代码如下:
package com.glh.project20170707.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;public class MyView extends View { //图形画笔 private Paint mCirclePaint; //矩形画笔 private Paint mRectPaint; private Context mContext; public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.mContext = context; this.initCirclePaint(); this.initRectPaint(); } private void initCirclePaint() { this.mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); this.mCirclePaint.setColor(Color.RED); } private void initRectPaint() { this.mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); this.mRectPaint.setColor(Color.GRAY); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(300, 400, 200, this.mCirclePaint); canvas.drawRect(280, 380, 700, 800, this.mRectPaint); }}
程序中通过onDraw方法绘制了两个图像,分别是圆和矩形。效果如下:
1、CLEAR模式
package com.glh.project20170707.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.util.AttributeSet;import android.view.View;public class MyView extends View { //图形画笔 private Paint mCirclePaint; //矩形画笔 private Paint mRectPaint; private Context mContext; public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.mContext = context; this.initCirclePaint(); this.initRectPaint(); } private void initCirclePaint() { this.mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); this.mCirclePaint.setColor(Color.RED); } private void initRectPaint() { this.mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); this.mRectPaint.setColor(Color.GRAY); this.mRectPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int canvasWidth = canvas.getWidth(); int canvasHeight = canvas.getHeight(); int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG); canvas.drawCircle(300, 400, 200, this.mCirclePaint); canvas.drawRect(280, 380, 700, 800, this.mRectPaint); this.mRectPaint.setXfermode(null); canvas.restoreToCount(layerId); }}
2、XOR模式
3、SRC_ATOP模式
PorterDuffXfermode的模式就演示这三种,其余的大家可以自行尝试,接着利用上面的混合模式来自定义一个刮刮乐的效果,效果如下:
代码如下:
package com.glh.project20170707.view;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import com.glh.project20170707.R;import com.glh.project20170707.util.WindowUtil;public class MyView extends View { private Bitmap mBitmap; private Bitmap mForegroundBitmap; private Canvas mCanvas; private Paint mPaint; private Path mPath; private Context mContext; public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.mContext = context; this.init(); } private void init() { // 实例化路径对象 mPath = new Path(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); // 设置画笔透明 mPaint.setColor(android.R.color.transparent); // 设置混合模式为DST_IN mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(30); //底图 mBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.image); //前背景 mForegroundBitmap = Bitmap.createBitmap(WindowUtil.getWindowWidth(mContext), WindowUtil.getWindowHeight(mContext), Bitmap.Config.ARGB_8888); mForegroundBitmap = Bitmap.createScaledBitmap(mForegroundBitmap, mBitmap.getWidth(), mBitmap.getHeight(), true); mCanvas = new Canvas(mForegroundBitmap); mCanvas.drawColor(Color.GRAY); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(mBitmap, 0, 0, null); canvas.drawBitmap(mForegroundBitmap, 0, 0, null); mCanvas.drawPath(mPath, mPaint); } private float mDownX; private float mDownY; @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPath.reset(); mPath.moveTo(x, y); mDownX = x; mDownY = y; break; case MotionEvent.ACTION_MOVE: float dx = Math.abs(x - mDownX); float dy = Math.abs(y - mDownY); if (dx >= 2 || dy >= 2) { mPath.quadTo(mDownX, mDownY, (x + mDownX) / 2, (y + mDownY) / 2); mDownX = x; mDownY = y; } break; } invalidate(); return true; }}
程序中创建了mBitmap也就是我们的刮刮乐的图片,并将它绘制在屏幕上,mForegroundBitmap是前景图,我们的mCanvas作用在它之上,并设置混合模式DST_IN,通过drawPath方法绘制路径,并通过Path的quadTo方法绘制圆滑的曲线,实现手指实时刮图,就需要监听手指的触摸事件,这里重写了onTouchEvent方法来记录手指的滑动起始到滑动所在位置的距离,通过invalidate方法重绘。
- 关于自定义View的Paint、Canvas和PorterDuffXfermode的用法
- 自定义View(二)、Canvas和Paint的用法、BitMap
- Android自定义View---前奏篇(Paint和Canvas的使用)
- Android自定义View,paint+canvas的使用
- Paint和canvas的用法
- Paint和canvas的用法
- 自定义View-Paint和Canvas
- canvas,paint的用法
- Canvas的drawBitmap以及Paint的PorterDuffXfermode使用心得
- 自定义View基础之——canvas,paint的基本用法
- Android Canvas 和Paint的用法
- 自定义View--Canvas和Paint详解
- android自定义view--Paint和Canvas
- Android自定义View(一)(Paint和Canvas的基本使用)
- android 自定义view学习笔记————Paint和Canvas的简单使用
- 自定义View时,用到Paint Canvas的一些温故,自定义Loading控件(“六边形”的旋转跳跃)
- Canvas和paint的使用
- 自定义View(二)之Paint和Canvas详解
- CNTK API文档翻译(7)——对MNIST数据使用卷积神经网络
- netty5 用户指南
- 验证二叉查找树-LintCode
- 递归优化
- h.265继承了h.264哪些编码技术,抛弃了h.264哪些编码技术,又引入了哪些新的编码技术?
- 关于自定义View的Paint、Canvas和PorterDuffXfermode的用法
- Python使用struct处理二进制
- display:inline display:block
- Linux问题—源码安装及库文件的一些经验
- 虚拟环境常用命令
- Redis
- 消息队列 RabbitMQ 与 Spring 整合使用
- React Native错误记录
- Unity常用的设计模式_观察者模式