AndroidジェスチャーImageView之(カスタムGestureDetector)
71283 ワード
前言:週末にジェスチャーImageView編の内容を作るつもりでしたが、結局また2日間昏睡してしまいました.ああ~~これからはそうはいかないようです.くだらないことは言わないで、今日のテーマに入りましょう.
先に前の内容の住所を貼り付けます. AndroidジェスチャーImageView三部作(一) AndroidジェスチャーImageView三部作(二) AndroidジェスチャーImageView三部作(三) 前にScaleGestureDetectorというツール類についてお話ししましたが、なぜScaleGestureDetectorを作ったのか疑問に思っています.よし~!Googleは開発者に自分のスペースを残したいに違いない~~
スライドジェスチャーの検出に特化したMoveGestureDetectorというツールクラスを定義します.MoveGestureDetectorを定義する前に、その後のRotateGestureDetectorなども考慮しなければなりません.そこで、BaseGestureDetectorという共通の方法を抽出しました.
次に、MoveGestureDetectorというクラスを定義して、BaseGestureDetectorを継承し、イベントの2つの抽象的な方法を定義します.
もし私たちが事件を検出したら、どのように呼び出し者に通知すればいいのでしょうか.はい、コールバックを使用する必要があります.ScaleGestureDetectorのコールバックインタフェースがどのように定義されているかを見てみましょう.
インタフェースが定義されています.OnScaleGestureListenerと呼ばれ、SimpleOnScaleGestureListenerと呼ばれています.SimpleOnScaleGestureListenerはOnScaleGestureListenerを実現しています.そこで、MoveGestureDetectorのインタフェースはこのように定義できます.
よし!1、MoveGestureDetectorを作成する
2、MoveGestureDetectorに事件を渡す
3、コールバック値の取得
どうですか.ScaleGestureDetectorと同じではないでしょうか.はっきりしていますね.枠は組んでいます.次に、その論理(つまり、handleStartProgressEventとhandleInProgressEventの方法)を実現します.
各行に注釈があるので、私は直接コードをつけました.
よし!書き終わったら、使ってみましょう.
よし~!わずか数行のコードで游ぶことができて、効果図は私は添付しないで、小さい仲间は自分で运行して、それではMoveGestureDetector私达は実现して、きっとRotateGestureDetectorもすぐに実现することができて、ハハ~!私は直接海外の大神が書いたコードを貼りました.
最後に、ScaleDetector、MoveDetector、RotateDetectorのジェスチャーを組み合わせてImageViewのコードをスケールします.
よし~~~仲間も自分でこのフレームワークのコードをダウンロードして研究してもいいし、私も自分の勉強の心得をみんなに分かち合うだけです.https://github.com/Almeros/android-gesture-detectors
うんうん!たくさん話して、最後に伝説のPhotoViewがどのように実現したのかを見てみましょう.
photoviewのgithubリンク:https://github.com/chrisbanes/PhotoViewary/
私たちの前の内容を見てからPhotoViewを見に行くと、そんなに迷わないかもしれません.次は一緒に神秘的なベールを開けましょう.
まずPhotoViewの使い方ですね.簡単です.皆さんはImageViewのように使えばいいです.
よし!画像をズーム、回転、移動操作できるようになりました~爽やかではありませんか?
ただし注意:
photoviewのスケールタイプはサポートされていません.そうしないと、直接エラーを報告して終了します.
ソースコードを見てみましょう
コードは多くなく、200行以上しかないことがわかります(ハハ!!私たち自身が実現したMatrixImageView 100行もまだ来ていません!!冗談ですが、PhotoViewで考えているものと互換性は、私たちが書いたMatrixImageViewでははるかに及ばないです)、主な処理所はPhotoViewというクラスにあります.
PhotoViewAttacher.JAva:コードが多すぎて、その構造方法を見てみましょう.
ジェスチャーの変幻を傍受するためのmScaleDragDetectorとmGestureDetectorも作成されていることがわかりますが、イベントはどこで処理されますか?
構築方法では、現在のimageViewにタッチリスニングを設定するコードも見つかりました.
仲間はすべて推測して、今事件を事件のリスナーにあげました:
最後にイベントの処理が完了すると、一連のコールバックになります.コールバックが完了すると、ImageViewにmatrixオブジェクトを再設定する必要があります.たとえば、スケール:
他にも似たようなハ~~~コードが多いと思いますが、カスタマイズしたコンポーネントを書くのはそんなに簡単なことではありませんが、頑張りましょう~!!!
end~
先に前の内容の住所を貼り付けます.
スライドジェスチャーの検出に特化したMoveGestureDetectorというツールクラスを定義します.MoveGestureDetectorを定義する前に、その後のRotateGestureDetectorなども考慮しなければなりません.そこで、BaseGestureDetectorという共通の方法を抽出しました.
public abstract class BaseGestureDetector {
protected final Context mContext;
protected boolean mGestureInProgress;
protected MotionEvent mPrevEvent;
protected MotionEvent mCurrEvent;
protected float mCurrPressure;
protected float mPrevPressure;
protected long mTimeDelta;
/**
* event pressure/ pressure, ?
* ,
* ,
*/
protected static final float PRESSURE_THRESHOLD = 0.67f;
public BaseGestureDetector(Context context) {
mContext = context;
}
/**
* ScaleGesture ,
* @param event
* @return
*/
public boolean onTouchEvent(MotionEvent event){
// ACTION_POINTER_UP & MotionEvent.ACTION_MASK
final int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
/**
* handleInProgressEvent
*/
if (!mGestureInProgress) {
// mGestureInProgress false ,
handleStartProgressEvent(actionCode, event);
} else {
//
handleInProgressEvent(actionCode, event);
}
return true;
}
/**
*
* @param actionCode
* @param event
*/
protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event);
/**
*
* @param actionCode
* @param event
*/
protected abstract void handleInProgressEvent(int actionCode, MotionEvent event);
/**
* event , event, event
* @param curr
*/
protected void updateStateByEvent(MotionEvent curr){
final MotionEvent prev = mPrevEvent;
// Reset mCurrEvent
if (mCurrEvent != null) {
mCurrEvent.recycle();
mCurrEvent = null;
}
mCurrEvent = MotionEvent.obtain(curr);
// event event
mTimeDelta = curr.getEventTime() - prev.getEventTime();
// event event
mCurrPressure = curr.getPressure(curr.getActionIndex());
mPrevPressure = prev.getPressure(prev.getActionIndex());
}
/**
*
*/
protected void resetState() {
if (mPrevEvent != null) {
mPrevEvent.recycle();
mPrevEvent = null;
}
if (mCurrEvent != null) {
mCurrEvent.recycle();
mCurrEvent = null;
}
mGestureInProgress = false;
}
/**
* Returns {@code true} if a gesture is currently in progress.
* @return {@code true} if a gesture is currently in progress, {@code false} otherwise.
*/
public boolean isInProgress() {
return mGestureInProgress;
}
/**
* Return the time difference in milliseconds between the previous accepted
* GestureDetector event and the current GestureDetector event.
*
* @return Time difference since the last move event in milliseconds.
*/
public long getTimeDelta() {
return mTimeDelta;
}
/**
* Return the event time of the current GestureDetector event being
* processed.
*
* @return Current GestureDetector event time in milliseconds.
*/
public long getEventTime() {
return mCurrEvent.getEventTime();
}
}
次に、MoveGestureDetectorというクラスを定義して、BaseGestureDetectorを継承し、イベントの2つの抽象的な方法を定義します.
public class MoveGestureDetector extends BaseGestureDetector{
@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event){
}
@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event){
}
}
もし私たちが事件を検出したら、どのように呼び出し者に通知すればいいのでしょうか.はい、コールバックを使用する必要があります.ScaleGestureDetectorのコールバックインタフェースがどのように定義されているかを見てみましょう.
public interface OnScaleGestureListener {
public boolean onScale(ScaleGestureDetector detector);
public boolean onScaleBegin(ScaleGestureDetector detector);
public void onScaleEnd(ScaleGestureDetector detector);
}
public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
public boolean onScale(ScaleGestureDetector detector) {
return false;
}
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
public void onScaleEnd(ScaleGestureDetector detector) {
// Intentionally empty
}
}
インタフェースが定義されています.OnScaleGestureListenerと呼ばれ、SimpleOnScaleGestureListenerと呼ばれています.SimpleOnScaleGestureListenerはOnScaleGestureListenerを実現しています.そこで、MoveGestureDetectorのインタフェースはこのように定義できます.
/**
* ScaleGestureDetector
*/
public interface OnMoveGestureListener {
/**
*
*/
public boolean onMove(MoveGestureDetector detector);
/**
*
*/
public boolean onMoveBegin(MoveGestureDetector detector);
/**
*
*/
public void onMoveEnd(MoveGestureDetector detector);
}
public static class SimpleOnMoveGestureListener implements OnMoveGestureListener {
public boolean onMove(MoveGestureDetector detector) {
return false;
}
public boolean onMoveBegin(MoveGestureDetector detector) {
return true;
}
public void onMoveEnd(MoveGestureDetector detector) {
// Do nothing, overridden implementation may be used
}
}
よし!1、MoveGestureDetectorを作成する
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
//
scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener);
// MoveGestureDetector
moveGestureDetector=new MoveGestureDetector(context,onMoveGestureListener);
}
2、MoveGestureDetectorに事件を渡す
@Override
public boolean onTouchEvent(MotionEvent event) {
// scaleDetector
scaleDetector.onTouchEvent(event);
// moveGestureDetector
moveGestureDetector.onTouchEvent(event);
return true;
}
3、コールバック値の取得
private MoveGestureDetector.SimpleOnMoveGestureListener onMoveGestureListener=new MoveGestureDetector.SimpleOnMoveGestureListener(){
@Override
public boolean onMove(MoveGestureDetector detector) {
return super.onMove(detector);
}
};
どうですか.ScaleGestureDetectorと同じではないでしょうか.はっきりしていますね.枠は組んでいます.次に、その論理(つまり、handleStartProgressEventとhandleInProgressEventの方法)を実現します.
各行に注釈があるので、私は直接コードをつけました.
*/
public class MoveGestureDetector extends BaseGestureDetector {
/**
* ScaleGestureDetector
*/
public interface OnMoveGestureListener {
/**
*
*/
public boolean onMove(MoveGestureDetector detector);
/**
*
*/
public boolean onMoveBegin(MoveGestureDetector detector);
/**
*
*/
public void onMoveEnd(MoveGestureDetector detector);
}
public static class SimpleOnMoveGestureListener implements OnMoveGestureListener {
public boolean onMove(MoveGestureDetector detector) {
return false;
}
public boolean onMoveBegin(MoveGestureDetector detector) {
return true;
}
public void onMoveEnd(MoveGestureDetector detector) {
// Do nothing, overridden implementation may be used
}
}
private static final PointF FOCUS_DELTA_ZERO = new PointF();
private final OnMoveGestureListener mListener;
private PointF mCurrFocusInternal;
private PointF mPrevFocusInternal;
private PointF mFocusExternal = new PointF();
private PointF mFocusDeltaExternal = new PointF();
public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
super(context);
mListener = listener;
}
@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event){
switch (actionCode) {
//
case MotionEvent.ACTION_DOWN:
// (currevent preevent)
resetState(); // In case we missed an UP/CANCEL event
// event mPrevEvent
mPrevEvent = MotionEvent.obtain(event);
// event
mTimeDelta = 0;
// state
updateStateByEvent(event);
break;
case MotionEvent.ACTION_MOVE:
// onMoveBegin,mGestureInProgress ( handleInProgressEvent)
//mGestureInProgress
mGestureInProgress = mListener.onMoveBegin(this);
break;
}
}
/**
*
*/
@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event){
switch (actionCode) {
//
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// onMoveEnd,move
mListener.onMoveEnd(this);
// state
resetState();
break;
case MotionEvent.ACTION_MOVE:
//
updateStateByEvent(event);
// event press / event onMove
// CurrPressure / mPrevPressure ,
if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
/**
* onMove , updatePrevious
* updatePrevious ,
* updatePrevious event,
* false mPrevEvent down event
* true , move event mPrevEvent
*/
final boolean updatePrevious = mListener.onMove(this);
if (updatePrevious) {
mPrevEvent.recycle();
mPrevEvent = MotionEvent.obtain(event);
}
}
break;
}
}
/**
* ScaleGestureDetector
* move
* updateStateByEvent
*
*/
protected void updateStateByEvent(MotionEvent curr) {
super.updateStateByEvent(curr);
final MotionEvent prev = mPrevEvent;
//
mCurrFocusInternal = determineFocalPoint(curr);
// event
mPrevFocusInternal = determineFocalPoint(prev);
//
boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount();
// mFocusDeltaExternal (0,0), event event
mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x, mCurrFocusInternal.y - mPrevFocusInternal.y);
//
mFocusExternal.x += mFocusDeltaExternal.x;
mFocusExternal.y += mFocusDeltaExternal.y;
}
/**
* ( ScaleGestureDetector)
*/
private PointF determineFocalPoint(MotionEvent e){
// Number of fingers on screen
final int pCount = e.getPointerCount();
float x = 0f;
float y = 0f;
for(int i = 0; i < pCount; i++){
x += e.getX(i);
y += e.getY(i);
}
return new PointF(x/pCount, y/pCount);
}
/**
*
*/
public float getFocusX() {
return mFocusExternal.x;
}
public float getFocusY() {
return mFocusExternal.y;
}
/**
* x y
*/
public PointF getFocusDelta() {
return mFocusDeltaExternal;
}
}
よし!書き終わったら、使ってみましょう.
package com.leo.gestureimageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
import com.leo.gestureimageview.GestureDetectors.MoveGestureDetector;
public class MatrixImageView extends ImageView {
private Matrix currMatrix;
private float scaleFactor=1f;//
private float transX,transY;
private ScaleGestureDetector scaleDetector;
private MoveGestureDetector moveGestureDetector;
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
//
scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener);
// MoveGestureDetector
moveGestureDetector=new MoveGestureDetector(context,onMoveGestureListener);
}
private void initView() {
currMatrix = new Matrix();
DisplayMetrics dm = getResources().getDisplayMetrics();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// scaleDetector
scaleDetector.onTouchEvent(event);
// moveGestureDetector
moveGestureDetector.onTouchEvent(event);
return true;
}
private void setMatrix(){
currMatrix.reset();
currMatrix.postScale(scaleFactor,scaleFactor,getMeasuredWidth()/2,getMeasuredHeight()/2);
currMatrix.postTranslate(transX,transY);
setImageMatrix(currMatrix);
}
private ScaleGestureDetector.SimpleOnScaleGestureListener onScaleGestureListener=new ScaleGestureDetector.SimpleOnScaleGestureListener(){
@Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor(); // scale change since previous event
// Don't let the object get too small or too large.
scaleFactor = Math.max(0.1f, Math.min(scaleFactor, 10.0f));
setMatrix();
/**
* getScaleFactor= (preEvent)/ (currEvent)
* true , move event,
* false , event
*/
return true;
}
};
private MoveGestureDetector.SimpleOnMoveGestureListener onMoveGestureListener=new MoveGestureDetector.SimpleOnMoveGestureListener(){
@Override
public boolean onMove(MoveGestureDetector detector) {
transX=detector.getFocusX();
transY=detector.getFocusY();
setMatrix();
return true;
}
};
}
よし~!わずか数行のコードで游ぶことができて、効果図は私は添付しないで、小さい仲间は自分で运行して、それではMoveGestureDetector私达は実现して、きっとRotateGestureDetectorもすぐに実现することができて、ハハ~!私は直接海外の大神が書いたコードを貼りました.
public class RotateGestureDetector extends TwoFingerGestureDetector {
/**
* Listener which must be implemented which is used by RotateGestureDetector
* to perform callbacks to any implementing class which is registered to a
* RotateGestureDetector via the constructor.
*
* @see SimpleOnRotateGestureListener
*/
public interface OnRotateGestureListener {
public boolean onRotate(RotateGestureDetector detector);
public boolean onRotateBegin(RotateGestureDetector detector);
public void onRotateEnd(RotateGestureDetector detector);
}
/**
* Helper class which may be extended and where the methods may be
* implemented. This way it is not necessary to implement all methods
* of OnRotateGestureListener.
*/
public static class SimpleOnRotateGestureListener implements OnRotateGestureListener {
public boolean onRotate(RotateGestureDetector detector) {
return false;
}
public boolean onRotateBegin(RotateGestureDetector detector) {
return true;
}
public void onRotateEnd(RotateGestureDetector detector) {
// Do nothing, overridden implementation may be used
}
}
private final OnRotateGestureListener mListener;
private boolean mSloppyGesture;
public RotateGestureDetector(Context context, OnRotateGestureListener listener) {
super(context);
mListener = listener;
}
@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event){
switch (actionCode) {
case MotionEvent.ACTION_POINTER_DOWN:
// At least the second finger is on screen now
resetState(); // In case we missed an UP/CANCEL event
mPrevEvent = MotionEvent.obtain(event);
mTimeDelta = 0;
updateStateByEvent(event);
// See if we have a sloppy gesture
mSloppyGesture = isSloppyGesture(event);
if(!mSloppyGesture){
// No, start gesture now
mGestureInProgress = mListener.onRotateBegin(this);
}
break;
case MotionEvent.ACTION_MOVE:
if (!mSloppyGesture) {
break;
}
// See if we still have a sloppy gesture
mSloppyGesture = isSloppyGesture(event);
if(!mSloppyGesture){
// No, start normal gesture now
mGestureInProgress = mListener.onRotateBegin(this);
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (!mSloppyGesture) {
break;
}
break;
}
}
@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event){
switch (actionCode) {
case MotionEvent.ACTION_POINTER_UP:
// Gesture ended but
updateStateByEvent(event);
if (!mSloppyGesture) {
mListener.onRotateEnd(this);
}
resetState();
break;
case MotionEvent.ACTION_CANCEL:
if (!mSloppyGesture) {
mListener.onRotateEnd(this);
}
resetState();
break;
case MotionEvent.ACTION_MOVE:
updateStateByEvent(event);
// Only accept the event if our relative pressure is within
// a certain limit. This can help filter shaky data as a
// finger is lifted.
if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
final boolean updatePrevious = mListener.onRotate(this);
if (updatePrevious) {
mPrevEvent.recycle();
mPrevEvent = MotionEvent.obtain(event);
}
}
break;
}
}
@Override
protected void resetState() {
super.resetState();
mSloppyGesture = false;
}
/**
* Return the rotation difference from the previous rotate event to the current
* event.
*
* @return The current rotation //difference in degrees.
*/
public float getRotationDegreesDelta() {
double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX) - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX);
return (float) (diffRadians * 180 / Math.PI);
}
}
最後に、ScaleDetector、MoveDetector、RotateDetectorのジェスチャーを組み合わせてImageViewのコードをスケールします.
package com.leo.gestureimageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
import com.leo.gestureimageview.GestureDetectors.MoveGestureDetector;
import com.leo.gestureimageview.GestureDetectors.RotateGestureDetector;
public class MatrixImageView2 extends ImageView {
private Matrix mMatrix = new Matrix();
private float mScaleFactor =1f;
private float mRotationDegrees = 0.f;
private float mFocusX = 0.f;
private float mFocusY = 0.f;
private ScaleGestureDetector mScaleDetector;
private RotateGestureDetector mRotateDetector;
private MoveGestureDetector mMoveDetector;
public MatrixImageView2(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
//
DisplayMetrics dm = getResources().getDisplayMetrics();
// ImageView ( imageview )
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
mRotateDetector = new RotateGestureDetector(getContext(), new RotateListener());
mMoveDetector = new MoveGestureDetector(getContext(), new MoveListener());
mFocusX = dm.widthPixels/2f;
mFocusY = dm.heightPixels/2f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// mScaleDetector
mScaleDetector.onTouchEvent(event);
// mRotateDetector
mRotateDetector.onTouchEvent(event);
// mMoveDetector
mMoveDetector.onTouchEvent(event);
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor(); // scale change since previous event
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
changeMatrix();
return true;
}
}
private class RotateListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
@Override
public boolean onRotate(RotateGestureDetector detector) {
mRotationDegrees -= detector.getRotationDegreesDelta();
changeMatrix();
return true;
}
}
private class MoveListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
@Override
public boolean onMove(MoveGestureDetector detector) {
PointF d = detector.getFocusDelta();
mFocusX += d.x;
mFocusY += d.y;
changeMatrix();
return true;
}
}
private void changeMatrix(){
float scaledImageCenterX = (getDrawable().getIntrinsicWidth()*mScaleFactor)/2;
float scaledImageCenterY = (getDrawable().getIntrinsicHeight()*mScaleFactor)/2;
mMatrix.reset();
mMatrix.postScale(mScaleFactor, mScaleFactor);
mMatrix.postRotate(mRotationDegrees, scaledImageCenterX, scaledImageCenterY);
mMatrix.postTranslate(mFocusX - scaledImageCenterX, mFocusY - scaledImageCenterY);
setImageMatrix(mMatrix);
}
}
よし~~~仲間も自分でこのフレームワークのコードをダウンロードして研究してもいいし、私も自分の勉強の心得をみんなに分かち合うだけです.https://github.com/Almeros/android-gesture-detectors
うんうん!たくさん話して、最後に伝説のPhotoViewがどのように実現したのかを見てみましょう.
photoviewのgithubリンク:https://github.com/chrisbanes/PhotoViewary/
私たちの前の内容を見てからPhotoViewを見に行くと、そんなに迷わないかもしれません.次は一緒に神秘的なベールを開けましょう.
まずPhotoViewの使い方ですね.簡単です.皆さんはImageViewのように使えばいいです.
.co.senab.photoview.PhotoView
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitxy"
/>
よし!画像をズーム、回転、移動操作できるようになりました~爽やかではありませんか?
ただし注意:
photoviewのスケールタイプはサポートされていません.そうしないと、直接エラーを報告して終了します.
android:scaleType="matrix"
ソースコードを見てみましょう
public class PhotoView extends ImageView implements IPhotoView {
private PhotoViewAttacher mAttacher;
private ScaleType mPendingScaleType;
public PhotoView(Context context) {
this(context, null);
}
public PhotoView(Context context, AttributeSet attr) {
this(context, attr, 0);
}
public PhotoView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
super.setScaleType(ScaleType.MATRIX);
init();
}
protected void init() {
if (null == mAttacher || null == mAttacher.getImageView()) {
mAttacher = new PhotoViewAttacher(this);
}
if (null != mPendingScaleType) {
setScaleType(mPendingScaleType);
mPendingScaleType = null;
}
}
@Override
public void setRotationTo(float rotationDegree) {
mAttacher.setRotationTo(rotationDegree);
}
@Override
public void setRotationBy(float rotationDegree) {
mAttacher.setRotationBy(rotationDegree);
}
@Override
public boolean canZoom() {
return mAttacher.canZoom();
}
@Override
public RectF getDisplayRect() {
return mAttacher.getDisplayRect();
}
@Override
public void getDisplayMatrix(Matrix matrix) {
mAttacher.getDisplayMatrix(matrix);
}
@Override
public boolean setDisplayMatrix(Matrix finalRectangle) {
return mAttacher.setDisplayMatrix(finalRectangle);
}
@Override
public float getMinimumScale() {
return mAttacher.getMinimumScale();
}
@Override
public float getMediumScale() {
return mAttacher.getMediumScale();
}
@Override
public float getMaximumScale() {
return mAttacher.getMaximumScale();
}
@Override
public float getScale() {
return mAttacher.getScale();
}
@Override
public ScaleType getScaleType() {
return mAttacher.getScaleType();
}
@Override
public Matrix getImageMatrix() {
return mAttacher.getImageMatrix();
}
@Override
public void setAllowParentInterceptOnEdge(boolean allow) {
mAttacher.setAllowParentInterceptOnEdge(allow);
}
@Override
public void setMinimumScale(float minimumScale) {
mAttacher.setMinimumScale(minimumScale);
}
@Override
public void setMediumScale(float mediumScale) {
mAttacher.setMediumScale(mediumScale);
}
@Override
public void setMaximumScale(float maximumScale) {
mAttacher.setMaximumScale(maximumScale);
}
@Override
public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
mAttacher.setScaleLevels(minimumScale, mediumScale, maximumScale);
}
@Override
// setImageBitmap calls through to this method
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
if (null != mAttacher) {
mAttacher.update();
}
}
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
if (null != mAttacher) {
mAttacher.update();
}
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
if (null != mAttacher) {
mAttacher.update();
}
}
@Override
protected boolean setFrame(int l, int t, int r, int b) {
boolean changed = super.setFrame(l, t, r, b);
if (null != mAttacher) {
mAttacher.update();
}
return changed;
}
@Override
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
mAttacher.setOnMatrixChangeListener(listener);
}
@Override
public void setOnLongClickListener(OnLongClickListener l) {
mAttacher.setOnLongClickListener(l);
}
@Override
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
mAttacher.setOnPhotoTapListener(listener);
}
@Override
public void setOnViewTapListener(OnViewTapListener listener) {
mAttacher.setOnViewTapListener(listener);
}
@Override
public void setScale(float scale) {
mAttacher.setScale(scale);
}
@Override
public void setScale(float scale, boolean animate) {
mAttacher.setScale(scale, animate);
}
@Override
public void setScale(float scale, float focalX, float focalY, boolean animate) {
mAttacher.setScale(scale, focalX, focalY, animate);
}
@Override
public void setScaleType(ScaleType scaleType) {
if (null != mAttacher) {
mAttacher.setScaleType(scaleType);
} else {
mPendingScaleType = scaleType;
}
}
@Override
public void setZoomable(boolean zoomable) {
mAttacher.setZoomable(zoomable);
}
@Override
public Bitmap getVisibleRectangleBitmap() {
return mAttacher.getVisibleRectangleBitmap();
}
@Override
public void setZoomTransitionDuration(int milliseconds) {
mAttacher.setZoomTransitionDuration(milliseconds);
}
@Override
public IPhotoView getIPhotoViewImplementation() {
return mAttacher;
}
@Override
public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
mAttacher.setOnDoubleTapListener(newOnDoubleTapListener);
}
@Override
public void setOnScaleChangeListener(PhotoViewAttacher.OnScaleChangeListener onScaleChangeListener) {
mAttacher.setOnScaleChangeListener(onScaleChangeListener);
}
@Override
public void setOnSingleFlingListener(PhotoViewAttacher.OnSingleFlingListener onSingleFlingListener) {
mAttacher.setOnSingleFlingListener(onSingleFlingListener);
}
@Override
protected void onDetachedFromWindow() {
mAttacher.cleanup();
mAttacher = null;
super.onDetachedFromWindow();
}
@Override
protected void onAttachedToWindow() {
init();
super.onAttachedToWindow();
}
}
コードは多くなく、200行以上しかないことがわかります(ハハ!!私たち自身が実現したMatrixImageView 100行もまだ来ていません!!冗談ですが、PhotoViewで考えているものと互換性は、私たちが書いたMatrixImageViewでははるかに及ばないです)、主な処理所はPhotoViewというクラスにあります.
PhotoViewAttacher.JAva:コードが多すぎて、その構造方法を見てみましょう.
public PhotoViewAttacher(ImageView imageView, boolean zoomable) {
mImageView = new WeakReference<>(imageView);
imageView.setDrawingCacheEnabled(true);
imageView.setOnTouchListener(this);
ViewTreeObserver observer = imageView.getViewTreeObserver();
if (null != observer)
observer.addOnGlobalLayoutListener(this);
// Make sure we using MATRIX Scale Type
setImageViewScaleTypeMatrix(imageView);
if (imageView.isInEditMode()) {
return;
}
// Create Gesture Detectors...
mScaleDragDetector = VersionedGestureDetector.newInstance(
imageView.getContext(), this);
mGestureDetector = new GestureDetector(imageView.getContext(),
new GestureDetector.SimpleOnGestureListener() {
// forward long click listener
@Override
public void onLongPress(MotionEvent e) {
if (null != mLongClickListener) {
mLongClickListener.onLongClick(getImageView());
}
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
if (mSingleFlingListener != null) {
if (getScale() > DEFAULT_MIN_SCALE) {
return false;
}
if (MotionEventCompat.getPointerCount(e1) > SINGLE_TOUCH
|| MotionEventCompat.getPointerCount(e2) > SINGLE_TOUCH) {
return false;
}
return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY);
}
return false;
}
});
mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
mBaseRotation = 0.0f;
// Finally, update the UI so that we're zoomable
setZoomable(zoomable);
}
ジェスチャーの変幻を傍受するためのmScaleDragDetectorとmGestureDetectorも作成されていることがわかりますが、イベントはどこで処理されますか?
構築方法では、現在のimageViewにタッチリスニングを設定するコードも見つかりました.
imageView.setOnTouchListener(this);
仲間はすべて推測して、今事件を事件のリスナーにあげました:
@Override
public boolean onTouch(View v, MotionEvent ev) {
boolean handled = false;
if (mZoomEnabled && hasDrawable((ImageView) v)) {
ViewParent parent = v.getParent();
switch (ev.getAction()) {
case ACTION_DOWN:
// First, disable the Parent from intercepting the touch
// event
if (null != parent) {
parent.requestDisallowInterceptTouchEvent(true);
} else {
LogManager.getLogger().i(LOG_TAG, "onTouch getParent() returned null");
}
// If we're flinging, and the user presses down, cancel
// fling
cancelFling();
break;
case ACTION_CANCEL:
case ACTION_UP:
// If the user has zoomed less than min scale, zoom back
// to min scale
if (getScale() < mMinScale) {
RectF rect = getDisplayRect();
if (null != rect) {
v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
rect.centerX(), rect.centerY()));
handled = true;
}
}
break;
}
// Try the Scale/Drag detector
if (null != mScaleDragDetector) {
boolean wasScaling = mScaleDragDetector.isScaling();
boolean wasDragging = mScaleDragDetector.isDragging();
handled = mScaleDragDetector.onTouchEvent(ev);
boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();
mBlockParentIntercept = didntScale && didntDrag;
}
// Check to see if the user double tapped
if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
handled = true;
}
}
return handled;
}
最後にイベントの処理が完了すると、一連のコールバックになります.コールバックが完了すると、ImageViewにmatrixオブジェクトを再設定する必要があります.たとえば、スケール:
@Override
public void setScale(float scale, float focalX, float focalY,
boolean animate) {
ImageView imageView = getImageView();
if (null != imageView) {
// Check to see if the scale is within bounds
if (scale < mMinScale || scale > mMaxScale) {
LogManager
.getLogger()
.i(LOG_TAG,
"Scale must be within the range of minScale and maxScale");
return;
}
if (animate) {
imageView.post(new AnimatedZoomRunnable(getScale(), scale,
focalX, focalY));
} else {
mSuppMatrix.setScale(scale, scale, focalX, focalY);
checkAndDisplayMatrix();
}
}
}
他にも似たようなハ~~~コードが多いと思いますが、カスタマイズしたコンポーネントを書くのはそんなに簡単なことではありませんが、頑張りましょう~!!!
end~