本文共 41538 字,大约阅读时间需要 138 分钟。
一直在情调,时间都是可以自己调节的,不然世界上哪有这么多牛X的人
今天就开始读第六章了,算日子也刚好一个月了,一个月就读一半,这效率也确实有点低了,自己还要加把劲,争取四月底全部看完,第六章讲的是Android的绘图机制,应该算是比较核心的东西了,不管什么功能,最终都是以图形的方式呈现给用户的,因此,掌握Android的绘图技巧,可以在让你设计应用的时候更加的随心所欲,对Android的理解更高
基本的绘图方法,相信读者都已经很清楚了,我们这章就开始玩高级的东西了
屏幕的尺寸,我在画.9的时候已经提过
主要还是因为Android的屏幕确实五花八门,所以在一定程度上的适配问题也是很捉急的,所以我们要对这块屏幕充分的认识
一块屏幕通常具备以下的几个参数
指屏幕对角线的长度,通常用寸来表示,例如4.7寸,5.5寸
分辨率是指实际屏幕的像素点个数,例如720X1280就是指屏幕的分辨率,宽有720个像素点,高有1280个像素点
每英寸像素又称为DPI,他是由对角线的的像素点数除以屏幕的大小所得,通常有400PPI就已经很6了
每个厂商的安卓手机具有不同的大小尺寸和像素密度的屏幕,安卓系统如果要精确到每种DPI的屏幕,基本上是不可能的,因此系统定义了几个标准的DPI
这是由于各种屏幕密度的不同,导致同样像素大小的长度,在不同密度的屏幕上显示长度不同,因此相同长度的屏幕,高密度的屏幕包含更多的像素点,在安卓系统中使用mdpi密度值为160的屏幕作为标准,在这个屏幕上,1px = 1dp,其他屏幕则可以通过比例进行换算,例如同样是100dp的长度,mdpi中为100px,而在hdpi中为150,我们也可以得出在各个密度值中的换算公式,在mdpi中 1dp = 1px, 在hdpi中, 1dp = 1.5px,在xhdpi中,1dp = 2px,在xxhdpi中1dp = 3px,由此可见,我们换算公司 l:m:h:xh:xxh = 3:4:6:8:12
在程序中,我们可以非常方便地对一些单位的换算,下面的代码给出了一种换算的方法我们可以把这些代码作为工具类保存在项目中
package com.lgl.playview;import android.content.Context;/** * dp,sp转换成px的工具类 * Created by lgl on 16/3/23. */public class DisplayUtils { /** * 将px值转换成dpi或者dp值,保持尺寸不变 * * @param content * @param pxValus * @return */ public static int px2dip(Context content, float pxValus) { final float scale = content.getResources().getDisplayMetrics().density; return (int) (pxValus / scale + 0.5f); } /** * 将dip和dp转化成px,保证尺寸大小不变。 * * @param content * @param pxValus * @return */ public static int dip2px(Context content, float pxValus) { final float scale = content.getResources().getDisplayMetrics().density; return (int) (pxValus / scale + 0.5f); } /** * 将px转化成sp,保证文字大小不变。 * * @param content * @param pxValus * @return */ public static int px2sp(Context content, float pxValus) { final float fontScale = content.getResources().getDisplayMetrics().scaledDensity; return (int) (pxValus / fontScale + 0.5f); } /** * 将sp转化成px,保证文字大小不变。 * * @param content * @param pxValus * @return */ public static int sp2px(Context content, float pxValus) { final float fontScale = content.getResources().getDisplayMetrics().scaledDensity; return (int) (pxValus / fontScale + 0.5f); }}
其实的density就是前面所说的换算比例,这里使用的是公式换算方法进行转换,同时系统也提供了TypedValue帮助我们转换
/** * dp2px * @param dp * @return */ protected int dp2px(int dp){ return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,getResources().getDisplayMetrics()); } /** * sp2px * @param dp * @return */ protected int sp2px(int sp){ return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics()); }
系统通过提供的Canvas对象来提供绘图方法,它提供了各种绘制图像的API,drawLine,deawPoint,drawRect,drawVertices,drawAce,drawCircle等等,我以前写过,通过他们的名字我们基本可以大致了解他们的功能,当然,Paint作为一个非常重要的元素功能也非常强大这里也简单地列举了一些他的属性
setAntiAlias(); //设置画笔的锯齿效果 setColor(); //设置画笔的颜色 setARGB(); //设置画笔的A、R、G、B值 setAlpha(); //设置画笔的Alpha值 setTextSize(); //设置字体的尺寸 setStyle(); //设置画笔的风格(空心或实心) setStrokeWidth(); //设置空心边框的宽度 getColor(); //获取画笔的颜色
是由于画笔功能的不一样,再结合各种不同的绘图API,这样任意组合就可以实现不同的绘图效果,比如同样是矩形设置Paint就Style可以画出空心或者实心,我们来看看RectView
package com.lgl.playview;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;/** * Created by lgl on 16/3/23. */public class RectView extends View { private Paint paint1,paint2; public RectView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } private void initView() { paint1 = new Paint(); paint1.setColor(Color.BLUE); paint1.setAntiAlias(true); //空心 paint1.setStyle(Paint.Style.STROKE); paint2 = new Paint(); paint2.setColor(Color.BLUE); //实心 paint2.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawRect(50,100,300,300,paint1); canvas.drawRect(350,400,700,700,paint2); }}
运行起来就是
如果有不明白的,可以仔细看下我,这里就不再详细描述了
XML在安卓系统中可不仅仅是JAVA中的一个布局文件配置列表,在安卓开发者的手头上他甚至可以变成一张画,一幅画,Android开发者给XML提供了几个强大的属性
在XML中使用Bitmap很简单
通过这样引用图片就可以将图片直接转化成Bitmap让我们在程序中使用
通过Shape可以绘制各种图形,下面展示一下shape的参数
android:dashWidth= "1dp" android:dashGap= "1dp"
shape可以说是xml绘图的精华所在,而且功能十分的强大,无论是扁平化,拟物化还是渐变,都是十分的OK,我们现在来做一个阴影的效果
看效果
Layer是在PhotoShop中是非常常用的功能,在Android中,我们同样可以实现图层的效果
Selector的作用是帮助开发者实现静态View的反馈,通过设置不同的属性呈现不同的效果
这一方法可以帮助开发者迅速制作View的反馈,通过配置不同的触发事件,selector会自动选中不同的图片,特别是自定义button的时候,而我们不再使用原生单调的背景,而是使用selector特别制作的背景,就能完美实现触摸反馈了
通常情况下,上面提到的这些方法都可以共同实现,下面这个例子就展示了在一个selector中使用shape作为他的item的例子,实现一个具体点击反馈效果的,圆角矩形的selector,代码如下
我们来看一下效果图
在学完Android的基本绘图之后我们来讲解一下常用的绘图技巧
Canvas作为绘制图形的直接对象,提供了一下几个非常有用的方法
首先,我们来看一下前面两个方法
在讲解这两个方法之前,首先来了解一下Android绘图的坐标体系,这个其实这个前面已经讲了,这里不赘述,而Canvas.save()这个方法,从字面上的意思可以理解为保存画布,他的作用就是讲之前的图像保存起来,让后续的操作能像在新的画布一样操作,这跟PS的图层基本差不多
而Canvas.restore()这个方法,则可以理解为合并图层,,就是讲之前保存下来的东西合并
而后面两个方法尼?从字母上理解画布平移或者旋转,但是把他理解为坐标旋转更加形象,前面说了,我们绘制的时候默认坐标点事左上角的起始点,那么我们调用translate(x,y)之后,则将原点(0,0)移动到(x,y)之后的所有绘图都是在这一点上执行的,这里可能说的不够详细,最典型的例子是画一个表盘了,那我们这里就演示一下画一个表盘
我们先来分析一下这个表盘有什么?我们可以将他分解
相信如果现实中叫你去画一个仪表盘的话,你应该也会这样的步骤去画,实际上Android上的绘图远比现实中的绘制十分的相似,与PS的绘图更家相似,当然,我们在绘制一个复杂的图形之后,不妨先把思路请清除了
这个示例中,我们第一步,先画表盘,现在画个圆应该很轻松了。关键在于确定圆心和半径,这里直接居中吧
// 画外圆Paint paintCircle = new Paint();paintCircle.setAntiAlias(true);paintCircle.setStyle(Paint.Style.STROKE);paintCircle.setStrokeWidth(5);canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, paintCircle);
下面,我们来画刻度尺,这个也很简单,一条线而已,只要确定两个端点的位置就可以,第一根线还是比较容易确定的,那后面的我们怎么去确定尼?那些斜着的我们可以用三角函数去实现计算,但是这其实就是一个很简单的画图,三角函数的运算也要这么多,我们不经要思考,该怎么去简化他尼?其实Google已经为我们想好了
我们治国与会觉得这个不好画,主要是这个角度,那么如果我们将画布以中心为原点旋转到需要的角度尼?每当画好一根线,我们就旋转多少级角度,但是下一次划线的时候依然是第一次的坐标,但是实际上我们把画布重新还原到旋转钱的坐标了,所有的刻度线就已经画好了,通过旋转画布——实际上是旋转了画图的坐标轴,这就避免了万恶的三角函数了,通过这样一种相对论式的变换,间接简化了绘图,这时再去绘制这些刻度,是不是要简单了,只需要区别整点和非整点的刻度了
// 画刻度 Paint paintDegree = new Paint(); paintDegree.setStrokeWidth(3); for (int i = 0; i < 24; i++) { // 区别整点和非整点 if (i == 0 || i == 6 || i == 12 || i == 18) { paintDegree.setStrokeWidth(5); paintDegree.setTextSize(30); canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth, mHeight / 2 - mWidth / 2 + 60, paintDegree); String degree = String.valueOf(i); canvas.drawText(degree, mWidth / 2 - paintDegree.measureText(degree) / 2, mHeight / 2 - mWidth / 2 + 90, paintDegree); } else { paintDegree.setStrokeWidth(3); paintDegree.setTextSize(15); canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth, mHeight / 2 - mWidth / 2 + 30, paintDegree); String degree = String.valueOf(i); canvas.drawText(degree, mWidth / 2 - paintDegree.measureText(degree) / 2, mHeight / 2 - mWidth / 2 + 60, paintDegree); } // 通过旋转画布简化坐标运算 canvas.rotate(15, mWidth / 2, mHeight / 2); }
我们看看效果
紧接着,我们就可以来绘制两根针了,我们可以这样来绘制
// 画指针 Paint paintHour = new Paint(); paintHour.setStrokeWidth(20); Paint paintMinute = new Paint(); paintMinute.setStrokeWidth(10); canvas.save(); canvas.translate(mWidth / 2, mHeight / 2); canvas.drawLine(0, 0, 100, 100, paintHour); canvas.drawLine(0, 0, 100, 200, paintMinute); canvas.restore();
这样运行的效果就和最上面的效果图一样了,这里贴上完整的代码
package com.lgl.dial;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;import android.view.WindowManager;public class DialView extends View { // 宽高 private int mWidth; private int mHeight; // 构造方法 public DialView(Context context, AttributeSet attrs) { super(context, attrs); // 获取屏幕的宽高 WindowManager wm = (WindowManager) getContext().getSystemService( Context.WINDOW_SERVICE); mWidth = wm.getDefaultDisplay().getWidth(); mHeight = wm.getDefaultDisplay().getHeight(); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); // 画外圆 Paint paintCircle = new Paint(); paintCircle.setAntiAlias(true); paintCircle.setStyle(Paint.Style.STROKE); paintCircle.setStrokeWidth(5); canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, paintCircle); // 画刻度 Paint paintDegree = new Paint(); paintDegree.setStrokeWidth(3); for (int i = 0; i < 24; i++) { // 区别整点和非整点 if (i == 0 || i == 6 || i == 12 || i == 18) { paintDegree.setStrokeWidth(5); paintDegree.setTextSize(30); canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth / 2, mHeight / 2 - mWidth / 2 + 60, paintDegree); String degree = String.valueOf(i); canvas.drawText(degree, mWidth / 2 - paintDegree.measureText(degree) / 2, mHeight / 2 - mWidth / 2 + 90, paintDegree); } else { paintDegree.setStrokeWidth(3); paintDegree.setTextSize(15); canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth / 2, mHeight / 2 - mWidth / 2 + 30, paintDegree); String degree = String.valueOf(i); canvas.drawText(degree, mWidth / 2 - paintDegree.measureText(degree) / 2, mHeight / 2 - mWidth / 2 + 60, paintDegree); } // 通过旋转画布简化坐标运算 canvas.rotate(15, mWidth / 2, mHeight / 2); } // 画指针 Paint paintHour = new Paint(); paintHour.setStrokeWidth(20); Paint paintMinute = new Paint(); paintMinute.setStrokeWidth(10); canvas.save(); canvas.translate(mWidth / 2, mHeight / 2); canvas.drawLine(0, 0, 100, 100, paintHour); canvas.drawLine(0, 0, 100, 200, paintMinute); canvas.restore(); }}
Android中的绘图API,很大程度上都来自绘图的API,特别是借鉴了很多PS的原理,比如图层的概念,相信看过图层的也都知道是个什么样的,我们画个图来分析一下
Android通过saveLayer()方法,saveLayerAlpha()将一个图层入栈,使用restore()方法,restoreToCount()方法将一个图层出栈,入栈的时候,后面的所有才做都是发生在这个图层上的,而出栈的时候,则会把图层绘制在上层Canvas上,我们仿照API Demo来
@Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); canvas.drawColor(Color.WHITE); mPaint.setColor(Color.BLUE); canvas.drawCircle(150, 150, 100, mPaint); canvas.saveLayerAlpha(0, 0,400,400,127,LAYER_TYPE_NONE); mPaint.setColor(Color.RED); canvas.drawCircle(200, 200, 100, mPaint); canvas.restore(); }
当绘制两个相交的圆时,就是图层
接下来将图层后面的透明度设置成0-255不同值 我们分别演示 127 255 0三个
Android对于图片的处理,最常用的数据结构是位图——Bitmap,他包含了一张图片的所有数据,整个图片都是由点阵和颜色值去组成的,所谓的点阵就是一个包含像素的矩形,每一个元素对应的图片就是一个像素,而颜色值——ARGB,分别对应透明度红,绿,蓝,这四个通用的分量,他们共同决定了每个像素点显示的颜色
在色彩处理中,我们通常用三个角度描述一张图片
而在Android中,系统会使用一个颜色矩阵——ColorMatrix,来处理这些色彩的效果,Android中的颜色矩阵是4X5的数字矩阵,他用来对颜色色彩进行处理,而对于每一个像素点,都有一个颜色分量矩阵来保存ARGB值
图中矩阵A就是一个4X5的颜色矩阵,在Android中,他们会以一段一维数组的形式来存储[a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t],则C就是一个颜色矩形分量,在处理图像中,使用矩阵乘法运算AC处理颜色分量矩阵如下图
矩阵运算的乘法公式相信大家都在线性代数课程中学过,计算过程如下
从这个公式我们可以发现
这些是这样归类的
这样划分好各自的范围之后,这些值就比较慢明确了,不过只这样说可能会比较抽象,我们通过几个小李子不断的去分析
首先,我们来看一下矩阵变换的计算公式,以R分量为例,计算过程是
R1 = a * R + b* G + c*B+d *A + e
如果让a = 1,b,c,d,e都等于0,那么计算的结果为R1 = R,因此我们可以构建一个矩阵
如果把这个矩阵公式带入R1 = AC,那么根据矩阵的乘法运算法则,可以得到R1 = R;因此,这个矩阵通常是用来作为初始的颜色矩阵来使用,他不会对原有颜色进行任何改变
那么当我们要变换颜色值的时候通常有两种方法,一个是直接改变颜色的offset,即修改颜色的分量。另一种方法直接改变RGBA值的系数来改变颜色分量的值。
从前面的分析中,可以知道要修改R1的值,只要将第五列的值进行修改即可,即改变颜色的偏移量,其它值保存初始矩阵的值,如图所示的颜色矩阵实现了这样的效果。
在上面这个矩阵中,我们修改了R,G,所对应的颜色偏移量,那么最后的处理结果,就是图像的红色绿色分别增加了100,而我们知道,红色混合绿色的到了黄色,所以最终的颜色处理结果就是让整个图片的色调偏黄色
如果修改颜色分量中的某个系数值而其他只依然保存初始矩阵的值。
在上面这个矩阵中改变了G分量所对应的系数据g,这样在矩形运算后G分量会变成以前的两倍,最终效果就是图像的色调更加偏绿。
图像的色调,饱和度,亮度这三个属性在图像处理中使用的非常多,因此在颜色矩阵中也封装了一些API来快速调用这些参数,而不用每次都去计算矩阵的值。下面通过一个实例来看看如何通过句正改变图像的色调,饱和度和亮度。
在Android中,系统封装了一个类——ColorMatrix,也就是前面所说的颜色矩阵。通过这个类,可以很方便地通过改变矩阵值来处理颜色的效果,创建一个ColorMatrix对象非常简单代码如下。
ColorMatrix colorMatrix = new ColorMatrix();
下面我们来处理不同的色光属性
安卓安卓系统提供了setRotate()帮助我们设置三个颜色的色调,第一个参数系统分别使用0,1,2来代表red green blue 三个颜色的处理而第二个参数就是需要处理的值了
ColorMatrix colorMatrix = new ColorMatrix(); colorMatrix.setRotate(0, 2); colorMatrix.setRotate(1, 4); colorMatrix.setRotate(2, 3);
通过这样的方法,可以为RGB三个颜色的分量重新设置不同的值了
Android系统中提供了setSaturation()来设置颜色的饱和度,参数即代表饱和度的值,当饱和度为0的时候是灰色的
ColorMatrix colorMatrix = new ColorMatrix();colorMatrix.setSaturation(10);
当三原色以相同的比例及及混合的时候,就会显示出白色,系统也正在使用这个原理来改变一个图像的亮度,代码如下,当亮度为零时图像会变成全黑。
ColorMatrix colorMatrix = new ColorMatrix();colorMatrix.setScale(10, 10, 10, 10);
除了单独使用上面的三种方法来经颜色效果的处理之外,Android系统还封装了矩阵的乘法运算,它提供了PostConcat方法来将矩阵的作用效果混合,从而叠加处理效果代码如下。
ColorMatrix colorMatrix = new ColorMatrix(); colorMatrix.postConcat(colorMatrix); colorMatrix.postConcat(colorMatrix); colorMatrix.postConcat(colorMatrix);
**这个例子有机会做单独介绍,这就不提**在了解了改变色光属性之后,我们回到现实中的例子,在本例子中,我们通过seekBar来改变不同的数值,并将这些数值作用到对应的矩阵中,最后通过PostConcat方法混合处理
滑动seekbar获得输入值的代码如下
![这里写图片描述](https://img-blog.csdn.net/20160329223612151)通过前面的分析,我们可以知道调整颜色矩阵可以改变一张图片的颜色属性,图像处理很大程度上就是寻找处理图像的颜色矩阵,不仅仅可以通过Android系统提供的API完成,我们自己也可以修改
下面我们可以模拟一个4X5的颜色矩阵,通过修改矩阵的值,一来验证前面所说的改变图像色彩效果的原理是否正确,二来让读者对矩阵产生的作用效果更加清晰和认识
看下怎么实现的
这里我们需要动态的去添加EditText
private Bitmap bitmap; private GridLayout mGroup; private ImageView mImageView; // 高宽 private int mEtWidth, mEtHeight; private EditText [] mEts = new EditText[20];bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.logo); mImageView = (ImageView) findViewById(R.id.imageview); mGroup = (GridLayout) findViewById(R.id.group); mImageView.setImageBitmap(bitmap); mGroup.post(new Runnable() { @Override public void run() { // 获取高宽信息 mEtWidth = mGroup.getWidth() / 5; mEtHeight = mGroup.getHeight() / 4; addEts(); initMatrix(); } }); } /** * 添加输入框 */ private void addEts() { for (int i = 0; i < 20; i++) { EditText editText = new EditText(this); mEts[i] = editText; mGroup.addView(editText, mEtWidth, mEtHeight); } } /** * 初始化颜色矩阵为初始状态 */ private void initMatrix() { for (int i = 0; i < 20; i++) { if (i % 6 == 0) { mEts[i].setText(String.valueOf(1)); } else { mEts[i].setText(String.valueOf(0)); } } }
我们无法再onCreate里获得视图的宽高值,所以通过View的Post方法,,在视图创建完成后获得其宽高值
接下来,只需要获得修改后的edittext值,并将矩阵值设置给颜色矩阵即可,在Android中使用一个20的一位数组板寸,通过colormatrix用set方法,将一个数组转换成colormatrix
/** * 获得矩阵值 */ private void getMatrix() { for (int i = 0; i < 20; i++) { mColorMatrix[i] = Float.valueOf(mEts[i].getText().toString()); } } /** * 将矩阵值设置到图像 */ private void setImageMatrix() { Bitmap bitmap = Bitmap.createBitmap(mEtWidth, mEtHeight, Bitmap.Config.ARGB_8888); ColorMatrix colorMatrix = new ColorMatrix(); colorMatrix.set(mColorMatrix); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); canvas.drawBitmap(bitmap, 0, 0, paint); mImageView.setImageBitmap(bitmap); }
最后设置两个按钮的点击事件
/** * 作用点击事件 */ public void btnChange(View view) { getMatrix(); setImageMatrix(); } /** * 重置矩阵效果 */ public void btnReset(View view) { initMatrix(); getMatrix(); setImageMatrix(); }
这里主要是说一些矩阵达到一些效果,比如
作为更加精确的图像处理方式,可以通过改变每个像素点的具体ARGB值,达到处理一张图片效果的目的,这里要注意的是,传递进来的原始图片是不能修改的,一般根据原始图片生成一张新的图片来修改
在Android中,系统系统提供了Bitmap.getPixels()方法来帮我们提取整个Bitmap中的像素密度点,并保存在一个数组中,该方法如下
bitmap.getPixels(oldPx, 0, bitmap.getWidth(), 0, 0, width, height);
这几个参数的具体含义如下:
通常情况下,可以这样
接下来,我们可以获取每一个像素具体的ARGB值,代码如下
color = oldPx[i]; r = Color.red(color); g = Color.green(color) b = Color.blue(color); a = Color.alpha(color);
当获取到具体的颜色值后,就可以通过相应的算法去修改这个ARGB值了,从而重构一张图片,当然,这些算法是前辈们研究的,总结出来的图像处理方法,由于我们不是专业的图像处理人员,所以就直接拿来用了
r1 = (int) (0.393 * r + 0.769 * g + 0.189 * b); g1 = (int) (0.349 * r + 0.686 * g + 0.168 * b); b1 = (int) (0.272 * r + 0.534 * g + 0.131 * b);
再通过如下代码将新的RGBA值合成像素点
newPx[i] = Color.argb(a, r1, b1, g1);
最后将处理后的像素点重新设置成新的bitmap
bmp.setPixels(newPx, 0, width, 0, 0, width, height);
这些处理方法同样是专业人士研究成果,通过特定的算法改变颜色的矩阵
我们先看原图
若在ABC三个像素点上,要求B点对应的底片效果算法,代码如下
B.r = 255 - B.r; B.g = 255 - B.g; B.b = 255 - B.b;
实际代码如下
/** * 底片效果 * * @param bm * @return */ public Bitmap handleImageNegative(Bitmap bm) { int width = bm.getWidth(); int height = bm.getHeight(); int color; int r, g, b, a; Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); int[] oldPx = new int[width * height]; int[] newPx = new int[width * height]; bm.getPixels(oldPx, 0, width, 0, 0, width, height); for (int i = 0; i < width * height; i++) { color = oldPx[i]; r = Color.red(color); g = Color.green(color); b = Color.blue(color); a = Color.alpha(color); r = 255 - r; g = 255 - g; b = 255 - b; if (r > 255) { r = 255; } else if (r < 0) { r = 0; } if (g > 255) { g = 255; } else if (g < 0) { g = 0; } if (b > 255) { b = 255; } else if (b < 0) { b = 0; } newPx[i] = Color.argb(a, r, g, b); } bmp.setPixels(newPx, 0, width, 0, 0, width, height); return bmp; }
我们再来看看效果。你叫很惊讶的
后面的大多数是相同的,只有一小部分不同,所以我们只改算法部分
黑白照片
r = (int) (0.393 * r + 0.769 * g + 0.189 * b); g = (int) (0.349 * r + 0.686 * g + 0.168 * b); b = (int) (0.272 * r + 0.534 * g + 0.131 * b);
效果
……
B.r = C.r - B.r + 127;B.g = C.g - B.g + 127;B.b = C.b - B.b + 127;
我们也了解了老半天的色彩处理效果,接下来我们再来探索一下图像方面的处理技巧
对于图像的处理,Android系统提供了ColorMatrix颜色矩阵来帮助我们进行图像处理,而对于图像的图形变换,Android系统也可以通过矩阵来帮忙,每个像素点都表达了其xy的信息,Android的变化就是一个3X3的矩阵
当使用变换矩阵去处理每一个像素点的时候,与颜色矩阵的算法公司其实是一样的
X1 = a x X +b x Y +c; Y1 = d x X +e x Y +f; 1 = g x X +hx Y + i;
通常情况下,会让g = h = 0,i = 1;这样使1 = g x X + h x Y + i恒成立,因此只需要关注这几个参数就行
与色彩变换矩阵的初始化矩阵一样,图形变换矩阵也需要一个初始矩阵,很明显就是对角线元素,其他元素为0的矩阵
图像的变形处理包含以下四类基本变换
我们先看一张图
平移变换的坐标就如图,即将每个点的像素点进行平移变换,当p(x0,y0)平移到p(x,y)时,坐标发生了如下的变化
X = X0 + ^X;Y = Y0 + ^Y;
通过计算我们可以发现如下等式
X = X0 + ^X;Y = Y0 + ^Y;
这就是我们前面所说的实现平移的平移公式
选择变换实际上就是一个点围绕一个中心旋转到一个新的点上
通过计算,额可以还原以上等式
前面是以坐标原点为旋转中心旋转变换,如果以任一点0为旋转中心进行旋转变换,通常需要以下三个步骤
通过上述三个步骤,就可以以任一点进行旋转了
一个像素点是不存在缩放的概念的,但是由于图像是由很多人像素点组成,如果将每个像素点的坐标都进行相同比例的缩放,最终就会形成让整个图像缩放的效果,缩放效果的计算公式
x = K1 X x0;y = K2 X y0;
如果写成矩阵的话
通过计算,就可以还原上述等式了
错切变换(skew)在数学上又称为Shear mapping(可译为“剪切变换”),或者Transvection(缩并),它是一种比较特殊的线性变换,错切变换的效果就是让所有的X坐标标尺不变,一般分水平和垂直
处置错切就不画了
在了解了矩阵变换规律之后,通过类似多彩矩阵中模拟矩阵的例子来模拟变形矩阵,整个代码与模块颜色矩阵所使用的代码基本一致,在图形变换,同样是通过了一个一位数组将一个一位数组转换为变换矩阵
private float [] mImageMatrix = new float[9];Matrix matrix = new Matrix();matrix.setValues(mImageMatrix);canvas.drawBitmap(mBitmmap,matrix,null);
后面一小部分不写
与我们当初了解色彩的处理效果时类似,使用了颜色矩阵和像素点处理两种方式来进行图像处理,这里在进行图像的处理方式也有两种,即前面讲的使用矩阵来进行图像变换和马上要使用到的drawBitmapMesh()方法来处理,操作像素块的原理,该方法如下
canvas.drawBitmapMesh(Bitmap bitmap,int meshWidth,int meshHeight,float [] verts,int vertOffset,int [] color,int colorOffset,Paint paint);
这个方法的参数非常的多
前面说了,要想使用drawBitmapMesh()的方法先要将突破分成若干份,所以在图像各画N-1条线,将图片分成N块,这里我来演示一下
mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.nice); float bitmapWidth = mBitmap.getWidth(); float bitmapHeight = mBitmap.getHeight(); int index = 0; for (int y = 0;y<=HEIGHT;y++){ float fy = bitmapHeight*y/HEIGHT; for (int x = 0;x<= WIDTH;x++){ float fx = bitmapWidth*x/WIDTH; orig[index*2+0] = verts[index*2+0] = fx; //这里人为将坐标+100是为了让图像下移,避免扭曲后被屏幕遮挡 orig[index*2+1] = verts[index*2+1] = fx+100; index +=1; } }
接下来,在onDraw()方法中改变交叉点的纵坐标的值,为了实现旗帜飘飘的效果,使用sin x,来改变交叉点的坐标值,使横坐标不变,将其变化后的值保存在数组中
private void flagWava(){ for (int j = 0; j<=HEIGHT;j++){ for (int i = 0; i<=WIDTH;i++){ verts[(j*(WIDTH+1)+i)*2+0] +=0; float offsetY = (float)Math.sin((float)i/WIDTH*2*Math.PI); verts[(j*(WIDTH+1)+i)*2+1] = orig[(j*(WIDTH+i)+i)*2+1]+offsetY*A; } } }
然后我们就可以使用了
canvas.drawBitmapMesh(mBitmap,WIDTH,HEIGHT,verts,0,null,0,null);
为了能够让图片动起来,可以充分利用正弦函数的周期性来实现,在获取纵坐标的偏移量的时候给函数增加一个周期
float offsetY = (float)Math.sin((float)i/WIDTH*2*Math.PI+Math.PI*k);
而在重绘 d 时候,给K值增加
flagWava();k+=0.1f; canvas.drawBitmapMesh(mBitmap,WIDTH,HEIGHT,verts,0,null,0,null); invalidate();
这里,每次重绘的时候,通过改变相位来改变偏移量,从而造成一个动态的效果,就好像旗帜在空中飘扬一样
使用drawBitmapMesh()方法可以创建很多复杂的特效,但是对他的使用也相对来讲比较复杂,需要开发者对图像处理有很深的功底,同时对算法要求比较高,才能做出特效
这一章没有截图,原书乱七八糟的例子,也可能是自己渣渣了吧
不管是在我们的世界里,还是在Android的世界里,要想画出好的图片,就必须账务画笔的特效,今天我们就来玩玩这个Paint的特殊Get
在学习这个东西之前,我们先来看看一张非常经典的图片,来自API DEMO。基本上讲PorterDuffXfermode的都是根据这张图片来的
途中列举了16中PorterDuffXfermode,有点像数学中的集合,主要是一个混合显示模式
这里注意了,PorterDuffXfermode设置两个图层交集区域的显示方法des是先画的图像,src是后画的
当然,这些模式也不是经常的用到,用到最多的事,使用一张图片作为另一张图片的遮罩,通过控制遮罩层的图形,来控制下面被遮罩的显示效果,其中最常用的就是通过DST_IN.SRC_IN模式来实现将一个矩形变成圆角图片的效果,我们这里来实现一下
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.nice); mOut = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(mOut); mPaint = new Paint(); mPaint.setAntiAlias(true); canvas.drawRoundRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), 80, 80, mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(mBitmap,0,0,mPaint);
下面我们来做一个比较类似于刮刮卡的效果,其实效果就是两张图片,上面那张一刮就显示下面的那张看,这个效果同样我们可以使用PorterDuffXfermode去实现,我们首先要做的就是初始化一些数据
/** * 初始化 */ private void init() { mPaint = new Paint(); mPaint.setAlpha(0); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeWidth(50); mPaint.setStrokeCap(Paint.Cap.ROUND); mPath = new Path(); mBgBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.nice); mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(), mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mFgBitmap); mCanvas.drawColor(Color.GRAY); }
在这里我们给paint设置一些属性,让他更圆一点,然后我们再监听触摸时间
/** * 触摸事件 * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mPath.reset(); mPath.moveTo(event.getX(),event.getY()); break; case MotionEvent.ACTION_UP: mPath.lineTo(event.getX(),event.getY()); break; } mCanvas.drawPath(mPath,mPaint); invalidate(); return true; }
最后只需要使用DST_IN来完善覆盖即可,这里我把源码放上来
package com.lgl.playview;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;/** * 涂鸦 * Created by lgl on 16/4/2. */public class PlayView extends View { private Bitmap mBgBitmap, mFgBitmap; private Paint mPaint; private Canvas mCanvas; private Path mPath; /** * 构造方法 * * @param context * @param attrs */ public PlayView(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 初始化 */ private void init() { mPaint = new Paint(); mPaint.setAlpha(0); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeWidth(50); mPaint.setStrokeCap(Paint.Cap.ROUND); mPath = new Path(); mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.nice); mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(), mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mFgBitmap); mCanvas.drawColor(Color.GRAY); } /** * 触摸事件 * * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPath.reset(); mPath.moveTo(event.getX(), event.getY()); break; case MotionEvent.ACTION_UP: mPath.lineTo(event.getX(), event.getY()); break; } mCanvas.drawPath(mPath, mPaint); invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(mBgBitmap, 0, 0, null); canvas.drawBitmap(mFgBitmap, 0, 0, null); }}
这里我们再运行一下
这里唯一要注意的就是使用PorterDuffXfermode的时候最好把硬件加速给关掉,因为有些不支持
Shader又被称为着色器。渲染器,它可以实现渲染,渐变等效果,Android中的Shader包括以下几种
除了第一个之外,其他的都是比较正常的,实现了名副其实的渐变,渲染效果,而与其他Shader的效果不同的是,BitmapShader所产生的是一个图像,有点类似PS里面的图像填充,他的作用是通过Paint对画布进行制定bitmap的填充,填充式有一下几个模式供选择。
这几种模式的含义都非常好理解的,与字面意识基本相同,这里最常用的是CLAMP拉伸模式,虽然他会拉伸最后一个像素,但是只要将突破设置成一个固定的像素,是可以避免的,下面我们来举个例子;
mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.nice); mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP); mPaint = new Paint(); mPaint.setShader(mBitmapShader); canvas.drawCircle(500,250,200,mPaint);
运行
这里模式是选取的CLAMP,我们再来看,我们这里更改为REPEAT
mBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher); mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT,Shader.TileMode.REPEAT); mPaint = new Paint(); mPaint.setShader(mBitmapShader); canvas.drawCircle(500,250,200,mPaint);
我们再运行一下
很好玩,我们接下来看LinearGradient,理解起来就是线性渐变的意思,我们也来写个例子
mPaint = new Paint(); mPaint.setShader(new LinearGradient(0,0,400,400, Color.BLUE,Color.YELLOW, Shader.TileMode.REPEAT)); canvas.drawRect(0,0,400,400,mPaint);
我们运行的结果
其实这几种模式都差不多相同,只是渐变显示的效果不一样罢了
其实,这些渐变效果通常会直接使用在程序里,我们再来用一个倒影的例子来解释这个原理吧
package com.lgl.playview;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.LinearGradient;import android.graphics.Matrix;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.Shader;import android.util.AttributeSet;import android.view.View;/** * 倒影 * Created by lgl on 16/4/2. */public class InvertedView extends View { private Bitmap mSrcBitmap, mRefBitmap; private Paint mPaint; private PorterDuffXfermode xfermode; /** * 构造方法 * * @param context * @param attrs */ public InvertedView(Context context, AttributeSet attrs) { super(context, attrs); initRes(context); } /** * 初始化 * * @param context */ private void initRes(Context context) { mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.nice); Matrix matrix = new Matrix(); matrix.setScale(1F, -1F); mRefBitmap = Bitmap.createBitmap(mSrcBitmap, 0, 0, mSrcBitmap.getWidth(), mSrcBitmap.getHeight(), matrix, true); mPaint = new Paint(); mPaint.setShader(new LinearGradient(0, mSrcBitmap.getHeight(), 0, mSrcBitmap.getHeight() + mSrcBitmap.getHeight() / 4, 0XDD000000, 0X10000000, Shader.TileMode.CLAMP)); xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); } /** * 绘制 * * @param canvas */ @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.BLACK); canvas.drawBitmap(mSrcBitmap, 0, 0, null); canvas.drawBitmap(mRefBitmap, 0, mSrcBitmap.getHeight(), null); mPaint.setXfermode(xfermode); //绘制渐变效果矩形 canvas.drawRect(0, mSrcBitmap.getHeight(), mRefBitmap.getWidth(), mSrcBitmap.getHeight() * 2, mPaint); mPaint.setXfermode(null); }}
我们直接预览
要想了解PathEffect,还是直接看一张图比较直观吧
PathEffect就是指,用各种笔触效果绘制路径,Android系统提供的从上到下
这样,我们来做一个简单的实例:
package com.lgl.playview;import android.content.Context;import android.graphics.Canvas;import android.graphics.ComposePathEffect;import android.graphics.CornerPathEffect;import android.graphics.DashPathEffect;import android.graphics.DiscretePathEffect;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PathDashPathEffect;import android.graphics.PathEffect;import android.util.AttributeSet;import android.view.View;/** * PathEffect * Created by lgl on 16/4/2. */public class PathEffectView extends View{ private Path mPath; private PathEffect [] mEffect = new PathEffect[6]; private Paint mPaint; /** * 构造方法 * @param context * @param attrs */ public PathEffectView(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 初始化 */ private void init() { mPaint = new Paint(); mPath = new Path(); mPath.moveTo(0,0); for (int i = 0; i<= 30;i++){ mPath.lineTo(i*35,(float)(Math.random()*100)); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mEffect[0] = null; mEffect[1] = new CornerPathEffect(30); mEffect[2] = new DiscretePathEffect(3.0F,5.0F); mEffect[3] = new DashPathEffect(new float[]{ 20,10,5,10},0); Path path = new Path(); path.addRect(0,0,8,8,Path.Direction.CCW); mEffect[4]= new PathDashPathEffect(path,12,0,PathDashPathEffect.Style.ROTATE); mEffect[5] = new ComposePathEffect(mEffect[3],mEffect[1]); for (int i = 0; i
我们再来运行一下
Android系统提供了VieW进行绘图处理, vieW可以满足大部分的绘图需求,但在某些时却也有些心有余而力不足,特别是在进行一些开发的时候。 我们知道,VieW通过刷新来视图, Android系统通过发出VSYNC信号来进行屏幕的重绘, 刷新的间隔时间为I6ms。在16ms内View完成了你所需要执行的所有操作,那么用户在视觉上, 就不会产生卡顿的感觉:而如果执行的操作逻辑太多,特别是需要频繁刷新的界面上,例如游戏界面,那么就塞主线程,从而导致画面卡顿,很多时候,在自定义VieW的Log中经常会看见如下示的警告
Skipped 47 frames! The application may be doing too much work on its main thread
这些警告的产生,很多情况下就是因为在绘制过程中, 处理逻辑太多造成的为了避免这一问题的产生一 Android系统提供了surfacevicW组件来解决这个问题,可以说是VieW的孪生兄弟,但它与View还是有所不同的,它们的区别主要体现
总结起来,如果你的自定义View需要频繁刷新,或者刷新时数据处理量比较大,那么你就可以考虑使用surfaceVicw取代View了
surfaceView的使用虽然比View复杂.但是surfaeView在使用时,
有一套使用的模板代码,大部分的surfaceView绘图操作都可以套用这样的模板代码来进行编写:因此,其实surfaceView的使用更加简单通常情况下 使用以下步骤来创建一个surfaceView的模板。
创建自定义的surfaceView,实现它的两个接口
public class SurfaView extends SurfaceView implements SurfaceHolder.Callback,Runnable
通过实现它的两个几口,就会有三个回调方法
@Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { }
分别对应的是SurfaceView的创建,改变和销毁的过程
对于Runnable接口,会实现他的回调
@Override public void run() { }
在自定义的SurfaceView构造方法中,需要对SurfaceView进行出还刷,我们首先要定义三个成员变量
//SurfaceHolder private SurfaceHolder mHolder; //用于绘制的Canvas private Canvas mCanvas; //子线程标志位 private boolean mIsDrawing;
初始化方法就是对回调初始化以及注册
mHolder = getHolder();mHolder.addCallback(this);
另外两个成员变量一Canvas和标志位。 对Canvas我们已经非常熟悉了,与在VieW的ondraw()方法中吏用Canvas绘图一样,在surfaceView中,我们也要使用Canvas来进行绘图,另一个标志位则是用来控制子线程的
通过SurfaceHolder对象的lockCanvas方法,就可以获得当前Canvas绘图对象,接下来,就可以与在View中进行的绘制操作一样进行绘制了,不过这里要注意的是Canvas对象还是继续上次的Canvas对象,而不是一 个新的对象。因此,之前的操作都将被保留,如果需要擦除,则可以在绘制前, 通过drawColoro方法来进行清屏操作
绘制的时候,充分利用SurfaceView的回调方法,在surfaceCreated方法中开启子线程进行绘制,而子线程开启了一个while(mIsDrawing)的循环来不停的绘制,而在具体的逻辑中,通过lookCanvas()方法获取Canvas对象进行绘制,,整个代码模块如下:
package com.lgl.playview;import android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceView;/** * SurfaceView的使用 * Created by lgl on 16/4/2. */public class SurfaView extends SurfaceView implements SurfaceHolder.Callback, Runnable { //SurfaceHolder private SurfaceHolder mHolder; //用于绘制的Canvas private Canvas mCanvas; //子线程标志位 private boolean mIsDrawing; /** * 构造方法 * * @param context * @param attrs */ public SurfaView(Context context, AttributeSet attrs) { super(context, attrs); mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); setKeepScreenOn(true); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; } @Override public void run() { while (mIsDrawing) { draw(); } } private void draw() { try { mCanvas = mHolder.lockCanvas(); } catch (Exception e) { } finally { if (mCanvas != null) { //提交 mHolder.unlockCanvasAndPost(mCanvas); } } }}
以上的代码基本可以满足大部分的SurfaceView绘制,唯一需要注意的就是绘制的时候mHolder.unlockCanvasAndPost(mCanvas);放在finally中,来保证每次都能将任务提交
下面我们通过两个实例来玩玩SurfaceView
package com.lgl.playview;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceView;/** * SurfaceView的使用 * Created by lgl on 16/4/2. */public class SurfaView extends SurfaceView implements SurfaceHolder.Callback, Runnable { //SurfaceHolder private SurfaceHolder mHolder; //用于绘制的Canvas private Canvas mCanvas; //子线程标志位 private boolean mIsDrawing; private int x, y = 0; private Path mPath; private Paint mPaint; /** * 构造方法 * * @param context * @param attrs */ public SurfaView(Context context, AttributeSet attrs) { super(context, attrs); mPath = new Path(); mPaint = new Paint(); mPaint.setStrokeWidth(20); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); setKeepScreenOn(true); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; } @Override public void run() { while (mIsDrawing) { draw(); x += 1; y = (int) (100 * Math.sin(x * 2 * Math.PI / 180) + 400); mPath.lineTo(x, y); } } private void draw() { try { mCanvas = mHolder.lockCanvas(); mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath, mPaint); } catch (Exception e) { } finally { if (mCanvas != null) { //提交 mHolder.unlockCanvasAndPost(mCanvas); } } }}
我们运行一下
我们再来看下一个例子
我们来实现一个画板
package com.lgl.playview;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.SurfaceHolder;import android.view.SurfaceView;/** * SurfaceView的使用 * Created by lgl on 16/4/2. */public class SurfaView extends SurfaceView implements SurfaceHolder.Callback, Runnable { //SurfaceHolder private SurfaceHolder mHolder; //用于绘制的Canvas private Canvas mCanvas; //子线程标志位 private boolean mIsDrawing; private int x, y = 0; private Path mPath; private Paint mPaint; /** * 构造方法 * * @param context * @param attrs */ public SurfaView(Context context, AttributeSet attrs) { super(context, attrs); mPath = new Path(); mPaint = new Paint(); mPaint.setStrokeWidth(20); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); setKeepScreenOn(true); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; } @Override public void run() { while (mIsDrawing) { draw();// x += 1;// y = (int) (100 * Math.sin(x * 2 * Math.PI / 180) + 400);// mPath.lineTo(x, y); } } private void draw() { try { mCanvas = mHolder.lockCanvas(); mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath, mPaint); } catch (Exception e) { } finally { if (mCanvas != null) { //提交 mHolder.unlockCanvasAndPost(mCanvas); } } } /** * 触摸事件 * * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPath.moveTo(x, y); break; case MotionEvent.ACTION_MOVE: mPath.lineTo(x, y); break; case MotionEvent.ACTION_UP: break; } return true; }}
这个可好玩多了
好了,到这里,第六章终于写完了,我有一种想吐的感觉,太长了,希望大家多支持哈
因为很长,所以用了MAC和windows两个平台写,PPT和Demo有些不完整,不过不影响观看!