您的位置:首页 > 移动开发 > Android开发

实现可缩放的马赛克控件---Android

2015-12-14 17:06 477 查看
需求:实现可以缩放、移动和打马赛克的控件。

由于之前对图片处理的经验很缺乏,所以拿到需求的第一步我就从github上面找相关的项目。

然后,就找到了这个项目:ProMosaic

这个项目有两个痛点

1.加载图片未处理尺寸,当尺寸过大时,会内存溢出(小问题)

2.未实现缩放功能

正文:

一、ProMosaic实现马赛克原理分析

首先,在内存中有三层Bitmap:

bmBaseLayer ---- 原图 ,

bmCoverLayer ---- 将整张原图转成马赛克效果

bmTouchLayer ---- 记录手指滑过的路径Path

每次手指滑动时,将手指的Path保存下来,并且将所有Path绘制在bmTouchLayer中,然后将bmCoverLayer和bmTouchLayer合并,合并的算法采用的是Xfermode的DST_IN效果(具体Xfermode请自己查询相关内容)。反正最终的结果就是生成一张马赛克图层bmMosaicLayer,这个图层就是要打马赛克的部分。

然后,将bmMosaicLayer绘制在bmBaseLayer图层上,就实现了最终的马赛克效果。

这个方法对内存的消耗比较大,如果图片压缩不够,在绘制马赛克过程很超级卡。

二、加载图片时压缩图片(优化内存)

项目中的控件名是MosaicView,在设置图片时调用的是setSrcPath(),在这个函数中对要加载的图片进行压缩。可以采用BitmapFactory.Options的inSampleSize来压缩。

相关的资料网上很多,我就不赘述了。

三、添加缩放和移动功能

接下来是最重要的实现缩放的功能了。

实现思路:

1.如何检测缩放手势? -------- 采用ScaleGestureDetector来检测手势多点缩放

2.如何缩放图片? -------- 通过设置Canvas上的绘制区域大小来实现缩放(通过canvas.scale()函数来实现,会出现手势坐标无法转换的问题。)

3.手势坐标如何转换? ------- 这里的尺寸有两类:图片的真实大小 和 图片显示的大小。需要将显示图片的坐标换算到真实图片上的坐标。通过两个尺寸的比例进行换算即可。

图片的真实大小 ---- 是Bitmap建立时的大小,这个尺寸并不直接显示在屏幕上,仅仅存在内存中。

图片的显示大小 ---- 是在Canvas上的绘制大小。调用canvas.drawBitmap(Bitmap bitmap,rect src,rect dst,Paint paint)时,dst这个参数就是设置bitmap在画布Canvas上的绘制区域。dst越大,图片显示就越大。

说到这里,思路就算完成了。

具体实现:

首先,实现手势检测,让控件实现OnScaleGestureListener接口

public class MosaicView extends ViewGroup implements ScaleGestureDetector.OnScaleGestureListener{


ScaleGestureDetector.onTouchEvent必须在onTouchEvent()函数中调用才可调用OnScaleGestureListener接口中的函数。

@Override
public boolean onTouchEvent(MotionEvent event) {
mScaleGestureDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}


然后,需要初始化Canvas的绘制区域Rect变量。mImageRect和mInitImageRect,第一个是现在的大小,第二个是初始化的大小用于计算缩放比例。

初始化在onLayout中进行。

protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
if (mImageWidth <= 0 || mImageHeight <= 0) {
return;
}

int contentWidth = right - left;
int contentHeight = bottom - top;
int viewWidth = contentWidth - mPadding * 2;
int viewHeight = contentHeight - mPadding * 2;
float widthRatio = viewWidth / ((float) mImageWidth);
float heightRatio = viewHeight / ((float) mImageHeight);
float ratio = widthRatio < heightRatio ? widthRatio : heightRatio;
int realWidth = (int) (mImageWidth * ratio);
int realHeight = (int) (mImageHeight * ratio);

int imageLeft = (contentWidth - realWidth) / 2;
int imageTop = (contentHeight - realHeight) / 2;
int imageRight = imageLeft + realWidth;
int imageBottom = imageTop + realHeight;
mImageRect.set(imageLeft, imageTop, imageRight, imageBottom);
mInitImageRect.set(imageLeft,imageTop,imageRight,imageBottom);
}


最后,实现onScale()函数,检测缩放。

@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = detector.getScaleFactor();
scaleFactor *= scale;
if (scaleFactor < 1.0f){
scaleFactor = 1.0f;
}
if (scaleFactor > 2.0f)
scaleFactor = 2.0f;

if (mImageRect != null){
int addWidth =(int) (mInitImageRect.width() * scaleFactor) - mImageRect.width ();
int addHeight=(int) (mInitImageRect.height()*scaleFactor) - mImageRect.height();
float centerWidthRatio = (detector.getFocusX()-mImageRect.left)/mImageRect.width();
float centerHeightRatio = (detector.getFocusY() - mImageRect.left)/mImageRect.height();

int leftAdd = (int) (addWidth * centerWidthRatio);
int topAdd = (int) (addHeight * centerHeightRatio);

mImageRect.left =  mImageRect.left - leftAdd;
mImageRect.right = mImageRect.right + (addWidth - leftAdd);
mImageRect.top = mImageRect.top - topAdd;
mImageRect.bottom = mImageRect.bottom + (addHeight - topAdd);
checkCenterWhenScale();
}

//Log.d("Javine","detector's scaleFactor is "+scale);
invalidate();
return true;
}

private void checkCenterWhenScale() {
int deltaX = 0;
int deltaY = 0;
if (mImageRect.left > mInitImageRect.left){
//mImageRect.offsetTo(mInitImageRect.left,mImageRect.top);
deltaX = mInitImageRect.left - mImageRect.left;
}
if (mImageRect.right < mInitImageRect.right){
deltaX = mInitImageRect.right - mImageRect.right;
}
if (mImageRect.top > mInitImageRect.top){
deltaY = mInitImageRect.top - mImageRect.top;
}
if (mImageRect.bottom < mInitImageRect.bottom){
deltaY = mInitImageRect.bottom - mImageRect.bottom;
}
mImageRect.offset(deltaX,deltaY);
}


当然,还需要对dispatchTouchEvent()进行修改,在单指滑动是进行打马赛克,在多指操作时进行缩放和移动。这里就不多说了。

项目源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: