画面分割和偏移计算
首先计算出Unit可见部分的准确范围,具体计算公式可以参考我以前的文章《自定义控件——原创仿地图瓦片动态加载_阶段1_动态添加和移除View》的末尾部分,如果有Unit部分可见区域越过了父控件的边界,例如Unit左边框比父控件左边框坐标值还要小,那么截图块的区域横坐标就是父控件左边框了,同样的推导也可以用于确定右边、上边、底边的位置,从而最终确定对绘制图层的截图图块的准确区域。具体运算代码如下,targetRect即是要目标区域,getX()得到的是Unit控件初始化时的左上角坐标,getWidth()、getHeight()是控件初始化后的宽度、高度,getScaleX()、getScaleY()分别是当前宽度、高度缩放到原来的比例:
//获取本控件在屏幕可见区域对应的位图像素,并制作成Bitmap显示
Rect targetRect = new Rect(0, 0, 0, 0);
//计算本Unit需要截取父控件固定画布Bitmap的范围Start:
if (getX() + (1 - getScaleX()) / 2 * getWidth() < parent.getLeft()) { //左边缘小于0
targetRect.left = parent.getLeft();
} else {
targetRect.left = (int)(getX() + (1 - getScaleX()) / 2 * getWidth());
}
if (getX() + (1 - getScaleX()) / 2 * getWidth() + getWidth() * getScaleX() > parent.getRight()) { //单元的右边缘大于父控件右边缘
targetRect.right = parent.getRight();
} else {
targetRect.right = (int)(getX() + (1 - getScaleX()) / 2 * getWidth() + getWidth() * getScaleX());
}
if (getY() + (1 - getScaleY()) / 2 * getHeight() < parent.getTop()) {
targetRect.top = parent.getTop();
} else {
targetRect.top = (int)(getY() + (1 - getScaleY()) / 2 * getHeight());
}
if (getY() + (1 - getScaleY()) / 2 * getHeight() + getHeight() * getScaleY() > parent.getBottom()) {
targetRect.bottom = parent.getBottom();
} else {
targetRect.bottom = (int)(getY() + (1 - getScaleY()) / 2 * getHeight() + getHeight() * getScaleY());
}
? ? ? ?我的Unit控件是继承于ImageView的,所以如果此时把截取下来的像素块生成Bitmap之后直接使用setBitmap的话,会使得原来的图像被覆盖为该像素块的画面的同时,该像素块的画面的画面可能会和原来在绘图图层时产生了位置或大小上的误差。所以当某个Unit的实际可见区域的左上角坐标在父控件左边界之外时,要把图块左边界右移到父控件的左边界对齐,即向右移动“父控件左边界 - Unit左边界”那么长的距离,而且由于该距离是视觉上的距离,而不是控件内部参考系的距离,所以还要除以“缩放比例”得到控件内部参考系的距离。而越过父控件右边界时,则Unit实际显示的横向范围是“父控件右边界 - Unit可见区域左边界”,然后再除以缩放比例得到控件内部参考系的长度(不是可见区域多长就是多长,可以理解控件按比例缩小放大其实只是拉远拉近了,不代表控件内部长度发生了变化,所以一定要除以缩放比例才能把可见长度变成控件内部长度),上边和底边的运算也类似,然后得到图块的拉伸的具体范围。然后再把该范围的宽高除以图片的宽高,以偏移值即其实的x、y值为中心缩放图块,再使用matrix工具缩放图块后写到原本的cacheBitmap中叠加起来,并保存到外存中。换算方法可能比较绕口,可以参考我之前的文章《Android View跟随手势漫游缩放方法》、Bitmap无图像损失的截取和保存方法:《Android开发中一种原封不动地保存Bitmap数据的办法》。具体代码如下:
? ?
//如果Unit的做边框在父控件左侧外面,则需要把像素块移动到Unit可见区域的骑士位置再拉伸。所以这里计算左边框要偏移多少
float dx = 0;
float dy = 0;
if(getX() + (1 - getScaleX()) / 2 * getWidth() < 0){
dx = -(getX() + (1 - getScaleX()) / 2 * getWidth()) / getScaleX(); //左偏了多少,图片就要右偏多少,而且即使格子缩小了也只是视觉上缩少了,格子的像素量不变,所以截出来的图的坐标还要反向放大进行偏移
}
if(getY() + (1 - getScaleY()) / 2 * getHeight() < 0){
dy = -(getY() + (1 - getScaleY()) / 2 * getHeight()) / getScaleY();
}
matrix.postTranslate(dx, dy);
//之前的步骤计算了图片覆盖的左起点和顶起点,现在计算图片覆盖的右终点和底终点
float parentWidth = ((FrameLayout) getParent()).getRight(); //父控件右边终点
float parentHeight = ((FrameLayout) getParent()).getBottom();
float unitRightBoarder = (getX() + (1 - getScaleX()) / 2 * getWidth() + getWidth() * getScaleX()); //本Unit在屏幕实际的右边界位置
/**如果Unit右边界位置超出父控件右边界,则“Unit可见区域右边界 = 父控件右边界 - Unit实际可见左边界”并反向放大至和图片等效比例尺。
/否则直接用Unit真实右边界**/
float rightBoarder = unitRightBoarder > parentWidth ?
(parentWidth - (getX() + (1 - getScaleX()) / 2 * getWidth())) / getScaleX() :
getWidth() ;//右边框
//原理同上
float unitBottomBoarder = (getY() + (1 - getScaleY()) / 2 * getHeight() + getHeight() * getScaleY());
float bottomBoarder = unitBottomBoarder > parentHeight ?
(parentHeight - (getY() + (1 - getScaleY()) / 2 * getHeight())) / getScaleY(http://www.amjmh.com/v/): //(屏幕边界 - View实际起始边界) / 缩放率 反向放大
getHeight() ;
//图片在Unit中实际应显示范围
RectF visibleRect = new RectF(dx, dy, rightBoarder, bottomBoarder);
//以实际应显示范围的左上角作为缩放中心,长、宽分别除以像素块的长、宽,得到像素块应该拉伸或者收缩的比例:
matrix.postScale(visibleRect.width() / newBitmap.getWidth(), visibleRect.height() / newBitmap.getHeight(), dx, dy);
canvas.drawBitmap(newBitmap, matrix, null);
setImageBitmap(cacheBitmap);
//保存本次绘图
saveUnitBitmap();