ldpi:mdpi:hdpi:xhdpi:xxhdpi=3:4:6:8:12。
DiaplayUtil:[https://github.com/rainyandsunny/AndroidUtil]
在xml中使用Bitmap非常简单,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_launcher"
/>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp"
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
/><!--圆角-->
<gradient
android:angle="45"
android:centerColor="@android:color/holo_green_light"
android:endColor="@android:color/holo_blue_bright"
android:gradientRadius="10"
android:startColor="@android:color/background_light"
android:type="linear"
android:useLevel="true"
/><!--渐变-->
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp"
/><!--内边距-->
<size
android:width="200dp"
android:height="100dp"
/><!--指定大小-->
<solid
android:color="@android:color/holo_red_dark"
/><!--填充颜色-->
<stroke
android:width="4dp"
android:color="@android:color/holo_blue_light"
android:dashWidth="4dp"
android:dashGap="0dp"
/><!--指定边框-->
</shape>
gradient定义该形状里面为渐变色填充,startColor起始颜色,endColor结束颜色,angle表示方向角度。当angle=0时,渐变色是从左向右。 然后逆时针方向转,当angle=90时为从下往上。
类似于图层的效果。
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/bitmap"
/>
<item
android:drawable="@drawable/bitmap"
android:left="30.0dp"
android:top="30.0dp"
android:right="30.0dp"
android:bottom="30.0dp"
/>
</layer-list>
selector的作用在于帮助开发者实现静态绘图中的点击事件反馈,通过给不同的事件设置不同的图像,从而在程序中根据用户输入,返回不同的效果。
Canvas作为绘制图形的直接对象,提供了以下几个非常有用的方法。
Canvas.save(): 作用就是将之前的所有已绘制图像保存起来,让后续的操作就好像在一个新的图层上操作一样。
Canvas.restore(): 将我们在save()之后绘制的所有图像与save()之前的图像进行合并。
Canvas.translate():将画布原点平移至(x,y)
Canvas.rotate(): 画布旋转
一张复杂的画可以由很多个图层叠加起来,形成一个复杂的图像。在Android中,使用saveLayer()方法来创建一个图层,图层同样是基于栈的结构进行管理的。
Android通过调用saveLayerAlpha()、saveLayer()方法将一个图层入栈,使用restore()、restoreToCount()方法将一个图层出栈。入栈的时候,后面所有操作都发生在这个图层上,出栈的时候,则会把图像绘制到上层Canvas上。
色调——物体传播的颜色
饱和度——颜色的纯度,从0(灰)到100%(饱和)
亮度——颜色的相对明暗程度
在Android中,系统使用一个颜色矩阵——ColorMatrix,来处理图像的这些色彩效果。Android中的颜色矩阵是一个4*5的数字矩阵,它用来对图片的色彩进行处理。而对于每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值。
颜色矩阵如下:
矩阵乘法运算如下:
R1 = a*R + b*G + c*B + d*A + e;
G1 = f*R + g*G + h*B + i*A + j;
B1 = k*R + 1*G + m*B + n*A + o;
A1 = p*R + q*G + r*B + s*A + t;
由运算我们知道:
第一行的abcde值用来决定新的颜色值中的R——红色
第二行的fghij值用来决定新的颜色值中的G——绿色
第三行的klmno值用来决定新的颜色值中的B——蓝色
第四行的pqrst值用来决定新的颜色值中的A——透明度
矩阵A中的第五列——ejot值分别用来决定每个分量中的offset,即偏移量、
从上面可以知道,只需要将第五列的值进行修改即可。即可改变颜色的偏移量,其他值保存初始矩阵的值。
如果修改颜色分量重的某个系数值,而其他值依然保存初始矩阵的值。
Android中,系统封装了一个类——ColorMatrix,也就是前面说的颜色矩阵。
Android系统提供了setRotate(in 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);
Android系统提供了setSaturation(float sat)方法来设置颜色的饱和度,参数即代表设置颜色饱和度的值,当饱和度为0时,图像就变成灰度图像。
ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation);
当三原色以相同的比例进行混合的时候,就会显示出白色。系统也是利用这个原理来改变一个图像的亮度的,代码如下。当亮度为0时,图像就变为全黑了。
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);
在设置好处理的颜色矩阵后,通过使用Paint类的setColorFilter()方法,将通过imageMatrix构造的ColorMatrixColorFilter对象传递进去
,并使用这个画笔来绘制原来的图像,从而将颜色矩阵作用到原图中。
paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bm,0,0,paint);
Android系统不允许直接修改原图,必须通过原图创建一个同样大小的Bitmap,并将原图绘制到该Bitmap中,以一个副本的形式来修改图像。
如下:
Bitmap bmp = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(),Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
canvas.drawBitmap(bm,0,0,paint);
其中bm为原图,bmp为创建的副本。
灰度效果
0.33F,0.59F,0.11F,0,0,
0.33F,0.59F,0.11F,0,0,
0.33F,0.59F,0.11F,0,0,
0,0,0,1,0
图像反转
-1,0,0,1,1,
0,-1,0,1,1,
0,0,-1,1,1,
0,0,0,1,0,
怀旧效果
0.393F,0.769F,0.189F,0,0,
0.349F,0.686F,0.168F,0,0,
0.272F,0.534F,0.131F,0,0,
0,0,0,1,0
去色效果
1.5F,1.5F,1.5F,0,-1,
1.5F,1.5F,1.5F,0,-1,
1.5F,1.5F,1.5F,0,-1,
0,0,0,1,0
高饱和度
1.438F,-0.122F,-0.016F,0,-0.03F,
-0.062F,1.378F,-0.016F,0,0.05F,
-0.062F,-0.122F,1.483F,0,-0.02F,
0,0,0,1,0
作为更加精确的图像处理方式,可通过改变每个像素点的具体ARGB值,来达到处理一张图像效果的目的。这里要注意的是,传递进来的原始图片是不能修改的(mutable),一般根据原始图片生成一张新的图片来修改。
在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,bm.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);
然后就可以对各个像素进行相应的处理了:
r1 = (int)(0.393*r + 0.769*g + 0.189*b);
g1 = ...
b1 = ...
在通过如下所示代码将新的RGBA值合成像素点:
newPx[i] = Color.argb(a,r1,g1,b1);
最后将新的处理之后的像素点数组重新set给我们的bitmap,达到像素处理的目的。
bmp.setPixels(newPx,0,width,0,0,width,height);
若存在B像素点,要求对B点对应的底片效果算法,代码如下:
B.r = 255 - B.r;
B.g = 255 - B.g;
B.b = 255 - B.b;
求某像素点的老照片效果算法,代码如下:
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);
若存在ABC3个像素点,要求B点对应的浮雕效果算法。代码如下:
B.r = C.r - B.r + 127;
B.g = C.g - B.g + 127;
B.b = C.b - B.b + 127;
Android的图形变换矩阵是一个3*3的矩阵:
假设点P(x0,y0),其与X轴正方向夹角为α,那么:
x0 = rcosα
y0 = rsinα
以坐标原点为旋转中心旋转到P(x,y),则:
x = rcos(α+θ) = rcosαcosθ - rsinαsinθ = x0cosθ - y0sinθ
y = rsin(α+θ) = rsinαcosθ + rcosαsinθ = y0cosθ + x0sinθ
x = k1*x0
y = k2+y0
水平错切:
垂直错切:
错切变换的计算公式为:
x = x0 + K1*y0;
y = K2*x0 + y0;
在图形变换矩阵中,同样是通过一个一维数组来模拟矩阵,并通过setValues()方法将一个一维数组转换为图形变换矩阵。
float[] mImageMatrix = new float[9];
Matrix matrix = new Matrix();
matrix.setValues(mImageMatrix);
获得了变换矩阵后,就可以通过以下代码将一个图像以这个变换矩阵的形式绘制出来:
canvas.drawBitmap(mBitmap,matrix,null);
Android中使用Matrix类来封装矩阵,并提供了以下几个方法来实现上面的几种变换方式:
matrix.setRotate()——旋转变换
matrix.setTranslate()——平移变换
matrix.setScale()——缩放变换
matrix.setSkew()——错切变换
pre()和post()——提供矩阵的前乘和后乘运算
Matrix类的set方法会重置矩阵中的所有值,而post和pre方法不会,这两个方法常用来。
后乘运算: 当前矩阵乘上参数代表的矩阵。
前乘运算: 参数代表的矩阵乘上当前矩阵。
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)坐标对的数目。
这个方法将图片分割为若干个图像块,靠改变网格交叉点的坐标值改变来重新定位每一个图像块,从而达到图像效果处理的功能。但是使用起来也非常复杂,关键在于计算,确定新的交叉点的坐标。
PorterDuffXfermode设置的是两个图层交集区的显示方式,dst是先画的图形,而src是后画的图形。
SRC :只绘制源图像
DST :只绘制目标图像
DST_OVER :在源图像的顶部绘制目标图像
DST_IN :只在源图像和目标图像相交的地方绘制目标图像
DST_OUT :只在源图像和目标图像不相交的地方绘制目标图像
DST_ATOP :在源图像和目标图像相交的地方绘制目标图像,在不相交的地方绘制源图像
SRC_OVER :在目标图像的顶部绘制源图像
SRC_IN :只在源图像和目标图像相交的地方绘制源图像
SRC_OUT :只在源图像和目标图像不相交的地方绘制源图像
SRC_ATOP :在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像
XOR :在源图像和目标图像重叠之外的任何地方绘制他们,而在不重叠的地方不绘制任何内容
LIGHTEN :获得每个位置上两幅图像中最亮的像素并显示
DARKEN :获得每个位置上两幅图像中最暗的像素并显示
MULTIPLY :将每个位置的两个像素相乘,除以255,然后使用该值创建一个新的像素进行显示。结果颜色=顶部颜色*底部颜色/255
SCREEN :反转每个颜色,执行相同的操作(将他们相乘并除以255),然后再次反转。结果颜色=255-(((255-顶部颜色)*(255-底部颜色))/255)
圆角,圆形图片处理,代码见:
https://github.com/rainyandsunny/AndroidUtil/tree/master/AndroidUtil/app/src/main/java/com/swjtu/deanstar/util
Shader又被称为着色器,渲染器,它用来实现一系列的渐变、渲染效果。Android中的Shader包括以下几种。
除了第一个Shader以外,其他的Shader都比较正常,实现了真正的渐变、渲染效果。BitmapShader产生的是一个图像。它的作用就是通过Paint对画布进行制定Bitmap的填充,填充时有以下几种模式:
CLAMP——拉伸的是图片最后的那一个像素,不断重复
REPEAT重复——横向、纵向不断重复
MIRROR镜像——横向不断翻转重复,纵向不断翻转重复
例如,我们可以用CLAMP来画出圆角图片等。
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.timg,null);
Paint mPaint = new Paint();
Bitmap out = Bitmap.createBitmap(bitmap.getWidth()
,bitmap.getHeight(),Bitmap.Config.ARGB_8888);
BitmapShader mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
mPaint.setShader(mBitmapShader);
Canvas canvas = new Canvas(out);
canvas.drawCircle(500,250,200,mPaint);
将拐角处变得圆滑
使用这个效果,线段上就会出现很多杂点。
绘制虚线。用一个数组来设置各个点之间的间隔,另一个参数phase则用来控制绘制时数组的一个偏移量。
和DashPathEffect类似,它的功能更强大,可以设置显示点的图形,即方形点的虚线,圆形点的虚线。
通过ComposePathEffect来组合PathEffect,这个方法的功能就是将路径特性组合起来形成一个新的效果。
依次为:无效果、CornerPathEffect、DiscretePathEffect、PathDashPathEffect,ComposePathEffect(CornerPathEffect,DashPathEffect)。
View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新。
View 在主线程中对画面进行刷新,而SurfaceView通过一个子线程来进行画面的刷新。
View在绘图时没有使用双缓冲机制,而SurfaceView在底层实现中就已经实现了双缓冲机制。
SurfaceView的使用通常可以使用下面的模板:
创建自定义的SurfaceView继承自SurfaceView,并实现两个接口——SurfaceHolder.Callback和Runnable。
public class SurfaceViewTemplate 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){}
对于Runnable接口,实现run()方法。
在自定义SurfaceView的构造方法中,需要对SurfaceView进行初始化,在自定义的SurfaceView中,通常需要定义以下三个成员变量,代码如下:
//SurfaceHolder
private SurfaceHolder mHolder;
//用于绘图的Canvas
private Canvas mCanvas;
//子线程标志位
private boolean mIsDrawing;
初始化方法就是对SurfaceHolder进行初始化,通过以下代码来初始化一个SurfaceHolder对象,并注册SurfaceHolder的回调方法。
mHolder = getHolder();
mHolder.addCallback(this);
通过SurfaceHolder对象的lockCanvas()方法,就可以获得当前Canvas绘图。接下来,就可以与在View中进行的绘制操作一样进行绘制了。获取到的Canvas对象还是继续上次的Canvas对象,而不是一个新的对象。因此之前的操作都将被保留,如果需要擦除,则可以在绘制前,通过drawColor()方法来进行清屏操作。
绘制的时候,充分利用SurfaceView的三个回调方法,在surfaceCreated()方法中开启子线程进行绘制,而子线程使用一个while(mIsDrawing)的循环来不停地进行绘制,而在绘制的具体逻辑中,通过lockCanvas()方法获得的Canvas对象进行绘制,并通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交。
SurfaceView的使用示例代码见: