
æè¿ãAndroidçšã®Mail.Ruã¢ãã€ã«ã¡ãŒã«ã§SlideStackViewãéçºããçµéš
ã«ã€ããŠè©±ããŸããã ãã®åŸãè¿ãå°æ¥ã第2éšãæºåããããžã¥ã¢ã«ã³ã³ããŒãã³ãã®ããã°ã©ãã³ã°ã®èŠ³ç¹ããæãèå³æ·±ãéšåãå®è£
ããæ¹æ³ã«ã€ããŠèª¬æããããšãçŽæããŸããã åœç¶ãã¢ããªã±ãŒã·ã§ã³ã«ã€ã³ã¿ã©ã¯ãã£ãæ©èœãè¿œå ãããã®ãã€ãŸãã¢ãã¡ãŒã·ã§ã³ã«ã€ããŠèª¬æããŸãã ã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ã¯ãŠãŒã¶ãŒã®æäœã«å¿çããå¿
èŠããããšããäºå®ã«ã誰ããé·ãéæ
£ã芪ããã§ããŸãã æããã«ãã¢ããªã±ãŒã·ã§ã³ãšå¯Ÿè©±ããäž»ãªæ¹æ³ã¯ãã¿ããã¹ã¯ãªãŒã³ã䜿çšããããšã§ãã
Navigation Stack-SlideStackView-ãäœæããŠãããããã¢ããªã±ãŒã·ã§ã³ã®äž»èŠéšåéã®ã¢ãã¡ãŒã·ã§ã³åããããã©ã³ãžã·ã§ã³ã«ã€ã³ã¿ã©ã¯ãã£ãæ©èœãè¿œå ããŸãã Mail.Ruã¢ãã€ã«ã¡ãŒã«ã§ã¯ããããã¯3ã€ã®ãã©ã°ã¡ã³ãã§ããã¢ããªã±ãŒã·ã§ã³ã«è¿œå ãããã¢ã«ãŠã³ãã®ãªã¹ããéžæããã¡ãŒã«ããã¯ã¹å
ã®ãã©ã«ããŒã®ãªã¹ãã1ã€ã®ãã©ã«ããŒã®å
容ã瀺ãæåã®ãªã¹ãã§ãã
æåã®éšåã§æžããããã«ãããã¯ãã¹ãŠãéçã¹ã©ã€ãã®é
眮ãšæç»ã®æ¹æ³ãåŠã¶ããšããå§ãŸããŸãã 次ã®ã¹ãããã¯ããããã®ã¹ã©ã€ããã¢ãã¡ãŒã·ã§ã³åããæ¹æ³ãåŠã¶ããšã§ãã
ã»ãšãã©ã®éçºè
ã¯ã圌ãã解決ããããšããŠããã¿ã¹ã¯ã®ãã¬ãŒã ã¯ãŒã¯ã§ãããã«åºããããAndroidãã¬ãŒã ã¯ãŒã¯ã§MotionEventãã©ã®ããã«åŠçãããããããçšåºŠç解ããŠãããšæããŸãããããã§ãæåããå§ããŸãã
ãã®ãããåºæ¬ã¯ã©ã¹ã®Viewã¯ãMotionEventã®æœåšçãªåŠçãæ瀺ãããããªæ¹æ³ã§èšèšãããŠããŸãã ãããŠã察å¿ããã¢ã¯ã·ã§ã³ã«å¿ããŠããŠã£ãžã§ããã¯ãã®MotionEventãåãåããŸãããã®MotionEventã«ã¯ããŠãŒã¶ãŒãTouchScreenãšå¯Ÿè©±ããæ¹æ³ã«é¢ãããã¹ãŠã®å¿
èŠãªæ
å ±ãæ ŒçŽãããŸãã
ãã®äŸã§ã¯ãã¹ã©ã€ãã¹ã¿ãã¯å
ã®ã¹ã©ã€ããã¹ã¯ããŒã«ããã®ã«åœ¹ç«ã€ã€ãã³ãã®ã¿ã«é¢å¿ããããŸãã
ããããã³ãŒããæžãå§ããåã«ãäœãèµ·ãã£ãŠãããã®æãæ£ç¢ºãªã¢ãã«ãèªåã®ããã«æ§ç¯ããããã«ãæåã«å©çšå¯èœãªãã¹ãŠã®ããŒã¿ãèŠä»¶ãããã³åžæãããŒãã«åéããããšãåžžã«å¥œã¿ãŸãã åœç¶ã®ããšãªãããããããªããšãäžåºŠã«èããããšã¯äžå¯èœã§ããã³ãŒããæžããšãã¯ãã¢ãŒããã¯ãã£ã®åçŽããæ¯æããããã«ããŸãã¯éçºæéãççž®ããããã«ãã¢ã€ãã¢ãæšãŠãå¿
èŠããããŸãã ããããç§ããã£ãšåã«æ°ã¥ããããã«ãããã¯æå°éã®æ倱ã§è¡ãããŸãã座ã£ãŠãããããã®ãããŒå³ãçžäºäœçšè¡šãæãã1ã€ã®ããã°ãã¯ã©ã¹ãçžäºäœçšããã³ã³ããŒãã³ãã«åå²ãããšãã·ã¹ãã ã®ç¬ç«ããéšåã«åœ±é¿ãäžããã«åäœãç°¡åã«å€æŽã§ããŸã ããŸããŸãªãŠã£ãžã§ãããäœæããéããã®ç解ã¯çŽæã¬ãã«ã§èªç¶ã«è¡ãããŸããå¿
èŠãªå€æŽãè¡ãã«ã¯å
æã«æžããããã¹ãŠã®ã³ãŒããã·ã£ãã«ã§ã·ã£ãã«ããå¿
èŠããããšããèãã«é ãã€ããããšã¯ãããŸããã ããã§ã¯ããã¹ãŠã®ããã°ã©ããŒã«ãšã£ãŠéåžžã«ã·ã³ãã«ã§éŠŽæã¿ã®ããã«ãŒã«ãåé¢ã«åºãŠããŸãã
- äžèŠãªæ©èœã§ã¯ã©ã¹ããªãŒããŒããŒãããªãã§ãã ãã
- ãã®éšåãåŸã§åœ¹ç«ã€ãšæãããå Žåã¯ãåžžã«ã³ãŒãããªãã¡ã¯ã¿ãªã³ã°ããŠãã ãã
- äºåã«æé©åããªãã§ãã ãã
- åãã£ãŒã«ããŸãã¯ã¡ãœããã¯ãã³ã³ããŒãã³ãèªäœããåé¢ããŠèŠããšãã«å°æãããŠã¯ãªããŸãã
æåŸã®æ®µèœã§ã¯ãããããæãé£ããã§ãããããªããªããå€ãã®å Žåãç®çã®çµæãéæããããã«æ¥ãã§ããã¹ãŠã®ããã°ã©ããŒã劥åãããã°ãã°ãããã«å€æŽããŸãããšããã¡ã¢ãä»ããŠãããã«åäœãããããã«ããåŸåãããããã§ã ç§ã¯ããã絶察ã«ééã£ãŠãããšèšã£ãŠããã®ã§ã¯ãããŸãããäœããå€æŽããã«ã¯é
ãããããããã«æªãããšã«ããããåŸã§èŠããŠããå¯èœæ§ãé«ãããšãèŠåãããã ãã§ãã
ãããŠã泚æããå¿
èŠããããšç§ãèããæåŸã®ãã€ã³ãã¯ããããã«ç©è°ãéžãé
åã§ãã çä¿¡ãªãã«äžèšã®ãã¹ãŠãè¡ããŸãã ã€ãŸããã¢ããªã±ãŒã·ã§ã³ã®ç»é¢éã移åããããã®ã³ã³ãããŒã«ãšããŠäœ¿çšããããŠã£ãžã§ãããäœæããã¿ã¹ã¯ã«çŽé¢ããŠãããèŠä»¶ã®äž»ãªã¿ã¹ã¯ãã¿ããã¹ã¯ãªãŒã³ãä»ããæäœã®å®¹æãã§ããå Žåããããéæããåã«ããã«æ³šæãæãå¿
èŠã¯ãããŸããã¿ããã³ã³ãããŒã«ãªã©ã®ç¬éã ãã¡ãããåã«é»è©±ãåããã移åãããããŠããã²ãŒãããããšã¯éåžžã«ã¯ãŒã«ã§äŸ¿å©ã§ãããããŸãããããã§ãããã¯äž»èŠãªã¿ã¹ã¯ã§ã¯ãªããããã°ãããè¯ãæ代ãŸã§ã延æã§ããŸãã
ããã§ã¯ãã©ã ã«æ»ããŸãããã
äžèŠããã¹ãŠãéåžžã«åçŽã§ãããäžèŠããã ãã§ãã 以åã«ã¹ã¯ããŒã«ãåŠçããå¿
èŠããã£ãå Žåããã¹ãŠã®çš®é¡ã®MotionEventã®äžã§ããŠãŒã¶ãŒã®çã®æå³ãå€æããã®ã¯ããã»ã©ç°¡åã§ã¯ãªãããšããåãã§ãããã 圌ã¯ã¹ã©ã€ããåã«æšªã«ç§»åããããšã決ããŸãããããŸãã¯ãªã¹ãå
ã«ããèŠçŽ ãã¯ãªãã¯ããŸããïŒ ãã®ããžãã¯ã¯ãã¹ãŠãã¹ã©ã€ãã¹ã¿ãã¯ãšã¯å¥ã«å®è£
ã§ãããããã¹ã¯ããŒã«åŠçã¢ã«ãŽãªãºã å
šäœãåå¥ã®SlideStackScrollerã³ã³ããŒãã³ãã«åé¢ãããšäŸ¿å©ã§ãã
public static class SlideScroller extends Scroller implements OnTouchListener{ private final ScrollingListener mListener; private final GestureDetector mGestureDetector; public SlideScroller(Context context, ScrollingListener listener, OnGestureListener gestureListener) { super(context); this.mListener = listener; this.mGestureDetector = new GestureDetector(context, gestureListener); mGestureDetector.setIsLongpressEnabled(false); } }
ã¹ã¯ããŒã«ã¯é©æ°çãªã¿ã¹ã¯ã§ã¯ãªãããããã¡ããã圹ç«ã€ã¯ã©ã¹ãçšæãããŠããŸãã ã€ãŸããScrollerãšGestureDetectorã§ãã 1ã€ç®ã¯ã¹ã¯ããŒã«ãèšç®ããããã®äŸ¿å©ãªã€ã³ã¿ãŒãã§ã€ã¹ãæäŸãã2ã€ç®ã¯æšæºã¿ã€ãã®ãžã§ã¹ãã£ã決å®ããã®ã«åœ¹ç«ã¡ãŸãããã®å ŽåãæãéèŠãªã®ã¯ããªã³ã°ãžã§ã¹ãã£ã§ãã æ¢è£œã®ãœãªã¥ãŒã·ã§ã³ã䜿çšããäž»ãªå©ç¹ã«å ããŠïŒæ lazãªããã°ã©ããŒã¯ç§ãç解ããŸãïŒ-ãã®ããžãã¯ãèªåã§èšè¿°ããå¿
èŠã¯ãããŸãã-ãã©ãããã©ãŒã ãæäŸãããœãªã¥ãŒã·ã§ã³ã䜿çšãããšãããããäžè²«ãããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ããŸãã¯ãŠãŒã¶ãŒã«éŠŽæã¿ãä»ã®ã³ã³ããŒãã³ãã®äžã§ç®ç«ããªãåäœãç°¡åã«éæã§ããŸããã©ãããã©ãŒã ã ãŸããã·ã¹ãã ã®ã€ã³ã¿ã©ã¯ãã£ããªéšåãéçºããŠããå Žåããããèæ
®ããããšã¯ç¹ã«éèŠã§ãã
ã¹ã¯ããŒã©ãŒã¯ãã¹ã©ã€ãã¹ã¿ãã¯ã®èŠ³ç¹ãããéèŠãªã€ãã³ããã€ãŸã次ã®ãã®ãã¬ããŒãããã€ã³ã¿ãŒãã§ã€ã¹ãã¹ã©ã€ãã¹ã¿ãã¯ã«æäŸããå¿
èŠããããŸãã
public interface ScrollingListener { void onScroll(int distance); void onStarted(); void onFinished(); void onJustify(); }
ã¹ã¯ããŒã«ãéå§ããããšããã¹ã©ã€ããäžå®æ°ã®ãã¯ã»ã«ã移åãããšãããã®ã¹ã¯ããŒã«ã¯çµäºããã¹ã©ã€ãã®äœçœ®ãæããå¿
èŠããããã¹ã¯ããŒã«ã¯å®å
šã«å®äºããŸããã
ã¹ã©ã€ãã¹ã¿ãã¯ã®èŠ³ç¹ããèŠããšããã以äžç°¡åãªãã®ã¯ãããŸããããã¹ãŠã®ã¿ããã€ãã³ãã®åŠçãã¹ã¯ããŒã©ãŒã«å§ä»»ããã ãã§ãå®éã«äœãèµ·ãã£ãã®ããããããå¿
èŠãªã³ãŒã«ããã¯ãçºçããŸãã
ãæ°ã¥ãã®ãšããããã¹ãŠã®å§ä»»ã¯ãandroid.view.View.OnTouchListenerã€ã³ã¿ãŒãã§ã€ã¹ã®onTouchïŒView vãMotionEventã€ãã³ãïŒã¡ãœãããä»ããŠè¡ãããŸãã
@Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN:{ int pointerId = MotionEventCompat.findPointerIndex(event, mActivePointerId); if (pointerId == INVALID_POINTER_ID){ break; } mLastTouchX = MotionEventCompat.getX(event, pointerId); mJustifying = false; forceFinished(true); clearMessages(); break; } case MotionEvent.ACTION_MOVE:{ int pointerId = MotionEventCompat.findPointerIndex(event, mActivePointerId); if (pointerId == INVALID_POINTER_ID){ break; }
é çªã«è¡ããŸãããïŒ
- ACTION_DOWNã¯ããŠãŒã¶ãŒãç¹å®ã®åº§æšã§ç¹å®ã®é åã®ç»é¢ã«è§Šããããšã瀺ããŸãã ãã®å Žåããã®ãããªå Žåã«ã¯å®å
šã«æšæºçãªæé ãå®è¡ããå¿
èŠããããŸããã€ãŸããå°é¢ãæºåããç»é¢äžã®åŸç¶ã®ãŠãŒã¶ãŒã®åãã«åããŠãããã«å¿ããŠã¹ã©ã€ãã¹ã¿ãã¯ãã¹ã¯ããŒã«ããŸãã ãã®å Žåããã®æé ã«ã¯ãåæäœçœ®ã®ä¿åããã¹ãŠã®ãã©ã°ããã³çŸåšã®ã¹ã¯ããŒã«ã®ãªã»ãããå«ãŸããŸãïŒå®è¡ãããå ŽåïŒã
- ACTION_MOVEã¯ã移åèªäœãã€ãŸããŠãŒã¶ãŒã¿ãããæ°ãã座æšã«ç§»åããããšã瀺ããŸãã 座æšã®å·®ãèšç®ãããŠãŒã¶ãŒãã¹ã¯ããŒã«ããããšãã¹ã©ã€ãã¹ã¿ãã¯ã«éç¥ããå¿
èŠãããããšãæšæž¬ããããšã¯é£ãããããŸããã SlideStackScrollerãªã©ã®ã³ã³ããŒãã³ãã匷調衚瀺ããããšã§ãã¢ãŒã·ã§ã³ã€ãã³ãã®åŠçãšã¹ã©ã€ãã¹ã¿ãã¯ã®å
éšããžãã¯ãšã®éå°ãªçµåãåé¿ããŸããã èšãæããã°ãã¹ã©ã€ãã¹ã¿ãã¯ã®çŸåšã®ç¶æ
ãã€ãŸãäžæ¹åã«ã¹ã¯ããŒã«ã§ãããã©ããã¯å¿é
ããŠããŸããã ãŠãŒã¶ãŒãäœããããããããŠããã«å¿çãããã©ããããããŠã©ã®ããã«åå¿ãããã圌ã«äŒããã ãã§ãã¹ã©ã€ãã¹ã¿ãã¯ã決å®ããŸã
- ACTION_UP ãã®ãããå€ãã®å ŽåããŠãŒã¶ãŒã¯ç»é¢ãšã®å¯Ÿè©±ãçµäºããŸãã ããããããã¯ãªã©ãã¯ã¹ã§ãããšããæå³ã§ã¯ãããŸããã ãããããäœæ¥ã®æãåçŽãªéšåã ããããã§çµãããŸãã ç§ã¯èª°ããæ
£ããŠãããžã§ã¹ãã£ãŒãæå³ããéåžžã¯æãé£ã°ããšåŒã°ããŸãã ãªãã©ã«ç¿»èš³ïŒãã¹ããŒãïŒã§ã¯ããããäœãæå³ããã®ãç解ã§ããŸããã ããã«é¢é£ããŠããžã§ã¹ãã£ãšã¯ããŠãŒã¶ãŒãç»é¢äžã§æãåãããããããèŠçŽ ãå éãã觊ããªãããã«å®éã®èº«äœãšåãããã«èŠçŽ ãåãããšãæåŸ
ãããšããæå³ã§ãã ã€ãŸããæ
£æ§ã«ãããã®ã§ãã ããšãã°ãé»è©±ãããŒãã«ã®äžã§ä»ã®äººã«ããã·ã¥ããå Žåãã©ã®ããã«ã¹ã©ã€ããããã
- æåŸã«ãã¹ã©ã€ãã®äœçœ®ã調æŽã§ããããã«ãžã§ã¹ãã£ãçµäºãããã©ããããŸãã¯ãŠãŒã¶ãŒã«ãã£ãŠè¡ããããžã§ã¹ãã£ãããªã³ã°ãšããŠè§£éã§ãããã©ãããå€æããå¿
èŠããããŸãããã®åŸãé©åãªã¢ã¯ã·ã§ã³ãå®è¡ããå®äºãããŸã§åŸ
ã£ãŠããã¹ã©ã€ãã調æŽããå¿
èŠããããŸã
ããã«èšãããã®ã¯ãäžèŠãããšè¿œå çã§éèŠã§ã¯ãªãããã«æãããããªã³ã°ããã¹ã©ã€ãã¹ã¯ããŒã«ã®æãé »ç¹ãªå®äºã§ãããšããããšã§ãã åé¡ã¯ãã»ãšãã©ã®ãŠãŒã¶ãŒããã©ãããã©ãŒã èªäœãšå€ãã®ã¢ããªã±ãŒã·ã§ã³ã®äž¡æ¹ã§æ°Žå¹³ã¹ã¯ã€ãã«æ
£ããŠããããããã®ãžã§ã¹ãã£ãŒãçŽæçãã€è¿
éã«å®è¡ããããšã§ãã
if ((!mGestureDetector.onTouchEvent(event) || ((SlideStackView)v).isOverScrolled()) && (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL)){ justify(); }
ãã®ç¶æ
ãããäžåºŠèŠãŠã¿ãŸãããã ããã§ã¯ãGestureDetectorããžã§ã¹ãã£ã«ã©ã®ããã«å¿çãããã確èªããã®ã§ã¯ãªãããã¹ãŠã®ãžã§ã¹ãã£ãã©ãã ãééãããã確èªããããšãéåžžã«éèŠã§ãã ãã®å ŽåããŠãŒã¶ãŒã®åäžã®åããèŠéããŠããªãããšã確èªããçºçãããšãã«å¿
èŠãªãžã§ã¹ãã£ãŒãæ£ç¢ºã«å€æã§ããããã«ããŸãã ãã®ç¶æ
ã§æ€èšŒã®é åºã䞊ã¹æ¿ãããšãã»ãšãã©ã®MotionEventããžã§ã¹ãã£ãŒæ€åºåšã«å°éããªããããã¹ã©ã€ãã¹ã¿ãã¯ãã¹ã¯ã€ãã«å¿çããªããªããŸãã
ãã®çµæãããªã³ã°ãå®äºãããšãonFlingã¡ãœããïŒMotionEvent e1ãMotionEvent e2ãfloat velocityXãfloat velocityYïŒãåŒã³åºãããšã§ãã¹ãŠãçµäºããŸãïŒãŸãã¯ãå¿
èŠã«å¿ããŠéå§ããŸãïŒã
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (mScroller.isTouchScrolling()){ LOG.w("mTouchScrolling in fling"); } SlideInfo slide = getSlideInfo(mSelected); int dx = getAdjustedTargetX(velocityX) - slide.mOffset; mScroller.fling(-(int)getVelocity(dx)); return true; }
ãã®æ¹æ³ã§ã¯ãæçµçãªåº§æšãèæ
®ãããŸããããã¯ãããªã³ã°ãå®äºããåŸã«å°éããå¿
èŠããããŸãã次ã«ãã¹ã©ã€ãã®çŸåšäœçœ®ã®ä¿®æ£ãè¡ããã移åã®åæé床ãèšç®ãããããªã³ã°èªäœãéå§ãããŸãã
ãã§ã«äžããããŠããã®ã«ããªãé床ãåãã®ã§ããïŒ åé¡ã¯ãé床ãç°ãªãå¯èœæ§ãããããšã§ããããã®å Žåãç¹å®ã®ãã€ã³ãã§ããªã³ã°ãçµäºããããšãéèŠã§ããããã®ãã€ã³ãã¯getAdjustedTargetXïŒïŒã¡ãœããã§èšç®ãããŸãã
private int getAdjustedTargetX(float velocityX) { int result = 0; if (velocityX > 0){ result = getRightEdge(mSelected) - getLeftEdge(mSelected) + mRightOverScrollInFling;
ã¡ãœããã®æ¬äœãããããããã«ããã®ãã€ã³ãã®èšç®ã¯ããªã³ã°ã®æ¹åã«äŸåããŸãããæçµçãªäœçœ®ã¯å€ãããŸããã å³ãžã®é£ã³ã®å Žåãããã¯ã¹ã©ã€ãã®å³ç«¯ã®äœçœ®+ã¹ã©ã€ããå³ç«¯ã®äœçœ®ãããé ãã«é£ã¶ãã¯ã»ã«æ°ã«ãããããªãããã§ãã ããªã³ã°ãå·Šã«ããå Žåãããã¯å·Šç«¯ã®äœçœ®-ã¹ãããã§ãã ãããŠãããã¯åãã®æåã®é床ã§ãã ãã®ãããªå¶éã¯äººçºçã«è¡ãããŸããããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã®åæã«ããããã®ã³ã³ãããŒã«ã§ã¯ç¹ã«ãã®åäœãããèªç¶ã«èŠããããšã瀺ãããããã§ãã
Androidãã©ãããã©ãŒã ã§ã¯ãã¹ã¯ããŒã«äžã«èŠçŽ ã®éååŠãèšç®ããã®ã«ã¹ã¯ããŒã©ãŒãé¢äžããŸãã ã¢ãã«ãšããŠãGoogleã®éçºè
ã¯ãåŠæ ¡ã§ç¥ãããŠããåäžã«å éãããã¢ãŒã·ã§ã³ã®å
¬åŒãæ¡çšããŸããïŒS = V
0 * t-ïŒg * t
2 ïŒ/ 2ã 移åã®åæé床ã¯ããã£ãŠããŸãã移åã®éå§ããæéã枬å®ã§ããŸããã¹ã©ã€ããåæ¢ããå é床ãéžæããã ãã§ãã
éçºè
ã¯ãèªç±èœäžã®å éãæšæž¬ãããåºç€ãšããŠæ¡çšããŸããã
mDefaultDeceleration = SensorManager.GRAVITY_EARTH
åæ¢ããå®è£
ãã泚æãããããšãç°ãªãåæé床ã§ããªã³ã°äžã«çºçããå¯èœæ§ã®ãã4ã€ã®ã·ããªãªãåºå¥ã§ããŸãã
次ã®ããã«ãéããã¹ã©ã€ããšéããã¹ã©ã€ãã®äœçœ®ãåããŸãã


éå§äœçœ®ããããªã³ã°ãè¡ããšä»®å®ããŸãã äžèšã®4ã€ã®å¯èœãªãªãã·ã§ã³ã¯ã巊端ïŒ1ã¹ã¯ãªãŒã³ã·ã§ããïŒãšå³ç«¯ïŒ2ã¹ã¯ãªãŒã³ã·ã§ããïŒã®ã¹ã©ã€ãäœçœ®ãåºæºã«ç§»åè·é¢ããœãŒãããããšã«ããåŸãããŸãã ãããã®äœçœ®éã®è·é¢S
n
S <S
n / 2
ãã®ç¶æ³ã¯ããŠãŒã¶ãŒãé£ãã§ãèŠçŽ ã«åæé床ãäžããŠããããšã瀺ããŠããŸããããã¯ãèŠçŽ ãçµäºäœçœ®ãŸã§ã®è·é¢ã®ååãè¶
ããã«ã¯äžååã§ãã ãã®å Žåãèªç¶ãªæ¯ãèãããšãã¥ã¬ãŒãããããã«ããŠãŒã¶ãŒãèŠçŽ ã«äžããé床ã§æãé£ã°ããèŠçŽ ãåæ¢ããã®ãåŸ
ã£ãŠãããå
ã®ç¶æ
ã«æ»ãããšãã§ããŸãã 巊端ã®äœçœ®ã

S
n / 2 <S <S
nããã§ã¯ãèŠçŽ ã®åæé床ã¯ã¿ãŒã²ãããŸã§ã®è·é¢ã®ååãå
æããã«ã¯ååã§ããããçµç¹ã«å°éããã«ã¯äžååã§ããã ãããã£ãŠããã®ã·ããªãªã決å®ããããèŠçŽ ã«ããå°ãé床ãäžããããšãã§ããŸã-ç®çå°ã«å°éããã«ã¯ååã§ãã ãã®ããã«ãåäžã«å éãããåãã®ç©çåŠãæãåºããŠãå éãšçµæã®è·é¢ãç¥ã£ãŠãæããåæé床ãèšç®ãã䟡å€ããããŸãã
S
n <S <S
n +ãªãŒããŒã¹ã¯ããŒã«
ãã®çµæã¯ããŠãŒã¶ãŒãèŠçŽ ã«ååãªé床ãäžããŠããããšã瀺ããŠããŸãããã®æç¹ã§ããŠãŒã¶ãŒã¯ãã§ã«å³ç«¯ãè¶
ããŠç§»åãçµäºããŸãããæ倧蚱容ã¹ãããã«ã¯éããŸããã ãã®å Žåããã¹ãŠãæå³ãããšããã«æ£ç¢ºã«è¡ãããŸããã€ãŸãããŠãŒã¶ãŒã¯èŠçŽ ãæµ·å€ã«ç§»åããããã«èŠãããã®åŸãèŠçŽ ã¯æŽåããŠå³ç«¯ã®äœçœ®ã«æ»ããŸãã
S> S
n +ãªãŒããŒã¹ã¯ããŒã«
ãã®ç¶æ³ã¯å°ãæªãã§ãã èŠçŽ ã®åæé床ã¯éåžžã«é«ããããå³ç«¯ã®äœçœ®ãè¶
ããŠç§»åãããã®ããªããã¯æ倧蚱容å€ãè¶
ããŸãã çè«çã«ã¯ãå·®ã¯å€§å¹
ã«å€§ãããªãå¯èœæ§ããããèªç¶ã§ã¯ãããèŠèŠããèŠèŠå¹æãçã¿åºããŸãã è¯å®çãªç¹ã¯ãéåžžã«éãåãã§ã¯ããŠãŒã¶ãŒãé床ã調æŽããæ¹æ³ã«æ°ä»ãã®ãããé£ããããšã§ã-äž»ãªããšã¯ããã¹ãŠãè¿
éã«è¡ãããèŠçŽ ãç»é¢ã®å¢çãã¯ããã«è¶
ããªãããšã§ãã
ããªã³ã°ãä»ã®æ¹åã«å解ãããšãåç
§è·é¢ãå°ãç°ãªãæ¹æ³ã§èšç®ãããããšãé€ããŠããã¹ãŠåãã±ãŒã¹ã衚瀺ãããŸãã
ããã§ãç§ãã¡ã¯ãããªã³ã°ãã©ã®ããã«åŠçããããèãåºããŸããã 次ã«ããã®ãããªåãã®äžã§ã¢ãã¡ãŒã·ã§ã³ãäœæããæ¹æ³ã決å®ããå¿
èŠããããŸãã ãããè¡ãã«ã¯ã次ã®æ¹æ³ãæ€èšããŠãã ããã
public void fling(int velocity){ mLastX = 0; final int maxX = 0x7FFFFFFF; final int minX = -maxX; fling(mLastX, 0, velocity, 0, minX, maxX, 0, 0); setNextMessage(MSG_SCROLL); }
ãã®æ¹æ³ã¯ãæ°Žå¹³ããªã³ã°å°çšã«å¿
èŠã§ãããéå§äœçœ®ãšçµäºäœçœ®ãèæ
®ããŸããã ãããã£ãŠãåæäœçœ®ãèŠããŠãå¯èœãªæ倧å¢çãèšå®ããçžç¶äººã¯ã©ã¹ã§ããªã³ã°èªäœãéå§ããã¹ã¯ããŒã«ãåŠçããå¿
èŠããããšããã¡ãã»ãŒãžãèªåã§éä¿¡ããŸãã
ãã®ã¡ãœããã¯éåžžã«ç°¡åã§ãã以åã«ããã«çœ®ããããã¹ãŠã®ã¡ãã»ãŒãžãã¯ãªã¢ããçŸåšå¿
èŠãªã¡ãã»ãŒãžã1ã€ã ããã³ãã©ãŒã«éä¿¡ããŸãã
private final Handler mAnimationHandler = new AnimationHandler(); private void setNextMessage(int message) { clearMessages(); mAnimationHandler.sendEmptyMessage(message); } private void clearMessages() { mAnimationHandler.removeMessages(MSG_SCROLL); mAnimationHandler.removeMessages(MSG_JUSTIFY); }
mAnimationHandlerã¯ãéåžžã®è£å©çãªå
éšã¯ã©ã¹ã§ããããŠãŒã¶ãŒãTouchScreenã«ã¢ã¯ã»ã¹ããåŸã«å®è¡ããããäœããã®ã¹ã¯ããŒã«ãè¡ã£ãŠãããšããã¡ãã»ãŒãžãåãåããŸãã
private final class AnimationHandler extends Handler { @Override public void handleMessage(Message msg) { computeScrollOffset(); int currX = getCurrX(); int delta = mLastX - currX; mLastX = currX; if (delta != 0) { mListener.onScroll(-delta); } ⊠} }
ãã¹ãŠãéåžžã«åçŽã§ããæåã«ããã®ã¡ãã»ãŒãžãåãåã£ãæç¹ã§ãã¹ã¯ããŒã©ãŒã«ã¢ãã¡ãŒã·ã§ã³ã®çŸåšäœçœ®ãèšç®ããããã«äŸé ŒããŸãã 次ã«ãæ°ããäœçœ®ãšæåŸã«èªèãããäœçœ®ã®å·®ãèšç®ããŸãã æåŸã«ãã¹ã¯ããŒã«ã¹ã¿ãã¯ã«ã¹ã¯ããŒã«ãå®äºããããšãéç¥ããŸãã onTouchEventïŒïŒã¡ãœããã®æ¬äœããè¡ã£ãããã«ã
ããŠãããã¯å°ããªããšã§ã-ãããã®ã¡ãã»ãŒãžã«å¿ããŠãç»é¢äžã®ã¹ã©ã€ãã®äœçœ®ãå€æŽããå¿
èŠããããŸãã åºçºç¹ã¯ãonScrollïŒïŒã¡ãœããã®å®è£
ã§ãã
@Override public void onScroll(int distance) { if (distance == 0){ return; }
ããã«å®å¿ãããã§ããã¡ãœããå
ã§ã¯ãdoScrollInternalïŒïŒã¡ãœããã®åŒã³åºãã®åŸã«åäžè¡ãç¶ããŸããããã®äžã«ã¯ãå®éã«ã¯DoScrollïŒïŒã¡ãœããã®åŒã³åºããªã©ããããŸãã ãã®å Žåãã¹ã¯ããŒã«ã¹ã¿ãã¯ã ãã§ãªããããšãã°APIãä»ããŠããã°ã©ã ã§ã¹ã©ã€ãã¹ã¿ãã¯ã«ã¹ã¯ããŒã«ã§ããããã«ãã¢ã«ãŽãªãºã ãåå²ããããšã«ããŸããã ããããããã«ã€ããŠã¯åŸã§ã
private void doScroll(int distance) { adjustScrollDistance(distance);
ããã§ã¯ãadjustScrollDistanceïŒïŒã¡ãœããã®è©³çŽ°ã«ã¯è§ŠããŸããã ãŠãŒã¶ãŒãã¹ã©ã€ããéåžžã®äœçœ®ãè¶
ããŠåŒã£åŒµãããšããå Žåã«ãã¹ã©ã€ãã®äœçœ®ã«å¿ããŠè·é¢ã®å€ãå€åãã匟åã®ããå¹æãçã¿åºããšèšããŸãã
次ã«ãçŸåšã®ã¹ã©ã€ããèŠã€ããŸãããŠãŒã¶ãŒããã®åããå®è¡ããããšãããšããã®ã¡ãœãããåŒã³åºãããŸãã ãã®ã¹ã©ã€ããçžå¯Ÿçãªãã¯ã»ã«æ°ã«ç§»åããŸãã ã¹ã©ã€ããç¹å®ã®äœçœ®ã«ç§»åãããããšãã¹ã©ã€ãã¹ã¿ãã¯ãªã¹ããŒã«éç¥ããŸãã
äž»ãªéšåã¯ã»ãŒå®æããŠãããããæ°ããäœçœ®ãèæ
®ããŠã¹ã©ã€ããè¿œå ãŸãã¯åé€ããã ãã§ãããèªåãèŠã€ããæ°ããäœçœ®ã«å¿ããŠã衚瀺ãŸãã¯é衚瀺ãå€æŽããŸãã ãããã©ã®ããã«è¡ããããã«ã€ããŠã®è©³çŽ°ã¯ãèšäºã®æåã®éšåã§èªãããšãã§ããŸãã ããŠããããŠä»æãéèŠãªããš-移åã®ããã«å€æŽãããç»é¢ã®é åãåæç»ããå¿
èŠããããŸãã
ãªã¹ããããã¡ãœããã«ã€ããŠããå°ã説æããŸãã
private void scrollChildBy(View child, int scrollDelta) { SlideInfo info = getSlideInfo(child);
èšäºã®ååã§æ¢ã«è¿°ã¹ãããã«ããã®å Žåã®mDirtyã¯ãå€ãã®äžæãªããžã§ã¯ããäœæããã1ã€ã ãã䜿çšããããšãä¿èšŒããã®ã«åœ¹ç«ã¡ãŸãã ã¡ã¢ãªã®åççãªäœ¿çšã«å ããŠã1ã€ã®ãªããžã§ã¯ãããã®å€æŽãããã³ãã®ãªããžã§ã¯ããžã®ã¢ã¯ã»ã¹ãç£èŠããã ãã§ååã§ããããããããã°ãç°¡çŽ åãããŸãã ããã«ããããšã©ãŒãæ°ä»ãããªãå¯èœæ§ã倧å¹
ã«æžå°ããŸãã
private void notifyScrollPerformed() { if (mScrollListener != null){ final float p = getSlidePositionInBounds(mSelected);
å€ãã®å Žåãåå©çšãå¿
èŠãªã³ã³ããŒãã³ããèšèšãããšããäžéšã®ãŠãŒã¶ãŒãã³ã³ããŒãã³ãå
ã§çºçããã€ãã³ãã«ã€ããŠç¥ãããšãã©ã®ããã«åœ¹ç«ã€ããèããå¿
èŠããããŸãã ãã®ããšãå¿ããªãã§ãã ãããå¯èœã§ããã°ãã¯ã©ã¹ã®ãŠãŒã¶ãŒã«äœããã®ã€ã³ã¿ãŒãã§ã€ã¹ãæäŸããŸããããã䜿çšããŠãå
éšã§äœãèµ·ãã£ãŠãããã«ã€ããŠã®é«ã¬ãã«ã®æ
å ±ãåãåãããšãã§ããŸãã ç§ã®å Žåããããã¯ã¹ã©ã€ãã¹ã¿ãã¯ã®ã¹ã¯ããŒã«ã«é¢é£ãã2ã€ã®äž»ãªã€ãã³ãã§ãã ã€ãã³ãã¯éåžžã«æçœã§ããããªãã¡ïŒ
çŸåšã®ã¹ã©ã€ããã¹ã©ã€ãã¹ã¿ãã¯å
ã§å€æŽãããããšããŠãŒã¶ãŒã«éç¥ããã€ãã³ãããã®ã€ãã³ãã¯ãã¹ã©ã€ããèŠèŠçã«å®éã«ã¢ã¯ãã£ãã«ãªã£ãç¬éã§ã¯ãªããã¹ã¯ããŒã«ãå®å
šã«å®äºããç¬éã«ã®ã¿çºçããããšã«æ³šæããŠãã ããã
ã¹ã©ã€ãã®äœçœ®ãå
ã®å¢çã«å¯ŸããŠå€æŽãããããšããŠãŒã¶ãŒã«éç¥ããã€ãã³ãã ç§ã®å Žåãçžå¯Ÿçãªäœçœ®ã¯å¢ç[0ã1]ãè¶
ããå¯èœæ§ããããããã¯ãŸãã«ãããªããããæå³ããŸããããã«ã€ããŠã¯ããã«è©³ãã説æããŸãã
public interface OnSlideScrollListener{ void onSlideChanged(int selectedSlide); void onSlideScrolled(int position, float p); }
æåŸã«ããã§ã«é »ç¹ã«æºããããŠãã3ã€ã®æ¹æ³ãããããããã¯2å以äžæºããããŸãã ãããã®ç®çã¯æ確ãã€çå®ã§ãããã³ãŒããããã«ã€ããŠããå€ãã®ã³ã¡ã³ãã話ã人ã«ãšã£ãŠã¯ïŒ
private int getLeftEdge(int position){ return mAdapter == null ? 0 :mAdapter.getSlideOffset(position); }
éåžžã«ç°¡åãªæ¹æ³ã éããŠããã¹ã©ã€ãã®å·Šã®å¢çç·ãæ§æã§ããããã«ããããã«ã®ã¿å¿
èŠã§ãã ã¢ããã¿ãŒããã¹ã©ã€ããååŸãããããæ°ããã¢ããã¿ãŒãèšå®ããããšã§ããŠãŒã¶ãŒãã¹ã©ã€ãã¹ã¿ãã¯å
ã§ã¹ã©ã€ããåžæã®äœçœ®ã«é
眮ã§ããããã«ããã®æ
å ±ãããããååŸããããšã«ããŸããã
@Mail.Ru , , . - , , - :
private int getRightEdge(int position) { int rightEdge = getRight() - getRightEdgeDelta(position); return rightEdge; } private int getRightEdgeDelta(int position){ if (position < 0){ return 0; } int delta = mSlideInvisibleOffsetFirst + mSlideInvisibleOffset * position; return delta; }
, , . , . . , - , , .
2 : onScrollPerformed(), adjustScrollDistance().
, - . , , , - . , -, . , , , . , . , - .
bouncing effect. , -, . , , Android- , iOS. , . look&feel. , , , iOS , . , . , , ,
, , - , .
, : , . -. adjustScrollDistance():
private void adjustScrollDistance(int distance) { mScrollDelta = distance; if (mScroller.isJustifying()){ processNonOverScroll(distance); } else if (mScrollDelta < 0 && isRightOverScrolled()){ processOverScroll(distance); } else if (mScrollDelta > 0 && isLeftOverScrolled()){ processOverScroll(distance); } else { processNonOverScroll(distance); } }
SlideStackView, , , , , , . , , .
. , :
private boolean isRightOverScrolled(){ SlideInfo info = getSlideInfo(getSelectedViewIndex()); if (mSelected == mFirst + getChildCount() - 1){ if (info.mOffset > getLeftEdge(info.mPosition)){ return true; } } int left = info.mOffset + getLeftEdge(mSelected); if (left > getRightEdge(mSelected)){ return true; } return false; }
, , :
private boolean isLeftOverScrolled(){ View selected = getChild(getSelectedViewIndex()); SlideInfo info = getSlideInfo(selected); return selected.getRight() + info.mOffset < getRightEdge(mSelected - 1); }
, . , , , () . , , (. getRightEdge()).
, , :
private void processOverScroll(int distance) {
, touchScrolling, , - . , : - , . , . , mOverscrollFactor . . , , . , overscroll factor = 5, , distance = 1. . . , , . , . , 5 , , , 1 . , , .
mOverScrollOffset â , . mLastOverScrollOffset â , . , , .
: , , mOverScrollOffset mLastOverScrollOffset. , ( ), . , . :
private void processNonOverScroll(int distance) { mScrollDelta = distance; if (isOverScrolled()){ mLastOverScrollOffset += distance; mOverScrollOffset = (int) (mLastOverScrollOffset * mOverScrollFactor); } else { mLastOverScrollOffset = 0; mOverScrollOffset = 0; } }
: mOverScrollOffset mLastOverScrollOffset , , , , . â .
: , .
, . , ( ) . , , , , Scroller. , :
S=V
0 *t-(g*t
2 )/2 ( )
, , . 3 :
S=V
0 *t-(g*t
2 )/2
,
S2=V
k *t2- ((g*p)*ãt2ã
2 )/2
, , p .
S3=V
k2 *t3- ((g*p*p)*ãt3ã
2 )/2
: , .
, .
mScroller.setDecelerationFactor(mDecelerationFactor);
, . , , :
public void setDecelerationFactor(float factor){ mVelocity = mVelocity - mDeceleration * mPassed / 1000.0f; float velocity = mVelocity; mDeceleration *= factor; mDuration = (int) (1000.0f * mVelocity / mDeceleration); int startX = mStartX = mCurrX; int startY = mStartY = mCurrY; int totalDistance = (int) ((velocity * velocity) / (2.0f * mDeceleration)); mFinalX = startX + Math.round(totalDistance * mCoeffX);
, , , ,
hide :
public float getCurrVelocity() { return mVelocity - mDeceleration * timePassed() / 2000.0f; }
, v(t)=v
0 +at (1000 â ). - ,
, .
, event' , . , . , , , . Android Framework , . , . , ViewGroup.dispatchTouchEvent().
. , , . , , :
- child, touchEvent.
- child , event, . , event ViewGroup.
- MotionEvent'a ViewGroup . MotionEvent ViewGroup, child'.
, , View .
, , . , . , event' , : .
. :
@Override public boolean onInterceptTouchEvent(MotionEvent ev) {
, , , \\ .
, :
final int action = MotionEventCompat.getActionMasked(ev); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP){ mBeingDrag = false; mUnableToDrag = false; mScroller.justify();
(, MotionEvent), event' , .
, , , event .
switch (action){ case MotionEvent.ACTION_DOWN:{ mInitialX = ev.getX(); mInitialY = ev.getY(); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); mScroller.setActivePointer(mActivePointerId); if (mScroller.isScrolling() || isHiddenSlideMove(false)){ mBeingDrag = true; mUnableToDrag = false; } else { mBeingDrag = false; mUnableToDrag = false; }
event , . . , MotionEvent.ACTION_DOWN. event' , . , , event' child' . , isHiddenSlideMove(). , event' «», , , . , .
, :
private boolean isHiddenSlideMove(boolean extend) { int x = (int) mInitialX; int y = (int) mInitialY; Rect rightSide = new Rect(); boolean right = false; for (int i = getLastHiddenSlideIndex(); i >= 0 && !right; i--) { View view = getChild(i); Rect rect = new Rect(); view.getHitRect(rect); rightSide.union(rect); if (rightSide.contains(x, y) || (extend && rightSide.contains(x + mTouchExtension, y))) { right = true; } } return right; }
â :
case MotionEvent.ACTION_MOVE:{ final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER_ID) {
. . -, , , , , ( ). , , . ,
. , . . , , 22,5 , .

, , â . . , .
. , , , ( ), , . , Google (. ViewPager):
if (dx != 0 && canScroll(this, false, (int) dx, (int) x, (int) y)) {
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { if (v instanceof ViewGroup) { final ViewGroup group = (ViewGroup) v; final int scrollX = v.getScrollX(); final int scrollY = v.getScrollY(); final int count = group.getChildCount();
View, «», , , ViewCompat.canScrollHorizontally(). , , . ViewCompat, .
public class ViewCompat { public static boolean canScrollHorizontally(View v, int direction){ if (v instanceof QuickActionView){ return ((QuickActionView)v).canScrollHorizontally(direction); } else { return android.support.v4.view.ViewCompat.canScrollHorizontally(v, direction); } } } }
. .
@Override public boolean onTouchEvent(MotionEvent event) { LOG.i("onTouchEvent: " + event); if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
event' , . , , , View.
@Override public boolean dispatchTouchEvent(MotionEvent ev) {
. - , .
, , , MotionEvent'.
â . dispatchTouchEvent(), onInterceptTouchEvent() onTouchEvent(), , event. , , . developer.android.com , event'.
, . , , .
:)