之前寫(xiě)過(guò)一篇關(guān)于圖像變換處理的文章《Android開(kāi)發(fā)之圖像處理那點(diǎn)事——變換》米罚,學(xué)以致用,這次我們來(lái)實(shí)現(xiàn)仿微博的貼紙效果丈探,我打算分成兩部分來(lái)寫(xiě):
基礎(chǔ)篇:單圖貼紙效果录择,利用矩陣變化+手勢(shì)識(shí)別實(shí)現(xiàn)貼紙的自由縮放、旋轉(zhuǎn)、平移隘竭,以面向過(guò)程的代碼讓你知道每一步操作的實(shí)現(xiàn)原理塘秦。
強(qiáng)化篇:仿美圖秀秀的多圖貼紙效果,以面向?qū)ο蟮乃季S告訴你如何將圖像动看、矩陣封裝尊剔,包括貼紙的聚焦處理、重疊場(chǎng)景的交互分析菱皆、圖像的二次采樣须误、合成等知識(shí)點(diǎn)。
關(guān)于矩陣的基礎(chǔ)操作這里就不再重復(fù)闡述搔预,不了解矩陣的朋友可以看一下《Android開(kāi)發(fā)之圖像處理那點(diǎn)事——變換》霹期。
先來(lái)看下本篇文章要實(shí)現(xiàn)的效果圖:
實(shí)現(xiàn)思路:
我們可以將上述效果大致分成2部分理解,貼圖的展示拯田、手勢(shì)的操作:
貼圖的展示:
首先它是一個(gè)可以顯示圖像的自定義View历造,且它的大小,角度船庇,扭曲程度都是由Matrix矩陣來(lái)維護(hù)的吭产,所以我們很自然的可以想到 Canvas.drawBitmap(Bitmap, Matrix, Paint)
這個(gè)繪制方法。
手勢(shì)的操作:
關(guān)于手勢(shì)的操作鸭轮,我們大致可以分成這三種臣淤,單指的拖動(dòng)平移,雙指的放大縮小窃爷,雙指的旋轉(zhuǎn)邑蒋,這里我們需要先了解onTouch中MotionEvent給我們返回的幾種事件:
ACTION_DOWN:當(dāng)手指觸摸屏幕的時(shí)候觸發(fā)。
ACTION_MOVE:當(dāng)手指滑動(dòng)屏幕的時(shí)候觸發(fā)按厘。
ACTION_UP:當(dāng)手指抬起的時(shí)候觸發(fā)(此時(shí)屏幕無(wú)手指觸摸)医吊。
ACTION_POINTER_DOWN:當(dāng)多根手指觸摸屏幕的時(shí)候觸發(fā)。
ACTION_POINTER_UP:在多根手機(jī)觸摸屏幕的情況下逮京,抬起其中一根手指的時(shí)候觸發(fā)卿堂。
根據(jù)以上的觸發(fā)事件,我們就可以得到一些我們想要的場(chǎng)景了懒棉,比如當(dāng)單指觸摸貼紙的時(shí)候草描,我們將貼紙的屬性設(shè)為可拖動(dòng)(不可縮放、旋轉(zhuǎn))策严,當(dāng)雙指觸摸貼紙的時(shí)候穗慕,我們將貼紙的屬性設(shè)為不可拖動(dòng)(可縮放、旋轉(zhuǎn))妻导,而縮放因子我們可以通過(guò)雙指間距離的改變得到揍诽,旋轉(zhuǎn)角度我們可以通過(guò)雙指移動(dòng)形成的夾角得到诀蓉,這些下文會(huì)具體分析,先大致有個(gè)思路就行暑脆。
好了渠啤,既然有了思路,我們就開(kāi)始擼碼吧~
編碼實(shí)現(xiàn):
首先我們需要將圖片加載成Bitmap添吗,并用矩陣Matrix去維護(hù)它沥曹,在自定義View中畫(huà)出來(lái):
canvas.drawBitmap(mBitmap, mMatrix, null);
這里定義三種標(biāo)志,分別表示當(dāng)前貼紙?zhí)幱诳梢苿?dòng)碟联、可縮放妓美、可旋轉(zhuǎn)狀態(tài):
private boolean mCanTranslate;//標(biāo)志是否可移動(dòng)
private boolean mCanScale;//標(biāo)志是否可縮放
private boolean mCanRotate;//標(biāo)志是否可旋轉(zhuǎn)
下面我們來(lái)分別實(shí)現(xiàn)貼紙的移動(dòng),縮放鲤孵,旋轉(zhuǎn)效果:
貼紙的移動(dòng):
思路:當(dāng)用戶手指(單指)按下屏幕的時(shí)候壶栋,需要判斷手指的觸摸點(diǎn)是否在貼紙上,如果在普监,將貼紙的狀態(tài)標(biāo)記為可移動(dòng)并記錄下當(dāng)前坐標(biāo)贵试,當(dāng)用戶手指滑動(dòng)屏幕的時(shí)候,需要計(jì)算出當(dāng)前手指所在坐標(biāo)與剛才按下屏幕坐標(biāo)的相對(duì)距離凯正,通過(guò)維護(hù)貼紙的矩陣來(lái)做平移操作毙玻。
代碼實(shí)現(xiàn):在onTouch的ACTION_DOWN中去記錄觸摸點(diǎn)并判斷手指觸摸點(diǎn)是否在貼紙上,如果在廊散,把狀態(tài)標(biāo)記為可移動(dòng):
case MotionEvent.ACTION_DOWN:
mLastSinglePoint.set(event.getX(), event.getY());
if (isInStickerView(event)) {
//觸摸點(diǎn)是否在貼紙范圍內(nèi)
mCanTranslate = true;
}
mCanScale = false;
mCanRotate = false;
break;
檢測(cè)觸摸點(diǎn)的方法桑滩,這里簡(jiǎn)單介紹下Matrix類(lèi)中的兩個(gè)方法:
invert:Matrix類(lèi)中給我們提供了invert方法用來(lái)反轉(zhuǎn)矩陣,舉個(gè)例子允睹,一個(gè)向左旋轉(zhuǎn)30°的矩陣运准。通過(guò)invert可以得到一個(gè)基于當(dāng)前(左旋轉(zhuǎn)30°的矩陣)向右旋轉(zhuǎn)30°的矩陣。
mapPoints:Matrix類(lèi)中給我們提供了mapPoints方法用來(lái)映射所有坐標(biāo)點(diǎn)經(jīng)過(guò)矩陣變化后的新坐標(biāo)點(diǎn)位置缭受。
有了上面的2個(gè)方法戳吝,我們就可以根據(jù)當(dāng)前的矩陣得到它變換之前的原矩陣,然后再把當(dāng)前觸摸的點(diǎn)通過(guò)原矩陣映射回原來(lái)觸摸的點(diǎn)贯涎,再判斷觸摸點(diǎn)是否在原來(lái)貼紙的矩形框范圍內(nèi)即可。
/**
* 檢測(cè)當(dāng)前觸摸是否在貼紙上
*
* @return
*/
private boolean isInStickerView(MotionEvent motionEvent) {
if (motionEvent.getPointerCount() == 1) {
float[] dstPoints = new float[2];
float[] srcPoints = new float[]{motionEvent.getX(), motionEvent.getY()};
Matrix matrix = new Matrix();
mMatrix.invert(matrix);
matrix.mapPoints(dstPoints, srcPoints);
if (mBitmapBound.contains(dstPoints[0], dstPoints[1])) {
return true;
}
}
if (motionEvent.getPointerCount() == 2) {
float[] dstPoints = new float[4];
float[] srcPoints = new float[]{motionEvent.getX(0), motionEvent.getY(0), motionEvent.getX(1), motionEvent.getY(1)};
Matrix matrix = new Matrix();
mMatrix.invert(matrix);
matrix.mapPoints(dstPoints, srcPoints);
if (mBitmapBound.contains(dstPoints[0], dstPoints[1]) || mBitmapBound.contains(dstPoints[2], dstPoints[3])) {
return true;
}
}
return false;
}
在onTouch的ACTION_MOVE中去計(jì)算x慢洋,y相對(duì)移動(dòng)的坐標(biāo)塘雳,然后調(diào)用矩陣的平移方法即可:
case MotionEvent.ACTION_MOVE:
if (mCanTranslate) {
translate(event.getX() - mLastSinglePoint.x, event.getY() - mLastSinglePoint.y);
mLastSinglePoint.set(event.getX(), event.getY());
}
break;
/**
* 平移操作
*
* @param dx
* @param dy
*/
private void translate(float dx, float dy) {
mMatrix.postTranslate(dx, dy);
mMatrix.mapPoints(mDstPoints, mScrPoints);
}
貼紙的縮放:
思路:當(dāng)用戶手指(雙指)按下屏幕的時(shí)候,需要判斷手指的觸摸點(diǎn)是否在貼紙上普筹,如果在败明,將貼紙的狀態(tài)標(biāo)記為可縮放并記錄下手指之間的距離,當(dāng)用戶手指滑動(dòng)屏幕的時(shí)候太防,我們可以計(jì)算出當(dāng)前手指之間的距離與剛才按下屏幕手指間距離的比值妻顶,這個(gè)比值就是貼紙的縮放因子酸员,大于1表示放大,小于1表示縮小讳嘱,縮放中心為貼紙中心幔嗦。
代碼實(shí)現(xiàn):在onTouch的ACTION_POINTER_DOWN中去判斷手指觸摸點(diǎn)的個(gè)數(shù)和觸摸點(diǎn)位置,如果觸摸點(diǎn)為2且在貼紙上沥潭,將狀態(tài)標(biāo)記為可縮放邀泉,并記錄下手指間的距離:
case MotionEvent.ACTION_POINTER_DOWN:
if (event.getPointerCount() == 2 && isInStickerView(event)) {
mCanTranslate = false;
mCanScale = true;
mCanRotate = true;
//計(jì)算雙指之間向量
mLastDistancePoint.set(event.getX(0) - event.getX(1), event.getY(0) - event.getY(1));
//計(jì)算雙指之間距離
mLastDistance = calculateDistance(event);
}
break;
根據(jù)直角三角形勾股定理可以得到手指間的距離:
/**
* 計(jì)算兩點(diǎn)之間的距離
*/
private float calculateDistance(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
在onTouch的ACTION_MOVE中得到新的手指間的距離,與剛才手指按下屏幕時(shí)記錄的距離做對(duì)比得到縮放因子钝鸽,然后調(diào)用矩陣的縮放方法即可汇恤,縮放中心為貼紙中點(diǎn):
case MotionEvent.ACTION_MOVE:
if (mCanScale && event.getPointerCount() == 2) {
//操作自由縮放
//手指間距離
float distance = calculateDistance(event);
//根據(jù)雙指移動(dòng)的距離獲取縮放因子
float scale = distance / mLastDistance;
scale(scale, scale, getMidPoint().x, getMidPoint().y);
mLastDistance = distance;
}
break;
/**
* 縮放操作
*
* @param sx
* @param sy
* @param px
* @param py
*/
private void scale(float sx, float sy, float px, float py) {
mMatrix.postScale(sx, sy, px, py);
mMatrix.mapPoints(mDstPoints, mScrPoints);
}
貼紙的旋轉(zhuǎn):
思路:當(dāng)用戶手指(雙指)按下屏幕的時(shí)候,需要判斷手指的觸摸點(diǎn)是否在貼紙上拔恰,如果在因谎,將貼紙的狀態(tài)標(biāo)記為可旋轉(zhuǎn)并記錄下手指所形成的向量,當(dāng)用戶手指滑動(dòng)屏幕的時(shí)颜懊,需要計(jì)算出當(dāng)前手指所形成的向量與剛才按下屏幕手指所形成的向量的角度差财岔,這個(gè)角度差就是貼紙應(yīng)該旋轉(zhuǎn)的角度了,旋轉(zhuǎn)中心為貼紙中點(diǎn):
代碼實(shí)現(xiàn):在onTouch的ACTION_POINTER_DOWN中去判斷手指觸摸點(diǎn)的個(gè)數(shù)和觸摸點(diǎn)位置饭冬,如果觸摸點(diǎn)在貼紙上使鹅,將狀態(tài)標(biāo)記為可旋轉(zhuǎn)并記錄下手指間的所形成的向量:
case MotionEvent.ACTION_POINTER_DOWN:
if (event.getPointerCount() == 2 && isInStickerView(event)) {
mCanTranslate = false;
mCanScale = true;
mCanRotate = true;
//計(jì)算雙指之間向量
mLastDistancePoint.set(event.getX(0) - event.getX(1), event.getY(0) - event.getY(1));
//計(jì)算雙指之間距離
mLastDistance = calculateDistance(event);
}
break;
在onTouch的ACTION_MOVE中得到新的手指間所形成的向量,然后去計(jì)算它們之間所形成的夾角值:
case MotionEvent.ACTION_MOVE:
if (mCanRotate && event.getPointerCount() == 2) {
//操作自由旋轉(zhuǎn)
mDistancePoint.set(event.getX(0) - event.getX(1), event.getY(0) - event.getY(1));
rotate(calculateDegrees(mLastDistancePoint, mDistancePoint), getMidPoint().x, getMidPoint().y);
mLastDistancePoint.set(mDistancePoint.x, mDistancePoint.y);
}
break;
我們?cè)谶@里可以通過(guò)計(jì)算向量的斜率差來(lái)獲取手指間的旋轉(zhuǎn)角度:
/**
* 計(jì)算旋轉(zhuǎn)角度
*
* @param lastPoint
* @param pointF
* @return
*/
private float calculateDegrees(PointF lastPoint, PointF pointF) {
float lastDegrees = (float) Math.atan2(lastPoint.y, lastPoint.x);
float currentDegrees = (float) Math.atan2(pointF.y, pointF.x);
return (float) Math.toDegrees(currentDegrees - lastDegrees);
}
/**
* 旋轉(zhuǎn)操作
*
* @param degrees
* @param px
* @param py
*/
private void rotate(float degrees, float px, float py) {
mMatrix.postRotate(degrees, px, py);
mMatrix.mapPoints(mDstPoints, mScrPoints);
}
以上就是實(shí)現(xiàn)貼紙效果的核心代碼了昌抠,很簡(jiǎn)單吧患朱,其實(shí)就只是矩陣、手勢(shì)炊苫、三角函數(shù)的綜合運(yùn)用裁厅。
補(bǔ)充說(shuō)明:
1、細(xì)心的朋友會(huì)發(fā)現(xiàn)在上面的代碼中侨艾,平移执虹、縮放、旋轉(zhuǎn)操作都伴隨著一行代碼mMatrix.mapPoints(mDstPoints, mScrPoints);
唠梨,這句話是做什么用的呢袋励?其實(shí)一開(kāi)始圖片加載成Bitmap對(duì)象的時(shí)候,我就記錄下了一些特殊點(diǎn):
//初始化圖像
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.icon);
//記錄圖像一些點(diǎn)位置
mScrPoints = new float[]{
0, 0,//左上
mBitmap.getWidth(), 0,//右上
mBitmap.getWidth(), mBitmap.getHeight(),//右下
0, mBitmap.getHeight(),//左下
mBitmap.getWidth() / 2, mBitmap.getHeight() / 2//中間點(diǎn)
};
//拷貝點(diǎn)位置
mDstPoints = mScrPoints.clone();
然后根據(jù)上文介紹mapPoints方法当叭,將每一次矩陣變化所影響的點(diǎn)位置都做了映射茬故,這樣我們就可以很方便的得到任一時(shí)刻的最新點(diǎn)位置,比如我們要知道某一時(shí)刻圖片的中點(diǎn)位置蚁鳖,我們就可以這樣做:
/**
* 獲取圖像中心點(diǎn)
*
* @return
*/
private PointF getMidPoint() {
mMidPoint.set(mDstPoints[8], mDstPoints[9]);
return mMidPoint;
}
2磺芭、關(guān)于旋轉(zhuǎn)角度的計(jì)算,這邊可以有很多方法醉箕,上文我采用的是計(jì)算出手指間的向量钾腺,然后求出他們的斜率差徙垫,然后轉(zhuǎn)換成角度,這里額外多介紹一種求角度的方法:
通過(guò)余弦定理求夾角:我們以圖片的中點(diǎn)為旋轉(zhuǎn)中心放棒,加上我們雙指的觸碰點(diǎn)姻报,我們就可以知道三角形的三個(gè)點(diǎn)坐標(biāo),就可以知道三邊的距離哨查,通過(guò)余弦定理我們很輕松的可以得到cos值逗抑,再將其轉(zhuǎn)換成角度即可。
如果不清楚余弦定理的朋友請(qǐng)戳:余弦定理視頻講解
這里需要注意象限問(wèn)題寒亥,也就是cos值的正負(fù)邮府,因?yàn)樾D(zhuǎn)有正時(shí)針?lè)较蚝湍鏁r(shí)針?lè)较颍@里我們可以通過(guò)向量積來(lái)判斷:
3溉奕、在做完一些列手勢(shì)操作褂傀,手指抬起的時(shí)候,我們把狀態(tài)重置:
/**
* 重置狀態(tài)
*/
private void reset() {
mCanTranslate = false;
mCanScale = false;
mCanRotate = false;
mLastDistance = 0f;
mMidPoint.set(0f, 0f);
mLastSinglePoint.set(0f, 0f);
mLastDistancePoint.set(0f, 0f);
mDistancePoint.set(0f, 0f);
}
好了加勤,到這里文章就結(jié)束啦仙辟,這里給出完整代碼(啟蒙思路,優(yōu)化版請(qǐng)見(jiàn)下一篇文章):
package com.lcw.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import com.lcw.view.R;
/**
* 自定義貼紙View
* Create by: chenWei.li
* Date: 2018/11/22
* Time: 下午11:02
* Email: lichenwei.me@foxmail.com
*/
/**
* @Deprecated 基礎(chǔ)貼紙類(lèi)鳄梅,廢棄不再使用
*/
public class StickerView extends View implements View.OnTouchListener {
private Bitmap mBitmap;//貼紙圖片
private Matrix mMatrix;//維護(hù)圖像變化的矩陣
private float[] mScrPoints;//矩陣變換前的點(diǎn)坐標(biāo)
private float[] mDstPoints;//矩陣變換后的點(diǎn)坐標(biāo)
private RectF mBitmapBound;//圖片的外圍邊框的點(diǎn)坐標(biāo)
private boolean mCanTranslate;//標(biāo)志是否可移動(dòng)
private boolean mCanScale;//標(biāo)志是否可縮放
private boolean mCanRotate;//標(biāo)志是否可旋轉(zhuǎn)
private float mLastDistance;//記錄上一次雙指之間的距離
private PointF mMidPoint = new PointF();//記錄圖片中心點(diǎn)
private PointF mLastSinglePoint = new PointF();//記錄上一次單指觸摸屏幕的點(diǎn)坐標(biāo)
private PointF mLastDistancePoint = new PointF();//記錄上一次雙指觸摸屏幕的點(diǎn)坐標(biāo)
private PointF mDistancePoint = new PointF();//記錄當(dāng)前雙指觸摸屏幕的點(diǎn)坐標(biāo)
private Paint mPaint;
public StickerView(Context context) {
super(context);
init(context);
}
public StickerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public StickerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
/**
* 完成一些初始化操作
*
* @param context
*/
private void init(Context context) {
//初始化畫(huà)筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.GRAY);
//初始化圖像
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.icon);
//記錄圖像一些點(diǎn)位置
mScrPoints = new float[]{
0, 0,//左上
mBitmap.getWidth(), 0,//右上
mBitmap.getWidth(), mBitmap.getHeight(),//右下
0, mBitmap.getHeight(),//左下
mBitmap.getWidth() / 2, mBitmap.getHeight() / 2//中間點(diǎn)
};
//拷貝點(diǎn)位置
mDstPoints = mScrPoints.clone();
mBitmapBound = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
//初始化矩陣
mMatrix = new Matrix();
//移動(dòng)圖像到屏幕中心
// WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// DisplayMetrics displayMetrics = new DisplayMetrics();
// windowManager.getDefaultDisplay().getMetrics(displayMetrics);
// float dx = displayMetrics.widthPixels / 2 - mBitmap.getWidth() / 2;
// float dy = displayMetrics.heightPixels / 2 - mBitmap.getHeight() / 2;
// translate(dx, dy);
//設(shè)置觸摸監(jiān)聽(tīng)
setOnTouchListener(this);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mBitmap, mMatrix, null);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mLastSinglePoint.set(event.getX(), event.getY());
if (isInStickerView(event)) {
//觸摸點(diǎn)是否在貼紙范圍內(nèi)
mCanTranslate = true;
}
mCanScale = false;
mCanRotate = false;
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (event.getPointerCount() == 2 && isInStickerView(event)) {
mCanTranslate = false;
mCanScale = true;
mCanRotate = true;
//計(jì)算雙指之間向量
mLastDistancePoint.set(event.getX(0) - event.getX(1), event.getY(0) - event.getY(1));
//計(jì)算雙指之間距離
mLastDistance = calculateDistance(event);
}
break;
case MotionEvent.ACTION_MOVE:
if (mCanTranslate) {
translate(event.getX() - mLastSinglePoint.x, event.getY() - mLastSinglePoint.y);
mLastSinglePoint.set(event.getX(), event.getY());
}
if ((mCanScale || mCanRotate) && event.getPointerCount() == 2) {
//操作自由縮放
float distance = calculateDistance(event);
//根據(jù)雙指移動(dòng)的距離獲取縮放因子
float scale = distance / mLastDistance;
scale(scale, scale, getMidPoint().x, getMidPoint().y);
mLastDistance = distance;
//操作自由旋轉(zhuǎn)
mDistancePoint.set(event.getX(0) - event.getX(1), event.getY(0) - event.getY(1));
rotate(calculateDegrees(mLastDistancePoint, mDistancePoint), getMidPoint().x, getMidPoint().y);
mLastDistancePoint.set(mDistancePoint.x, mDistancePoint.y);
}
break;
case MotionEvent.ACTION_UP:
reset();
break;
}
invalidate();
return true;
}
/**
* 平移操作
*
* @param dx
* @param dy
*/
private void translate(float dx, float dy) {
mMatrix.postTranslate(dx, dy);
mMatrix.mapPoints(mDstPoints, mScrPoints);
}
/**
* 縮放操作
*
* @param sx
* @param sy
* @param px
* @param py
*/
private void scale(float sx, float sy, float px, float py) {
mMatrix.postScale(sx, sy, px, py);
mMatrix.mapPoints(mDstPoints, mScrPoints);
}
/**
* 旋轉(zhuǎn)操作
*
* @param degrees
* @param px
* @param py
*/
private void rotate(float degrees, float px, float py) {
mMatrix.postRotate(degrees, px, py);
mMatrix.mapPoints(mDstPoints, mScrPoints);
}
/**
* 檢測(cè)當(dāng)前觸摸是否在貼紙上
*
* @return
*/
private boolean isInStickerView(MotionEvent motionEvent) {
if (motionEvent.getPointerCount() == 1) {
float[] dstPoints = new float[2];
float[] srcPoints = new float[]{motionEvent.getX(), motionEvent.getY()};
Matrix matrix = new Matrix();
mMatrix.invert(matrix);
matrix.mapPoints(dstPoints, srcPoints);
if (mBitmapBound.contains(dstPoints[0], dstPoints[1])) {
return true;
}
}
if (motionEvent.getPointerCount() == 2) {
float[] dstPoints = new float[4];
float[] srcPoints = new float[]{motionEvent.getX(0), motionEvent.getY(0), motionEvent.getX(1), motionEvent.getY(1)};
Matrix matrix = new Matrix();
mMatrix.invert(matrix);
matrix.mapPoints(dstPoints, srcPoints);
if (mBitmapBound.contains(dstPoints[0], dstPoints[1]) || mBitmapBound.contains(dstPoints[2], dstPoints[3])) {
return true;
}
}
return false;
}
/**
* 獲取圖像中心點(diǎn)
*
* @return
*/
private PointF getMidPoint() {
mMidPoint.set(mDstPoints[8], mDstPoints[9]);
return mMidPoint;
}
/**
* 計(jì)算兩點(diǎn)之間的距離
*/
private float calculateDistance(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
/**
* 計(jì)算旋轉(zhuǎn)角度
*
* @param lastPoint
* @param pointF
* @return
*/
private float calculateDegrees(PointF lastPoint, PointF pointF) {
float lastDegrees = (float) Math.atan2(lastPoint.y, lastPoint.x);
float currentDegrees = (float) Math.atan2(pointF.y, pointF.x);
return (float) Math.toDegrees(currentDegrees - lastDegrees);
}
/**
* 重置狀態(tài)
*/
private void reset() {
mCanTranslate = false;
mCanScale = false;
mCanRotate = false;
mLastDistance = 0f;
mMidPoint.set(0f, 0f);
mLastSinglePoint.set(0f, 0f);
mLastDistancePoint.set(0f, 0f);
mDistancePoint.set(0f, 0f);
}
}
下一篇:《Android開(kāi)發(fā)之仿微博貼紙效果實(shí)現(xiàn)——進(jìn)階篇》
源碼下載:
這里附上源碼地址(歡迎Star叠国,歡迎Fork):StickerView