ããã§ã¯ãå§ããŸãããã åçãèŠãããã®ã¢ããªã±ãŒã·ã§ã³ãéçºããŸãã å®æããã¢ããªã±ãŒã·ã§ã³ã¯æ¬¡ã®ããã«ãªããŸãïŒãã¡ãããã¹ã¯ãªãŒã³ã·ã§ããã¯æ©èœæ§ãååã«äŒããŠããŸãããïŒã


ã¢ããªã±ãŒã·ã§ã³ã¯ã ããŒã±ããããã€ã³ã¹ããŒã«ããããããããæåã§ã€ã³ã¹ããŒã«ã§ããŸã ã ãœãŒã¹ã³ãŒãã¯ãã¡ãããå ¥æã§ããŸã ã
ã¢ããªã±ãŒã·ã§ã³ã®äž»ãªèŠçŽ ã¯ãéçºãã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ã¯ã©ã¹ã®æçµã³ãŒãã¯ããœãŒã¹ã§è¡šç€ºã§ããŸãã
以äžã§ãã ç§ã¯ããã圹ã«ç«ã€ããšã«ãªãããšãé¡ã£ãŠããŸãã