Android圖片手勢控件

效果圖:


Github鏈接:https://github.com/boycy815/PinchImageView

使用:

1:自定義控件通過java代碼:PinchImageView imageView =new PinchImageView(this);獲得image對象餐塘,

2:直接在xml中調用下面的類,自定義控件的用法叮趴;



工具類:

package com.lzyi.tpm.utils;

import android.animation.ValueAnimator;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Matrix;

import android.graphics.PointF;

import android.graphics.RectF;

import android.util.AttributeSet;

import android.view.GestureDetector;

import android.view.MotionEvent;

import android.widget.ImageView;

import java.util.ArrayList;

import java.util.LinkedList;

import java.util.List;

import java.util.Queue;

/**

* 手勢圖片控件

*

* @author clifford

*/

public class PinchImageViewextends ImageView? {

////////////////////////////////配置參數(shù)////////////////////////////////

? ? /**

? ? * 圖片縮放動畫時間

? ? */

? ? public static final int SCALE_ANIMATOR_DURATION =200;

? ? /**

? ? * 慣性動畫衰減參數(shù)

? ? */

? ? public static final float FLING_DAMPING_FACTOR =0.9f;

? ? /**

? ? * 圖片最大放大比例

? ? */

? ? private static final float MAX_SCALE =4f;

? ? ////////////////////////////////監(jiān)聽器////////////////////////////////

? ? /**

? ? * 外界點擊事件

? ? *

? ? * @see #setOnClickListener(OnClickListener)

*/

? ? private OnClickListenermOnClickListener;

? ? /**

? ? * 外界長按事件

? ? *

? ? * @see #setOnLongClickListener(OnLongClickListener)

*/

? ? private OnLongClickListenermOnLongClickListener;

? ? @Override

? ? public void setOnClickListener(OnClickListener l) {

//默認的click會在任何點擊情況下都會觸發(fā)样眠,所以搞成自己的

? ? ? ? mOnClickListener = l;

? ? }

@Override

? ? public void setOnLongClickListener(OnLongClickListener l) {

//默認的long click會在任何長按情況下都會觸發(fā)友瘤,所以搞成自己的

? ? ? ? mOnLongClickListener = l;

? ? }

////////////////////////////////公共狀態(tài)獲取////////////////////////////////

? ? /**

? ? * 手勢狀態(tài):自由狀態(tài)

? ? *

? ? * @see #getPinchMode()

*/

? ? public static final int PINCH_MODE_FREE =0;

? ? /**

? ? * 手勢狀態(tài):單指滾動狀態(tài)

? ? *

? ? * @see #getPinchMode()

*/

? ? public static final int PINCH_MODE_SCROLL =1;

? ? /**

? ? * 手勢狀態(tài):雙指縮放狀態(tài)

? ? *

? ? * @see #getPinchMode()

*/

? ? public static final int PINCH_MODE_SCALE =2;

? ? /**

? ? * 外層變換矩陣,如果是單位矩陣檐束,那么圖片是fit center狀態(tài)

? ? *

? ? * @see #getOuterMatrix(Matrix)

? ? * @see #outerMatrixTo(Matrix, long)

*/

? ? private MatrixmOuterMatrix =new Matrix();

? ? /**

? ? * 矩形遮罩

? ? *

? ? * @see #getMask()

? ? * @see #zoomMaskTo(RectF, long)

*/

? ? private RectFmMask;

? ? /**

? ? * 當前手勢狀態(tài)

? ? *

? ? * @see #getPinchMode()

? ? * @see #PINCH_MODE_FREE

? ? * @see #PINCH_MODE_SCROLL

? ? * @see #PINCH_MODE_SCALE

*/

? ? private int mPinchMode =PINCH_MODE_FREE;

? ? /**

? ? * 獲取外部變換矩陣.

*

? ? * 外部變換矩陣記錄了圖片手勢操作的最終結果,是相對于圖片fit center狀態(tài)的變換.

? ? * 默認值為單位矩陣,此時圖片為fit center狀態(tài).

*

? ? * @param matrix 用于填充結果的對象

? ? * @return 如果傳了matrix參數(shù)則將matrix填充后返回,否則new一個填充返回

? ? */

? ? public MatrixgetOuterMatrix(Matrix matrix) {

if (matrix ==null) {

matrix =new Matrix(mOuterMatrix);

? ? ? ? }else {

matrix.set(mOuterMatrix);

? ? ? ? }

return matrix;

? ? }

/**

? ? * 獲取內部變換矩陣.

*

? ? * 內部變換矩陣是原圖到fit center狀態(tài)的變換,當原圖尺寸變化或者控件大小變化都會發(fā)生改變

? ? * 當尚未布局或者原圖不存在時,其值無意義.所以在調用前需要確保前置條件有效,否則將影響計算結果.

*

? ? * @param matrix 用于填充結果的對象

? ? * @return 如果傳了matrix參數(shù)則將matrix填充后返回,否則new一個填充返回

? ? */

? ? public MatrixgetInnerMatrix(Matrix matrix) {

if (matrix ==null) {

matrix =new Matrix();

? ? ? ? }else {

matrix.reset();

? ? ? ? }

if (isReady()) {

//原圖大小

? ? ? ? ? ? RectF tempSrc = MathUtils.rectFTake(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

? ? ? ? ? ? //控件大小

? ? ? ? ? ? RectF tempDst = MathUtils.rectFTake(0, 0, getWidth(), getHeight());

? ? ? ? ? ? //計算fit center矩陣

? ? ? ? ? ? matrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER);

? ? ? ? ? ? //釋放臨時對象

? ? ? ? ? ? MathUtils.rectFGiven(tempDst);

? ? ? ? ? ? MathUtils.rectFGiven(tempSrc);

? ? ? ? }

return matrix;

? ? }

/**

? ? * 獲取圖片總變換矩陣.

*

? ? * 總變換矩陣為內部變換矩陣x外部變換矩陣,決定了原圖到所見最終狀態(tài)的變換

? ? * 當尚未布局或者原圖不存在時,其值無意義.所以在調用前需要確保前置條件有效,否則將影響計算結果.

*

? ? * @param matrix 用于填充結果的對象

? ? * @return 如果傳了matrix參數(shù)則將matrix填充后返回,否則new一個填充返回

? ? *

? ? * @see #getOuterMatrix(Matrix)

? ? * @see #getInnerMatrix(Matrix)

*/

? ? public MatrixgetCurrentImageMatrix(Matrix matrix) {

//獲取內部變換矩陣

? ? ? ? matrix = getInnerMatrix(matrix);

? ? ? ? //乘上外部變換矩陣

? ? ? ? matrix.postConcat(mOuterMatrix);

? ? ? ? return matrix;

? ? }

/**

? ? * 獲取當前變換后的圖片位置和尺寸

? ? *

? ? * 當尚未布局或者原圖不存在時,其值無意義.所以在調用前需要確保前置條件有效,否則將影響計算結果.

*

? ? * @param rectF 用于填充結果的對象

? ? * @return 如果傳了rectF參數(shù)則將rectF填充后返回,否則new一個填充返回

? ? *

? ? * @see #getCurrentImageMatrix(Matrix)

*/

? ? public RectFgetImageBound(RectF rectF) {

if (rectF ==null) {

rectF =new RectF();

? ? ? ? }else {

rectF.setEmpty();

? ? ? ? }

if (!isReady()) {

return rectF;

? ? ? ? }else {

//申請一個空matrix

? ? ? ? ? ? Matrix matrix = MathUtils.matrixTake();

? ? ? ? ? ? //獲取當前總變換矩陣

? ? ? ? ? ? getCurrentImageMatrix(matrix);

? ? ? ? ? ? //對原圖矩形進行變換得到當前顯示矩形

? ? ? ? ? ? rectF.set(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

? ? ? ? ? ? matrix.mapRect(rectF);

? ? ? ? ? ? //釋放臨時matrix

? ? ? ? ? ? MathUtils.matrixGiven(matrix);

? ? ? ? ? ? return rectF;

? ? ? ? }

}

/**

? ? * 獲取當前設置的mask

*

? ? * @return 返回當前的mask對象副本,如果當前沒有設置mask則返回null

*/

? ? public RectFgetMask() {

if (mMask !=null) {

return new RectF(mMask);

? ? ? ? }else {

return null;

? ? ? ? }

}

/**

? ? * 獲取當前手勢狀態(tài)

? ? *

? ? * @see #PINCH_MODE_FREE

? ? * @see #PINCH_MODE_SCROLL

? ? * @see #PINCH_MODE_SCALE

*/

? ? public int getPinchMode() {

return mPinchMode;

? ? }

/**

? ? * 與ViewPager結合的時候使用

? ? * @param direction

? ? * @return

? ? */

? ? @Override

? ? public boolean canScrollHorizontally(int direction) {

if (mPinchMode == PinchImageView.PINCH_MODE_SCALE) {

return true;

? ? ? ? }

RectF bound = getImageBound(null);

? ? ? ? if (bound ==null) {

return false;

? ? ? ? }

if (bound.isEmpty()) {

return false;

? ? ? ? }

if (direction >0) {

return bound.right > getWidth();

? ? ? ? }else {

return bound.left <0;

? ? ? ? }

}

/**

? ? * 與ViewPager結合的時候使用

? ? * @param direction

? ? * @return

? ? */

? ? @Override

? ? public boolean canScrollVertically(int direction) {

if (mPinchMode == PinchImageView.PINCH_MODE_SCALE) {

return true;

? ? ? ? }

RectF bound = getImageBound(null);

? ? ? ? if (bound ==null) {

return false;

? ? ? ? }

if (bound.isEmpty()) {

return false;

? ? ? ? }

if (direction >0) {

return bound.bottom > getHeight();

? ? ? ? }else {

return bound.top <0;

? ? ? ? }

}

////////////////////////////////公共狀態(tài)設置////////////////////////////////

? ? /**

? ? * 執(zhí)行當前outerMatrix到指定outerMatrix漸變的動畫

? ? *

? ? * 調用此方法會停止正在進行中的手勢以及手勢動畫.

? ? * 當duration為0時,outerMatrix值會被立即設置而不會啟動動畫.

*

? ? * @param endMatrix 動畫目標矩陣

? ? * @param duration 動畫持續(xù)時間

? ? *

? ? * @see #getOuterMatrix(Matrix)

*/

? ? public void outerMatrixTo(Matrix endMatrix, long duration) {

if (endMatrix ==null) {

return;

? ? ? ? }

//將手勢設置為PINCH_MODE_FREE將停止后續(xù)手勢的觸發(fā)

? ? ? ? mPinchMode =PINCH_MODE_FREE;

? ? ? ? //停止所有正在進行的動畫

? ? ? ? cancelAllAnimator();

? ? ? ? //如果時間不合法立即執(zhí)行結果

? ? ? ? if (duration <=0) {

mOuterMatrix.set(endMatrix);

? ? ? ? ? ? dispatchOuterMatrixChanged();

? ? ? ? ? ? invalidate();

? ? ? ? }else {

//創(chuàng)建矩陣變化動畫

? ? ? ? ? ? mScaleAnimator =new ScaleAnimator(mOuterMatrix, endMatrix, duration);

? ? ? ? ? ? mScaleAnimator.start();

? ? ? ? }

}

/**

? ? * 執(zhí)行當前mask到指定mask的變化動畫

? ? *

? ? * 調用此方法不會停止手勢以及手勢相關動畫,但會停止正在進行的mask動畫.

? ? * 當前mask為null時,則不執(zhí)行動畫立即設置為目標mask.

? ? * 當duration為0時,立即將當前mask設置為目標mask,不會執(zhí)行動畫.

*

? ? * @param mask 動畫目標mask

? ? * @param duration 動畫持續(xù)時間

? ? *

? ? * @see #getMask()

*/

? ? public void zoomMaskTo(RectF mask, long duration) {

if (mask ==null) {

return;

? ? ? ? }

//停止mask動畫

? ? ? ? if (mMaskAnimator !=null) {

mMaskAnimator.cancel();

? ? ? ? ? ? mMaskAnimator =null;

? ? ? ? }

//如果duration為0或者之前沒有設置過mask,不執(zhí)行動畫,立即設置

? ? ? ? if (duration <=0 ||mMask ==null) {

if (mMask ==null) {

mMask =new RectF();

? ? ? ? ? ? }

mMask.set(mask);

? ? ? ? ? ? invalidate();

? ? ? ? }else {

//執(zhí)行mask動畫

? ? ? ? ? ? mMaskAnimator =new MaskAnimator(mMask, mask, duration);

? ? ? ? ? ? mMaskAnimator.start();

? ? ? ? }

}

/**

? ? * 重置所有狀態(tài)

? ? *

? ? * 重置位置到fit center狀態(tài),清空mask,停止所有手勢,停止所有動畫.

? ? * 但不清空drawable,以及事件綁定相關數(shù)據(jù).

*/

? ? public void reset() {

//重置位置到fit

? ? ? ? mOuterMatrix.reset();

? ? ? ? dispatchOuterMatrixChanged();

? ? ? ? //清空mask

? ? ? ? mMask =null;

? ? ? ? //停止所有手勢

? ? ? ? mPinchMode =PINCH_MODE_FREE;

? ? ? ? mLastMovePoint.set(0, 0);

? ? ? ? mScaleCenter.set(0, 0);

? ? ? ? mScaleBase =0;

? ? ? ? //停止所有動畫

? ? ? ? if (mMaskAnimator !=null) {

mMaskAnimator.cancel();

? ? ? ? ? ? mMaskAnimator =null;

? ? ? ? }

cancelAllAnimator();

? ? ? ? //重繪

? ? ? ? invalidate();

? ? }

////////////////////////////////對外廣播事件////////////////////////////////

? ? /**

? ? * 外部矩陣變化事件通知監(jiān)聽器

? ? */

? ? public interface OuterMatrixChangedListener {

/**

? ? ? ? * 外部矩陣變化回調

? ? ? ? *

? ? ? ? * 外部矩陣的任何變化后都收到此回調.

? ? ? ? * 外部矩陣變化后,總變化矩陣,圖片的展示位置都將發(fā)生變化.

*

? ? ? ? * @param pinchImageView

? ? ? ? *

? ? ? ? * @see #getOuterMatrix(Matrix)

? ? ? ? * @see #getCurrentImageMatrix(Matrix)

? ? ? ? * @see #getImageBound(RectF)

*/

? ? ? ? void onOuterMatrixChanged(PinchImageView pinchImageView);

? ? }

/**

? ? * 所有OuterMatrixChangedListener監(jiān)聽列表

? ? *

? ? * @see #addOuterMatrixChangedListener(OuterMatrixChangedListener)

? ? * @see #removeOuterMatrixChangedListener(OuterMatrixChangedListener)

*/

? ? private ListmOuterMatrixChangedListeners;

? ? /**

? ? * 當mOuterMatrixChangedListeners被鎖定不允許修改時,臨時將修改寫到這個副本中

? ? *

? ? * @see #mOuterMatrixChangedListeners

*/

? ? private ListmOuterMatrixChangedListenersCopy;

? ? /**

? ? * mOuterMatrixChangedListeners的修改鎖定

? ? *

? ? * 當進入dispatchOuterMatrixChanged方法時,被加1,退出前被減1

*

? ? * @see #dispatchOuterMatrixChanged()

? ? * @see #addOuterMatrixChangedListener(OuterMatrixChangedListener)

? ? * @see #removeOuterMatrixChangedListener(OuterMatrixChangedListener)

*/

? ? private int mDispatchOuterMatrixChangedLock;

? ? /**

? ? * 添加外部矩陣變化監(jiān)聽

? ? *

? ? * @param listener

? ? */

? ? public void addOuterMatrixChangedListener(OuterMatrixChangedListener listener) {

if (listener ==null) {

return;

? ? ? ? }

//如果監(jiān)聽列表沒有被修改鎖定直接將監(jiān)聽添加到監(jiān)聽列表

? ? ? ? if (mDispatchOuterMatrixChangedLock ==0) {

if (mOuterMatrixChangedListeners ==null) {

mOuterMatrixChangedListeners =new ArrayList();

? ? ? ? ? ? }

mOuterMatrixChangedListeners.add(listener);

? ? ? ? }else {

//如果監(jiān)聽列表修改被鎖定,那么嘗試在監(jiān)聽列表副本上添加

? ? ? ? ? ? //監(jiān)聽列表副本將會在鎖定被解除時替換到監(jiān)聽列表里

? ? ? ? ? ? if (mOuterMatrixChangedListenersCopy ==null) {

if (mOuterMatrixChangedListeners !=null) {

mOuterMatrixChangedListenersCopy =new ArrayList(mOuterMatrixChangedListeners);

? ? ? ? ? ? ? ? }else {

mOuterMatrixChangedListenersCopy =new ArrayList();

? ? ? ? ? ? ? ? }

}

mOuterMatrixChangedListenersCopy.add(listener);

? ? ? ? }

}

/**

? ? * 刪除外部矩陣變化監(jiān)聽

? ? *

? ? * @param listener

? ? */

? ? public void removeOuterMatrixChangedListener(OuterMatrixChangedListener listener) {

if (listener ==null) {

return;

? ? ? ? }

//如果監(jiān)聽列表沒有被修改鎖定直接在監(jiān)聽列表數(shù)據(jù)結構上修改

? ? ? ? if (mDispatchOuterMatrixChangedLock ==0) {

if (mOuterMatrixChangedListeners !=null) {

mOuterMatrixChangedListeners.remove(listener);

? ? ? ? ? ? }

}else {

//如果監(jiān)聽列表被修改鎖定,那么就在其副本上修改

? ? ? ? ? ? //其副本將會在鎖定解除時替換回監(jiān)聽列表

? ? ? ? ? ? if (mOuterMatrixChangedListenersCopy ==null) {

if (mOuterMatrixChangedListeners !=null) {

mOuterMatrixChangedListenersCopy =new ArrayList(mOuterMatrixChangedListeners);

? ? ? ? ? ? ? ? }

}

if (mOuterMatrixChangedListenersCopy !=null) {

mOuterMatrixChangedListenersCopy.remove(listener);

? ? ? ? ? ? }

}

}

/**

? ? * 觸發(fā)外部矩陣修改事件

? ? *

? ? * 需要在每次給外部矩陣設置值時都調用此方法.

*

? ? * @see #mOuterMatrix

*/

? ? private void dispatchOuterMatrixChanged() {

if (mOuterMatrixChangedListeners ==null) {

return;

? ? ? ? }

//增加鎖

? ? ? ? //這里之所以用計數(shù)器做鎖定是因為可能在鎖定期間又間接調用了此方法產生遞歸

? ? ? ? //使用boolean無法判斷遞歸結束

? ? ? ? mDispatchOuterMatrixChangedLock++;

? ? ? ? //在列表循環(huán)過程中不允許修改列表,否則將引發(fā)崩潰

? ? ? ? for (OuterMatrixChangedListener listener :mOuterMatrixChangedListeners) {

listener.onOuterMatrixChanged(this);

? ? ? ? }

//減鎖

? ? ? ? mDispatchOuterMatrixChangedLock--;

? ? ? ? //如果是遞歸的情況,mDispatchOuterMatrixChangedLock可能大于1,只有減到0才能算列表的鎖定解除

? ? ? ? if (mDispatchOuterMatrixChangedLock ==0) {

//如果期間有修改列表,那么副本將不為null

? ? ? ? ? ? if (mOuterMatrixChangedListenersCopy !=null) {

//將副本替換掉正式的列表

? ? ? ? ? ? ? ? mOuterMatrixChangedListeners =mOuterMatrixChangedListenersCopy;

? ? ? ? ? ? ? ? //清空副本

? ? ? ? ? ? ? ? mOuterMatrixChangedListenersCopy =null;

? ? ? ? ? ? }

}

}

////////////////////////////////用于重載定制////////////////////////////////

? ? /**

? ? * 獲取圖片最大可放大的比例

? ? *

? ? * 如果放大大于這個比例則不被允許.

? ? * 在雙手縮放過程中如果圖片放大比例大于這個值,手指釋放將回彈到這個比例.

? ? * 在雙擊放大過程中不允許放大比例大于這個值.

? ? * 覆蓋此方法可以定制不同情況使用不同的最大可放大比例.

*

? ? * @return 縮放比例

? ? *

? ? * @see #scaleEnd()

? ? * @see #doubleTap(float, float)

*/

? ? protected float getMaxScale() {

return MAX_SCALE;

? ? }

/**

? ? * 計算雙擊之后圖片接下來應該被縮放的比例

? ? *

? ? * 如果值大于getMaxScale或者小于fit center尺寸辫秧,則實際使用取邊界值.

? ? * 通過覆蓋此方法可以定制不同的圖片被雙擊時使用不同的放大策略.

*

? ? * @param innerScale 當前內部矩陣的縮放值

? ? * @param outerScale 當前外部矩陣的縮放值

? ? * @return 接下來的縮放比例

? ? *

? ? * @see #doubleTap(float, float)

? ? * @see #getMaxScale()

*/

? ? protected float calculateNextScale(float innerScale, float outerScale) {

float currentScale = innerScale * outerScale;

? ? ? ? if (currentScale

return MAX_SCALE;

? ? ? ? }else {

return innerScale;

? ? ? ? }

}

////////////////////////////////初始化////////////////////////////////

? ? public PinchImageView(Context context) {

super(context);

? ? ? ? initView();

? ? }

public PinchImageView(Context context, AttributeSet attrs) {

super(context, attrs);

? ? ? ? initView();

? ? }

public PinchImageView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

? ? ? ? initView();

? ? }

private void initView() {

//強制設置圖片scaleType為matrix

? ? ? ? super.setScaleType(ScaleType.MATRIX);

? ? }

//不允許設置scaleType,只能用內部設置的matrix

? ? @Override

? ? public void setScaleType(ScaleType scaleType) {}

////////////////////////////////繪制////////////////////////////////

? ? @Override

? ? protected void onDraw(Canvas canvas) {

//在繪制前設置變換矩陣

? ? ? ? if (isReady()) {

Matrix matrix = MathUtils.matrixTake();

? ? ? ? ? ? setImageMatrix(getCurrentImageMatrix(matrix));

? ? ? ? ? ? MathUtils.matrixGiven(matrix);

? ? ? ? }

//對圖像做遮罩處理

? ? ? ? if (mMask !=null) {

canvas.save();

? ? ? ? ? ? canvas.clipRect(mMask);

? ? ? ? ? ? super.onDraw(canvas);

? ? ? ? ? ? canvas.restore();

? ? ? ? }else {

super.onDraw(canvas);

? ? ? ? }

}

////////////////////////////////有效性判斷////////////////////////////////

? ? /**

? ? * 判斷當前情況是否能執(zhí)行手勢相關計算

? ? *

? ? * 包括:是否有圖片,圖片是否有尺寸,控件是否有尺寸.

*

? ? * @return 是否能執(zhí)行手勢相關計算

? ? */

? ? private boolean isReady() {

return getDrawable() !=null && getDrawable().getIntrinsicWidth() >0 && getDrawable().getIntrinsicHeight() >0

? ? ? ? ? ? ? ? && getWidth() >0 && getHeight() >0;

? ? }

////////////////////////////////mask動畫處理////////////////////////////////

? ? /**

? ? * mask修改的動畫

? ? *

? ? * 和圖片的動畫相互獨立.

*

? ? * @see #zoomMaskTo(RectF, long)

*/

? ? private MaskAnimatormMaskAnimator;

? ? /**

? ? * mask變換動畫

? ? *

? ? * 將mask從一個rect動畫到另外一個rect

*/

? ? private class MaskAnimatorextends ValueAnimatorimplements ValueAnimator.AnimatorUpdateListener {

/**

? ? ? ? * 開始mask

*/

? ? ? ? private float[]mStart =new float[4];

? ? ? ? /**

? ? ? ? * 結束mask

*/

? ? ? ? private float[]mEnd =new float[4];

? ? ? ? /**

? ? ? ? * 中間結果mask

*/

? ? ? ? private float[]mResult =new float[4];

? ? ? ? /**

? ? ? ? * 創(chuàng)建mask變換動畫

? ? ? ? *

? ? ? ? * @param start 動畫起始狀態(tài)

? ? ? ? * @param end 動畫終點狀態(tài)

? ? ? ? * @param duration 動畫持續(xù)時間

? ? ? ? */

? ? ? ? public MaskAnimator(RectF start, RectF end, long duration) {

super();

? ? ? ? ? ? setFloatValues(0, 1f);

? ? ? ? ? ? setDuration(duration);

? ? ? ? ? ? addUpdateListener(this);

? ? ? ? ? ? //將起點終點拷貝到數(shù)組方便計算

? ? ? ? ? ? mStart[0] = start.left;

? ? ? ? ? ? mStart[1] = start.top;

? ? ? ? ? ? mStart[2] = start.right;

? ? ? ? ? ? mStart[3] = start.bottom;

? ? ? ? ? ? mEnd[0] = end.left;

? ? ? ? ? ? mEnd[1] = end.top;

? ? ? ? ? ? mEnd[2] = end.right;

? ? ? ? ? ? mEnd[3] = end.bottom;

? ? ? ? }

@Override

? ? ? ? public void onAnimationUpdate(ValueAnimator animation) {

//獲取動畫進度,0-1范圍

? ? ? ? ? ? float value = (Float) animation.getAnimatedValue();

? ? ? ? ? ? //根據(jù)進度對起點終點之間做插值

? ? ? ? ? ? for (int i =0; i <4; i++) {

mResult[i] =mStart[i] + (mEnd[i] -mStart[i]) * value;

? ? ? ? ? ? }

//期間mask有可能被置空了,所以判斷一下

? ? ? ? ? ? if (mMask ==null) {

mMask =new RectF();

? ? ? ? ? ? }

//設置新的mask并繪制

? ? ? ? ? ? mMask.set(mResult[0], mResult[1], mResult[2], mResult[3]);

? ? ? ? ? ? invalidate();

? ? ? ? }

}

////////////////////////////////手勢動畫處理////////////////////////////////

? ? /**

? ? * 在單指模式下:

? ? * 記錄上一次手指的位置,用于計算新的位置和上一次位置的差值.

*

? ? * 雙指模式下:

? ? * 記錄兩個手指的中點,作為和mScaleCenter綁定的點.

? ? * 這個綁定可以保證mScaleCenter無論如何都會跟隨這個中點.

*

? ? * @see #mScaleCenter

? ? * @see #scale(PointF, float, float, PointF)

? ? * @see #scaleEnd()

*/

? ? private PointFmLastMovePoint =new PointF();

? ? /**

? ? * 縮放模式下圖片的縮放中點.

*

? ? * 為其指代的點經(jīng)過innerMatrix變換之后的值.

? ? * 其指代的點在手勢過程中始終跟隨mLastMovePoint.

? ? * 通過雙指縮放時,其為縮放中心點.

*

? ? * @see #saveScaleContext(float, float, float, float)

? ? * @see #mLastMovePoint

? ? * @see #scale(PointF, float, float, PointF)

*/

? ? private PointFmScaleCenter =new PointF();

? ? /**

? ? * 縮放模式下的基礎縮放比例

? ? *

? ? * 為外層縮放值除以開始縮放時兩指距離.

? ? * 其值乘上最新的兩指之間距離為最新的圖片縮放比例.

*

? ? * @see #saveScaleContext(float, float, float, float)

? ? * @see #scale(PointF, float, float, PointF)

*/

? ? private float mScaleBase =0;

? ? /**

? ? * 圖片縮放動畫

? ? *

? ? * 縮放模式把圖片的位置大小超出限制之后觸發(fā).

? ? * 雙擊圖片放大或縮小時觸發(fā).

? ? * 手動調用outerMatrixTo觸發(fā).

*

? ? * @see #scaleEnd()

? ? * @see #doubleTap(float, float)

? ? * @see #outerMatrixTo(Matrix, long)

*/

? ? private ScaleAnimatormScaleAnimator;

? ? /**

? ? * 滑動產生的慣性動畫

? ? *

? ? * @see #fling(float, float)

*/

? ? private FlingAnimatormFlingAnimator;

? ? /**

? ? * 常用手勢處理

? ? *

? ? * 在onTouchEvent末尾被執(zhí)行.

*/

? ? private GestureDetectormGestureDetector =new GestureDetector(PinchImageView.this.getContext(), new GestureDetector.SimpleOnGestureListener() {

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

//只有在單指模式結束之后才允許執(zhí)行fling

? ? ? ? ? ? if (mPinchMode ==PINCH_MODE_FREE && !(mScaleAnimator !=null &&mScaleAnimator.isRunning())) {

fling(velocityX, velocityY);

? ? ? ? ? ? }

return true;

? ? ? ? }

public void onLongPress(MotionEvent e) {

//觸發(fā)長按

? ? ? ? ? ? if (mOnLongClickListener !=null) {

mOnLongClickListener.onLongClick(PinchImageView.this);

? ? ? ? ? ? }

}

public boolean onDoubleTap(MotionEvent e) {

//當手指快速第二次按下觸發(fā),此時必須是單指模式才允許執(zhí)行doubleTap

? ? ? ? ? ? if (mPinchMode ==PINCH_MODE_SCROLL && !(mScaleAnimator !=null &&mScaleAnimator.isRunning())) {

doubleTap(e.getX(), e.getY());

? ? ? ? ? ? }

return true;

? ? ? ? }

public boolean onSingleTapConfirmed(MotionEvent e) {

//觸發(fā)點擊

? ? ? ? ? ? if (mOnClickListener !=null) {

mOnClickListener.onClick(PinchImageView.this);

? ? ? ? ? ? }

return true;

? ? ? ? }

});

? ? @Override

? ? public boolean onTouchEvent(MotionEvent event) {

super.onTouchEvent(event);

? ? ? ? int action = event.getAction() & MotionEvent.ACTION_MASK;

? ? ? ? //最后一個點抬起或者取消被丧,結束所有模式

? ? ? ? if(action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {

//如果之前是縮放模式,還需要觸發(fā)一下縮放結束動畫

? ? ? ? ? ? if (mPinchMode ==PINCH_MODE_SCALE) {

scaleEnd();

? ? ? ? ? ? }

mPinchMode =PINCH_MODE_FREE;

? ? ? ? }else if (action == MotionEvent.ACTION_POINTER_UP) {

//多個手指情況下抬起一個手指,此時需要是縮放模式才觸發(fā)

? ? ? ? ? ? if (mPinchMode ==PINCH_MODE_SCALE) {

//抬起的點如果大于2盟戏,那么縮放模式還有效绪妹,但是有可能初始點變了,重新測量初始點

? ? ? ? ? ? ? ? if (event.getPointerCount() >2) {

//如果還沒結束縮放模式柿究,但是第一個點抬起了邮旷,那么讓第二個點和第三個點作為縮放控制點

? ? ? ? ? ? ? ? ? ? if (event.getAction() >>8 ==0) {

saveScaleContext(event.getX(1), event.getY(1), event.getX(2), event.getY(2));

? ? ? ? ? ? ? ? ? ? ? ? //如果還沒結束縮放模式,但是第二個點抬起了蝇摸,那么讓第一個點和第三個點作為縮放控制點

? ? ? ? ? ? ? ? ? ? }else if (event.getAction() >>8 ==1) {

saveScaleContext(event.getX(0), event.getY(0), event.getX(2), event.getY(2));

? ? ? ? ? ? ? ? ? ? }

}

//如果抬起的點等于2,那么此時只剩下一個點,也不允許進入單指模式,因為此時可能圖片沒有在正確的位置上

? ? ? ? ? ? }

//第一個點按下婶肩,開啟滾動模式,記錄開始滾動的點

? ? ? ? }else if (action == MotionEvent.ACTION_DOWN) {

//在矩陣動畫過程中不允許啟動滾動模式

? ? ? ? ? ? if (!(mScaleAnimator !=null &&mScaleAnimator.isRunning())) {

//停止所有動畫

? ? ? ? ? ? ? ? cancelAllAnimator();

? ? ? ? ? ? ? ? //切換到滾動模式

? ? ? ? ? ? ? ? mPinchMode =PINCH_MODE_SCROLL;

? ? ? ? ? ? ? ? //保存觸發(fā)點用于move計算差值

? ? ? ? ? ? ? ? mLastMovePoint.set(event.getX(), event.getY());

? ? ? ? ? ? }

//非第一個點按下貌夕,關閉滾動模式律歼,開啟縮放模式,記錄縮放模式的一些初始數(shù)據(jù)

? ? ? ? }else if (action == MotionEvent.ACTION_POINTER_DOWN) {

//停止所有動畫

? ? ? ? ? ? cancelAllAnimator();

? ? ? ? ? ? //切換到縮放模式

? ? ? ? ? ? mPinchMode =PINCH_MODE_SCALE;

? ? ? ? ? ? //保存縮放的兩個手指

? ? ? ? ? ? saveScaleContext(event.getX(0), event.getY(0), event.getX(1), event.getY(1));

? ? ? ? }else if (action == MotionEvent.ACTION_MOVE) {

if (!(mScaleAnimator !=null &&mScaleAnimator.isRunning())) {

//在滾動模式下移動

? ? ? ? ? ? ? ? if (mPinchMode ==PINCH_MODE_SCROLL) {

//每次移動產生一個差值累積到圖片位置上

? ? ? ? ? ? ? ? ? ? scrollBy(event.getX() -mLastMovePoint.x, event.getY() -mLastMovePoint.y);

? ? ? ? ? ? ? ? ? ? //記錄新的移動點

? ? ? ? ? ? ? ? ? ? mLastMovePoint.set(event.getX(), event.getY());

? ? ? ? ? ? ? ? ? ? //在縮放模式下移動

? ? ? ? ? ? ? ? }else if (mPinchMode ==PINCH_MODE_SCALE && event.getPointerCount() >1) {

//兩個縮放點間的距離

? ? ? ? ? ? ? ? ? ? float distance = MathUtils.getDistance(event.getX(0), event.getY(0), event.getX(1), event.getY(1));

? ? ? ? ? ? ? ? ? ? //保存縮放點中點

? ? ? ? ? ? ? ? ? ? float[] lineCenter = MathUtils.getCenterPoint(event.getX(0), event.getY(0), event.getX(1), event.getY(1));

? ? ? ? ? ? ? ? ? ? mLastMovePoint.set(lineCenter[0], lineCenter[1]);

? ? ? ? ? ? ? ? ? ? //處理縮放

? ? ? ? ? ? ? ? ? ? scale(mScaleCenter, mScaleBase, distance, mLastMovePoint);

? ? ? ? ? ? ? ? }

}

}

//無論如何都處理各種外部手勢

? ? ? ? mGestureDetector.onTouchEvent(event);

return true;

? ? }

/**

? ? * 讓圖片移動一段距離

? ? *

? ? * 不能移動超過可移動范圍,超過了就到可移動范圍邊界為止.

*

? ? * @param xDiff 移動距離

? ? * @param yDiff 移動距離

? ? * @return 是否改變了位置

? ? */

? ? private boolean scrollBy(float xDiff, float yDiff) {

if (!isReady()) {

return false;

? ? ? ? }

//原圖方框

? ? ? ? RectF bound = MathUtils.rectFTake();

? ? ? ? getImageBound(bound);

? ? ? ? //控件大小

? ? ? ? float displayWidth = getWidth();

? ? ? ? float displayHeight = getHeight();

? ? ? ? //如果當前圖片寬度小于控件寬度啡专,則不能移動

? ? ? ? if (bound.right - bound.left < displayWidth) {

xDiff =0;

? ? ? ? ? ? //如果圖片左邊在移動后超出控件左邊

? ? ? ? }else if (bound.left + xDiff >0) {

//如果在移動之前是沒超出的苗膝,計算應該移動的距離

? ? ? ? ? ? if (bound.left <0) {

xDiff = -bound.left;

? ? ? ? ? ? ? ? //否則無法移動

? ? ? ? ? ? }else {

xDiff =0;

? ? ? ? ? ? }

//如果圖片右邊在移動后超出控件右邊

? ? ? ? }else if (bound.right + xDiff < displayWidth) {

//如果在移動之前是沒超出的,計算應該移動的距離

? ? ? ? ? ? if (bound.right > displayWidth) {

xDiff = displayWidth - bound.right;

? ? ? ? ? ? ? ? //否則無法移動

? ? ? ? ? ? }else {

xDiff =0;

? ? ? ? ? ? }

}

//以下同理

? ? ? ? if (bound.bottom - bound.top < displayHeight) {

yDiff =0;

? ? ? ? }else if (bound.top + yDiff >0) {

if (bound.top <0) {

yDiff = -bound.top;

? ? ? ? ? ? }else {

yDiff =0;

? ? ? ? ? ? }

}else if (bound.bottom + yDiff < displayHeight) {

if (bound.bottom > displayHeight) {

yDiff = displayHeight - bound.bottom;

? ? ? ? ? ? }else {

yDiff =0;

? ? ? ? ? ? }

}

MathUtils.rectFGiven(bound);

? ? ? ? //應用移動變換

? ? ? ? mOuterMatrix.postTranslate(xDiff, yDiff);

? ? ? ? dispatchOuterMatrixChanged();

? ? ? ? //觸發(fā)重繪

? ? ? ? invalidate();

? ? ? ? //檢查是否有變化

? ? ? ? if (xDiff !=0 || yDiff !=0) {

return true;

? ? ? ? }else {

return false;

? ? ? ? }

}

/**

? ? * 記錄縮放前的一些信息

? ? *

? ? * 保存基礎縮放值.

? ? * 保存圖片縮放中點.

*

? ? * @param x1 縮放第一個手指

? ? * @param y1 縮放第一個手指

? ? * @param x2 縮放第二個手指

? ? * @param y2 縮放第二個手指

? ? */

? ? private void saveScaleContext(float x1, float y1, float x2, float y2) {

//記錄基礎縮放值,其中圖片縮放比例按照x方向來計算

? ? ? ? //理論上圖片應該是等比的,x和y方向比例相同

? ? ? ? //但是有可能外部設定了不規(guī)范的值.

? ? ? ? //但是后續(xù)的scale操作會將xy不等的縮放值糾正,改成和x方向相同

? ? ? ? mScaleBase = MathUtils.getMatrixScale(mOuterMatrix)[0] / MathUtils.getDistance(x1, y1, x2, y2);

? ? ? ? //兩手指的中點在屏幕上落在了圖片的某個點上,圖片上的這個點在經(jīng)過總矩陣變換后和手指中點相同

? ? ? ? //現(xiàn)在我們需要得到圖片上這個點在圖片是fit center狀態(tài)下在屏幕上的位置

? ? ? ? //因為后續(xù)的計算都是基于圖片是fit center狀態(tài)下進行變換

? ? ? ? //所以需要把兩手指中點除以外層變換矩陣得到mScaleCenter

? ? ? ? float[] center = MathUtils.inverseMatrixPoint(MathUtils.getCenterPoint(x1, y1, x2, y2), mOuterMatrix);

? ? ? ? mScaleCenter.set(center[0], center[1]);

? ? }

/**

? ? * 對圖片按照一些手勢信息進行縮放

? ? *

? ? * @param scaleCenter mScaleCenter

? ? * @param scaleBase mScaleBase

? ? * @param distance 手指兩點之間距離

? ? * @param lineCenter 手指兩點之間中點

? ? *

? ? * @see #mScaleCenter

? ? * @see #mScaleBase

*/

? ? private void scale(PointF scaleCenter, float scaleBase, float distance, PointF lineCenter) {

if (!isReady()) {

return;

? ? ? ? }

//計算圖片從fit center狀態(tài)到目標狀態(tài)的縮放比例

? ? ? ? float scale = scaleBase * distance;

? ? ? ? Matrix matrix = MathUtils.matrixTake();

? ? ? ? //按照圖片縮放中心縮放植旧,并且讓縮放中心在縮放點中點上

? ? ? ? matrix.postScale(scale, scale, scaleCenter.x, scaleCenter.y);

? ? ? ? //讓圖片的縮放中點跟隨手指縮放中點

? ? ? ? matrix.postTranslate(lineCenter.x - scaleCenter.x, lineCenter.y - scaleCenter.y);

? ? ? ? //應用變換

? ? ? ? mOuterMatrix.set(matrix);

? ? ? ? MathUtils.matrixGiven(matrix);

? ? ? ? dispatchOuterMatrixChanged();

? ? ? ? //重繪

? ? ? ? invalidate();

? ? }

/**

? ? * 雙擊后放大或者縮小

? ? *

? ? * 將圖片縮放比例縮放到nextScale指定的值.

? ? * 但nextScale值不能大于最大縮放值不能小于fit center情況下的縮放值.

? ? * 將雙擊的點盡量移動到控件中心.

*

? ? * @param x 雙擊的點

? ? * @param y 雙擊的點

? ? *

? ? * @see #calculateNextScale(float, float)

? ? * @see #getMaxScale()

*/

? ? private void doubleTap(float x, float y) {

if (!isReady()) {

return;

? ? ? ? }

//獲取第一層變換矩陣

? ? ? ? Matrix innerMatrix = MathUtils.matrixTake();

? ? ? ? getInnerMatrix(innerMatrix);

? ? ? ? //當前總的縮放比例

? ? ? ? float innerScale = MathUtils.getMatrixScale(innerMatrix)[0];

? ? ? ? float outerScale = MathUtils.getMatrixScale(mOuterMatrix)[0];

? ? ? ? float currentScale = innerScale * outerScale;

? ? ? ? //控件大小

? ? ? ? float displayWidth = getWidth();

? ? ? ? float displayHeight = getHeight();

? ? ? ? //最大放大大小

? ? ? ? float maxScale = getMaxScale();

? ? ? ? //接下來要放大的大小

? ? ? ? float nextScale = calculateNextScale(innerScale, outerScale);

? ? ? ? //如果接下來放大大于最大值或者小于fit center值辱揭,則取邊界

? ? ? ? if (nextScale > maxScale) {

nextScale = maxScale;

? ? ? ? }

if (nextScale < innerScale) {

nextScale = innerScale;

? ? ? ? }

//開始計算縮放動畫的結果矩陣

? ? ? ? Matrix animEnd = MathUtils.matrixTake(mOuterMatrix);

? ? ? ? //計算還需縮放的倍數(shù)

? ? ? ? animEnd.postScale(nextScale / currentScale, nextScale / currentScale, x, y);

? ? ? ? //將放大點移動到控件中心

? ? ? ? animEnd.postTranslate(displayWidth /2f - x, displayHeight /2f - y);

? ? ? ? //得到放大之后的圖片方框

? ? ? ? Matrix testMatrix = MathUtils.matrixTake(innerMatrix);

? ? ? ? testMatrix.postConcat(animEnd);

? ? ? ? RectF testBound = MathUtils.rectFTake(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

? ? ? ? testMatrix.mapRect(testBound);

? ? ? ? //修正位置

? ? ? ? float postX =0;

? ? ? ? float postY =0;

? ? ? ? if (testBound.right - testBound.left < displayWidth) {

postX = displayWidth /2f - (testBound.right + testBound.left) /2f;

? ? ? ? }else if (testBound.left >0) {

postX = -testBound.left;

? ? ? ? }else if (testBound.right < displayWidth) {

postX = displayWidth - testBound.right;

? ? ? ? }

if (testBound.bottom - testBound.top < displayHeight) {

postY = displayHeight /2f - (testBound.bottom + testBound.top) /2f;

? ? ? ? }else if (testBound.top >0) {

postY = -testBound.top;

? ? ? ? }else if (testBound.bottom < displayHeight) {

postY = displayHeight - testBound.bottom;

? ? ? ? }

//應用修正位置

? ? ? ? animEnd.postTranslate(postX, postY);

? ? ? ? //清理當前可能正在執(zhí)行的動畫

? ? ? ? cancelAllAnimator();

? ? ? ? //啟動矩陣動畫

? ? ? ? mScaleAnimator =new ScaleAnimator(mOuterMatrix, animEnd);

? ? ? ? mScaleAnimator.start();

? ? ? ? //清理臨時變量

? ? ? ? MathUtils.rectFGiven(testBound);

? ? ? ? MathUtils.matrixGiven(testMatrix);

? ? ? ? MathUtils.matrixGiven(animEnd);

? ? ? ? MathUtils.matrixGiven(innerMatrix);

? ? }

/**

? ? * 當縮放操作結束動畫

? ? *

? ? * 如果圖片超過邊界,找到最近的位置動畫恢復.

? ? * 如果圖片縮放尺寸超過最大值或者最小值,找到最近的值動畫恢復.

*/

? ? private void scaleEnd() {

if (!isReady()) {

return;

? ? ? ? }

//是否修正了位置

? ? ? ? boolean change =false;

? ? ? ? //獲取圖片整體的變換矩陣

? ? ? ? Matrix currentMatrix = MathUtils.matrixTake();

? ? ? ? getCurrentImageMatrix(currentMatrix);

? ? ? ? //整體縮放比例

? ? ? ? float currentScale = MathUtils.getMatrixScale(currentMatrix)[0];

? ? ? ? //第二層縮放比例

? ? ? ? float outerScale = MathUtils.getMatrixScale(mOuterMatrix)[0];

? ? ? ? //控件大小

? ? ? ? float displayWidth = getWidth();

? ? ? ? float displayHeight = getHeight();

? ? ? ? //最大縮放比例

? ? ? ? float maxScale = getMaxScale();

? ? ? ? //比例修正

? ? ? ? float scalePost =1f;

? ? ? ? //位置修正

? ? ? ? float postX =0;

? ? ? ? float postY =0;

? ? ? ? //如果整體縮放比例大于最大比例,進行縮放修正

? ? ? ? if (currentScale > maxScale) {

scalePost = maxScale / currentScale;

? ? ? ? }

//如果縮放修正后整體導致第二層縮放小于1(就是圖片比fit center狀態(tài)還胁「健)问窃,重新修正縮放

? ? ? ? if (outerScale * scalePost <1f) {

scalePost =1f / outerScale;

? ? ? ? }

//如果縮放修正不為1,說明進行了修正

? ? ? ? if (scalePost !=1f) {

change =true;

? ? ? ? }

//嘗試根據(jù)縮放點進行縮放修正

? ? ? ? Matrix testMatrix = MathUtils.matrixTake(currentMatrix);

? ? ? ? testMatrix.postScale(scalePost, scalePost, mLastMovePoint.x, mLastMovePoint.y);

? ? ? ? RectF testBound = MathUtils.rectFTake(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());

? ? ? ? //獲取縮放修正后的圖片方框

? ? ? ? testMatrix.mapRect(testBound);

? ? ? ? //檢測縮放修正后位置有無超出完沪,如果超出進行位置修正

? ? ? ? if (testBound.right - testBound.left < displayWidth) {

postX = displayWidth /2f - (testBound.right + testBound.left) /2f;

? ? ? ? }else if (testBound.left >0) {

postX = -testBound.left;

? ? ? ? }else if (testBound.right < displayWidth) {

postX = displayWidth - testBound.right;

? ? ? ? }

if (testBound.bottom - testBound.top < displayHeight) {

postY = displayHeight /2f - (testBound.bottom + testBound.top) /2f;

? ? ? ? }else if (testBound.top >0) {

postY = -testBound.top;

? ? ? ? }else if (testBound.bottom < displayHeight) {

postY = displayHeight - testBound.bottom;

? ? ? ? }

//如果位置修正不為0域庇,說明進行了修正

? ? ? ? if (postX !=0 || postY !=0) {

change =true;

? ? ? ? }

//只有有執(zhí)行修正才執(zhí)行動畫

? ? ? ? if (change) {

//計算結束矩陣

? ? ? ? ? ? Matrix animEnd = MathUtils.matrixTake(mOuterMatrix);

? ? ? ? ? ? animEnd.postScale(scalePost, scalePost, mLastMovePoint.x, mLastMovePoint.y);

? ? ? ? ? ? animEnd.postTranslate(postX, postY);

? ? ? ? ? ? //清理當前可能正在執(zhí)行的動畫

? ? ? ? ? ? cancelAllAnimator();

? ? ? ? ? ? //啟動矩陣動畫

? ? ? ? ? ? mScaleAnimator =new ScaleAnimator(mOuterMatrix, animEnd);

? ? ? ? ? ? mScaleAnimator.start();

? ? ? ? ? ? //清理臨時變量

? ? ? ? ? ? MathUtils.matrixGiven(animEnd);

? ? ? ? }

//清理臨時變量

? ? ? ? MathUtils.rectFGiven(testBound);

? ? ? ? MathUtils.matrixGiven(testMatrix);

? ? ? ? MathUtils.matrixGiven(currentMatrix);

? ? }

/**

? ? * 執(zhí)行慣性動畫

? ? *

? ? * 動畫在遇到不能移動就停止.

? ? * 動畫速度衰減到很小就停止.

*

? ? * 其中參數(shù)速度單位為 像素/秒

? ? *

? ? * @param vx x方向速度

? ? * @param vy y方向速度

? ? */

? ? private void fling(float vx, float vy) {

if (!isReady()) {

return;

? ? ? ? }

//清理當前可能正在執(zhí)行的動畫

? ? ? ? cancelAllAnimator();

? ? ? ? //創(chuàng)建慣性動畫

? ? ? ? //FlingAnimator單位為 像素/幀,一秒60幀

? ? ? ? mFlingAnimator =new FlingAnimator(vx /60f, vy /60f);

? ? ? ? mFlingAnimator.start();

? ? }

/**

? ? * 停止所有手勢動畫

? ? */

? ? private void cancelAllAnimator() {

if (mScaleAnimator !=null) {

mScaleAnimator.cancel();

? ? ? ? ? ? mScaleAnimator =null;

? ? ? ? }

if (mFlingAnimator !=null) {

mFlingAnimator.cancel();

? ? ? ? ? ? mFlingAnimator =null;

? ? ? ? }

}

/**

? ? * 慣性動畫

? ? *

? ? * 速度逐漸衰減,每幀速度衰減為原來的FLING_DAMPING_FACTOR,當速度衰減到小于1時停止.

? ? * 當圖片不能移動時,動畫停止.

*/

? ? private class FlingAnimatorextends ValueAnimatorimplements ValueAnimator.AnimatorUpdateListener {

/**

? ? ? ? * 速度向量

? ? ? ? */

? ? ? ? private float[]mVector;

? ? ? ? /**

? ? ? ? * 創(chuàng)建慣性動畫

? ? ? ? *

? ? ? ? * 參數(shù)單位為 像素/幀

? ? ? ? *

? ? ? ? * @param vectorX 速度向量

? ? ? ? * @param vectorY 速度向量

? ? ? ? */

? ? ? ? public FlingAnimator(float vectorX, float vectorY) {

super();

? ? ? ? ? ? setFloatValues(0, 1f);

? ? ? ? ? ? setDuration(1000000);

? ? ? ? ? ? addUpdateListener(this);

? ? ? ? ? ? mVector =new float[]{vectorX, vectorY};

? ? ? ? }

@Override

? ? ? ? public void onAnimationUpdate(ValueAnimator animation) {

//移動圖像并給出結果

? ? ? ? ? ? boolean result = scrollBy(mVector[0], mVector[1]);

? ? ? ? ? ? //衰減速度

? ? ? ? ? ? mVector[0] *=FLING_DAMPING_FACTOR;

? ? ? ? ? ? mVector[1] *=FLING_DAMPING_FACTOR;

? ? ? ? ? ? //速度太小或者不能移動了就結束

? ? ? ? ? ? if (!result || MathUtils.getDistance(0, 0, mVector[0], mVector[1]) <1f) {

animation.cancel();

? ? ? ? ? ? }

}

}

/**

? ? * 縮放動畫

? ? *

? ? * 在給定時間內從一個矩陣的變化逐漸動畫到另一個矩陣的變化

? ? */

? ? private class ScaleAnimatorextends ValueAnimatorimplements ValueAnimator.AnimatorUpdateListener {

/**

? ? ? ? * 開始矩陣

? ? ? ? */

? ? ? ? private float[]mStart =new float[9];

? ? ? ? /**

? ? ? ? * 結束矩陣

? ? ? ? */

? ? ? ? private float[]mEnd =new float[9];

? ? ? ? /**

? ? ? ? * 中間結果矩陣

? ? ? ? */

? ? ? ? private float[]mResult =new float[9];

? ? ? ? /**

? ? ? ? * 構建一個縮放動畫

? ? ? ? *

? ? ? ? * 從一個矩陣變換到另外一個矩陣

? ? ? ? *

? ? ? ? * @param start 開始矩陣

? ? ? ? * @param end 結束矩陣

? ? ? ? */

? ? ? ? public ScaleAnimator(Matrix start, Matrix end) {

this(start, end, SCALE_ANIMATOR_DURATION);

? ? ? ? }

/**

? ? ? ? * 構建一個縮放動畫

? ? ? ? *

? ? ? ? * 從一個矩陣變換到另外一個矩陣

? ? ? ? *

? ? ? ? * @param start 開始矩陣

? ? ? ? * @param end 結束矩陣

? ? ? ? * @param duration 動畫時間

? ? ? ? */

? ? ? ? public ScaleAnimator(Matrix start, Matrix end, long duration) {

super();

? ? ? ? ? ? setFloatValues(0, 1f);

? ? ? ? ? ? setDuration(duration);

? ? ? ? ? ? addUpdateListener(this);

? ? ? ? ? ? start.getValues(mStart);

? ? ? ? ? ? end.getValues(mEnd);

? ? ? ? }

@Override

? ? ? ? public void onAnimationUpdate(ValueAnimator animation) {

//獲取動畫進度

? ? ? ? ? ? float value = (Float) animation.getAnimatedValue();

? ? ? ? ? ? //根據(jù)動畫進度計算矩陣中間插值

? ? ? ? ? ? for (int i =0; i <9; i++) {

mResult[i] =mStart[i] + (mEnd[i] -mStart[i]) * value;

? ? ? ? ? ? }

//設置矩陣并重繪

? ? ? ? ? ? mOuterMatrix.setValues(mResult);

? ? ? ? ? ? dispatchOuterMatrixChanged();

? ? ? ? ? ? invalidate();

? ? ? ? }

}

////////////////////////////////防止內存抖動復用對象////////////////////////////////

? ? /**

? ? * 對象池

? ? *

? ? * 防止頻繁new對象產生內存抖動.

? ? * 由于對象池最大長度限制,如果吞度量超過對象池容量,仍然會發(fā)生抖動.

? ? * 此時需要增大對象池容量,但是會占用更多內存.

*

? ? * @param 對象池容納的對象類型

? ? */

? ? private static abstract class ObjectsPool {

/**

? ? ? ? * 對象池的最大容量

? ? ? ? */

? ? ? ? private int mSize;

? ? ? ? /**

? ? ? ? * 對象池隊列

? ? ? ? */

? ? ? ? private QueuemQueue;

? ? ? ? /**

? ? ? ? * 創(chuàng)建一個對象池

? ? ? ? *

? ? ? ? * @param size 對象池最大容量

? ? ? ? */

? ? ? ? public ObjectsPool(int size) {

mSize = size;

? ? ? ? ? ? mQueue =new LinkedList();

? ? ? ? }

/**

? ? ? ? * 獲取一個空閑的對象

? ? ? ? *

? ? ? ? * 如果對象池為空,則對象池自己會new一個返回.

? ? ? ? * 如果對象池內有對象,則取一個已存在的返回.

? ? ? ? * take出來的對象用完要記得調用given歸還.

? ? ? ? * 如果不歸還,讓然會發(fā)生內存抖動,但不會引起泄漏.

*

? ? ? ? * @return 可用的對象

? ? ? ? *

? ? ? ? * @see #given(Object)

*/

? ? ? ? public T take() {

//如果池內為空就創(chuàng)建一個

? ? ? ? ? ? if (mQueue.size() ==0) {

return newInstance();

? ? ? ? ? ? }else {

//對象池里有就從頂端拿出來一個返回

? ? ? ? ? ? ? ? return resetInstance(mQueue.poll());

? ? ? ? ? ? }

}

/**

? ? ? ? * 歸還對象池內申請的對象

? ? ? ? *

? ? ? ? * 如果歸還的對象數(shù)量超過對象池容量,那么歸還的對象就會被丟棄.

*

? ? ? ? * @param obj 歸還的對象

? ? ? ? *

? ? ? ? * @see #take()

*/

? ? ? ? public void given(T obj) {

//如果對象池還有空位子就歸還對象

? ? ? ? ? ? if (obj !=null &&mQueue.size()

mQueue.offer(obj);

? ? ? ? ? ? }

}

/**

? ? ? ? * 實例化對象

? ? ? ? *

? ? ? ? * @return 創(chuàng)建的對象

? ? ? ? */

? ? ? ? abstract protected T newInstance();

? ? ? ? /**

? ? ? ? * 重置對象

? ? ? ? *

? ? ? ? * 把對象數(shù)據(jù)清空到就像剛創(chuàng)建的一樣.

*

? ? ? ? * @param obj 需要被重置的對象

? ? ? ? * @return 被重置之后的對象

? ? ? ? */

? ? ? ? abstract protected T resetInstance(T obj);

? ? }

/**

? ? * 矩陣對象池

? ? */

? ? private static class MatrixPoolextends ObjectsPool {

public MatrixPool(int size) {

super(size);

? ? ? ? }

@Override

? ? ? ? protected MatrixnewInstance() {

return new Matrix();

? ? ? ? }

@Override

? ? ? ? protected MatrixresetInstance(Matrix obj) {

obj.reset();

? ? ? ? ? ? return obj;

? ? ? ? }

}

/**

? ? * 矩形對象池

? ? */

? ? private static class RectFPoolextends ObjectsPool {

public RectFPool(int size) {

super(size);

? ? ? ? }

@Override

? ? ? ? protected RectFnewInstance() {

return new RectF();

? ? ? ? }

@Override

? ? ? ? protected RectFresetInstance(RectF obj) {

obj.setEmpty();

? ? ? ? ? ? return obj;

? ? ? ? }

}

////////////////////////////////數(shù)學計算工具類////////////////////////////////

? ? /**

? ? * 數(shù)學計算工具類

? ? */

? ? public static class MathUtils {

/**

? ? ? ? * 矩陣對象池

? ? ? ? */

? ? ? ? private static MatrixPoolmMatrixPool =new MatrixPool(16);

? ? ? ? /**

? ? ? ? * 獲取矩陣對象

? ? ? ? */

? ? ? ? public static MatrixmatrixTake() {

return mMatrixPool.take();

? ? ? ? }

/**

? ? ? ? * 獲取某個矩陣的copy

*/

? ? ? ? public static MatrixmatrixTake(Matrix matrix) {

Matrix result =mMatrixPool.take();

? ? ? ? ? ? if (matrix !=null) {

result.set(matrix);

? ? ? ? ? ? }

return result;

? ? ? ? }

/**

? ? ? ? * 歸還矩陣對象

? ? ? ? */

? ? ? ? public static void matrixGiven(Matrix matrix) {

mMatrixPool.given(matrix);

? ? ? ? }

/**

? ? ? ? * 矩形對象池

? ? ? ? */

? ? ? ? private static RectFPoolmRectFPool =new RectFPool(16);

? ? ? ? /**

? ? ? ? * 獲取矩形對象

? ? ? ? */

? ? ? ? public static RectFrectFTake() {

return mRectFPool.take();

? ? ? ? }

/**

? ? ? ? * 按照指定值獲取矩形對象

? ? ? ? */

? ? ? ? public static RectFrectFTake(float left, float top, float right, float bottom) {

RectF result =mRectFPool.take();

? ? ? ? ? ? result.set(left, top, right, bottom);

? ? ? ? ? ? return result;

? ? ? ? }

/**

? ? ? ? * 獲取某個矩形的副本

? ? ? ? */

? ? ? ? public static RectFrectFTake(RectF rectF) {

RectF result =mRectFPool.take();

? ? ? ? ? ? if (rectF !=null) {

result.set(rectF);

? ? ? ? ? ? }

return result;

? ? ? ? }

/**

? ? ? ? * 歸還矩形對象

? ? ? ? */

? ? ? ? public static void rectFGiven(RectF rectF) {

mRectFPool.given(rectF);

? ? ? ? }

/**

? ? ? ? * 獲取兩點之間距離

? ? ? ? *

? ? ? ? * @param x1 點1

? ? ? ? * @param y1 點1

? ? ? ? * @param x2 點2

? ? ? ? * @param y2 點2

? ? ? ? * @return 距離

? ? ? ? */

? ? ? ? public static float getDistance(float x1, float y1, float x2, float y2) {

float x = x1 - x2;

? ? ? ? ? ? float y = y1 - y2;

? ? ? ? ? ? return (float) Math.sqrt(x * x + y * y);

? ? ? ? }

/**

? ? ? ? * 獲取兩點的中點

? ? ? ? *

? ? ? ? * @param x1 點1

? ? ? ? * @param y1 點1

? ? ? ? * @param x2 點2

? ? ? ? * @param y2 點2

? ? ? ? * @return float[]{x, y}

*/

? ? ? ? public static float[]getCenterPoint(float x1, float y1, float x2, float y2) {

return new float[]{(x1 + x2) /2f, (y1 + y2) /2f};

? ? ? ? }

/**

? ? ? ? * 獲取矩陣的縮放值

? ? ? ? *

? ? ? ? * @param matrix 要計算的矩陣

? ? ? ? * @return float[]{scaleX, scaleY}

*/

? ? ? ? public static float[]getMatrixScale(Matrix matrix) {

if (matrix !=null) {

float[] value =new float[9];

? ? ? ? ? ? ? ? matrix.getValues(value);

? ? ? ? ? ? ? ? return new float[]{value[0], value[4]};

? ? ? ? ? ? }else {

return new float[2];

? ? ? ? ? ? }

}

/**

? ? ? ? * 計算點除以矩陣的值

? ? ? ? *

* matrix.mapPoints(unknownPoint) -> point

? ? ? ? * 已知point和matrix,求unknownPoint的值.

*

? ? ? ? * @param point

? ? ? ? * @param matrix

? ? ? ? * @return unknownPoint

*/

? ? ? ? public static float[]inverseMatrixPoint(float[] point, Matrix matrix) {

if (point !=null && matrix !=null) {

float[] dst =new float[2];

? ? ? ? ? ? ? ? //計算matrix的逆矩陣

? ? ? ? ? ? ? ? Matrix inverse =matrixTake();

? ? ? ? ? ? ? ? matrix.invert(inverse);

? ? ? ? ? ? ? ? //用逆矩陣變換point到dst,dst就是結果

? ? ? ? ? ? ? ? inverse.mapPoints(dst, point);

? ? ? ? ? ? ? ? //清除臨時變量

? ? ? ? ? ? ? ? matrixGiven(inverse);

? ? ? ? ? ? ? ? return dst;

? ? ? ? ? ? }else {

return new float[2];

? ? ? ? ? ? }

}

/**

? ? ? ? * 計算兩個矩形之間的變換矩陣

? ? ? ? *

* unknownMatrix.mapRect(to, from)

? ? ? ? * 已知from矩形和to矩形,求unknownMatrix

*

? ? ? ? * @param from

? ? ? ? * @param to

? ? ? ? * @param result unknownMatrix

*/

? ? ? ? public static void calculateRectTranslateMatrix(RectF from, RectF to, Matrix result) {

if (from ==null || to ==null || result ==null) {

return;

? ? ? ? ? ? }

if (from.width() ==0 || from.height() ==0) {

return;

? ? ? ? ? ? }

result.reset();

? ? ? ? ? ? result.postTranslate(-from.left, -from.top);

? ? ? ? ? ? result.postScale(to.width() / from.width(), to.height() / from.height());

? ? ? ? ? ? result.postTranslate(to.left, to.top);

? ? ? ? }

/**

? ? ? ? * 計算圖片在某個ImageView中的顯示矩形

? ? ? ? *

? ? ? ? * @param container ImageView的Rect

? ? ? ? * @param srcWidth 圖片的寬度

? ? ? ? * @param srcHeight 圖片的高度

? ? ? ? * @param scaleType 圖片在ImageView中的ScaleType

? ? ? ? * @param result 圖片應該在ImageView中展示的矩形

? ? ? ? */

? ? ? ? public static void calculateScaledRectInContainer(RectF container, float srcWidth, float srcHeight, ScaleType scaleType, RectF result) {

if (container ==null || result ==null) {

return;

? ? ? ? ? ? }

if (srcWidth ==0 || srcHeight ==0) {

return;

? ? ? ? ? ? }

//默認scaleType為fit center

? ? ? ? ? ? if (scaleType ==null) {

scaleType = ScaleType.FIT_CENTER;

? ? ? ? ? ? }

result.setEmpty();

? ? ? ? ? ? if (ScaleType.FIT_XY.equals(scaleType)) {

result.set(container);

? ? ? ? ? ? }else if (ScaleType.CENTER.equals(scaleType)) {

Matrix matrix =matrixTake();

? ? ? ? ? ? ? ? RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

? ? ? ? ? ? ? ? matrix.setTranslate((container.width() - srcWidth) *0.5f, (container.height() - srcHeight) *0.5f);

? ? ? ? ? ? ? ? matrix.mapRect(result, rect);

? ? ? ? ? ? ? ? rectFGiven(rect);

? ? ? ? ? ? ? ? matrixGiven(matrix);

? ? ? ? ? ? ? ? result.left += container.left;

? ? ? ? ? ? ? ? result.right += container.left;

? ? ? ? ? ? ? ? result.top += container.top;

? ? ? ? ? ? ? ? result.bottom += container.top;

? ? ? ? ? ? }else if (ScaleType.CENTER_CROP.equals(scaleType)) {

Matrix matrix =matrixTake();

? ? ? ? ? ? ? ? RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

? ? ? ? ? ? ? ? float scale;

? ? ? ? ? ? ? ? float dx =0;

? ? ? ? ? ? ? ? float dy =0;

? ? ? ? ? ? ? ? if (srcWidth * container.height() > container.width() * srcHeight) {

scale = container.height() / srcHeight;

? ? ? ? ? ? ? ? ? ? dx = (container.width() - srcWidth * scale) *0.5f;

? ? ? ? ? ? ? ? }else {

scale = container.width() / srcWidth;

? ? ? ? ? ? ? ? ? ? dy = (container.height() - srcHeight * scale) *0.5f;

? ? ? ? ? ? ? ? }

matrix.setScale(scale, scale);

? ? ? ? ? ? ? ? matrix.postTranslate(dx, dy);

? ? ? ? ? ? ? ? matrix.mapRect(result, rect);

? ? ? ? ? ? ? ? rectFGiven(rect);

? ? ? ? ? ? ? ? matrixGiven(matrix);

? ? ? ? ? ? ? ? result.left += container.left;

? ? ? ? ? ? ? ? result.right += container.left;

? ? ? ? ? ? ? ? result.top += container.top;

? ? ? ? ? ? ? ? result.bottom += container.top;

? ? ? ? ? ? }else if (ScaleType.CENTER_INSIDE.equals(scaleType)) {

Matrix matrix =matrixTake();

? ? ? ? ? ? ? ? RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

? ? ? ? ? ? ? ? float scale;

? ? ? ? ? ? ? ? float dx;

? ? ? ? ? ? ? ? float dy;

? ? ? ? ? ? ? ? if (srcWidth <= container.width() && srcHeight <= container.height()) {

scale =1f;

? ? ? ? ? ? ? ? }else {

scale = Math.min(container.width() / srcWidth, container.height() / srcHeight);

? ? ? ? ? ? ? ? }

dx = (container.width() - srcWidth * scale) *0.5f;

? ? ? ? ? ? ? ? dy = (container.height() - srcHeight * scale) *0.5f;

? ? ? ? ? ? ? ? matrix.setScale(scale, scale);

? ? ? ? ? ? ? ? matrix.postTranslate(dx, dy);

? ? ? ? ? ? ? ? matrix.mapRect(result, rect);

? ? ? ? ? ? ? ? rectFGiven(rect);

? ? ? ? ? ? ? ? matrixGiven(matrix);

? ? ? ? ? ? ? ? result.left += container.left;

? ? ? ? ? ? ? ? result.right += container.left;

? ? ? ? ? ? ? ? result.top += container.top;

? ? ? ? ? ? ? ? result.bottom += container.top;

? ? ? ? ? ? }else if (ScaleType.FIT_CENTER.equals(scaleType)) {

Matrix matrix =matrixTake();

? ? ? ? ? ? ? ? RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

? ? ? ? ? ? ? ? RectF tempSrc =rectFTake(0, 0, srcWidth, srcHeight);

? ? ? ? ? ? ? ? RectF tempDst =rectFTake(0, 0, container.width(), container.height());

? ? ? ? ? ? ? ? matrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER);

? ? ? ? ? ? ? ? matrix.mapRect(result, rect);

? ? ? ? ? ? ? ? rectFGiven(tempDst);

? ? ? ? ? ? ? ? rectFGiven(tempSrc);

? ? ? ? ? ? ? ? rectFGiven(rect);

? ? ? ? ? ? ? ? matrixGiven(matrix);

? ? ? ? ? ? ? ? result.left += container.left;

? ? ? ? ? ? ? ? result.right += container.left;

? ? ? ? ? ? ? ? result.top += container.top;

? ? ? ? ? ? ? ? result.bottom += container.top;

? ? ? ? ? ? }else if (ScaleType.FIT_START.equals(scaleType)) {

Matrix matrix =matrixTake();

? ? ? ? ? ? ? ? RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

? ? ? ? ? ? ? ? RectF tempSrc =rectFTake(0, 0, srcWidth, srcHeight);

? ? ? ? ? ? ? ? RectF tempDst =rectFTake(0, 0, container.width(), container.height());

? ? ? ? ? ? ? ? matrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.START);

? ? ? ? ? ? ? ? matrix.mapRect(result, rect);

? ? ? ? ? ? ? ? rectFGiven(tempDst);

? ? ? ? ? ? ? ? rectFGiven(tempSrc);

? ? ? ? ? ? ? ? rectFGiven(rect);

? ? ? ? ? ? ? ? matrixGiven(matrix);

? ? ? ? ? ? ? ? result.left += container.left;

? ? ? ? ? ? ? ? result.right += container.left;

? ? ? ? ? ? ? ? result.top += container.top;

? ? ? ? ? ? ? ? result.bottom += container.top;

? ? ? ? ? ? }else if (ScaleType.FIT_END.equals(scaleType)) {

Matrix matrix =matrixTake();

? ? ? ? ? ? ? ? RectF rect =rectFTake(0, 0, srcWidth, srcHeight);

? ? ? ? ? ? ? ? RectF tempSrc =rectFTake(0, 0, srcWidth, srcHeight);

? ? ? ? ? ? ? ? RectF tempDst =rectFTake(0, 0, container.width(), container.height());

? ? ? ? ? ? ? ? matrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.END);

? ? ? ? ? ? ? ? matrix.mapRect(result, rect);

? ? ? ? ? ? ? ? rectFGiven(tempDst);

? ? ? ? ? ? ? ? rectFGiven(tempSrc);

? ? ? ? ? ? ? ? rectFGiven(rect);

? ? ? ? ? ? ? ? matrixGiven(matrix);

? ? ? ? ? ? ? ? result.left += container.left;

? ? ? ? ? ? ? ? result.right += container.left;

? ? ? ? ? ? ? ? result.top += container.top;

? ? ? ? ? ? ? ? result.bottom += container.top;

? ? ? ? ? ? }else {

result.set(container);

? ? ? ? ? ? }

}

}

}

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市覆积,隨后出現(xiàn)的幾起案子听皿,更是在濱河造成了極大的恐慌,老刑警劉巖宽档,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尉姨,死亡現(xiàn)場離奇詭異,居然都是意外死亡吗冤,警方通過查閱死者的電腦和手機又厉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椎瘟,“玉大人覆致,你說我怎么就攤上這事》挝担” “怎么了煌妈?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我璧诵,道長汰蜘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任腮猖,我火速辦了婚禮鉴扫,結果婚禮上赞枕,老公的妹妹穿的比我還像新娘澈缺。我一直安慰自己,他們只是感情好炕婶,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布姐赡。 她就那樣靜靜地躺著,像睡著了一般柠掂。 火紅的嫁衣襯著肌膚如雪项滑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天涯贞,我揣著相機與錄音枪狂,去河邊找鬼。 笑死宋渔,一個胖子當著我的面吹牛州疾,可吹牛的內容都是我干的。 我是一名探鬼主播皇拣,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼严蓖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了氧急?” 一聲冷哼從身側響起颗胡,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吩坝,沒想到半個月后毒姨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡钉寝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年手素,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘩蚪。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡泉懦,死狀恐怖,靈堂內的尸體忽然破棺而出疹瘦,到底是詐尸還是另有隱情崩哩,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站邓嘹,受9級特大地震影響酣栈,放射性物質發(fā)生泄漏。R本人自食惡果不足惜汹押,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一矿筝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棚贾,春花似錦窖维、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至怯伊,卻和暖如春琳轿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耿芹。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工崭篡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吧秕。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓琉闪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寇甸。 傳聞我的和親對象是個殘疾皇子塘偎,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容

  • 手勢圖片控件 PinchImageView 點擊圖片框架 photoView packagecom.example...
    Ztufu閱讀 717評論 0 1
  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經(jīng)驗。 張土汪:刷leetcod...
    土汪閱讀 12,724評論 0 33
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理拿霉,服務發(fā)現(xiàn)吟秩,斷路器,智...
    卡卡羅2017閱讀 134,600評論 18 139
  • 前言 最近有需求要做一個畫布壮池,這個畫布以一個圖片為背景,可以實現(xiàn)縮放杀怠,涂鴉以及貼紙的功能椰憋,縮放和涂鴉要兼顧,于是就...
    王巖_shang閱讀 7,195評論 8 29
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程赔退,因...
    小菜c閱讀 6,358評論 0 17