實現可縮放的馬賽克控件---Android

需求:實現可以縮放、移動和打馬賽克的控件。

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


項目源碼下載


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章