本章内容主要有:

    Android屏幕相关知识    Android绘图技巧    Android图像处理技巧    SurfaceView的使用

一、屏幕的尺寸信息

1、屏幕参数

一个屏幕通常具有以下几个参数。

屏幕大小

指屏幕对角线的长度,通常使用“寸”来度量,例如4.7寸手机。

分辨率

分辨率是指手机屏幕的像素点个数,例如720*1280就是指屏幕的分辨率,指宽有720个像素点,而高有1280个像素点。

PPI

每英寸像素(Picels Per Inch)又被称为DPI(Dots Per Inch)。它是由对角线的像素点除以屏幕的大小得到的,同样的分辨率,屏幕越大,像素点之间的距离越大,屏幕就越粗糙。实践证明,PPI低于240的让人的视觉可以察觉明显颗粒感,高于300则无法察觉颗粒,通常达到400PPI就已经是非常高的屏幕密度了。

2、系统屏幕密度

每个厂商的Android手机具有不同的大小尺寸和像素密度的屏幕。系统定义了几个标准的DPI值,作为手机固定的DPI:
这里写图片描述

3、独立像素密度dp

由于各种屏幕密度的不同,导致同样像素大小的长度,在不同密度的屏幕上显示长度不同。Android系统使用mdpi即密度值为160的屏幕作为标准,在这个屏幕上1px=1dp。其它屏幕都可以据此进行换算。比如,同样100dp的长度,在mdpi中为100px,在hdpi中为150px。由此,我们可以得到各个分辨率直接的换算比例:

ldpi : mdpi : hdpi : xhdpi : xxhdpi = 3 : 4 : 6 : 8 : 12

4、单位转换

在程序中对单位进行转化,可以直接使用如下代码,当做工具类保存到项目中:

/** *dp、sp转换为px的工具类 */public class DisplayUtil {    /**     * 将px值转换为dip或dp值,,保证尺寸大小不变     * @param context     * @param pxValue     * DisplayMetrics类中属性density     * @return     */    public static int px2dip(Context context, float pxValue){        final float scale = context.getResources().getDisplayMetrics().density;        return (int)(pxValue/scale + 0.5f);    }    /**     * 将dip或dp值转换为px值,保证尺寸大小不变     * @param context     * @param dipValue     * DisplayMetrics类中属性density     * @return     */    public static int dip2px(Context context,float dipValue){        final float scale = context.getResources().getDisplayMetrics().density;        return (int)(dipValue * scale + 0.5f);    }    /**     * 将px值转换为sp值,保证文字大小不变     * @param context     * @param pxValue     * DisplayMetrics类中属性density     * @return     */    public static int px2sp(Context context,float pxValue){        final float fontScale = context.getResources().getDisplayMetrics().density;        return (int)(pxValue/fontScale + 0.5f);    }    /**     * 将sp值转换为px值,保证文字大小不变     * @param context     * @param spValue     * @return     */    public static int sp2px(Context context,float spValue){        final float fontScale = context.getResources().getDisplayMetrics().density;        return (int)(spValue/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 sp     * @return     */    protected int sp2px(int sp){        return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics());    }

二、2D绘图基础

系统通过提供的Canvas对象来提供绘图方法。

它提供了各种绘制图像的API,如drawPoint(点)、drawLine(线)、drawRect(矩形)、drawVertices(多边形)、drawArc(弧)、drawCircle(圆)等等。

要绘制图形,首先要定义我们的画笔Paint,下面列举了它的一些属性和对应的功能:
    * 1.图形绘制    * setARGB(int a,int r,int g,int b);         * 设置绘制的颜色,a代表透明度,r,g,b代表颜色值。         *          * setAlpha(int a);         * 设置绘制图形的透明度。         *          * setColor(int color);         * 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。         *         * setAntiAlias(boolean aa);         * 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。         *          * setDither(boolean dither);         * 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰         *          * setFilterBitmap(boolean filter);         * 如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示         * 速度,本设置项依赖于dither和xfermode的设置         *          * setMaskFilter(MaskFilter maskfilter);         * 设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等       *          * setColorFilter(ColorFilter colorfilter);         * 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果         *          * setPathEffect(PathEffect effect);         * 设置绘制路径的效果,如点画线等         *          * setShader(Shader shader);         * 设置图像效果,使用Shader可以绘制出各种渐变效果         *         * setShadowLayer(float radius ,float dx,float dy,int color);         * 在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色         *          * setStyle(Paint.Style style);         * 设置画笔的样式,为FILL(实心),FILL_OR_STROKE,或STROKE (空心)        *          * setStrokeCap(Paint.Cap cap);         * 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式         * Cap.ROUND,或方形样式Cap.SQUARE         *          * setSrokeJoin(Paint.Join join);         * 设置绘制时各图形的结合方式,如平滑效果等         *          * setStrokeWidth(float width);         * 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度         *          * setXfermode(Xfermode xfermode);         * 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果         *          *2.文本绘制      * setFakeBoldText(boolean fakeBoldText);         * 模拟实现粗体文字,设置在小字体上效果会非常差         *          * setSubpixelText(boolean subpixelText);         * 设置该项为true,将有助于文本在LCD屏幕上的显示效果         *          * setTextAlign(Paint.Align align);         * 设置绘制文字的对齐方向         *        * setTextScaleX(float scaleX);        * 设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果         *          * setTextSize(float textSize);         * 设置绘制文字的字号大小         *          * setTextSkewX(float skewX);         * 设置斜体文字,skewX为倾斜弧度         *          * setTypeface(Typeface typeface);         * 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等         *          * setUnderlineText(boolean underlineText);         * 设置带有下划线的文字效果         *          * setStrikeThruText(boolean strikeThruText);         * 设置带有删除线的效果         *          **/   

下面重点来看一下Canvas家族的各个成员们:

1)DrawPoint,绘制点

canvas.drawPoint(x,y,mPaint);

2)DrawLine,绘制直线

canvas.drawLine(starX,starY,endX,endY,mPaint);

3)DrawLines,绘制多条直线

float[] pts = {startX1,startY1,endX1,endY1,...startXn,startYn,endXn,endY};canvas.drawLines(pts,mPaint);

4)DrawRect,绘制矩形

canvas.drawRect(left,top,right,bottom,mPaint);

Android群英传学习——第六章、Android绘图机制与处理技巧_第1张图片
5)DrawRoundRect,绘制圆角矩形

canvas.drawRoundRect(left,top,right,bottom,radiusX,radiusY,mPaint);

6)DrawCircle,绘制圆

canvas.drawCircle(circleX,circleY,radius,mPaint);

7)DrawArc,绘制弧形、扇形

mPaint.setStyle(Paint.Style.STROKE);canvas.drawArc(left,top,right,bottom,startAngle,sweepAngle,useCenter,mPaint);
这里注意,弧形与扇形的区分就是倒数第二个参数useCenter的区别,useCenter设为true绘制的是扇形,设为false绘制的是弧形。

8)DrawOval,绘制椭圆

//通过椭圆的外接矩形来绘制椭圆canvas.drawOval(left,top,right,bottom,mPaint);

9)DrawText,绘制文本

canvas.drawText(text,startX,startY,mPaint);

10)DrawPosText,在指定位置绘制文本

canvas,drawPosText(text,new float[]{X1,Y1,X2,Y2,...Xn,Yn},mPaint);

11)DrawPath,绘制路径

Path path = new Path();path.moveTo(startX,startY);path.lineTo(point1X,point1Y);path.lineTo(point2X,point2Y);path.lintTo(point3X,point3Y);canvas.drawPath(path,mPaint);

三、Android XML绘图

XML在Android系统中不仅仅是一个布局文件、配置列表。它甚至可以变成一张画、一副图。

1、Bitmap

在XML中使用Bitmap十分简单,代码如下:

<?xml version="1.0" encoding="utf-8"?><bitmap   xmlns:android="http://schemas.android.com/apk/res/android"    android:src = "@drawable/ic_launcher"/>
通过这样引用图片,就可以将图片之间转成了Bitmap让我们在程序中使用。

2、Shape

<?xml version="1.0" encoding="utf-8"?><shape    xmlns:android="http://schemas.android.com/apk/res/android"    <!--rectangle:矩形,填满整个包裹的控件,默认值-->    <!--oval:椭圆,会根据控件的尺寸自适应-->    <!--line:贯穿控件的横线,需要<stroke>标签来定义横线的宽度-->    <!--ring:环形-->    android:shape=["rectangle" | "oval" | "line" | "ring"] >    <corners<!-- 只有在shaperectangle时使用,以下参数取值必须大于1-->        android:radius="统一四个圆角设置,这个可以被以下任何一个覆盖对应的角落做单独角落处理"        android:topLeftRadius="integer"        android:topRightRadius="integer"        android:bottomLeftRadius="integer"        android:bottomRightRadius="integer" />    <gradient <!--渐变 -->        android:angle="渐变方向,0为从左至右,90为从下至上,逆时针方向旋转,"        android:centerX="渐变色中心的X相对位置(0-1.0)"        android:centerY="渐变色中心的Y相对位置(0-1.0),还不是很理解,当渐变方向为竖直方向时,该值设定渐变中心的位置"        android:centerColor="integer"        android:endColor="color"        android:gradientRadius="渐变色的半径 当type为radial时使用,调大些明显"        android:startColor="color"        android:type=["linear线性渐变,默认值" | "radial放射渐变,start color is the center color" | "sweep 扫线"]        android:usesLevel=["true" | "false"] />    <padding <!--控件内距 四周留出来的空白 -->        android:left="integer"        android:top="integer"        android:right="integer"        android:bottom="integer" />    <size        android:width="integer"        android:height="integer" />    <solid <!--填充-->        android:color="color" />    <stroke <!-- shapeline时是贯穿控件的线条,非line时用来描边-->        android:width="线条厚度"        android:color="color"        android:dashWidth="实线宽度"        android:dashGap="虚线宽度" />shape>

3、Layer

在Android中可以通过Layer来实现类似Photoshop中图层的概念。
下面我们通过使用layer、layer-list是想图片叠加效果:

在res-drawable目录下新建xml文件:
<?xml version="1.0" encoding="utf-8"?><layer-list    xmlns:android="http://schemas.android.com/apk/res/android">    <item        android:drawable="@drawable/ic_launcher"/>    <item        android:drawable="@drawable/ic_launcher"        android:left="20.0dp"        android:top="20.0dp"        android:right="20.0dp"        android:bottom="10.0dp"/>layer-list>

效果如图
Android群英传学习——第六章、Android绘图机制与处理技巧_第2张图片

4、Selector

Selector的作用在于帮助开发者实现静态绘图中的事件反馈,通过给不同的事件设置不同的图像,从而在程序中根据用户活动,返回不同的结果。

这一方法可以帮助开发者迅速制作View的触摸反馈。

<?xml version="1.0" encoding="utf-8"?><selector    xmlns:android="http://schemas.android.com/apk/res/android">        <item          android:drawable="@drawable/D1"/>        <item         android:state_window_focused="false"        android:drawable="@drawable/D2"/>        <item        android:state_focused="true"        android:state_pressed="true"        android:drawable="@drawable/D3"/>        <item        android:state_focused="false"        android:state_pressed="true"        android:drawable="@drawable/D4"/>        <item        android:state_selected="true"        android:drawable="@drawable/D5"/>        <item        android:state_focused="true"        android:drawable="@drawable/D6"/>selector>

下面实现一个圆角矩形点击后换背景颜色的效果:

<?xml version="1.0" encoding="utf-8"?><selector    xmlns:android="http://schemas.android.com/apk/res/android">    <item android:state_pressed="true">        <shape android:shape = "rectangle">                        <solid android:color = "#000"/>                                    <corners                android:radius = "5dip"/>                        <padding                android:bottom = "10dp"                android:left = "10dp"                android:right = "10dp"                android:top = "10dp"/>        shape>    item>    <item>        <shape android:shape = "rectangle">                        <solid android:color = "#6769"/>                                    <corners                android:radius = "5dip"/>                        <padding                android:bottom = "10dp"                android:left = "10dp"                android:right = "10dp"                android:top = "10dp"/>        shape>    item>selector>

四、Android绘图技巧

1、Layer图层

Android中绘图API,很大程度上都来自于现实生活中的绘图工具,特别是Photoshop中的概念,比如图层。一张画可以有很多图层叠加起来,形成一个复杂的图像。

在Android中,使用setLayer()方法来创建一个图层。

图层同样是基于栈的结构进行管理的。

Android通过调用saveLayer()方法、saveLayerAlpha()方法将一个图层入栈。  使用restore()方法、restoreToCount()方法将一个图层出栈。

入栈的时候,后面所有的操作都发生在这个图层上,出栈的时候,会把图像绘制到上层Canvas上。

@Override    protected void onDraw(Canvas canvas) {      canvas.drawColor(Color.WHITE);        mPaint.setColor(Color.BLUE);        canvas.drawCircle(150,150,100,mPaint);        canvas.saveLayerAlpha(0,0,400,400.127,LAYER_FLAGS);        mPaint.setColor(Color.RED);        canvas.drawCircle(200,200,100,mPaint);        canvas.restore();    }

本例中绘制了两个相交的圆,这两个圆位于两个图层上。
将后面的图层透明度设置0-255不同的数值:当透明度Wie0时,即完全透明;当透明度为127时,即半透明;当透明度为255时,即完全不透明。

2、Canvas

Canvas对象除了可以直接绘制图形外,也可以对图层进行操作,主要有以下几个方法:
●canvas.save();
●canvas.restore();
●canvas.translate();
●canvas.rotate();
1)canvas.save()方法,从字面上可以理解为保存画布。它的作用就是将之前的所有已绘制的图像保存起来,让后续的操作好像就在一个新的图层上操作一样。
2)canvas.restore()方法,可以理解为将我们在save()之后绘制的所有图像与save()之前的图像进行合并。
3)canvas.translate(x,y)方法,可以理解为画布平移,默认绘图坐标零点位于屏幕左上角,那么调用这个方法后,原件就从(0,0)移动到了(x,y)。
4)canvas.rotate()方法与translate()方法相似,它将坐标系旋转了一定的角度。

下面我们做一个仪表盘,来加深一下对于上述几个方法的印象。
先看效果图:
Android群英传学习——第六章、Android绘图机制与处理技巧_第3张图片

这样一个图形,我们可以将它分解成以下几个元素:

1)仪表盘——外面的大圆盘2)刻度线——包含四个长的刻度线和其他短的刻度线3)刻度值——包含长刻度线对应的大的刻度值和其他小的刻度值4)指针——中间的指针、一粗一细两根指针

那我们就一个一个来画,之间看代码吧:

public class YiBiao extends View {    private Paint paintCircle,paintDegree,paintHour,paintMinute;    private int mHeight,mWidth;    public YiBiao(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init();    }    private void init() {        //获取屏幕高宽        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);        mWidth = wm.getDefaultDisplay().getWidth();        mHeight = wm.getDefaultDisplay().getHeight();        //圆盘画笔        paintCircle = new Paint();        paintCircle.setColor(Color.BLACK);        paintCircle.setStrokeWidth(5);        paintCircle.setStyle(Paint.Style.STROKE);        paintCircle.setAntiAlias(true);        //刻度线画笔        paintDegree = new Paint();        paintDegree.setStrokeWidth(3);        //指针画笔        paintHour = new Paint();        paintHour.setStrokeWidth(20);        paintMinute = new Paint();        paintMinute.setStrokeWidth(10);    }    @Override    protected void onDraw(Canvas canvas) {        canvas.drawCircle(mWidth/2,mHeight/2,mWidth/2,paintCircle);        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);        }        //将保存前的图层与保存后的图存合并        canvas.save();        //将坐标原点移动到圆心的位置        canvas.translate(mWidth/2,mHeight/2);        canvas.drawLine(0,0,100,100,paintHour);        canvas.drawLine(0,0,100,200,paintMinute);    }}

啊~!困疯了,实在写不了啦。

五、Android图像处理之色彩特效处理

Android对于图片的处理,最常使用到的数据结构是位图——Bitmap,它包含了一张图片所有的数据。整个图片都是由点阵和颜色值组成的,所谓点阵就是一个包含像素的矩阵,每一个元素对应着图片的一个像素。而颜色值——ARGB,分别对应透明度、红、绿、蓝这四个通道分量,他们共同决定了每个像素点显示的颜色。
Android群英传学习——第六章、Android绘图机制与处理技巧_第4张图片
色光三原色

1、色彩矩阵分析

在色彩处理中,通常使用以下三个角度来描述一个图像。
● 色调——物体传播的颜色
● 饱和度——颜色的纯度,从0(灰)到100%(饱和)来进行描述
● 亮度——颜色的相对明暗程度
在Android中,系统使用一个颜色矩阵——ColorMatrix,来处理图像的这些色彩效果。这个颜色矩阵是一个4*5的数字矩阵,它以一维数组的形式来存储,如图中矩阵A。而对于每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值,如图中矩阵C。在处理图像时,使用矩阵乘法运算AC来处理颜色分量矩阵。
Android群英传学习——第六章、Android绘图机制与处理技巧_第5张图片
即:
这里写图片描述
从这个公式可以发现,矩阵A中的4*5颜色矩阵是按一下方式划分的:

● 第一行的abcde值用来决定新的颜色值中的R——红色● 第二行的fghij值用来决定新的颜色值中的G——绿色● 第三行的klmno值用来决定新的颜色值中的B——蓝色● 第四行的pqrst值用来决定新的颜色值中的A——透明度● 矩阵A中的第五列——ejot值分别用来决定每个分量中的offset,即偏移量

这样划分好各自的势力范围之后,这些值的作用就比较明确了。当我们要变换颜色值的时候,通常有两种办法,一个是直接改变颜色的offset,即偏移量的值来修改颜色分量,另一个方法是直接改变对应RGBA值的系数来调整颜色分量的值。

1)改变偏移量

从前面的分析中,可以知道要修改R1的值,只需要将第五列的值进行修改即可,即改变颜色的偏移量,其他值保存初始矩阵的值。
Android群英传学习——第六章、Android绘图机制与处理技巧_第6张图片

在这个矩阵中,我们修改了R、G对应的颜色偏移量,所以最后的处理结构就是图像中的红色、绿色分量增加了100。  红色混合绿色会得到黄色,所以最终的颜色处理结果就是让整个图像的色调偏黄色。

2)改变颜色系数

如果修改颜色分量中的某个系数值,而其他值依然保存初始矩阵的值
Android群英传学习——第六章、Android绘图机制与处理技巧_第7张图片

这个矩阵改变了G分量对应的系数g,这样在矩阵运算后G分量会变为以前的两倍,最终效果就是图像的色调更加偏绿。

3)改变色光属性

图像的色调、饱和度、亮度这三个属性在图像处理中的使用非常多。因此,在颜色矩阵中,也封装了一些API来快速调整这些参数,而不用每次都去计算矩阵的值。

ColorMatrix即颜色矩阵,可以很方便的通过改变矩阵值来处理颜色效果。创建一个ColorMatrix对象非常简单,代码如下:
 ColorMatrix colorMatrix = new ColorMatrix();

下面来处理不同的色光属性。
● 色调

Android系统提供了setRotate(int axis,float degree)来帮助我们设置颜色的色调。  第一个参数,系统分别使用0、1、2来代表Red、Green、Blue三种颜色的处理;第二个参数,就是需要处理的值:
ColorMatrix hueMatrix = new ColorMatrix();hueMatrix.setRotate(0,hue0);hueMatrix.setRotate(1,hue1);hueMatrix.setRotate(2,hue2);
通过这样的方法,可以为RGB三种颜色分量分别重新设置了不同的色调值。

● 饱和度

Android系统提供了setSaturation(float sat)方法来设置颜色的饱和度,参数即代表设置颜色饱和度的值:
ColorMatrix saturationMatrix = new ColorMatrix(); saturationMatrix.setSaturation(saturation);

● 亮度

当三原色以相同的比例进行混合的时候,就会显示出白色。系统也正是使用这个原理来改变一个图像的亮度的:
ColorMatrix lumMatrix = new ColorMatrix();lumMatrix.setScale(lum,lum,lum,1);

除了单独使用上面三种方式来进行颜色效果的处理之外,Android系统还封装了矩阵的乘法运算。它提供了postConcat()方法来将矩阵的作用效果混合,从而叠加处理效果:

ColorMatrix imageMatrix = new ColorMatrix();imageMatrix.postConcat(hueMatrix);imageMatrix.postConcat(saturationMatrix);imageMatrix.postConcat(lumMatrix);

小例子——

在本例中,通过滑动三个SeekBar来改变不同的数值,并将这些数值作用到对应的矩阵中。最后通过postConcat()方法来显示混合后的处理效果:

Android群英传学习——第六章、Android绘图机制与处理技巧_第8张图片
Android群英传学习——第六章、Android绘图机制与处理技巧_第9张图片

布局代码:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <ImageView        android:id="@+id/mImage"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="4" />    <LinearLayout        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"        android:orientation="horizontal">        <TextView            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:gravity="center"            android:text="色调:"            android:textColor="#000"            android:textSize="23dp" />        <SeekBar            android:id="@+id/seekbarHue"            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="4" />    LinearLayout>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"        android:orientation="horizontal">        <TextView            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:gravity="center"            android:text="饱和度:"            android:textColor="#000"            android:textSize="23dp" />        <SeekBar            android:id="@+id/seekbarSaturation"            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="4" />    LinearLayout>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"        android:orientation="horizontal">        <TextView            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:gravity="center"            android:text="亮度:"            android:textColor="#000"            android:textSize="23dp" />        <SeekBar            android:id="@+id/seekbarLum"            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="4" />    LinearLayout>    <Button        android:id="@+id/btn"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"        android:padding="10dp"        android:text="原图"        android:textSize="25sp" />LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener, View.OnClickListener {    private ImageView mImage;    private SeekBar seekbarHue, seekbarSaturation, seekbarLum;    private Button btn;    Bitmap bitmap;    float mHue, mSaturation, mLum;    //SeekBar的中间值    int MID_VALUE = 127;    //SeekBar的最大值    int MAX_VALUE = 255;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initView();    }    private void initView() {        mImage = (ImageView) findViewById(R.id.mImage);        mImage.setImageResource(R.drawable.image01);        //调节色调        seekbarHue = (SeekBar) findViewById(R.id.seekbarHue);        //调节饱和度        seekbarSaturation = (SeekBar) findViewById(R.id.seekbarSaturation);        //调节亮度        seekbarLum = (SeekBar) findViewById(R.id.seekbarLum);        seekbarHue.setOnSeekBarChangeListener(this);        seekbarHue.setMax(MAX_VALUE);        seekbarHue.setProgress(MID_VALUE);        seekbarSaturation.setOnSeekBarChangeListener(this);        seekbarSaturation.setMax(MAX_VALUE);        seekbarSaturation.setProgress(MID_VALUE);        seekbarLum.setOnSeekBarChangeListener(this);        seekbarLum.setMax(MAX_VALUE);        seekbarLum.setProgress(MID_VALUE);        //恢复原图按钮        btn = (Button) findViewById(R.id.btn);        btn.setOnClickListener(this);    }    @Override    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {        switch (seekBar.getId()) {            case R.id.seekbarHue:                mHue =progress * 1.0F  / MID_VALUE;                break;            case R.id.seekbarSaturation:                mSaturation = progress * 1.0F  / MID_VALUE;                break;            case R.id.seekbarLum:                mLum =  progress * 1.0F  / MID_VALUE;                //很多人运用如下公式,但是我用了以后发现效果并不好呀!                //mLum = (progress - MID_VALUE) * 1.0F / MID_VALUE * 180;                break;        }        mImage.setImageBitmap(handleImageEffect(mHue, mSaturation, mLum));    }    @Override    public void onStartTrackingTouch(SeekBar seekBar) {    }    @Override    public void onStopTrackingTouch(SeekBar seekBar) {    }    public  Bitmap handleImageEffect(float hue, float saturation, float lum) {        //Android不允许直接修改原图        //必须通过原图创建一个同样大小的Bitmap,并将原图绘制到该Bitmap中,以一个副本的形式来修改图像        //代码中bitmap为原图        //bmp为创建的副本        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image01);        // Bitmap bmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888);        Bitmap bmp = bitmap.copy(Bitmap.Config.ARGB_8888,true);        Canvas canvas = new Canvas(bmp);        Paint paint = new Paint();        /**         * 设置色调         * int axis:RGB标志,0 代表 RED, 1 代表 GREEN, 2 代表BLUE         * 如果需要分别设置RGB的话,就需要调用三次,传入不同的 axis值         *         * float degree: 控制具体的颜色,这里取值范围并非0-255,系统用了角度计算颜色值,         *      所以取值的时候是以0-360为颜色取值范围,超出此范围,呈周期性变化         */        ColorMatrix hueMatrix = new ColorMatrix();        hueMatrix.setRotate(0, hue);        hueMatrix.setRotate(1, hue);        hueMatrix.setRotate(2, hue);        /**         * 设置饱和度         * float set:取值范围未知,         *  0 为灰度图,纯黑白, 1 为与原图一样,但是取值可以更大         */        ColorMatrix saturationMatrix = new ColorMatrix();        saturationMatrix.setSaturation(saturation);        /**         * 设置亮度         * 原理是光的三原色同比例混合最终效果为白色,因此在在亮度上将         *  RGB的值等比例混合,值给到足够大时,就会变成纯白效果,         *  同样,没有亮度的时候就是黑色         * float rScale:红         * float gScale:绿         * float bScale:蓝         * float aScale:透明度        * 取值范围未知,0时为纯黑,但是1时不一定纯白        */        ColorMatrix lumMatrix = new ColorMatrix();        lumMatrix.setScale(lum, lum, lum, 1);        /**         * postConcat(ColorMatrix colorMatrix)         * 将多个ColorMatrix效果混合         * 之前试过将饱和度,亮度,色调设置到同一个ColorMatrix对象里面,         *  从而可以不使用postConcat()方法混合多个ColorMatrix对象,         *  但是色调和亮度设置会失效,原因还没研究         */        ColorMatrix imageMatrix = new ColorMatrix();        imageMatrix.postConcat(hueMatrix);        imageMatrix.postConcat(saturationMatrix);        imageMatrix.postConcat(lumMatrix);        //这里需要注意的是,在设置号颜色矩阵        // 通过使用Paint类的setColorFilter()方法,将通过imageMatrix构造的ColorMatrixColorFilter        //对象传递进去,并使用这个画笔来绘制原来的图像,从而将颜色矩阵作用到原图中。        paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));        canvas.drawBitmap(bmp, 0, 0, paint);        return bmp;    }    @Override    public void onClick(View view) {        //恢复中间值        seekbarHue.setProgress(MID_VALUE);        seekbarSaturation.setProgress(MID_VALUE);        seekbarLum.setProgress(MID_VALUE);    }}

2、Android颜色矩阵——ColorMatrix

通过前面的分析,我们知道了调整颜色矩阵可以改变一幅图像的色彩效果,图像处理很大程度上就是在寻找图像的颜色矩阵。不仅仅可以通过Android系统提供的API来进行ColorMatrix的修改,同样可以精确地修改矩阵的值来实现颜色效果的处理。

下面我们模拟一个4*5的颜色矩阵。

Android群英传学习——第六章、Android绘图机制与处理技巧_第10张图片
改变颜色偏移量
Android群英传学习——第六章、Android绘图机制与处理技巧_第11张图片
改变颜色系数

布局代码

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <ImageView        android:id="@+id/imageView"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="3" />    <GridLayout        android:id="@+id/mGroup"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="3"        android:columnCount="5"        android:rowCount="4">    GridLayout>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1.5"        android:orientation="vertical">        <Button            android:id="@+id/btn_change"            android:layout_width="match_parent"            android:layout_height="0dp"            android:layout_weight="1"            android:padding="10dp"            android:text="Change"            android:onClick="btnChange"            android:textSize="20sp" />        <Button            android:id="@+id/btn_reset"            android:layout_width="match_parent"            android:layout_height="0dp"            android:layout_weight="1"            android:padding="10dp"            android:onClick="btnReset"            android:text="Reset"            android:textSize="20sp" />    LinearLayout>LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {    private Bitmap bitmap;    private ImageView mImageView;    private GridLayout mGroup;    private EditText [] mEts = new EditText[20];    private int mEtWidth,mEtHeight;    private float[] mColorMatrix = new float[20];    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.image01);        mImageView = (ImageView) findViewById(R.id.imageView);        mGroup = (GridLayout) findViewById(R.id.mGroup);        mImageView.setImageBitmap(bitmap);        mGroup.post(new Runnable() {            @Override            public void run() {                //获取宽高信息                mEtWidth = mGroup.getWidth()/5;                mEtHeight = mGroup.getHeight()/4;                addEts();                initMatrix();            }        });    }    //添加EditText    private void addEts(){        for(int i = 0;i < 20;i++){            EditText editText = new EditText(MainActivity.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));            }        }    }    //获取矩阵值    private void getMatrix(){        for(int i = 0 ;i < 20;i++){            mColorMatrix[i] = Float.valueOf(mEts[i].getText().toString());        }    }    //将矩阵值设置到图像    private void setImageMatrix(){        Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),                Bitmap.Config.ARGB_8888);        android.graphics.ColorMatrix colorMatrix = new android.graphics.ColorMatrix();        colorMatrix.set(mColorMatrix);        Canvas canvas = new Canvas(bmp);        Paint paint = new Paint();        paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));        canvas.drawBitmap(bitmap,0, 0, paint);        mImageView.setImageBitmap(bmp);    }    /**     * 作用点击事件     */    public void btnChange(View view) {        getMatrix();        setImageMatrix();    }    /**     * 重置矩阵效果     */    public void btnReset(View view) {        initMatrix();        getMatrix();        setImageMatrix();    }}

3、常用图像颜色矩阵处理效果

这一部分展现一些比较经典、常用的颜色处理效果对应的颜色矩阵。

1)灰度效果

这里写图片描述

2)图像反转

这里写图片描述

3)怀旧效果

这里写图片描述

4)去色效果

这里写图片描述

5)高饱和度

这里写图片描述

4、像素点分析

作为更加精确的图像处理方式,可以通过改变每个像素点的具体ARGB值,达到处理一张图片效果的目的,这里要注意的是,传递进来的原始图片是不能修改的,一般根据原始图片生成一张新的图片来修改

在Android中,系统系统提供了Bitmap.getPixels()方法来帮我们提取整个Bitmap中的像素密度点,并保存在一个数组中,该方法如下:
bitmap.getPixels(pixels, offset, stride,x, y,width, height);

这几个参数的具体含义如下:

pixels ——接收位图颜色值的数组,offset——写入到pixels[]第一个像素索引值,stride——pixels[]中的行间距x——从位图中读取的第一个像素的x坐标y——从图中读取的第一个像素的的y坐标width——从每一行读取的像素宽度height——读取的行数

通常情况下,可以使用如下代码:

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);

5、常用图像像素点处理效果

1)底片效果

若存在A,B,C三个像素点,要求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;    }

2)老照片效果

 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);

3)浮雕效果

要求某像素点的对应的浮雕效果算法:
B.r = C.r - B.r + 127;B.g = C.g - B.g + 127;B.b = C.b - B.b + 127;

六、Android图像处理之图形特效处理

前面我们了解了关于图像色彩处理的相关技巧,下面继续学习图形图像方面的处理技巧。

1、Android变形矩阵——Matrix

对于图像的图形变换,Android系统也是通过矩阵来进行处理的,每个像素点都表达了其坐标的X、Y信息。Android的图形变换矩阵是一个3*3的矩阵。如图:
Android群英传学习——第六章、Android绘图机制与处理技巧_第12张图片

当使用变换矩阵去处理每一个像素点的时候,与颜色矩阵的矩阵乘法一样,计算公式如下所示:
X1 = a*X+b*Y+cY1 = d(X+e*Y+f1 = g*X+h*Y+i
通常情况下,会让g=h=0,i=1,这样使1 = g*X+h*Y+i恒成立。因此,只需要着重关注上面几个参数就可以了。与色彩变换矩阵的初始矩阵一样,图形变换矩阵也有一个初始矩阵:

Android群英传学习——第六章、Android绘图机制与处理技巧_第13张图片
图像的变形处理通常包含以下四类基本变换:
● Translate——平移变换
● Rotate ——旋转变换
● Scale——缩放变换
● Skew——错切变换

1)平移变换

平移变换的坐标值变换过程如图,即将每个像素点都进行平移变换:

Android群英传学习——第六章、Android绘图机制与处理技巧_第14张图片

当从p(x0,y0)平移到p(x,y)时,坐标值发生了如下所示的变换:
X = X0 + △XY = Y0 + △Y
这也就是前面所说的实现平移过程的平移公式。

2)旋转变换

旋转变换即指一个点围绕一个中心旋转到一个新的点,如图:

Android群英传学习——第六章、Android绘图机制与处理技巧_第15张图片

当从P(x0,y0)点,以坐标原点为旋转中心旋转到P(x,y)点时,可以将点的坐标都表达成OP与X轴正方向夹角的函数表达式:

Android群英传学习——第六章、Android绘图机制与处理技巧_第16张图片

通过计算,可以还原以上等式,下图所示矩阵也就是旋转变换矩阵。

Android群英传学习——第六章、Android绘图机制与处理技巧_第17张图片

前面是以坐标原点为旋转中心的旋转变换,如果以任意一点O为旋转中心来进行旋转变换,通常需要以下三个步骤:

●  将坐标原点平移到O点。●  使用前面讲的以坐标原点为中心的旋转方法进行旋转变换●  将左边原点还原

通过以上三个步骤,实现了以任意点为旋转中心的旋转变换。

3)缩放变换

一个像素点是不存在缩放的概念的,但是由于图像是由很多个像素点组成的,如果将每个点的坐标都进行相同比例的缩放,最终就会形成让整个图像缩放的效果,缩放效果的计算公式如下:

x = K1 * x0;y = K2 * y0;
写成矩阵形式,如下图:

Android群英传学习——第六章、Android绘图机制与处理技巧_第18张图片
通过计算就可以还原到以上等式。

4)错切变换

错切变换(skew)在数学上又称为Shear mapping(剪切变换)或者Transvection(缩并),它是一种比较特殊的线性变换。错切变换的效果就是让所有点的X坐标(或者Y坐标)保持不变,而对应的Y坐标(或者X坐标)则按比例发生平移,且平移的大小和该点到X轴(或Y轴)的垂直距离成正比。

错切变换通常包含两种——水平错切与垂直错切:

Android群英传学习——第六章、Android绘图机制与处理技巧_第19张图片

错切变换的计算公式如下:
x = x0 + K1 * y0;y = K2 * x0 + y0;

可以发现,矩阵中的a,b,c,d,e,f这六个矩阵元素分别对应以下变换:

● a和e控制Scale——缩放变换● b和d控制Skew——错切变换● c和f控制Trans——平移变换● a,b,d,e共同控制Rotate——旋转变换

了解了矩阵变换规律后,通过类似色彩矩阵中模拟矩阵的例子来模拟一下变形矩阵。同样通过一个一维数组来模拟矩阵,并通过setValues()方法将一个一维数组转换为图形变换矩阵:

private float [] mImageMatrix = new float[9];Matrix matrix = new Matrix();matrix.setValues(mImageMatrix)

得到了变换矩阵后,就可以通过以下代码将一个图像以这个变换矩阵的形式绘制出来。

canvas.drawBitmap(mBitmmap,matrix,null);

与色彩矩阵一样,Android系统同样提供了一些API来简化矩阵的运算,它使用Matrix类来封装矩阵,并提供了以下几个操作方法来实现上面的四种变换方式。

● matriX.setRoatate()——旋转变换● matriX.setTranslate()——平移变换● matriX.setScale()——缩放变换● matriX.setSkew()——错切变换● pre()和post()——提供矩阵的前乘和后乘运算

我们和上一篇色彩处理的例子一样,做一个图形矩阵,直观的看到图像变换的原理:
Android群英传学习——第六章、Android绘图机制与处理技巧_第20张图片

方便起见,只放一个平移的效果:

Android群英传学习——第六章、Android绘图机制与处理技巧_第21张图片

布局代码:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <ImageView        android:id="@+id/imageView"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="3" />    <GridLayout        android:id="@+id/mGroup"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="3"        android:columnCount="3"        android:rowCount="3">    GridLayout>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"        android:orientation="horizontal">        <Button            android:id="@+id/btn_change"            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:padding="10dp"            android:text="Change"            android:onClick="btnChange"            android:textSize="20sp" />        <Button            android:id="@+id/btn_reset"            android:layout_width="0dp"            android:layout_height="match_parent"            android:layout_weight="1"            android:padding="10dp"            android:onClick="btnReset"            android:text="Reset"            android:textSize="20sp" />    LinearLayout>LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {    private Bitmap bitmap;    private ImageView mImageView;    private GridLayout mGroup;    private EditText[] mEts = new EditText[9];    private int mEtWidth,mEtHeight;    private float[] mImageMatrix = new float[9];    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);        mImageView = (ImageView) findViewById(R.id.imageView);        mGroup = (GridLayout) findViewById(R.id.mGroup);        mImageView.setImageBitmap(bitmap);        mGroup.post(new Runnable() {            @Override            public void run() {                //获取宽高信息                mEtWidth = mGroup.getWidth()/3;                mEtHeight = mGroup.getHeight()/3;                addEts();                initMatrix();            }        });    }    private void initMatrix() {        for(int i= 0;i<9;i++){            if(i % 4 ==0 ) {                mEts[i].setText(String.valueOf(1));            }else{                mEts[i].setText(String.valueOf(0));            }        }    }    private void addEts() {        for(int i = 0;i < 9;i++){            EditText editText = new EditText(MainActivity.this);            mEts[i] = editText;            mGroup.addView(editText,mEtWidth,mEtHeight);        }    }    //获取矩阵值    private void getMatrix(){        for(int i = 0 ;i < 9;i++){            mImageMatrix[i] = Float.valueOf(mEts[i].getText().toString());        }    }    //将矩阵值设置到图像    private void setImageMatrix(){        Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),                Bitmap.Config.ARGB_8888);        Matrix mMatrix = new Matrix();        mMatrix.setValues(mImageMatrix);        Canvas canvas = new Canvas(bmp);        canvas.drawBitmap(bitmap, mMatrix, null);        mImageView.setImageBitmap(bmp);    }    /**     * 作用点击事件     */    public void btnChange(View view) {        getMatrix();        setImageMatrix();    }    /**     * 重置矩阵效果     */    public void btnReset(View view) {        initMatrix();        getMatrix();        setImageMatrix();    }}

2、像素块分析

在进行图像的特效处理时有两种方式,即前面讲的使用矩阵来进行图像变换和我们马上要学习的drawBitmapMesh()方法来进行处理。drawBitmapMesh()与操纵像素点来改变色彩的原理类似,只不过是把图形分成了一个个的小块,然后通过改变每一个图像块来修改整个图像。

该方法代码如下:
drawBitmapMesh(Bitmap bitmap,int meshWidth,int meshHeight,float [] verts,int vertOffset,int [] colors,int colorOffset,Paint paint)

这个方法的参数很多,关键的参数如下:

●bitmap:将要扭曲的图像。●meshWidth:需要的横向网格数目。●meshHeight:需要的纵向网格数目。●verts:网络交叉点坐标数组。●vertOffset:verts数组中开始跳过的(x,y)坐标对的数目。

其中最重要的参数是一个数组——verts。
在图像上横纵各画N-1条线,将图像分成N块,而这横纵各N条线交织成了N*N个点,而每个点的坐标则以x1,y1,x2,y2…….xn,yn的形式保存在verts数组中。而整个drawBitmapMesh()方法改变图像的方式,就是靠这些坐标值的改变来重新定位每一个图像块,从而达到图像效果处理的功能。

下面我们使用drawBitmapMesh()方法来实现一个随点击让画面呈现曲面的效果。想要达到这样的效果,只需要让图片中每个交织点的横坐标较之前坐标不发生变化,而纵坐标较之前呈现一个三角函数的周期性变化。

效果图:

Android群英传学习——第六章、Android绘图机制与处理技巧_第22张图片

ImageChange.java

public class ImageChange extends View {    Bitmap bitmap;    //定义两个常量,这两个常量指定该图片横向20格,纵向上都被划分为10格    private final int WIDTH = 20;    private final int HEIGHT = 10;    //记录该图像上包含的231个顶点    private final int COUNT = (WIDTH +1) * (HEIGHT + 1);    //定义一个数组,记录Bitmap上的21*11个点的坐标    private  final  float[] verts = new float[COUNT * 2];    //定义一个数组,记录Bitmap上的21*11个点经过扭曲后的坐标    //对图片扭曲的关键就是修改该数组里元素的值    private  final  float[] orig = new float[COUNT * 2];    public ImageChange(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init();    }    private void init() {        bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.test);        float bitmapWidth = bitmap.getWidth();        float bitmapHeight = bitmap.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] = fy + 100;                index += 1;            }        }    }    @Override    protected void onDraw(Canvas canvas) {        //对bitmap按verts数组进行扭曲        //从第一个点(由第5个参数0控制)开始扭曲        canvas.drawBitmapMesh(bitmap,WIDTH,HEIGHT,verts,0,null,0,null);    }    private void flagWave(float cx, float cy){        for(int i = 0; i < COUNT * 2; i += 2)        {            float dx = cx - orig[i + 0];            float dy = cy - orig[i + 1];            float dd = dx * dx + dy * dy;            //计算每个坐标点与当前点(cx,cy)之间的距离            float d = (float)Math.sqrt(dd);            //计算扭曲度,距离当前点(cx,cy)越远,扭曲度越小            float pull = 80000 / ((float)(dd * d));            //对verts数组(保存bitmap 上21 * 21个点经过扭曲后的坐标)重新赋值            if(pull >= 1)            {                verts[i + 0] = cx;                verts[i + 1] = cy;            }            else            {                //控制各顶点向触摸事件发生点偏移                verts[i + 0] = orig[i + 0] + dx * pull;                verts[i + 1] = orig[i + 1] + dx * pull;            }        }        //通知View组件重绘        invalidate();    }    public boolean onTouchEvent(MotionEvent event)    {        //调用warp方法根据触摸屏事件的坐标点来扭曲verts数组        flagWave(event.getX() , event.getY());        return true;    }}

MainActivity.java

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }}

七、Android图像处理之画笔特效处理

在前面的学习中,我们已经初步了解了一些常用 画笔属性,比如普通的画笔(Paint),带边框、填充的style,颜色(Color),宽度(StrokeWidth),抗锯齿(ANTI_ALIAS_FLAG)等,这些都是最基本的画笔属性。除此之外,还有各种各样专业的画笔工具,如记号笔、毛笔、蜡笔等,使用它们可以实现更加丰富的绘图效果。

1、PorterDuffXfermode

在开始学习之前,我们先看一张非常经典的图,出自API Demo,基本上所有讲PorterDuffDfermode的文章都会使用这张图作说明:
Android群英传学习——第六章、Android绘图机制与处理技巧_第23张图片

这里列举了16种PorterDuffXfermode,有点像数学中集合的交集、并集这样的概念。

16条Porter-Duff规则如下:

●1.PorterDuff.Mode.CLEAR——所绘制不会提交到画布上。●2.PorterDuff.Mode.SRC——显示上层绘制图片●3.PorterDuff.Mode.DST——显示下层绘制图片●4.PorterDuff.Mode.SRC_OVER——正常绘制显示,上下层绘制叠盖。●5.PorterDuff.Mode.DST_OVER——上下层都显示。下层居上显示。●6.PorterDuff.Mode.SRC_IN——取两层绘制交集。显示上层。●7.PorterDuff.Mode.DST_IN——取两层绘制交集。显示下层。●8.PorterDuff.Mode.SRC_OUT——取上层绘制非交集部分。●9.PorterDuff.Mode.DST_OUT——取下层绘制非交集部分。●10.PorterDuff.Mode.SRC_ATOP——取下层非交集部分与上层交集部分●11.PorterDuff.Mode.DST_ATOP——取上层非交集部分与下层交集部分●12.PorterDuff.Mode.XOR●13.PorterDuff.Mode.DARKEN●14.PorterDuff.Mode.LIGHTEN●15.PorterDuff.Mode.MULTIPLY●16.PorterDuff.Mode.SCREEN

下面做一个刮刮卡效果:

效果图:

Android群英传学习——第六章、Android绘图机制与处理技巧_第24张图片

CardImage.java

public class CardImage extends View {    Bitmap mBgBitmap,mFgBitmap;    Canvas mCanvas;    Paint mPaint;    Path mPath;    public CardImage(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        initCardImage();    }    private void initCardImage() {        mPaint = new Paint();        mPaint.setAlpha(0);  //将画笔的透明度设为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.image01);        mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(),mBgBitmap.getHeight(),Bitmap.Config.ARGB_8888);        mCanvas = new Canvas(mFgBitmap);        mCanvas.drawColor(Color.GRAY);    }    @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_MOVE:                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时还有一点需要注意,那就是最好在绘图时,将硬件加速关闭,因为有些模式并不支持硬件加速。

这个例子和下面的例子我都做在一个Demo里了,这里再放上布局文件和MainActivity:

布局文件

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <Button        android:id="@+id/btn_card"        android:layout_width="match_parent"        android:layout_height="80dp"        android:text="刮刮卡效果:"/>    <com.example.administrator.xfermodedemo1.CardImage        android:id="@+id/image_card"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:visibility="gone"/>    <Button        android:id="@+id/btn_shader"        android:layout_width="match_parent"        android:layout_height="80dp"        android:text="圆形图片效果"/>    <com.example.administrator.xfermodedemo1.ShaderImage        android:id="@+id/image_shader"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:visibility="gone"/>LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener {    Button btn_shader,btn_card;    ShaderImage image_shader;    CardImage image_card;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        init();    }    private void init() {        btn_shader = (Button) findViewById(R.id.btn_shader);        btn_shader.setOnClickListener(this);        btn_card = (Button) findViewById(R.id.btn_card);        btn_card.setOnClickListener(this);        image_shader = (ShaderImage) findViewById(R.id.image_shader);        image_card = (CardImage) findViewById(R.id.image_card);    }    @Override    public void onClick(View view) {        switch (view.getId()){            case  R.id.btn_shader:                image_shader.setVisibility(View.VISIBLE);                break;            case R.id.btn_card:                image_card.setVisibility(view.VISIBLE);                break;        }    }}

2、Shader

Shader又被称之为着色器、渲染器,它用来实现一系列的渐变、渲染效果。Android中的Shader包括以下几种。

●BitmapShader——位图Shader●LinearGradient——线性Shader●RadialGradient——光束Shader●SweepGradient——梯度Shader●ComposeShader——混合Shader

除了第一个Shader以外,其他的Shader都比较正常,实现了名副其实的渐变、渲染效果。而与其他的Shader所产生的渐变不同,BitmapShader产生的是一个图像,这有点像Photoshop中的图像填充渐变,它的作用就是通过Paint对画布进行指定Bitmap的填充,填充时有以下几种模式可以选择。

●CLAMP拉伸——拉伸的是图片最后的那一个像素、不断重复●REPEAT重复——横向、纵向不断重复●MIRROR镜像——横向不断翻转重复,纵向不断翻转重复

1)下面实现一个圆形图片的效果:

思路就是用一张图片创建一支具有图像填充功能的画笔,并使用这个画笔绘制一个圆形:

效果图:

Android群英传学习——第六章、Android绘图机制与处理技巧_第25张图片

ShaderImage.java

public class ShaderImage extends View {    Bitmap mBitmap;    BitmapShader mBitmapShader;    Paint mPaint;    public ShaderImage(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        initShaderImage();    }    private void initShaderImage() {        mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.image01);        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);        mPaint = new Paint();        mPaint.setShader(mBitmapShader);    }    @Override    protected void onDraw(Canvas canvas) {        canvas.drawCircle(mBitmap.getWidth()/2,mBitmap.getHeight()/2,mBitmap.getHeight()/2,mPaint);    }}

看一下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(350,200,200,mPaint);

Android群英传学习——第六章、Android绘图机制与处理技巧_第26张图片

2)LinearGradient

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);

3)实现图片倒影效果

效果图:

Android群英传学习——第六章、Android绘图机制与处理技巧_第27张图片

ReflectView.java

public class ReflectView extends View {    private Bitmap mSrcBitmap,mRefBitmap;    private Paint mPaint;    private PorterDuffXfermode mXfermode;    public ReflectView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        initRes(context);    }    private void initRes(Context context) {//将原图复制一份并进行翻转        mSrcBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.image01);        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));        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);    }    @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(mXfermode);        //绘制渐变效果矩形        canvas.drawRect(0,mSrcBitmap.getHeight(),mRefBitmap.getWidth(),mSrcBitmap.getHeight()*2,mPaint);        mPaint.setXfermode(null);    }}

3、PathEffect

首先来看一张比较直观的图,来了解一下什么是PathEffect.

Android群英传学习——第六章、Android绘图机制与处理技巧_第28张图片

PathEffect就是指,用各种笔触效果来绘制一个路径。图中展开的几种绘制PathEffect的方式,从上到下依次是:●没效果●CornerPathEffect——将拐角处变得圆滑,圆滑程度由参数决定。●DiscretePathEffect——这个效果使线段上产生许多杂点。●DashPathEffect——绘制虚线,用一个数组来设置各个点之间的间隔。●PathDashPathEffect——效果与DiscretePathEffect类似,不过它可以设置显示点的图形。●ComposePathEffect——组合PathEffect,将任意两种路径特性组合起来形成一个新的效果。

下面们来实现一下上图:

效果图

Android群英传学习——第六章、Android绘图机制与处理技巧_第29张图片

我这里的截图上没有显示全,因为我和上面的例子放到一起了,布局里有点放不下了。

ShowPath.java

public class ShowPath extends View {    Paint mPaint;    Path mPath;    PathEffect []mEffects;    public ShowPath(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        initPath();    }    private void initPath() {        //用随机数来生成一些随机的点,并形成一条路径        mPath = new Path();        mPath.moveTo(0,0);        for(int i = 0;i<= 30;i++){            mPath.lineTo(i * 35,(float)(Math.random() * 100));        }        mPaint = new Paint();    }    @Override    protected void onDraw(Canvas canvas) {        //通过不同的路径效果来绘制path        mEffects = new PathEffect[6];        mEffects[0] = null;        mEffects[1] = new CornerPathEffect(30);        mEffects[2] = new DiscretePathEffect(3.0F,5.0F);        mEffects[3] = new DashPathEffect(new float[]{20,10,5,10},0);        Path path = new Path();        path.addRect(0,0,8,8,Path.Direction.CCW);        mEffects[4] = new PathDashPathEffect(path,12,0,PathDashPathEffect.Style.ROTATE);        mEffects[5] = new ComposePathEffect(mEffects[3],mEffects[1]);        for(int i = 0 ; i < mEffects.length;i++){            mPaint.setPathEffect(mEffects[i]);            canvas.drawPath(mPath,mPaint);            //每绘制一个path,就将画布平移,从而将PathEffect依次绘制出来            canvas.translate(0,200);        }    }}

八、View之孪生兄弟——SurfaceView

1、SurfaceView与View的区别

Android系统提供了View进行绘图处理,View可以满足大部分的绘图需求,但在某些时候也心有余而力不足。我们知道,View通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的间隔时间为16ms,如果在16ms内完成View完成了你所需要执行的所有操作,那么用户在视觉上,就不会产生卡顿的感觉;而如果执行的操作逻辑太多,特别是需要频繁刷新的界面上,例如游戏界面,就会不断阻塞主线程,从而导致画面卡顿。

很多时候,在自定义View的Log中经常会看见如下所示警告。
“Skipped 47 frames!The application may be doing too much work on its main thread”
这些警告的产生,很多情况下就是因为在绘制过程中,处理逻辑太多造成的。

为了避免这一问题的产生,Andorid系统提供了SurfaceView组件来解决这个问题。SurfaceView可以说是View的孪生兄弟但它与View还是有所不同的,他们的主要区别体现在:

●View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新。●View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新。●View在绘图时没有使用双缓冲机制,而SurfaceView在底层实现机制中就已经实现了双缓冲机制。

总结一句话:如果你的自定义View需要频繁刷新,或者刷新时数据处理量比较大,那么就可以考虑使用SurfaceView来取代View了。

2、SurfaceView的使用

SurfaceView的使用虽然比View复杂,但是SurfaceView在使用时,有一套使用的模板代码,大部分的SurfaceView绘图操作都可以套用这样的模板代码来进行编写。

1)创建SurfaceView

创建自定义的SurfaceView继承自SurfaceView,并实现两个接口——SurfaceHolder.Callback和Runnable.
public class SurfaView extends SurfaceView implements SurfaceHolder.Callback,Runnable
通过实现这两个接口,就需要在自定义的SurfaceView中实现接口方法,对于SurfaceHolder.Callback方法,需实现如下方法:
//分别对应SurfaceView的创建、改变和销毁过程@Override    public void surfaceCreated(SurfaceHolder holder) {    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {    }
对于Runnable接口,需要实现run()方法,代码如下:
  @Override    public void run() {    }

2)初始化SurfaceView

在自定义的SurfaceView的构造方法中,需要对SuifaceView进行初始化。通常需要定义以下三个成员变量:
 //SurfaceHolder    private SurfaceHolder mHolder;    //用于绘制的Canvas    private Canvas mCanvas;     //子线程标志位    private boolean mIsDrawing;
初始化方法就是对SurfaceHolder进行初始化,通过以下代码来初始化一个SurfaceHolder对象,并注册SurfaceHolder的回调方法。
mHolder = getHolder();mHolder.addCallback(this);
另外两个成员变量——Canvas和标志位,Canvas用来绘图,而标志位则是用来控制子线程。

3)使用SurfaceView

通过SurfaceHolder对象的lockCanvas()方法,就可以获得当前的Canvas绘图对象。接下来,就可以与在View中进行的绘制操作一样进行绘制了。

这里需要注意,获取到的Canvas对象还是继续上次的Canvas对象,而不是一个新的对象。因此,之前的绘图操作都将被保留,若需擦除,可以在绘制钱,通过drawColor()方法进行清屏操作。

绘制时,充分利用SurfaceView的三个回调方法,在surfaveCreated()方法中开启子线程进行绘制,而子线程使用一个while(mIsDrawing)的循环来不停地进行绘制,而在绘制的具体逻辑中,通过lockCanvas()方法获得的Canvas对象进行绘制,并通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交。整个SurfaceView的模板代码如下:

public class SurfaceViewTest extends SurfaceView implements SurfaceHolder.Callback,Runnable {    //SurfaceHolder    private SurfaceHolder mHolder;    //用于绘图的Canvas    private Canvas mCanvas;    //子线程标志位    private boolean mIsDrawing;    public SurfaceViewTest(Context context, AttributeSet attrs) {        super(context, attrs);        initView();    }    private void initView() {        mHolder = getHolder();        mHolder.addCallback(this);        setFocusable(true);        setFocusableInTouchMode(true);        this.setKeepScreenOn(true);        //mHolder.setFormat(PixelFormat.OPAQUE);    }    @Override    public void surfaceCreated(SurfaceHolder surfaceHolder) {        mIsDrawing = true;        new Thread(this).start();    }    @Override    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {    }    @Override    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {        mIsDrawing = false;    }    @Override    public void run() {        while (mIsDrawing){            draw();        }    }    private void draw() {        try {            mCanvas = mHolder.lockCanvas();            //draw something        }catch (Exception e){        }finally {            if(mCanvas != null){                mHolder.unlockCanvasAndPost(mCanvas);            }        }    }}
以上代码基本满足大部分的SurfaceView绘图需求,唯一需注意的是在绘制方法中,将mHolder.unlockCanvasAndPost(mCanvas)方法放到finally代码块中,来保证每次都能将内容提交。

3、SurfaceView实例

1)正弦曲线

首先看一个类似示波器的例子,在界面上不断绘制一个正弦曲线。我们只需要不断修改横纵坐标的值,并让它们满足正弦曲线即可。因此,使用一个Path对象来保存正弦函数上的坐标点,在子线程的while循环中,不断改变横纵坐标值:

效果图

Android群英传学习——第六章、Android绘图机制与处理技巧_第30张图片

SurfaceViewTest.java

public class SurfaceViewTest extends SurfaceView implements SurfaceHolder.Callback,Runnable {    //SurfaceHolder    private SurfaceHolder mHolder;    //用于绘图的Canvas    private Canvas mCanvas;    //子线程标志位    private boolean mIsDrawing;    Paint mPaint;    Path mPath;    int x = 100,y =0;    public SurfaceViewTest(Context context, AttributeSet attrs) {        super(context, attrs);        initView();    }    private void initView() {        //初始化路径和画笔        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);        this.setKeepScreenOn(true);        //mHolder.setFormat(PixelFormat.OPAQUE);    }    @Override    public void surfaceCreated(SurfaceHolder surfaceHolder) {        mIsDrawing = true;        new Thread(this).start();    }    @Override    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {    }    @Override    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {        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();            //SurfaceView背景            mCanvas.drawColor(Color.WHITE);            mCanvas.drawPath(mPath,mPaint);        }catch (Exception e){        }finally {            if(mCanvas != null){                mHolder.unlockCanvasAndPost(mCanvas);            }        }    }}

2)绘图板

下面实现一个简单的绘图板,通过Path对象来记录手指滑动的路径来进行绘图。
 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);            }        }    }//在SurfaceView的onTouchEvent()中来记录Path路径。    @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;    }

一直到这里,这个实例与之前的实例都没有太大区别,但是我们现在需要在子线程的循环中进行优化。我们在子线程中进行sleep操作,尽可能的节省系统资源。

完整代码入下:

SurfaceViewTest2.java

public class SurfaceViewTest2 extends SurfaceView implements SurfaceHolder.Callback,Runnable {    //SurfaceHolder    private SurfaceHolder mHolder;    //用于绘图的Canvas    private Canvas mCanvas;    //子线程标志位    private boolean mIsDrawing;    Paint mPaint;    Path mPath;    int x = 100,y =0;    public SurfaceViewTest2(Context context, AttributeSet attrs) {        super(context, attrs);        initView();    }    private void initView() {        //初始化路径和画笔        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);        this.setKeepScreenOn(true);        //mHolder.setFormat(PixelFormat.OPAQUE);    }    @Override    public void surfaceCreated(SurfaceHolder surfaceHolder) {        mIsDrawing = true;        new Thread(this).start();    }    @Override    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {    }    @Override    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {        mIsDrawing = false;    }    @Override    public void run() {        long start = System.currentTimeMillis();        while (mIsDrawing){            draw();        }        long end = System.currentTimeMillis();        //50-100        if(end - start < 100){            try {                Thread.sleep(100 - (end - start));            }catch (InterruptedException e){                e.printStackTrace();            }        }    }    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);            }        }    }    @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;    }}

效果图:

Android群英传学习——第六章、Android绘图机制与处理技巧_第31张图片

妈呀,耗时几天~几天来着?!!快一周吧!终于把这章学完了。刚好半个月,书也看了一半儿了。现在的状态是马上就要转正了,很紧张啊,毕竟是事关工资的大事,这段时间对于公司项目的贡献实在不大,老大给我的机会太少了,我脸皮又太薄,所以还是有点尴尬的,好怕总监会劝退我(哭)。其实工作我是想多做点的,但是总是没机会。接下来就是希望能顺利转正,然后我也能更好更快的融入团队中,再辛苦都没关系,多做点工作,快点提升我的能力,这样我在同事面前说话也有底气了。下面半个月,还是要按时结束《Android群英传》这本书。

更多相关文章

  1. 使用点九图在Android Studio中实现与Axure设计图一致的阴影效果
  2. android仿网易云音乐引导页、仿书旗小说Flutter版、ViewPager切
  3. Android 中的ListView选中项的背景颜色怎么设置?
  4. Android 单独控件实现不同字体大小、不同颜色
  5. android学习笔记---59_各种图形的使用介绍,android炫酷效果的实
  6. android anim 动画效果 基础知识
  7. android win8效果实现进阶(一)
  8. 【Android UI】状态栏和toolbar颜色一致
  9. Android通过点击按钮改变Activity的背景颜色_个人笔记

随机推荐

  1. Android学习之系统默认路径
  2. animation of android (2)
  3. android其实很简单--内存相关
  4. Android获取屏幕分辨率及DisplayMetrics
  5. React-Native尝鲜计划-环境搭建及 hello w
  6. Android卡顿优化梳理
  7. 安卓Kotlin之小白翻译一
  8. Android输入法之输入系统
  9. 开发环境的搭建-----------Day01 2014-5-
  10. android之ImageView控件以及子类的学习