Android開(kāi)發(fā)之仿微博貼紙效果實(shí)現(xiàn)——基礎(chǔ)篇

之前寫(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市戴尸,隨后出現(xiàn)的幾起案子粟焊,更是在濱河造成了極大的恐慌,老刑警劉巖孙蒙,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件项棠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挎峦,警方通過(guò)查閱死者的電腦和手機(jī)香追,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坦胶,“玉大人透典,你說(shuō)我怎么就攤上這事《傥” “怎么了峭咒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)岖圈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)钙皮,這世上最難降的妖魔是什么蜂科? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任顽决,我火速辦了婚禮,結(jié)果婚禮上导匣,老公的妹妹穿的比我還像新娘才菠。我一直安慰自己,他們只是感情好贡定,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布赋访。 她就那樣靜靜地躺著缓待,像睡著了一般蚓耽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旋炒,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天瘫镇,我揣著相機(jī)與錄音鼎兽,去河邊找鬼。 笑死铣除,一個(gè)胖子當(dāng)著我的面吹牛谚咬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播尚粘,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼择卦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了背苦?” 一聲冷哼從身側(cè)響起互捌,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎行剂,沒(méi)想到半個(gè)月后秕噪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厚宰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年腌巾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铲觉。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡澈蝙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出撵幽,到底是詐尸還是另有隱情灯荧,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布盐杂,位于F島的核電站逗载,受9級(jí)特大地震影響哆窿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜厉斟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一挚躯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧擦秽,春花似錦码荔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至链快,卻和暖如春誉己,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背域蜗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工巨双, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霉祸。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓筑累,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親丝蹭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子慢宗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 前言 最近有需求要做一個(gè)畫(huà)布贱田,這個(gè)畫(huà)布以一個(gè)圖片為背景缅茉,可以實(shí)現(xiàn)縮放,涂鴉以及貼紙的功能男摧,縮放和涂鴉要兼顧蔬墩,于是就...
    王巖_shang閱讀 7,260評(píng)論 8 29
  • 效果圖: Github鏈接:https://github.com/boycy815/PinchImageView ...
    CQ_TYL閱讀 2,216評(píng)論 0 0
  • 概述 了解過(guò)自定義View的童鞋 對(duì)Canvas.drawBitmap(Bitmap, Matrix, Paint...
    RazorZ閱讀 8,117評(píng)論 6 70
  • 1 前言 OpenGL渲染3D模型離不開(kāi)空間幾何的數(shù)學(xué)理論知識(shí),而本篇文章的目的就是對(duì)空間幾何進(jìn)行簡(jiǎn)單的介紹耗拓,并對(duì)...
    RichardJieChen閱讀 6,985評(píng)論 1 11
  • 上班幾年拇颅,對(duì)于幸福的理解開(kāi)始發(fā)生一些小小的變化,也許每個(gè)人都有自己的幸福乔询,只不過(guò)狀態(tài)不同而已樟插。 1 剛剛上班的時(shí)候...
    知否紅瘦閱讀 504評(píng)論 0 1