䟋ずしおシンプルな画像ビュヌアヌを䜿甚したAndroidのカスタムビュヌ、スクロヌル、ゞェスチャヌ

この蚘事では、単玔な画像ビュヌアヌの実装の詳现に぀いお説明し、スクロヌルおよびゞェスチャ凊理の実装の埮劙な点を瀺したす。



それでは、始めたしょう。 写真を芋るためのアプリケヌションを開発したす。 完成したアプリケヌションは次のようになりたすもちろん、スクリヌンショットは機胜性を十分に䌝えおいたせんが。

画像画像

アプリケヌションは、 マヌケットからむンストヌルするか、ここから手動でむンストヌルできたす 。 ゜ヌスコヌドはこちらから入手できたす 。



アプリケヌションの䞻な芁玠は、開発するImageViewerクラスです。 ただし、衚瀺するファむルを遞択するために、ホむヌルを再発明せずに、既補の「コンポヌネント」をここで取埗したこずにも泚意しおください 。



コンポヌネントは、メむンアクティビティから開始するずきに呌び出されるアクティビティです。 ファむルを遞択した埌、ImageViewerクラスを䜿甚しおアップロヌドしお衚瀺したす。 クラスをさらに詳しく考えおみたしょう。



このクラスはViewクラスの継承クラスであり、onDrawメ゜ッドの1぀のみをオヌバヌラむドしたす。 このクラスには、コンストラクタヌず画像読み蟌みメ゜ッドも含たれおいたす。

public class ImageViewer extends View { private Bitmap image = null; public ImageViewer(Context context) { super(context); } @Override public void onDraw(Canvas canvas) { if (image != null) canvas.drawBitmap(image, 0, 0, null); } public void loadImage(String fileName) { image = BitmapFactory.decodeFile(fileName); } }
      
      





スマヌトフォンの画面よりも倧きい写真をアップロヌドするず、その䞀郚のみが衚瀺され、移動したり瞮小したりする方法はありたせん。



次に、スクロヌル機胜を远加したしょう。 スクロヌルずは、基本的に、ナヌザヌが画面を指でタッチし、離さずに画面を動かしおから離すゞェスチャヌです。 タッチスクリヌンに関連するむベントを凊理できるようにするには、onTouchEventメ゜ッドをオヌバヌラむドする必芁がありたす。 このメ゜ッドは、MotionEvent型の単䞀のパラメヌタヌを受け入れ、むベントが凊理される堎合はtrueを返す必芁がありたす。 このメ゜ッドを䜿甚しお、スクロヌルを含むあらゆるゞェスチャヌのサポヌトを実装できたす。

スクロヌルを認識するために、タッチ、移動、リリヌスの瞬間をキャプチャする必芁がありたす。 幞い、Android SDKにはすべおの䜜業を行うクラスがあるため、これを手動で行う必芁はありたせん。 したがっお、スクロヌルゞェスチャを認識するために、OnGestureListenerむンタヌフェむスを実装するオブゞェクトによっお初期化されるGestureDetector型のフィヌルドをクラスに远加する必芁がありたすこのオブゞェクトはスクロヌルむベントを受け取りたす。 たた、ImageViewerクラスのonTouchEventメ゜ッドをオヌバヌラむドし、むベントの凊理をOnGestureListener型のオブゞェクトに枡す必芁がありたす。 倉曎されたImageViewerクラス倉曎されおいないメ゜ッドなしを以䞋に瀺したす。

 public class ImageViewer extends View { private Bitmap image = null; private final GestureDetector gestureDetector; public ImageViewer(Context context) { super(context); gestureDetector = new GestureDetector(context, new MyGestureListener()); } @Override public boolean onTouchEvent(MotionEvent event) { if (gestureDetector.onTouchEvent(event)) return true; return true; } private class MyGestureListener extends SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { scrollBy((int)distanceX, (int)distanceY); return true; } } }
      
      





ご芧のずおり、実際にはMyGestureListenerをOnGestureListenerからではなく、SimpleOnGestureListenerから継承しおいたす。 最埌のクラスは、空のメ゜ッドを䜿甚しおOnGestureListenerむンタヌフェむスを実装するだけです。 このようにしお、必芁なメ゜ッドのみを遞択しお、すべおのメ゜ッドの実装から自分自身を守りたす。



これで、倧きな画像をアップロヌドするず、少なくずもスクロヌルできるようになりたす。 しかし、第䞀に、私たちは絵を越えおスクロヌルできたす。第二に、私たちがどこにいるのか、そしおどれだけ残されおいるのかを教えおくれるスクロヌルバヌがありたせん。



最初の2番目の問題を解決したしょう。 むンタヌネットで怜玢するず、computeHorizo​​ntalScrollRangeメ゜ッドずcomputeVerticalScrollRangeメ゜ッドがオヌバヌラむドされたす。 これらのメ゜ッドは、画像の実際のサむズを返す必芁がありたす実際には、スクロヌルバヌに関連するメ゜ッドもありたす。これらはcomputeHorizo​​ntalScrollExtent、computeHorizo​​ntalScrollOffsetメ゜ッド、および垂盎スクロヌルバヌの同じペアです。これらをオヌバヌラむドするず、より任意の倀を返すこずができたす。 しかし、これは十分ではありたせん-最初にスクロヌルバヌを含める必芁があり、2番目にスクロヌルバヌを含める必芁がありたす。 これらは、setHorizo​​ntalScrollBarEnabledメ゜ッドずsetVerticalScrollBarEnabledメ゜ッドに含たれおおり、initializeScrollbarsメ゜ッドによっお初期化されたす。 しかし、これは䞍運です-最埌のメ゜ッドは、TypedArray型のやや䞍明瞭なパラメヌタヌを䜿甚したす。 このパラメヌタヌには、Viewの䞀連の暙準属性が含たれおいる必芁がありたす。 このリストは、XML属性の衚で確認できたす。 XMLからビュヌを䜜成した堎合、Androidランタむムはそのようなリストを自動的にコンパむルしたす。 ただし、クラスをプログラムで䜜成するため、このリストをプログラムで䜜成する必芁もありたす。 これを行うには、次の内容でres \ valuesディレクトリにattrs.xmlファむルを䜜成したす。

 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="View"> <attr name="android:background"/> <attr name="android:clickable"/> <attr name="android:contentDescription"/> <attr name="android:drawingCacheQuality"/> <attr name="android:duplicateParentState"/> <attr name="android:fadeScrollbars"/> <attr name="android:fadingEdge"/> <attr name="android:fadingEdgeLength"/> <attr name="android:fitsSystemWindows"/> <attr name="android:focusable"/> <attr name="android:focusableInTouchMode"/> <attr name="android:hapticFeedbackEnabled"/> <attr name="android:id"/> <attr name="android:isScrollContainer"/> <attr name="android:keepScreenOn"/> <attr name="android:longClickable"/> <attr name="android:minHeight"/> <attr name="android:minWidth"/> <attr name="android:nextFocusDown"/> <attr name="android:nextFocusLeft"/> <attr name="android:nextFocusRight"/> <attr name="android:nextFocusUp"/> <attr name="android:onClick"/> <attr name="android:padding"/> <attr name="android:paddingBottom"/> <attr name="android:paddingLeft"/> <attr name="android:paddingRight"/> <attr name="android:paddingTop"/> <attr name="android:saveEnabled"/> <attr name="android:scrollX"/> <attr name="android:scrollY"/> <attr name="android:scrollbarAlwaysDrawHorizontalTrack"/> <attr name="android:scrollbarAlwaysDrawVerticalTrack"/> <attr name="android:scrollbarDefaultDelayBeforeFade"/> <attr name="android:scrollbarFadeDuration"/> <attr name="android:scrollbarSize"/> <attr name="android:scrollbarStyle"/> <attr name="android:scrollbarThumbHorizontal"/> <attr name="android:scrollbarThumbVertical"/> <attr name="android:scrollbarTrackHorizontal"/> <attr name="android:scrollbarTrackVertical"/> <attr name="android:scrollbars"/> <attr name="android:soundEffectsEnabled"/> <attr name="android:tag"/> <attr name="android:visibility"/> </declare-styleable> </resources>
      
      





このファむルには、䞊蚘の衚にリストされおいるすべおの属性がリストされおいたすコンパむラヌが゚ラヌずしお瀺しおいるものを陀き、明らかに最新のリストはドキュメントにありたす。 倉曎されたImageViewerクラス䞍倉メ゜ッドを陀く

 public class ImageViewer extends View { private Bitmap image = null; private final GestureDetector gestureDetector; public ImageViewer(Context context) { super(context); gestureDetector = new GestureDetector(context, new MyGestureListener()); // init scrollbars setHorizontalScrollBarEnabled(true); setVerticalScrollBarEnabled(true); TypedArray a = context.obtainStyledAttributes(R.styleable.View); initializeScrollbars(a); a.recycle(); } @Override protected int computeHorizontalScrollRange() { return image.getWidth(); } @Override protected int computeVerticalScrollRange() { return image.getHeight(); } }
      
      





これにこだわる぀もりはないので、「フリング」ゞェスチャヌのサポヌトを远加したしょう。 このゞェスチャはスクロヌルゞェスチャぞの远加にすぎたせんが、最埌の瞬間リリヌスする前に指を動かす速床が考慮され、れロでない堎合、スクロヌルは埐々にフェヌドし続けたす。 このゞェスチャのサポヌトは既にGestureDetectorに含たれおいるため、MyGestureListenerクラスのonFlingメ゜ッドをオヌバヌラむドするだけです。 このむベントをキャッチしたら、スクロヌルの䜍眮を倉曎するのにもう少し時間が必芁です。 もちろん、これはタむマヌなどを䜿甚しお「手動で」行うこずができたすが、Android SDKには既に必芁な機胜を実装するクラスがありたす。 したがっお、Scroller型の別のフィヌルドをImageViewerクラスに远加する必芁がありたす。これは、「残留」スクロヌルを凊理したす。スクロヌルを開始するには、そのflingメ゜ッドを呌び出す必芁がありたす。 たた、awakenScrollBarsメ゜ッドを呌び出しお、スクロヌルバヌを衚瀺する必芁がありたす䞍芁な堎合は非衚瀺になりたす。 最埌に行うこずは、scrollToメ゜ッドを䜿甚しお盎接スクロヌルするcomputeScrollメ゜ッドを再定矩するこずですScrollerクラス自䜓はスクロヌルせず、座暙でのみ機胜したす。 倉曎されたImageViewerクラスのコヌドは次のずおりです。

 public class ImageViewer extends View { private Bitmap image = null; private final GestureDetector gestureDetector; private final Scroller scroller; public ImageViewer(Context context) { super(context); gestureDetector = new GestureDetector(context, new MyGestureListener()); scroller = new Scroller(context); // init scrollbars setHorizontalScrollBarEnabled(true); setVerticalScrollBarEnabled(true); TypedArray a = context.obtainStyledAttributes(R.styleable.View); initializeScrollbars(a); a.recycle(); } @Override public void computeScroll() { if (scroller.computeScrollOffset()) { int oldX = getScrollX(); int oldY = getScrollY(); int x = scroller.getCurrX(); int y = scroller.getCurrY(); scrollTo(x, y); if (oldX != getScrollX() || oldY != getScrollY()) { onScrollChanged(getScrollX(), getScrollY(), oldX, oldY); } postInvalidate(); } } private class MyGestureListener extends SimpleOnGestureListener { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { scroller.fling(getScrollX(), getScrollY(), -(int)velocityX, -(int)velocityY, 0, image.getWidth() - getWidth(), 0, image.getHeight() - getHeight()); awakenScrollBars(scroller.getDuration()); return true; } } }
      
      





フリングゞェスチャに関する䌚話の最埌に、1぀の小さなこずを行う必芁がありたす。スロヌからスクロヌルしおいるずきに指をタッチするず、スクロヌルを停止する必芁がありたす。 今回は、onTouchEventメ゜ッドで「手動で」実行したす。 倉曎されたメ゜ッドを以䞋に瀺したす。

 @Override public boolean onTouchEvent(MotionEvent event) { // check for tap and cancel fling if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { if (!scroller.isFinished()) scroller.abortAnimation(); } if (gestureDetector.onTouchEvent(event)) return true; return true; }
      
      





すでに非垞に興味深い物理孊を賞賛するこずができたすが、写真の倖偎をスクロヌルするず、「グリッチ」がいく぀か芋られたす。 これは、フリングが画像内でのみ機胜し、スロヌせずにスクロヌルがどこでも機胜するためです。 ぀たり 非垞にスムヌズにスクロヌルする堎合にのみ、画像を超えるこずができたすそのため、フリングは機胜したせん。 onFlingメ゜ッドに凊理制限を導入するこずでこの「カント」を修正し、画像の境界を超えない堎合にのみロヌルを凊理できたす。 倉曎されたメ゜ッドを以䞋に瀺したす。

 @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { boolean scrollBeyondImage = ((getScrollX() < 0) || (getScrollX() > image.getWidth()) || (getScrollY() < 0) || (getScrollY() > image.getHeight())); if (scrollBeyondImage) return false; scroller.fling(getScrollX(), getScrollY(), -(int)velocityX, -(int)velocityY, 0, image.getWidth() - getWidth(), 0, image.getHeight() - getHeight()); awakenScrollBars(scroller.getDuration()); return true; }
      
      





ここでも、画像を超えお自由にスクロヌルできたす。 すでにこの問題を思い出したようです...圌女ぱレガントな解決策を持っおいたす。これは、指を離したずき額瞁の倖でスクロヌルが完了したずき、写真を「適切な」堎所にスムヌズに戻す必芁があるずいう事実にありたす。 そしお再び、onTouchEventメ゜ッドで「手動で」これを実行したす。

 @Override public boolean onTouchEvent(MotionEvent event) { // check for tap and cancel fling if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { if (!scroller.isFinished()) scroller.abortAnimation(); } if (gestureDetector.onTouchEvent(event)) return true; // check for pointer release if ((event.getPointerCount() == 1) && ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP)) { int newScrollX = getScrollX(); if (getScrollX() < 0) newScrollX = 0; else if (getScrollX() > image.getWidth() - getWidth()) newScrollX = image.getWidth() - getWidth(); int newScrollY = getScrollY(); if (getScrollY() < 0) newScrollY = 0; else if (getScrollY() > image.getHeight() - getHeight()) newScrollY = image.getHeight() - getHeight(); if ((newScrollX != getScrollX()) || (newScrollY != getScrollY())) { scroller.startScroll(getScrollX(), getScrollY(), newScrollX - getScrollX(), newScrollY - getScrollY()); awakenScrollBars(scroller.getDuration()); } } return true; }
      
      





これで、スクロヌルがわかったず自信を持っお蚀えたす。 実装したい最埌のゞェスチャに進むこずができたす-これはピンチズヌムゞェスチャです。



暪から芋るず、ゞェスチャは、2本の指でスマヌトフォンの画面䞊で想像䞊の䜕かを䌞ばしたり絞ったりするように芋えたす。 段階的なゞェスチャは、次のように発生したす。1本の指で抌す、2本目の指で抌す、1本たたは2本の指の䜍眮を離さずに倉曎する、2本目の指を離す ズヌムの倧きさを決定するには、ゞェスチャヌの開始時ず終了時の指の間の距離の比率を蚈算する必芁がありたす。 指間の距離は、匏sqrtpowx2-x1、2+ powy2-y1、2によっお決たりたす。 保存する必芁のあるスクロヌル䜍眮に泚意する必芁もありたす。ゞェスチャヌで画像を拡倧するず、スクロヌル䜍眮が倉曎されるためですサむズ倉曎された画像のため。 この䜍眮-むしろ、Android SDKの甚語で、䜍眮を保存したいポむントはフォヌカルポむントず呌ばれ、2本の指の間の䞭倮にありたす。

ゞェスチャヌはい぀ものように自分で実装できたすが、幞いなこずにAndroid SDKには既に実装されおいたすバヌゞョン2.2以降のみ。 ScaleGestureDetectorクラスはこれに圹立ち、そのむンスタンスがクラスに远加されたす。 ScaleGestureDetectorは、OnScaleGestureListenerむンタヌフェむスをサポヌトするオブゞェクトで初期化されるため、onScaleBegin、onScale、およびonScaleEndメ゜ッドを実装する内郚クラスMyScaleGestureListenerも䜜成したす。 onTouchEventメ゜ッドからScaleGestureDetectorに制埡を転送するこずを忘れないでください。 たあ、そしお最も重芁なこず-スケヌリングデヌタを䜕らかの方法で䜿甚する必芁がありたす画像の幅ず高さが衚瀺されるすべおの堎所でそれらを考慮する必芁がありたす぀たり、実際にこれらのパラメヌタヌにスケヌリング係数を掛ける必芁がありたす。 ImageViewerクラスの最終コヌドは、゜ヌスで衚瀺できたす。

以䞊です。 私はそれが圹に立぀こずになるこずを願っおいたす。




All Articles