Android基于PhotoView實現(xiàn)的頭像/圓形裁剪控件
前言
常見的圖片裁剪有兩種,一種是圖片固定,裁剪框移動放縮來確定裁剪區(qū)域,早期見的比較多,缺點在于不能直接預覽裁剪后的效果;還有一種現(xiàn)在比較普遍了,就是裁剪框固定,直接拖動縮放圖片,便于預覽裁剪結(jié)果。
我做的這個控件屬于后者。一般來說,做圖片裁剪的思路無外乎是先監(jiān)聽手勢,獲取坐標,再對圖片變形,最后確定裁剪區(qū)域的坐標對位圖進行裁剪,最后保存圖片到本地。我嘛還是個技術(shù)小白,一想到要監(jiān)控手勢這些就頭疼,碰巧項目之前為了做查看大圖而引入了大名鼎鼎的第三方圖片查看控件——PhotoView(使用步驟參考這篇文章:Android PhotoView使用步驟實例詳解)。于是轉(zhuǎn)念一想,能不能把到圖片變形為止的前幾步交給PhotoView來搞定,我只要負責確定確定裁剪區(qū)域后面這幾步呢。后來掉了好幾個坑導致偷懶也沒輕松多少其實ε=(´ο`*)))唉~
先簡要介紹一下設計思路,如上圖所示,主要分為兩部分,上層是遮罩(也可以理解為是裁剪框),用于預覽裁剪后的效果;下層是PhotoView,這里多包了一層改為正方形顯示。
下面是遮罩的代碼,比較簡單,這里就不贅述了。
/** * Created by MandyLu on 2018/7/14. * 圓形裁剪框 */ public class CircleCropView extends View { public final int CIRCLE_MARGIN = 50; public CircleCropView(Context context) { super(context); } public CircleCropView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public CircleCropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public CircleCropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, widthMeasureSpec); } @RequiresApi(api = Build.VERSION_CODES.O) @Override protected void onDraw(Canvas canvas) { canvas.save(); Path path = new Path(); Rect viewDrawingRect = new Rect(); getDrawingRect(viewDrawingRect); float radius = viewDrawingRect.width() / 2 - CIRCLE_MARGIN; path.addCircle(viewDrawingRect.left + radius + CIRCLE_MARGIN, viewDrawingRect.top + radius + CIRCLE_MARGIN, radius, Path.Direction.CW); Paint outsidePaint = new Paint(); outsidePaint.setAntiAlias(true); outsidePaint.setARGB(151, 0, 0, 0); canvas.clipPath(path, Region.Op.DIFFERENCE); canvas.drawRect(viewDrawingRect, outsidePaint); canvas.restore(); } }
SquarePhotoView只是在PhotoView的基礎上改了長寬,重寫一下onMeasure方法即可:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, widthMeasureSpec); }
那么現(xiàn)在最關(guān)鍵的一步,就是從PhotoView獲取當前圖片顯示區(qū)域的Drawable或Bitmap了。粗略看了一下PhotoView的函數(shù),并沒有找到能用的(囧)。解決第一個坑的笨辦法就是,自己動手豐衣足食——直接拿原圖的bitmap,然后問PhotoView要當前圖片的變形矩陣,自個兒通過矩陣一步步變形拿到對應的位圖。
思路其實是沒問題的,然而第二個坑又出現(xiàn)了(囧)。這里的變形矩陣,我最早百度的結(jié)果是getSuppMatrix,源碼我沒有細看,但掉坑的過程中據(jù)我觀察,猜測應該是對應最新一次的手勢變形結(jié)果(不確定= =,也可能是其他坑綜合導致的錯誤結(jié)果)??傊詈笪也榱艘粫创a,最終確定用的是getDisplayMatrix。
緊接著是第三個坑,坑多了就習慣了。矩陣中的XY位移量,我起初以為是顯示區(qū)域中心相對于原圖中心的位移,即如果僅有縮放操作的話,位移應該為0。但實際通過特殊位置(例如取四個頂點)的裁剪結(jié)果來看,這里的XY位移量實際最后顯示區(qū)域左上角的點相對原點(即原圖左上角)的位移,簡單點說,可以把位移量作為最終顯示區(qū)域左上角的坐標。
然后我就迎來了第四個坑(🙂)。這個坑現(xiàn)在回頭看其實是很簡單不應該栽進去的,然而當時還沒想通的時候確實很慌(唉)。這個坑的問題就出在,Matrix里的值是基于手勢的,也就是說,是基于屏幕像素(換句話說,是基于實際顯示的圖片)的。而對位圖進行裁剪時,是基于原圖像素的。那么這里還存在一個為了正常顯示而導致的縮放比例的問題,例如原圖是3000x4000,由于屏幕分辨率是1080*1920,那么實際顯示時,圖片是縮小了的,這個比例是9/25。所以在裁剪的過程中,需要把位移量再放大25/9倍進行還原。
下面是裁剪部分的關(guān)鍵代碼(最后偷了一下懶,沒有裁圓形,只是用CIrcleImageView顯示):
fun cropImage(){ var degree = ImageUtils.readPictureDegree(imagePath) var bitmap = ImageUtils.getRotatedBitmap(BitmapFactory.decodeFile(imagePath),degree) var width: Int = 0 var startX: Int = 0 var startY: Int = 0 if (bitmap.width < bitmap.height){ startY = (bitmap.height - bitmap.width) / 2 width = bitmap.width }else{ startX = (bitmap.width - bitmap.height) / 2 width = bitmap.height } var matrix = Matrix() photo_preview.getDisplayMatrix(matrix)//獲取變形矩陣,直接取scaleX或translationX沒用 var values = FloatArray(9, {0.0f}) matrix.getValues(values) var expWidth = Math.round(bitmap.width * values[0])//縮放x var expHeight = Math.round(bitmap.height * values[4])//縮放y var bitmap1 = Bitmap.createScaledBitmap(bitmap, expWidth, expHeight, false) val ratio = width * 1.0f / photo_preview.width startX = Math.round(startX * values[0] - values[2] * ratio) startY = Math.round(startY * values[4] - values[5] * ratio) var bitmap2 = Bitmap.createBitmap(bitmap1, startX, startY, width, width, null, false) saveImage(bitmap2) }
這里還有幾個小坑需要解釋一下:
讀取bitmap時需要注意一下角度。這個是我在裁剪本地圖片和網(wǎng)絡圖片的時候發(fā)現(xiàn)的,有些是正的有些就是轉(zhuǎn)了90度。每個手機也不一定一樣,所以保險起見,需要從圖片的EXIF信息里面獲取需要旋轉(zhuǎn)的角度,然后再進一步處理。
我這里因為最終顯示的是正方形,而且選的scaleType是centerCrop。所以默認就是顯示中間的那一塊。所以裁減時的原點也需要從正方形的左上角開始。這里是計算兩種情況下的原點坐標:
var startX: Int = 0 var startY: Int = 0 if (bitmap.width < bitmap.height){ startY = (bitmap.height - bitmap.width) / 2 width = bitmap.width }else{ startX = (bitmap.width - bitmap.height) / 2 width = bitmap.height }
縮放操作后,原點坐標也隨之變換,乘以相應的縮放比例,再根據(jù)相應的位移量確定裁剪區(qū)域的位置。
原本想直接使用Matrix進行變形,失?。ㄔ虿幻鳎2榭磩e的裁剪控件源碼,決定使用createScaledBitmap來進行方法操作。
最后還是要檢討一下:耍了小聰明想抄點近路,結(jié)果因為不熟悉源碼,遇到坑的時候也只能當成黑盒;只能通過不斷實驗來猜測問題所在,反倒是花了更多時間,得不償失了。以后有時間的時候,還是應該仔細研究源碼,踏踏實實從原理出發(fā)解決問題(* ̄︶ ̄)~
最后,感謝幾位博主的無私分享,特此鳴謝~
>>>Android Bitmap 常見的幾個操作:縮放,裁剪,旋轉(zhuǎn),偏移
>>>Android ImageCropper 矩形 圓形 裁剪框
>>>Android裁剪圖片為圓形圖片的實現(xiàn)原理與代碼
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
webview添加參數(shù)與修改請求頭的user-agent實例
這篇文章主要介紹了webview添加參數(shù)與修改請求頭的user-agent實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android中使用Canvas繪制南丁格爾玫瑰圖(Nightingale rose diagram)
這篇文章主要介紹了Android中使用Canvas繪制南丁格爾玫瑰圖(Nightingale rose diagram),本文直接給出實現(xiàn)代碼和運行效果圖,需要的朋友可以參考下2015-03-03Android5.1 取消錄制屏幕跳出的權(quán)限對話框問題
這篇文章主要介紹了Android5.1 取消錄制屏幕跳出的權(quán)限對話框問題,需要的朋友可以參考下2017-04-04Android5.0以上版本錄屏實現(xiàn)代碼(完整代碼)
這篇文章主要介紹了Android5.0以上版本錄屏實現(xiàn)代碼,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-01-01Android點亮屏幕或屏幕解鎖和鎖定以及其他相關(guān)權(quán)限實現(xiàn)代碼
本文將帶你實現(xiàn)Android屏幕解鎖和鎖定;Android屏幕常亮/點亮以及其他相關(guān)權(quán)限,感興趣的朋友可以參考下,希望本文對你有所幫助2013-01-01Android?MaterialButton使用實例詳解(告別shape、selector)
我們平時寫布局,當遇到按鈕需要圓角、或者描邊等,通常的方法是新建一個xml文件,在shape標簽下寫,然后通過android:background或setBackground(drawable)設置,這篇文章主要給大家介紹了關(guān)于Android?MaterialButton使用詳解的相關(guān)資料,需要的朋友可以參考下2022-09-09Android數(shù)據(jù)加密之Base64編碼算法的簡單實現(xiàn)
下面小編就為大家?guī)硪黄狝ndroid數(shù)據(jù)加密之Base64編碼算法的簡單實現(xiàn)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10Android自定義網(wǎng)絡連接工具類HttpUtil
這篇文章主要介紹了Android自定義網(wǎng)絡連接工具類HttpUtil,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11