22 package com.freerdp.freerdpcore.presentation;
24 import android.content.Context;
25 import android.graphics.Rect;
26 import android.util.AttributeSet;
27 import android.view.FocusFinder;
28 import android.view.KeyEvent;
29 import android.view.MotionEvent;
30 import android.view.VelocityTracker;
31 import android.view.View;
32 import android.view.ViewConfiguration;
33 import android.view.ViewGroup;
34 import android.view.ViewParent;
35 import android.view.animation.AnimationUtils;
36 import android.widget.FrameLayout;
37 import android.widget.LinearLayout;
38 import android.widget.Scroller;
39 import android.widget.TextView;
41 import java.util.List;
60 static final int ANIMATED_SCROLL_GAP = 250;
61 static final float MAX_SCROLL_FACTOR = 0.5f;
62 private final Rect mTempRect =
new Rect();
64 private long mLastScroll;
65 private Scroller mScroller;
66 private boolean scrollEnabled =
true;
72 private boolean mTwoDScrollViewMovedFocus;
76 private float mLastMotionY;
77 private float mLastMotionX;
82 private boolean mIsLayoutDirty =
true;
88 private View mChildToScrollTo =
null;
94 private boolean mIsBeingDragged =
false;
98 private VelocityTracker mVelocityTracker;
102 private int mTouchSlop;
103 private int mMinimumVelocity;
104 private int mMaximumVelocity;
108 initTwoDScrollView();
111 public ScrollView2D(Context context, AttributeSet attrs)
113 super(context, attrs);
114 initTwoDScrollView();
117 public ScrollView2D(Context context, AttributeSet attrs,
int defStyle)
119 super(context, attrs, defStyle);
120 initTwoDScrollView();
123 @Override
protected float getTopFadingEdgeStrength()
125 if (getChildCount() == 0)
129 final int length = getVerticalFadingEdgeLength();
130 if (getScrollY() < length)
132 return getScrollY() / (float)length;
137 @Override
protected float getBottomFadingEdgeStrength()
139 if (getChildCount() == 0)
143 final int length = getVerticalFadingEdgeLength();
144 final int bottomEdge = getHeight() - getPaddingBottom();
145 final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
148 return span / (float)length;
153 @Override
protected float getLeftFadingEdgeStrength()
155 if (getChildCount() == 0)
159 final int length = getHorizontalFadingEdgeLength();
160 if (getScrollX() < length)
162 return getScrollX() / (float)length;
167 @Override
protected float getRightFadingEdgeStrength()
169 if (getChildCount() == 0)
173 final int length = getHorizontalFadingEdgeLength();
174 final int rightEdge = getWidth() - getPaddingRight();
175 final int span = getChildAt(0).getRight() - getScrollX() - rightEdge;
178 return span / (float)length;
188 scrollEnabled = enable;
197 return (
int)(MAX_SCROLL_FACTOR * getHeight());
200 public int getMaxScrollAmountHorizontal()
202 return (
int)(MAX_SCROLL_FACTOR * getWidth());
205 private void initTwoDScrollView()
207 mScroller =
new Scroller(getContext());
209 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
210 setWillNotDraw(
false);
211 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
212 mTouchSlop = configuration.getScaledTouchSlop();
213 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
214 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
217 @Override
public void addView(View child)
219 if (getChildCount() > 0)
221 throw new IllegalStateException(
"TwoDScrollView can host only one direct child");
223 super.addView(child);
226 @Override
public void addView(View child,
int index)
228 if (getChildCount() > 0)
230 throw new IllegalStateException(
"TwoDScrollView can host only one direct child");
232 super.addView(child, index);
235 @Override
public void addView(View child, ViewGroup.LayoutParams params)
237 if (getChildCount() > 0)
239 throw new IllegalStateException(
"TwoDScrollView can host only one direct child");
241 super.addView(child, params);
244 @Override
public void addView(View child,
int index, ViewGroup.LayoutParams params)
246 if (getChildCount() > 0)
248 throw new IllegalStateException(
"TwoDScrollView can host only one direct child");
250 super.addView(child, index, params);
256 private boolean canScroll()
260 View child = getChildAt(0);
263 int childHeight = child.getHeight();
264 int childWidth = child.getWidth();
265 return (getHeight() < childHeight + getPaddingTop() + getPaddingBottom()) ||
266 (getWidth() < childWidth + getPaddingLeft() + getPaddingRight());
271 @Override
public boolean dispatchKeyEvent(KeyEvent event)
274 boolean handled = super.dispatchKeyEvent(event);
292 mTempRect.setEmpty();
297 View currentFocused = findFocus();
298 if (currentFocused ==
this)
299 currentFocused =
null;
301 FocusFinder.getInstance().findNextFocus(
this, currentFocused, View.FOCUS_DOWN);
302 return nextFocused !=
null && nextFocused !=
this &&
303 nextFocused.requestFocus(View.FOCUS_DOWN);
307 boolean handled =
false;
308 if (event.getAction() == KeyEvent.ACTION_DOWN)
310 switch (event.getKeyCode())
312 case KeyEvent.KEYCODE_DPAD_UP:
313 if (!event.isAltPressed())
322 case KeyEvent.KEYCODE_DPAD_DOWN:
323 if (!event.isAltPressed())
332 case KeyEvent.KEYCODE_DPAD_LEFT:
333 if (!event.isAltPressed())
342 case KeyEvent.KEYCODE_DPAD_RIGHT:
343 if (!event.isAltPressed())
357 @Override
public boolean onInterceptTouchEvent(MotionEvent ev)
368 final int action = ev.getAction();
369 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged))
375 mIsBeingDragged =
false;
378 final float y = ev.getY();
379 final float x = ev.getX();
382 case MotionEvent.ACTION_MOVE:
391 final int yDiff = (int)Math.abs(y - mLastMotionY);
392 final int xDiff = (int)Math.abs(x - mLastMotionX);
393 if (yDiff > mTouchSlop || xDiff > mTouchSlop)
395 mIsBeingDragged =
true;
399 case MotionEvent.ACTION_DOWN:
409 mIsBeingDragged = !mScroller.isFinished();
412 case MotionEvent.ACTION_CANCEL:
413 case MotionEvent.ACTION_UP:
415 mIsBeingDragged =
false;
423 return mIsBeingDragged;
426 @Override
public boolean onTouchEvent(MotionEvent ev)
429 if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0)
441 if (mVelocityTracker ==
null)
443 mVelocityTracker = VelocityTracker.obtain();
445 mVelocityTracker.addMovement(ev);
447 final int action = ev.getAction();
448 final float y = ev.getY();
449 final float x = ev.getX();
453 case MotionEvent.ACTION_DOWN:
458 if (!mScroller.isFinished())
460 mScroller.abortAnimation();
467 case MotionEvent.ACTION_MOVE:
469 int deltaX = (int)(mLastMotionX - x);
470 int deltaY = (int)(mLastMotionY - y);
476 if (getScrollX() < 0)
483 final int rightEdge = getWidth() - getPaddingRight();
484 final int availableToScroll =
485 getChildAt(0).getRight() - getScrollX() - rightEdge;
486 if (availableToScroll > 0)
488 deltaX = Math.min(availableToScroll, deltaX);
497 if (getScrollY() < 0)
504 final int bottomEdge = getHeight() - getPaddingBottom();
505 final int availableToScroll =
506 getChildAt(0).getBottom() - getScrollY() - bottomEdge;
507 if (availableToScroll > 0)
509 deltaY = Math.min(availableToScroll, deltaY);
516 if (deltaY != 0 || deltaX != 0)
517 scrollBy(deltaX, deltaY);
519 case MotionEvent.ACTION_UP:
520 final VelocityTracker velocityTracker = mVelocityTracker;
521 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
522 int initialXVelocity = (int)velocityTracker.getXVelocity();
523 int initialYVelocity = (int)velocityTracker.getYVelocity();
524 if ((Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) &&
527 fling(-initialXVelocity, -initialYVelocity);
529 if (mVelocityTracker !=
null)
531 mVelocityTracker.recycle();
532 mVelocityTracker =
null;
553 private View findFocusableViewInMyBounds(
final boolean topFocus,
final int top,
554 final boolean leftFocus,
final int left,
555 View preferredFocusable)
562 final int verticalFadingEdgeLength = getVerticalFadingEdgeLength() / 2;
563 final int topWithoutFadingEdge = top + verticalFadingEdgeLength;
564 final int bottomWithoutFadingEdge = top + getHeight() - verticalFadingEdgeLength;
565 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
566 final int leftWithoutFadingEdge = left + horizontalFadingEdgeLength;
567 final int rightWithoutFadingEdge = left + getWidth() - horizontalFadingEdgeLength;
569 if ((preferredFocusable !=
null) &&
570 (preferredFocusable.getTop() < bottomWithoutFadingEdge) &&
571 (preferredFocusable.getBottom() > topWithoutFadingEdge) &&
572 (preferredFocusable.getLeft() < rightWithoutFadingEdge) &&
573 (preferredFocusable.getRight() > leftWithoutFadingEdge))
575 return preferredFocusable;
577 return findFocusableViewInBounds(topFocus, topWithoutFadingEdge, bottomWithoutFadingEdge,
578 leftFocus, leftWithoutFadingEdge, rightWithoutFadingEdge);
595 private View findFocusableViewInBounds(
boolean topFocus,
int top,
int bottom,
boolean leftFocus,
598 List<View> focusables = getFocusables(View.FOCUS_FORWARD);
599 View focusCandidate =
null;
608 boolean foundFullyContainedFocusable =
false;
610 int count = focusables.size();
611 for (
int i = 0; i < count; i++)
613 View view = focusables.get(i);
614 int viewTop = view.getTop();
615 int viewBottom = view.getBottom();
616 int viewLeft = view.getLeft();
617 int viewRight = view.getRight();
619 if (top < viewBottom && viewTop < bottom && left < viewRight && viewLeft < right)
625 final boolean viewIsFullyContained = (top < viewTop) && (viewBottom < bottom) &&
626 (left < viewLeft) && (viewRight < right);
627 if (focusCandidate ==
null)
630 focusCandidate = view;
631 foundFullyContainedFocusable = viewIsFullyContained;
635 final boolean viewIsCloserToVerticalBoundary =
636 (topFocus && viewTop < focusCandidate.getTop()) ||
637 (!topFocus && viewBottom > focusCandidate.getBottom());
638 final boolean viewIsCloserToHorizontalBoundary =
639 (leftFocus && viewLeft < focusCandidate.getLeft()) ||
640 (!leftFocus && viewRight > focusCandidate.getRight());
641 if (foundFullyContainedFocusable)
643 if (viewIsFullyContained && viewIsCloserToVerticalBoundary &&
644 viewIsCloserToHorizontalBoundary)
651 focusCandidate = view;
656 if (viewIsFullyContained)
659 focusCandidate = view;
660 foundFullyContainedFocusable =
true;
662 else if (viewIsCloserToVerticalBoundary && viewIsCloserToHorizontalBoundary)
668 focusCandidate = view;
674 return focusCandidate;
693 boolean down = direction == View.FOCUS_DOWN;
694 int height = getHeight();
696 mTempRect.bottom = height;
699 int count = getChildCount();
702 View view = getChildAt(count - 1);
703 mTempRect.bottom = view.getBottom();
704 mTempRect.top = mTempRect.bottom - height;
707 return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom, 0, 0, 0);
711 boolean right = direction == View.FOCUS_DOWN;
712 int width = getWidth();
714 mTempRect.right = width;
717 int count = getChildCount();
720 View view = getChildAt(count - 1);
721 mTempRect.right = view.getBottom();
722 mTempRect.left = mTempRect.right - width;
725 return scrollAndFocus(0, 0, 0, direction, mTempRect.top, mTempRect.bottom);
742 private boolean scrollAndFocus(
int directionY,
int top,
int bottom,
int directionX,
int left,
745 boolean handled =
true;
746 int height = getHeight();
747 int containerTop = getScrollY();
748 int containerBottom = containerTop + height;
749 boolean up = directionY == View.FOCUS_UP;
750 int width = getWidth();
751 int containerLeft = getScrollX();
752 int containerRight = containerLeft + width;
753 boolean leftwards = directionX == View.FOCUS_UP;
754 View newFocused = findFocusableViewInBounds(up, top, bottom, leftwards, left, right);
755 if (newFocused ==
null)
759 if ((top >= containerTop && bottom <= containerBottom) ||
760 (left >= containerLeft && right <= containerRight))
766 int deltaY = up ? (top - containerTop) : (bottom - containerBottom);
767 int deltaX = leftwards ? (left - containerLeft) : (right - containerRight);
768 doScroll(deltaX, deltaY);
770 if (newFocused != findFocus() && newFocused.requestFocus(directionY))
772 mTwoDScrollViewMovedFocus =
true;
773 mTwoDScrollViewMovedFocus =
false;
787 View currentFocused = findFocus();
788 if (currentFocused ==
this)
789 currentFocused =
null;
790 View nextFocused = FocusFinder.getInstance().findNextFocus(
this, currentFocused, direction);
796 if (nextFocused !=
null)
798 nextFocused.getDrawingRect(mTempRect);
799 offsetDescendantRectToMyCoords(nextFocused, mTempRect);
801 doScroll(0, scrollDelta);
802 nextFocused.requestFocus(direction);
807 int scrollDelta = maxJump;
808 if (direction == View.FOCUS_UP && getScrollY() < scrollDelta)
810 scrollDelta = getScrollY();
812 else if (direction == View.FOCUS_DOWN)
814 if (getChildCount() > 0)
816 int daBottom = getChildAt(0).getBottom();
817 int screenBottom = getScrollY() + getHeight();
818 if (daBottom - screenBottom < maxJump)
820 scrollDelta = daBottom - screenBottom;
824 if (scrollDelta == 0)
828 doScroll(0, direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
833 if (nextFocused !=
null)
835 nextFocused.getDrawingRect(mTempRect);
836 offsetDescendantRectToMyCoords(nextFocused, mTempRect);
838 doScroll(scrollDelta, 0);
839 nextFocused.requestFocus(direction);
844 int scrollDelta = maxJump;
845 if (direction == View.FOCUS_UP && getScrollY() < scrollDelta)
847 scrollDelta = getScrollY();
849 else if (direction == View.FOCUS_DOWN)
851 if (getChildCount() > 0)
853 int daBottom = getChildAt(0).getBottom();
854 int screenBottom = getScrollY() + getHeight();
855 if (daBottom - screenBottom < maxJump)
857 scrollDelta = daBottom - screenBottom;
861 if (scrollDelta == 0)
865 doScroll(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta, 0);
876 private void doScroll(
int deltaX,
int deltaY)
878 if (deltaX != 0 || deltaY != 0)
892 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
893 if (duration > ANIMATED_SCROLL_GAP)
895 mScroller.startScroll(getScrollX(), getScrollY(), dx, dy);
896 awakenScrollBars(mScroller.getDuration());
901 if (!mScroller.isFinished())
903 mScroller.abortAnimation();
907 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
927 int count = getChildCount();
928 return count == 0 ? getHeight() : (getChildAt(0)).getBottom();
931 @Override
protected int computeHorizontalScrollRange()
933 int count = getChildCount();
934 return count == 0 ? getWidth() : (getChildAt(0)).getRight();
938 protected void measureChild(View child,
int parentWidthMeasureSpec,
int parentHeightMeasureSpec)
940 ViewGroup.LayoutParams lp = child.getLayoutParams();
941 int childWidthMeasureSpec;
942 int childHeightMeasureSpec;
944 childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
945 getPaddingLeft() + getPaddingRight(), lp.width);
946 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
948 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
952 protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec,
int widthUsed,
953 int parentHeightMeasureSpec,
int heightUsed)
955 final MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
956 final int childWidthMeasureSpec =
957 MeasureSpec.makeMeasureSpec(lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
958 final int childHeightMeasureSpec =
959 MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
961 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
964 @Override
public void computeScroll()
966 if (mScroller.computeScrollOffset())
984 int oldX = getScrollX();
985 int oldY = getScrollY();
986 int x = mScroller.getCurrX();
987 int y = mScroller.getCurrY();
988 if (getChildCount() > 0)
990 View child = getChildAt(0);
992 clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()),
993 clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(),
1000 if (oldX != getScrollX() || oldY != getScrollY())
1002 onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
1015 private void scrollToChild(View child)
1017 child.getDrawingRect(mTempRect);
1019 offsetDescendantRectToMyCoords(child, mTempRect);
1021 if (scrollDelta != 0)
1023 scrollBy(0, scrollDelta);
1035 private boolean scrollToChildRect(Rect rect,
boolean immediate)
1038 final boolean scroll = delta != 0;
1063 if (getChildCount() == 0)
1065 int height = getHeight();
1066 int screenTop = getScrollY();
1067 int screenBottom = screenTop + height;
1068 int fadingEdge = getVerticalFadingEdgeLength();
1072 screenTop += fadingEdge;
1076 if (rect.bottom < getChildAt(0).getHeight())
1078 screenBottom -= fadingEdge;
1080 int scrollYDelta = 0;
1081 if (rect.bottom > screenBottom && rect.top > screenTop)
1086 if (rect.height() > height)
1089 scrollYDelta += (rect.top - screenTop);
1094 scrollYDelta += (rect.bottom - screenBottom);
1098 int bottom = getChildAt(0).getBottom();
1099 int distanceToBottom = bottom - screenBottom;
1100 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
1102 else if (rect.top < screenTop && rect.bottom < screenBottom)
1108 if (rect.height() > height)
1111 scrollYDelta -= (screenBottom - rect.bottom);
1116 scrollYDelta -= (screenTop - rect.top);
1120 scrollYDelta = Math.max(scrollYDelta, -getScrollY());
1122 return scrollYDelta;
1125 @Override
public void requestChildFocus(View child, View focused)
1127 if (!mTwoDScrollViewMovedFocus)
1129 if (!mIsLayoutDirty)
1131 scrollToChild(focused);
1136 mChildToScrollTo = focused;
1139 super.requestChildFocus(child, focused);
1154 if (direction == View.FOCUS_FORWARD)
1156 direction = View.FOCUS_DOWN;
1158 else if (direction == View.FOCUS_BACKWARD)
1160 direction = View.FOCUS_UP;
1163 final View nextFocus = previouslyFocusedRect ==
null
1164 ? FocusFinder.getInstance().findNextFocus(
this,
null, direction)
1165 : FocusFinder.getInstance().findNextFocusFromRect(
1166 this, previouslyFocusedRect, direction);
1168 if (nextFocus ==
null)
1173 return nextFocus.requestFocus(direction, previouslyFocusedRect);
1177 public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
boolean immediate)
1180 rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY());
1181 return scrollToChildRect(rectangle, immediate);
1184 @Override
public void requestLayout()
1186 mIsLayoutDirty =
true;
1187 super.requestLayout();
1190 @Override
protected void onLayout(
boolean changed,
int l,
int t,
int r,
int b)
1192 super.onLayout(changed, l, t, r, b);
1193 mIsLayoutDirty =
false;
1195 if (mChildToScrollTo !=
null && isViewDescendantOf(mChildToScrollTo,
this))
1197 scrollToChild(mChildToScrollTo);
1199 mChildToScrollTo =
null;
1202 scrollTo(getScrollX(), getScrollY());
1205 @Override
protected void onSizeChanged(
int w,
int h,
int oldw,
int oldh)
1207 super.onSizeChanged(w, h, oldw, oldh);
1209 View currentFocused = findFocus();
1210 if (
null == currentFocused ||
this == currentFocused)
1216 currentFocused.getDrawingRect(mTempRect);
1217 offsetDescendantRectToMyCoords(currentFocused, mTempRect);
1220 doScroll(scrollDeltaX, scrollDeltaY);
1226 private boolean isViewDescendantOf(View child, View parent)
1228 if (child == parent)
1233 final ViewParent theParent = child.getParent();
1234 return (theParent instanceof ViewGroup) && isViewDescendantOf((View)theParent, parent);
1244 public void fling(
int velocityX,
int velocityY)
1246 if (getChildCount() > 0)
1248 int height = getHeight() - getPaddingBottom() - getPaddingTop();
1249 int bottom = getChildAt(0).getHeight();
1250 int width = getWidth() - getPaddingRight() - getPaddingLeft();
1251 int right = getChildAt(0).getWidth();
1253 mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, 0, right - width, 0,
1256 final boolean movingDown = velocityY > 0;
1257 final boolean movingRight = velocityX > 0;
1259 View newFocused = findFocusableViewInMyBounds(
1260 movingRight, mScroller.getFinalX(), movingDown, mScroller.getFinalY(), findFocus());
1261 if (newFocused ==
null)
1266 if (newFocused != findFocus() &&
1267 newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP))
1269 mTwoDScrollViewMovedFocus =
true;
1270 mTwoDScrollViewMovedFocus =
false;
1273 awakenScrollBars(mScroller.getDuration());
1286 if (getChildCount() > 0)
1288 View child = getChildAt(0);
1289 x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
1290 y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
1291 if (x != getScrollX() || y != getScrollY())
1293 super.scrollTo(x, y);
1298 private int clamp(
int n,
int my,
int child)
1300 if (my >= child || n < 0)
1319 if ((my + n) > child)
1331 public void setScrollViewListener(ScrollView2DListener scrollViewListener)
1333 this.scrollView2DListener = scrollViewListener;
1336 @Override
protected void onScrollChanged(
int x,
int y,
int oldx,
int oldy)
1338 super.onScrollChanged(x, y, oldx, oldy);
1339 if (scrollView2DListener !=
null)
1341 scrollView2DListener.onScrollChanged(
this, x, y, oldx, oldy);
1347 abstract void onScrollChanged(
ScrollView2D scrollView,
int x,
int y,
int oldx,
int oldy);