总结
首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。
另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。
之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。
下一步还是要查漏补缺,进行针对性复习。
最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!希望大家不要犯和我一样的错误呀!!!一定要看完!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
canvas.drawBitmap(splitBitmap, width, height, null);
return bitmap;
}
拼接图片前先创建了一个宽高都为splitBitmap宽高2倍的Bitmap作为容器,然后通过Canvas在容器的四个位置绘制4张一样的图片,最终效果如下。
![](https://img-blog.csdnimg.cn/e5751a6fddb545408c4ac6f6e36a5bae.png#pic_center)
##### 2.3 矩阵变换
Bitmap是像素点的集合,我们可以通过矩阵运算改变每个像素点的位置,达到图形变换的效果。Android中可以通过Matrix类来进行变换,Matrix本身是一个3x3的矩阵,可以通过`Matrix m = new Matrix()`新建一个单位矩阵,原始矩阵的值如下所示。
[1 0 0]
[0 1 0]
[0 0 1]
Matrix中各个位置的变换信息如下所示,scale表示缩放,skew表示错切,trans表示平移,persp等表示透视参数。Bitmap中的每个像素点可以使用一个3x1的矩阵表示,其中x表示当前像素点的横坐标,y表示纵坐标。用该矩阵左乘Bitmap中的所有像素后,就能得到变换后的图像。
[scaleX skewX transX] [x] [scaleX * x + skewX * y + transX]
[skewY scaleY transY] x [y] = [skewY * x + scaleY * y + transY]
[persp0 persp1 persp2] [1] [persp0 * x + persp1 * y + persp2]
简单来说,Matrix是一个容器,保存了用户期望的矩阵变换信息。在将Matrix应用于Bitmap之前,我们可以对其进行各种操作,将变换信息保存进去。矩阵运算可以实现平移、旋转、缩放、错切,因此Matrix也为提供了类似方法。
setTranslate(float dx,float dy): 控制 Matrix 进行位移。
setSkew(float kx,float ky): 控制 Matrix 进行倾斜,kx、ky为X、Y方向上的比例。
setSkew(float kx,float ky,float px,float py): 控制 Matrix 以 px, py 为轴心进行倾斜,kx、ky为X、Y方向上的倾斜比例
setRotate(float degrees): 控制 Matrix 进行 depress 角度的旋转,轴心为(0,0)
setRotate(float degrees,float px,float py): 控制 Matrix 进行 depress 角度的旋转,轴心为(px,py)
setScale(float sx,float sy): 设置 Matrix 进行缩放,sx, sy 为 X, Y方向上的缩放比例。
setScale(float sx,float sy,float px,float py): 设置 Matrix 以(px,py)为轴心进行缩放,sx、sy 为 X、Y方向上的缩放比例
很多时候矩阵变换并不是单一的平移、旋转或缩放,这些变换经常结合在一起使用,此时`setXXX()`方法无法满足要求。因此Matrix提供了`preXXX()`和`postXXX()`方法来组合多个矩阵操作,多个矩阵操作之间通过乘法运算。由于矩阵的乘法是不满足交换律的,因此进行矩阵运算时需要注意乘法的顺序。
在使用`preXXX()`和`postXXX()`时,我们可以将矩阵变换的所有计算看成一个乘法列表,调用`preXXX()`方法时就是向列表头部添加操作,调用`postXXX()`时就是向列表尾部添加操作。例如以下代码中矩阵乘法的执行顺序就是2->1->3->4。需要注意的是,`setXXX()`方法会重置Matrix的变换,如果对下方执行完4个运算的Matrix调用`setTranslate()`方法,那么该Matrix就只有平移效果了。
Matrix matrix = new Matrix();
matrix.preScale(…); // 1
matrix.preTranslate(…); // 2
matrix.postTranslate(…); // 3
matrix.postRotate(…); // 4
##### 2.4 颜色变换
颜色变换主要通过ColorFilter进行,通过`Paint.setColorFilter(ColorFilter filter)`可以设置颜色过滤器,该过滤器会对每一个像素的颜色进行过滤,得到最终的图像。ColorFilter有3个子类,这里主要介绍ColorMatrixColorFilter。
###### 2.4.1 ColorMatrixColorFilter
该颜色过滤器通过矩阵进行色彩变换,先来介绍一下色彩矩阵,Android中的色彩是以ARGB的形式存储的,我们可以通过ColorMatrix修改颜色的值,ColorMatrix定义了一个4x5的float矩阵,矩阵的4行分别表示在RGBA上的向量,其范围值在0f-2f之间,如果为1就是原效果。每一行的第5列表示偏移量,就是指在当前通道上增大或减小多少。
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
});
ColorMatrix与颜色之间的运算如下所示,其实就是矩阵运算,与上一节的Matrix类似。
[a, b, c, d, e] [R] [aR + bG + cB + dA + e]
[f, g, h, i, j] [G] [fR + gG + hB + iA + j]
[k, l, m, n, o] x [B] = [kR + lG + mB + nA + o]
[p, q, r, s, t] [A] [pR + qG + rB + sA + t]
[1]
有了ColorMatrix,就可以对Bitmap上所有的颜色进行修改,例如调整每个通道的值,将初始值1改为0.5,就可以将Bitmap变暗。不过我不太了解色彩,也没在项目中实际使用过,感兴趣的朋友可以看参考1。
##### 2.5 图像混合
图像混合是指对两张原始图像(我们称为DST和SRC)的内容按某种规则合成,从而形成一张包含DST和SRC特点的新图像。例如DST为圆形图像,SRC为照片,可以将它们合成为圆形照片。
Android通过PorterDuffXfermode实现图像混合,它实际上是通过公式对两张图像在Canvas上的所有像素进行ARGB运算,最终在每个像素点得到新的ARGB值。需要注意的是,在`onDraw(Canvas)`方法中进行图像混合时,先绘制的图像为DST,后绘制的图像为SRC,因此需要注意图像的绘制顺序。
PorterDuffXfermode一共提供了18种混合模式,它们的计算公式如下,Sa表示SRC的ALPHA通道,Sc表示SRC的颜色;Da表示DST的ALPHA通道,Dc表示DST的颜色。以CLEAR为例,该模式会清除SRC区域的所有内容。
| 合成模式 | 公式 |
| --- | --- |
| CLEAR | [0, 0] |
| SRC | [Sa, Sc] |
| DST | [Da, Dc] |
| SRC\_OVER | [Sa + (1 - Sa)\*Da, Rc = Sc + (1 - Sa)\*Dc] |
| DST\_OVER | [Sa + (1 - Sa)\*Da, Rc = Dc + (1 - Da)\*Sc] |
| SRC\_IN | [Sa \* Da, Sc \* Da] |
| DST\_IN | [Sa \* Da, Sa \* Dc] |
| SRC\_OUT | [Sa \* (1 - Da), Sc \* (1 - Da)] |
| DST\_OUT | [Da \* (1 - Sa), Dc \* (1 - Sa)] |
| SRC\_ATOP | [Da, Sc \* Da + (1 - Sa) \* Dc] |
| DST\_ATOP | [Sa, Sa \* Dc + Sc \* (1 - Da)] |
| XOR | [Sa + Da - 2 \* Sa \* Da, Sc \* (1 - Da) + (1 - Sa) \* Dc] |
| DARKEN | [Sa + Da - Sa \* Da, Sc \* (1 - Da) + Dc \* (1 - Sa) + min(Sc, Dc)] |
| LIGHTEN | [Sa + Da - Sa \* Da, Sc \* (1 - Da) + Dc \* (1 - Sa) + max(Sc, Dc)] |
| MULTIPLY | [Sa \* Da, Sc \* Dc] |
| SCREEN | [Sa + Da - Sa \* Da, Sc + Dc - Sc \* Dc] |
| ADD | Saturate(S + D) |
如果你使用过PorterDuffXfermode,你可能见过下面这张图,Android官方的样例就是这个效果,不过官方只提供了16种混合模式的样例,我在Demo中把ADD和OVERLAY也添加了进去。
![](https://img-blog.csdnimg.cn/6201b557d6694ff8b45538e83ab176aa.png#pic_center)
当然你也可能见过这张图。
![](https://img-blog.csdnimg.cn/f89b49526df641a4a521f35fb331d04b.png#pic_center)
乍一看,这两张图中的DST和SRC原始图像都是一样的,但是为什么使用了同样的混合模式后,显示的结果不同呢?
关键就在于DST和SRC的大小,第一张图中的DST和SRC都是Bitmap,它们的大小与Canvas相等,只是在Bitmap的某个区域绘制了圆和矩形。Demo代码如下,可以看到`makeDst()`和`makeSrc()`中创建的Bitmap与整个View(或者说Canvas)是相等的。这也解释了为什么第一张图的CLEAR模式下的结果是空白的,因为SRC的大小是整个View的大小,CLEAR模式表示清除SRC区域的内容,最终将整个View的内容清除了。
public class XFerModeView extends View {
private Paint mPaint;
private PorterDuffXfermode mPorterDuffXfermode;
private int mWidth;
private int mHeight;
// 省略构造方法......
private void init() {
setLayerType(LAYER_TYPE_SOFTWARE, null);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mWidth != w || mHeight != h) {
mWidth = w;
mHeight = h;
invalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
drawCompositionInFullSize(canvas);
canvas.restoreToCount(sc);
}
private void drawBackground(Canvas canvas) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.BLACK);
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
}
private void drawCompositionInFullSize(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
Bitmap dst = makeDst();
Bitmap src = makeSrc();
// 绘制DST
canvas.drawBitmap(dst, 0, 0, mPaint);
// 设置图像混合模式
mPaint.setXfermode(mPorterDuffXfermode);
// 绘制SRC
canvas.drawBitmap(src, 0, 0, mPaint);
// 清除图像混合模式
mPaint.setXfermode(null);
}
private Bitmap makeDst() {
Bitmap bm = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
mPaint.setColor(0xFFFFCC44);
c.drawOval(10, 10, mWidth * 3f / 4, mHeight * 3f / 4, mPaint);
return bm;
}
private Bitmap makeSrc() {
Bitmap bm = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
mPaint.setColor(0xFF66AAFF);
c.drawRect(mWidth * 1f / 3, mHeight * 1f / 3,
mWidth * 19f / 20, mHeight * 19f / 20, mPaint);
return bm;
}
在第二张图中,绘制SRC和DST时创建的图像大小就是圆或矩形的大小,最终的结果也与第一张图有所不同,修改后的代码如下。还是以CLEAR模式为例,此时清除的就只是矩形区域SRC的图像,可以看到DST中与SRC相交的部分被清除了。
public class XFerModeView extends View {
// 省略重复代码......
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
drawCompositionInSelfSize(canvas);
canvas.restoreToCount(sc);
}
/**
* 混合图像的大小只有可见区域大小
*/
private void drawCompositionInSelfSize(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(0xFFFFCC44);
canvas.drawOval(10, 10, mWidth * 3f / 4, mHeight * 3f / 4, mPaint);
mPaint.setXfermode(mPorterDuffXfermode);
mPaint.setColor(0xFF66AAFF);
canvas.drawRect(mWidth * 1f / 3, mHeight * 1f / 3,
mWidth * 19f / 20, mHeight * 19f / 20, mPaint);
mPaint.setXfermode(null);
}
}
使用图像混合时还有一个要注意的地方:上述代码在`onDraw(Canvas)`方法中绘制混合图像时会先调用`int sc = canvas.saveLayer(...)`生成一个新的图层(Layer),sc表示图层的编号,随后在新Layer上绘制DST和SRC,绘制完后将该Layer添加到Canvas上。那么这里为什么需要新的Layer来绘制DST和SRC,而不是直接在Canvas上绘制呢?
Layer可以理解为画布Canvas的一个层级,默认情况下Canvas只有一个Layer,所有的绘制都在同一图层上。当需要绘制多层图像时,可以通过`canvas.saveLayer(...)`生成新的Layer,在新Layer上绘制的内容是独立的,不会影响到其他Layer的内容,调用`canvas. restoreToCount(int sc)`时将该Layer覆盖到Canvas现有的图像上。Canvas通过栈的形式管理Layer,示意图如下。
![](https://img-blog.csdnimg.cn/44f6e96861f74737a50538726277d901.png#pic_center)
之前提到进行图像混合时,先绘制的内容是DST,后绘制的是SRC。如果不新建Layer的话,在绘制SRC时,Canvas上的所有内容都会被当作DST,所以背景等内容也会参与图像混合,很容易得到错误的效果。
以上就是图像混合的基本介绍,图像混合的应用场景比较广泛,这里介绍几种常见的场景。
###### 2.5.1 图像切割
图像切割用于将图像切割成特定的形状。可以是常见形状如圆形或圆角矩形,也可以切割为五角星这样的非常规形状,使用这一类非常规形状时需要该形状的底图。
将图像裁剪为圆角矩形时比较简单,在`onDraw(Canvas)`中新建图层,绘制圆角矩形作为DST,再绘制原图作为SRC即可,此时图像混合模式应设置为SRC\_IN,代码如下,`decodeSampledBitmapFromResource(...)`就是1.2节的大图采样。
public class RoundCornerView extends View {
private Paint mPaint;
private PorterDuffXfermode mFerMode;
private Bitmap mBitmap;
private Rect mBitmapRect;
private int mWidth;
private int mHeight;
// 省略构造函数...
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFerMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mWidth != w || mHeight != h) {
mWidth = w;
mHeight = h;
mBitmap = BitmapUtils.decodeSampledBitmapFromResource(
getContext().getResources(), R.drawable.compress_test, mWidth, mHeight);
mBitmapRect = new Rect(0, 0, mWidth, mHeight);
invalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
canvas.drawRoundRect(0, 0, mWidth, mHeight, 50, 50, mPaint);
mPaint.setXfermode(mFerMode);
canvas.drawBitmap(mBitmap, null, mBitmapRect, mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(sc);
}
......
}
最终效果如下,同理可以将图像切割为圆形等基础形状。
![](https://img-blog.csdnimg.cn/e1adadddafad4cd9bab7e5109faeedaf.png)
如果要将图像切割为五角星这样的图案,就需要使用一张五角星的底图,需要注意的是,底图上五角星以外的部分应该是透明的,否则切割出来还是原来的形状。其代码与切割为圆角矩形大同小异,只需要将绘制圆角矩形的部分换成绘制五角星即可。
public class StarPicView extends View {
private Paint mPaint;
private PorterDuffXfermode mMode;
private Bitmap mBgBitmap;
private Bitmap mBitmap;
private int mWidth, mHeight;
private Rect mDrawRect;
// 省略构造函数......
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mWidth != w || mHeight != h) {
mWidth = w;
mHeight = h;
mBgBitmap = BitmapUtils.decodeSampledBitmapFromResource(
getContext().getResources(), R.drawable.star4, mWidth, mHeight);
mBitmap = BitmapUtils.decodeSampledBitmapFromResource(
getContext().getResources(), R.drawable.icon3, mWidth, mHeight);
mDrawRect = new Rect(0, 0, mWidth, mHeight);
invalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
canvas.drawBitmap(mBgBitmap, null, mDrawRect, mPaint);
mPaint.setXfermode(mMode);
canvas.drawBitmap(mBitmap, null, mDrawRect, mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(sc);
}
}
最终的效果如下。
![](https://img-blog.csdnimg.cn/7b4d0360ef954978af2cd5e5462a8c60.png)
###### 2.5.2 色彩合成
色彩合成可以为图片添加新的效果,当使用纯色与照片混合时,可以改变图片整体的色调。例如黄色可以让图片具有泛黄的怀旧效果,红色可以让图片更温暖。以下代码通过SCREEN混合模式将半透明的红色与图片混合。
public class ColorComposeView extends View {
......
private void init() {
setLayerType(LAYER_TYPE_SOFTWARE, null);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMode = new PorterDuffXfermode(PorterDuff.Mode.SCREEN);
}
......
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
canvas.drawColor(0x44FF0000);
mPaint.setXfermode(mMode);
canvas.drawBitmap(mBitmap, null, mRect, mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(sc);
}
}
怎么样,是不是觉得小姐姐看上去都温柔了一些?
![](https://img-blog.csdnimg.cn/af72dd05347b4e33aee7f27a4e54a3a8.png)
出了纯色混合,也可以将两张图片进行合成,例如通过一张毛玻璃的底图,可以为照片添加一定的模糊效果,底图如下所示。
![](https://img-blog.csdnimg.cn/27165b39a24942dea9226b87b3b099f2.png)
绘制时将底图作为DST,将照片作为SRC,混合模式使用OVERLAY,代码与绘制切割五角星的大同小异,不再赘述。最终得到如下的效果。
### 尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
**进阶学习视频**
![](https://img-blog.csdnimg.cn/img_convert/a6749ab8ed20ed11964dd5916b08f512.webp?x-oss-process=image/format,png)
**附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题** (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
![](https://img-blog.csdnimg.cn/img_convert/3822dd1c45211ab238992b5154ad5e98.webp?x-oss-process=image/format,png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
学习视频**
[外链图片转存中...(img-OW0w39bv-1714834422273)]
**附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题** (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中...(img-22GYxWNJ-1714834422274)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**