需求:實現可以縮放、移動和打馬賽克的控件。
由於之前對圖片處理的經驗很缺乏,所以拿到需求的第一步我就從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()進行修改,在單指滑動是進行打馬賽克,在多指操作時進行縮放和移動。這裏就不多說了。