From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige

跟着爱哥打天下

自定义控件其实很简单5:

drawBitmapMesh:

它可以对Bitmap做几乎任何改变

我们可以使用drawBitmapMesh来模拟错切skew的效果:

public class BitmapMeshView extends View {/*** 横向分割成的网格数量*/public static final int WIDTH = 19;/*** 纵向分割成的网格数量*/public static final int HEIGHT = 19;/*** 横纵向网格交织产生的点数量*/public static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);/*** 位图资源*/private Bitmap mBitmap;/*** 交点的坐标数组*/private float[] verts;/*** 构造函数*/public BitmapMeshView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);//获取位图mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap5);//实例化数组verts = new float[COUNT * 2];/*** 生成各个交点坐标*/int index = 0;float multiple = mBitmap.getWidth();for (int y = 0; y <= HEIGHT; y++) {float fy = mBitmap.getHeight() * y / HEIGHT;for (int x = 0; x <= WIDTH; x++) {float fx = mBitmap.getWidth() * x / WIDTH + ((HEIGHT - y) * 1.0F / HEIGHT * multiple);setXY(fx, fy, index);index += 1;}}}/*** 将计算后的交点坐标存入数组*/private void setXY(float fx, float fy, int index) {verts[index * 2 + 0] = fx;verts[index * 2 + 1] = fy;}@Overrideprotected void onDraw(Canvas canvas) {// 绘制网格位图canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);}}

关键代码就一段:

/*** 生成各个交点坐标*/int index = 0;float multiple = mBitmap.getWidth();for (int y = 0; y <= HEIGHT; y++) {float fy = mBitmap.getHeight() * y / HEIGHT;for (int x = 0; x <= WIDTH; x++) {float fx = mBitmap.getWidth() * x / WIDTH + ((HEIGHT - y) * 1.0F / HEIGHT * multiple);setXY(fx, fy, index);index += 1;}}

这段代码生成了200个点的坐标数据全部存入verts数组,verts数组中,偶数位表示x轴坐标,奇数位表示y轴坐标,最终verts数组中的元素构成为:[x,y,x,y,x,y,x,y,x,y,x,y,x,y………………]共200 * 2=400个元素,为什么是400个?如果你不是蠢13的话一定能计算过来。那么现在我们一定很好奇,drawBitmapMesh到底是个什么个意思呢?,其实drawBitmapMesh的原理灰常简单,它按照meshWidth和meshHeight这两个参数的值将我们的图片划分成一定数量的网格,比如上面我们传入的meshWidth和meshHeight均为19,意思就是把整个图片横纵向分成19份:

横纵向19个网格那么意味着横纵向分别有20条分割线对吧,这20条分割线交织又构成了20 * 20个交织点
每个点又有x、y两个坐标……而drawBitmapMesh的verts参数就是存储这些坐标值的,不过是图像变化后的坐标值,什么意思?说起来有点抽象,借用国外大神的两幅图来理解:

如上图,黄色的点是使用mesh分割图像后分割线的交点之一,而drawBitmapMesh的原理就是通过移动这些点来改变图像:

如上图,移动黄色的点后,图像被扭曲改变,你能想象在一幅刚画好的油画上有手指尖一抹的感觉么?油画未干,手指抹过的地方必将被抹得一塌糊涂,drawBitmapMesh的原理就与之类似,只不过我们不常只改变一点,而是改变大量的点来达到效果,而参数verts则存储了改变后的坐标,drawBitmapMesh依据这些坐标来改变图像,如果上面的代码中我们不将每行的x轴坐标进行平移而是单纯地计算了一下均分后的各点坐标:

/* * 生成各个交点坐标 */
int index = 0;
//      float multiple = mBitmap.getWidth();
for (int y = 0; y <= HEIGHT; y++) {  float fy = mBitmap.getHeight() * y / HEIGHT;  for (int x = 0; x <= WIDTH; x++) {  float fx = mBitmap.getWidth() * x / WIDTH;
//              float fx = mBitmap.getWidth() * x / WIDTH + ((HEIGHT - y) * 1.0F / HEIGHT * multiple);  setXY(fx, fy, index);  index += 1;  }
}  

你会发现图像没有任何改变,为什么呢?因为上面我们说过,verts表示了图像变化后各点的坐标,而点坐标的变化是参照最原始均分后的坐标点,也就是图:

中的各个交织点,在此基础上形成变化,比如我们最开始的错切效果,原理很简单,我们这里把图像分成了横竖20条分割线(实际上错切变换只需要四个顶点即可,这里我只作点稍复杂的演示),我们只需将第一行的点x轴向上移动一定距离,而第二行的点移动的距离则比第一行点稍短,依次类推即可,每行点移动的距离我们通过

(HEIGHT - y) * 1.0F / HEIGHT * multiple 

来计算,最终形成错切的效果
drawBitmapMesh不能存储计算后点的值,每次调用drawBitmapMesh方法改变图像都是以基准点坐标为参考的,也就是说,不管你执行drawBitmapMesh方法几次,只要参数没改变,效果不累加。

不知道为什么会缺一个角

public class BitmapMeshView extends View {/*** 横向分割成的网格数量*/public static final int WIDTH = 19;/*** 纵向分割成的网格数量*/public static final int HEIGHT = 19;/*** 横纵向网格交织产生的点数量*/public static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);/*** 位图资源*/private Bitmap mBitmap;/*** 交点的坐标数组*/private float[] verts;/*** 构造函数*/public BitmapMeshView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);//获取位图mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap4);//实例化数组verts = new float[COUNT * 2];/*** 生成各个交点坐标*///错切
//        skewBitmap();//放大镜效果magnifierBitmap();}/*** 放大镜效果*/private void magnifierBitmap() {int index = 0;//一个单元格的高度float multipleY = mBitmap.getHeight() * 1.0F / HEIGHT;//一个单元格的宽度float multipleX = mBitmap.getWidth() * 1.0F / WIDTH;for (int y = 0; y <= HEIGHT; y++) {//从起点开始,高度依次增加float fy = multipleY * y;for (int x = 0; x <= WIDTH; x++) {//宽度依次增加float fx = multipleX * x;setXY(fx, fy, index);//-------正常分割操作线//Index是进行覆盖的,所以数组不变if (5 == y) {if (8 == x) {//x后退一格,y后退一格setXY(fx - multipleX, fy - multipleY, index);}if (9 == x) {//x前进一格,y后退一格setXY(fx + multipleX, fy - multipleY, index);}}if (6 == y) {if (8 == x) {//x后退一格,y前进一格setXY(fx - multipleX, fy + multipleY, index);}if (9 == x) {//x前进一格,y前进一格setXY(fx + multipleX, fy + multipleY, index);}}index += 1;}}}/*** 网格切图*/private void cutBitmap(Canvas canvas, Bitmap bitmap, int widthCount, int heightCount) {canvas.save();Paint drawLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);drawLinePaint.setStyle(Paint.Style.STROKE);drawLinePaint.setStrokeWidth(1);drawLinePaint.setColor(Color.GREEN);float width = bitmap.getWidth();float height = bitmap.getHeight();float fx = width / widthCount;float fy = height / heightCount;Log.d("TAG", "width=" + width);Log.d("TAG", "height=" + height);for (float y = 0; y <= height; y += fy) {canvas.drawLine(0, y, width, y, drawLinePaint);}for (float x = 0; x <= width; x += fx) {canvas.drawLine(x, 0, x, height, drawLinePaint);}canvas.restore();}/*** 错切效果*/private void skewBitmap() {int index = 0;float multiple = mBitmap.getWidth();for (int y = 0; y <= HEIGHT; y++) {float fy = mBitmap.getHeight() * y / HEIGHT;for (int x = 0; x <= WIDTH; x++) {float fx = mBitmap.getWidth() * x / WIDTH + ((HEIGHT - y) * 1.0F / HEIGHT * multiple);setXY(fx, fy, index);index += 1;}}}/*** 将计算后的交点坐标存入数组*/private void setXY(float fx, float fy, int index) {verts[index * 2 + 0] = fx;verts[index * 2 + 1] = fy;}@Overrideprotected void onDraw(Canvas canvas) {// 绘制网格位图canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);//切图cutBitmap(canvas, mBitmap, 19, 19);}}

public class BitmapMeshView2 extends View {/*** 分割数*/public static final int WIDTH = 9, HEIGHT = 9;/*** 焦点数*/public static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);/*** 位图对象*/private Bitmap mBitmap;/*** 基准点数组坐标*/private float[] matrixOriganal = new float[COUNT * 2];/*** 交换后点坐标数组*/private float[] matrixMoved = new float[COUNT * 2];/*** 触摸屏幕时手指的xy坐标*/private float clickX, clickY;/*** 基准点、交换点和线段的绘制Paint*/private Paint origPaint, movePaint, linePaint;/*** 构造函数*/public BitmapMeshView2(Context context, @Nullable AttributeSet attrs) {super(context, attrs);//获取焦点setFocusable(true);//实例化画笔并设置颜色origPaint = new Paint(Paint.ANTI_ALIAS_FLAG);origPaint.setColor(0x660000FF);movePaint = new Paint(Paint.ANTI_ALIAS_FLAG);movePaint.setColor(0x99FF0000);linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);linePaint.setColor(0xFFFFFB00);//设置位图资源mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap8);//初始化坐标数组int index = 0;for (int y = 0; y <= HEIGHT; y++) {float fy = mBitmap.getHeight() * y / HEIGHT;for (int x = 0; x <= WIDTH; x++) {float fx = mBitmap.getWidth() * x / WIDTH;setXY(matrixMoved, index, fx, fy);setXY(matrixOriganal, index, fx, fy);index += 1;}}}/*** 设置坐标数组*/private void setXY(float[] array, int index, float x, float y) {array[index * 2 + 0] = x;array[index * 2 + 1] = y;}@Overrideprotected void onDraw(Canvas canvas) {//绘制网格位图canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, matrixMoved, 0, null, 0, null);//绘制参考元素drawGuide(canvas);//Cut图
//        CutBitmap.cutBitmap(canvas, mBitmap, WIDTH, HEIGHT);}/*** 绘制参考元素*/private void drawGuide(Canvas canvas) {for (int i = 0; i < COUNT * 2; i += 2) {float x = matrixOriganal[i + 0];float y = matrixOriganal[i + 1];canvas.drawCircle(x, y, 4, origPaint);float x1 = matrixOriganal[i + 0];float y1 = matrixOriganal[i + 1];float x2 = matrixMoved[i + 0];float y2 = matrixMoved[i + 1];canvas.drawLine(x1, y1, x2, y2, origPaint);}for (int i = 0; i < COUNT * 2; i += 2) {float x = matrixMoved[i + 0];float y = matrixMoved[i + 1];canvas.drawCircle(x, y, 4, movePaint);}canvas.drawCircle(clickX, clickY, 6, linePaint);}/*** 计算变换数组坐标*/private void smudge() {for (int i = 0; i < COUNT * 2; i += 2) {float xOriginal = matrixOriganal[i + 0];float yOriginal = matrixOriganal[i + 1];float dist_click_to_origin_x = clickX - xOriginal;float dist_click_to_origin_y = clickY - yOriginal;float kv_kat = dist_click_to_origin_x * dist_click_to_origin_x + dist_click_to_origin_y * dist_click_to_origin_y;//这个算法不太懂float pull = (float) (1000000 / kv_kat / Math.sqrt(kv_kat));if (pull >= 1) {matrixMoved[i + 0] = clickX;matrixMoved[i + 1] = clickY;} else {matrixMoved[i + 0] = xOriginal + dist_click_to_origin_x * pull;matrixMoved[i + 1] = yOriginal + dist_click_to_origin_y * pull;}}}@Overridepublic boolean onTouchEvent(MotionEvent event) {clickX = event.getX();clickY = event.getY();smudge();invalidate();return true;}
}

图上我们绘制了很多蓝色和红色的点,默认状态下,蓝色和红色的点是重合在一起的,两者间通过一线段连接,当我们手指在图片上移动时,会出现一个黄色的点,黄色的点代表我们当前的触摸点,而红色的点代表变换后的坐标点,蓝色的点代表基准坐标点:

可以看到越靠近触摸点的红点越向触摸点坍塌,红点表示当前变换后的点坐标,蓝点表示基准点的坐标,所有的变化都是参照蓝点进行的,这个例子可以很容易地理解drawBitmapMesh:

drawBitmapMesh参数中有个vertOffset,该参数是verts数组的偏移值,意为从第一个元素开始才对位图就行变化,这些大家自己去尝试下吧,还有colors和colorOffset,类似。
drawBitmapMesh说实话真心很屌,但是计算复杂确是个鸡肋,这么屌的一个方法被埋没其实是由原因可循的,高不成低不就,如上所示,有些变换我们可以使用Matrix等其他方法简单实现,但是drawBitmapMesh就要通过一些列计算,太复杂。那真要做复杂的图形效果呢,考虑到效率我们又会首选OpenGL……这真是一个悲伤的故事……无论怎样,请记住这位烈士一样的方法…………总有用处的


自定义View(二-番外4-drawBitmapMesh)相关推荐

  1. android下雨动画效果,Android 自定义View(二) 下雨效果

    Rain.gif Android 自定义View(二) 下雨效果 一 实现思路, 雨点用线段表示,通过控制线段的大小和宽度来表示不同的线段. 一个雨点下雨的过程可以表示为一条直线,一次雨点在下雨的过程 ...

  2. 自定义View(二),强大的Canvas

    本文转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android中使用图形处理引擎,2D部分是 ...

  3. Android自定义View(二)

    文章目录 1.构造函数 2.onMeasure() 2.1.MeasureSpec 3.onSizeChanged() 4.onLayout() 5.onDraw() 上一篇: Android自定义V ...

  4. 自定义view(二) Path绘画详解 圆形进度条

    目录 简介 基础api 圆形进度条 总结 简介 view的绘制可以由无数个形状组成,在canvas基础图形绘制中,我们已经把api提供好的基本图形讲过了.Path之所以单独一章出来是因为path可以由 ...

  5. 精通Android自定义View(二十)自定义仿微信扫一扫效果

    1 效果 2 源码 /*** 自动上下扫描*/public class AutoScannerView extends View {private static final String TAG = ...

  6. android自定义view(二)-仿华为卡包效果

    前段时间产品经理出了一个卡列表展示效果,效果的大致样子是仿照华为门禁卡卡包效果,研究了一下大致效果出来了,但和华为比还有点差距,主要是动画不是很流畅,仅供大家参考指正,有好的优化方式也可以告诉我,大家 ...

  7. Android 自定义View绘制的基本开发流程 Android自定义View(二)

    1 View绘制的过程 View的测量--onMeasure() View的位置确定--onLayout() View的绘制--onDraw() 2 View的测量--onMeasure() Andr ...

  8. Android 自定义View二(深入了解自定义属性attrs.xml)

    1.为什么要自定义属性 要使用属性,首先这个属性应该存在,所以如果我们要使用自己的属性,必须要先把他定义出来才能使用.但我们平时在写布局文件的时候好像没有自己定义属性,但我们照样可以用很多属性,这是为 ...

  9. JAVA_OA管理系统(二)番外篇:IoC原理

    在网上看到一篇文章,感觉写得挺不错的,转载一下,本文转载自:http://blog.csdn.net/m13666368773/article/details/7802126 一. IoC理论的背景 ...

最新文章

  1. R语言ggplot2可视化、使用axis.ticks.length函数设置坐标轴间隔标签竖线的长度、并设置坐标轴间隔标签在图像内部(刻度标记放置在图像内部)
  2. 求只有2,3,5组成的第n小个数字
  3. 【Javascript 拾遗之三】Closure 闭包
  4. 【LeetCode】031. Next Permutation
  5. [Unity] GameFramework 学习记录 1
  6. 使用Hutool来实现深拷贝
  7. java算法腐烂橘子,答案——腐烂的橘子算法题目
  8. 转发表是什么鬼?怎么工作?
  9. 《51单片机应用开发从入门到精通》——1.3 Keil uVision2集成开发环境
  10. 普林斯顿微积分读本篇十二:洛必达法则
  11. html中div中文字如何上下居中,div中文字各种垂直居中的方法
  12. Html5开发工具介绍
  13. 人工智能剥夺就业岗位?不妨听听马斯克是如何建议的
  14. LeetCode通关:听说链表是门槛,这就抬脚跨门而入
  15. 精进——如何成为很厉害的人(采铜)
  16. 记一次因为丢帧导致视频播放花屏问题的排查
  17. oracle开放查询表权限_(转载)Oracle创建用户并给用户授权查询指定表或视图的权限...
  18. location 拦截所有_AdGuard for Mac(广告拦截软件)
  19. feko金属球远场RCS双站
  20. 『状态』驱动的世界:ReactiveCocoa

热门文章

  1. ROS 小车原地转圈的解决办法及调试方法
  2. 基于OPenCV的视频播放变慢
  3. 排班算法 java_【算法】基于优先级的排班算法实现
  4. 安卓Hook微信-计步器、万能骰子、自动回复、反撤回、抢红包思路分享
  5. c语言isnan,C# Double.IsNaN()用法及代码示例
  6. MATLAB常用语句(1)---rem 和mod
  7. AI创作教程之什么是Stable Diffusion?
  8. echarts详细说明
  9. 目前最流畅的android手机,目前公认最流畅的4大手机系统,华为仅排第三,第一实至名归!...
  10. 开心移动企业管理平台SaaS OA