Home | History | Annotate | Download | only in launcher3
      1 package com.android.launcher3;
      2 
      3 import android.animation.AnimatorSet;
      4 import android.animation.ObjectAnimator;
      5 import android.animation.PropertyValuesHolder;
      6 import android.animation.ValueAnimator;
      7 import android.animation.ValueAnimator.AnimatorUpdateListener;
      8 import android.appwidget.AppWidgetHostView;
      9 import android.appwidget.AppWidgetProviderInfo;
     10 import android.content.Context;
     11 import android.graphics.Point;
     12 import android.graphics.Rect;
     13 import android.util.AttributeSet;
     14 import android.view.KeyEvent;
     15 import android.view.MotionEvent;
     16 import android.view.View;
     17 import android.view.ViewGroup;
     18 
     19 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
     20 import com.android.launcher3.dragndrop.DragLayer;
     21 import com.android.launcher3.util.FocusLogic;
     22 import com.android.launcher3.widget.LauncherAppWidgetHostView;
     23 
     24 public class AppWidgetResizeFrame extends AbstractFloatingView implements View.OnKeyListener {
     25     private static final int SNAP_DURATION = 150;
     26     private static final float DIMMED_HANDLE_ALPHA = 0f;
     27     private static final float RESIZE_THRESHOLD = 0.66f;
     28 
     29     private static final Rect sTmpRect = new Rect();
     30 
     31     // Represents the cell size on the grid in the two orientations.
     32     private static Point[] sCellSize;
     33 
     34     private static final int HANDLE_COUNT = 4;
     35     private static final int INDEX_LEFT = 0;
     36     private static final int INDEX_TOP = 1;
     37     private static final int INDEX_RIGHT = 2;
     38     private static final int INDEX_BOTTOM = 3;
     39 
     40     private final Launcher mLauncher;
     41     private final DragViewStateAnnouncer mStateAnnouncer;
     42 
     43     private final View[] mDragHandles = new View[HANDLE_COUNT];
     44 
     45     private LauncherAppWidgetHostView mWidgetView;
     46     private CellLayout mCellLayout;
     47     private DragLayer mDragLayer;
     48 
     49     private Rect mWidgetPadding;
     50 
     51     private final int mBackgroundPadding;
     52     private final int mTouchTargetWidth;
     53 
     54     private final int[] mDirectionVector = new int[2];
     55     private final int[] mLastDirectionVector = new int[2];
     56 
     57     private final IntRange mTempRange1 = new IntRange();
     58     private final IntRange mTempRange2 = new IntRange();
     59 
     60     private final IntRange mDeltaXRange = new IntRange();
     61     private final IntRange mBaselineX = new IntRange();
     62 
     63     private final IntRange mDeltaYRange = new IntRange();
     64     private final IntRange mBaselineY = new IntRange();
     65 
     66     private boolean mLeftBorderActive;
     67     private boolean mRightBorderActive;
     68     private boolean mTopBorderActive;
     69     private boolean mBottomBorderActive;
     70 
     71     private int mResizeMode;
     72 
     73     private int mRunningHInc;
     74     private int mRunningVInc;
     75     private int mMinHSpan;
     76     private int mMinVSpan;
     77     private int mDeltaX;
     78     private int mDeltaY;
     79     private int mDeltaXAddOn;
     80     private int mDeltaYAddOn;
     81 
     82     private int mTopTouchRegionAdjustment = 0;
     83     private int mBottomTouchRegionAdjustment = 0;
     84 
     85     private int mXDown, mYDown;
     86 
     87     public AppWidgetResizeFrame(Context context) {
     88         this(context, null);
     89     }
     90 
     91     public AppWidgetResizeFrame(Context context, AttributeSet attrs) {
     92         this(context, attrs, 0);
     93     }
     94 
     95     public AppWidgetResizeFrame(Context context, AttributeSet attrs, int defStyleAttr) {
     96         super(context, attrs, defStyleAttr);
     97 
     98         mLauncher = Launcher.getLauncher(context);
     99         mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
    100 
    101         mBackgroundPadding = getResources()
    102                 .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
    103         mTouchTargetWidth = 2 * mBackgroundPadding;
    104     }
    105 
    106     @Override
    107     protected void onFinishInflate() {
    108         super.onFinishInflate();
    109 
    110         ViewGroup content = (ViewGroup) getChildAt(0);
    111         for (int i = 0; i < HANDLE_COUNT; i ++) {
    112             mDragHandles[i] = content.getChildAt(i);
    113         }
    114     }
    115 
    116     public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
    117         Launcher launcher = Launcher.getLauncher(cellLayout.getContext());
    118         AbstractFloatingView.closeAllOpenViews(launcher);
    119 
    120         DragLayer dl = launcher.getDragLayer();
    121         AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
    122                 .inflate(R.layout.app_widget_resize_frame, dl, false);
    123         frame.setupForWidget(widget, cellLayout, dl);
    124         ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
    125 
    126         dl.addView(frame);
    127         frame.mIsOpen = true;
    128         frame.snapToWidget(false);
    129     }
    130 
    131     private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
    132             DragLayer dragLayer) {
    133         mCellLayout = cellLayout;
    134         mWidgetView = widgetView;
    135         LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
    136                 widgetView.getAppWidgetInfo();
    137         mResizeMode = info.resizeMode;
    138         mDragLayer = dragLayer;
    139 
    140         mMinHSpan = info.minSpanX;
    141         mMinVSpan = info.minSpanY;
    142 
    143         mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
    144                 widgetView.getAppWidgetInfo().provider, null);
    145 
    146         if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
    147             mDragHandles[INDEX_TOP].setVisibility(GONE);
    148             mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
    149         } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
    150             mDragHandles[INDEX_LEFT].setVisibility(GONE);
    151             mDragHandles[INDEX_RIGHT].setVisibility(GONE);
    152         }
    153 
    154         // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
    155         // cells (same if not resized, or different) will be marked as occupied when the resize
    156         // frame is dismissed.
    157         mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
    158 
    159         setOnKeyListener(this);
    160     }
    161 
    162     public boolean beginResizeIfPointInRegion(int x, int y) {
    163         boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
    164         boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
    165 
    166         mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
    167         mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
    168         mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
    169         mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
    170                 && verticalActive;
    171 
    172         boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
    173                 || mTopBorderActive || mBottomBorderActive;
    174 
    175         if (anyBordersActive) {
    176             mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
    177             mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
    178             mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
    179             mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
    180         }
    181 
    182         if (mLeftBorderActive) {
    183             mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
    184         } else if (mRightBorderActive) {
    185             mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
    186         } else {
    187             mDeltaXRange.set(0, 0);
    188         }
    189         mBaselineX.set(getLeft(), getRight());
    190 
    191         if (mTopBorderActive) {
    192             mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
    193         } else if (mBottomBorderActive) {
    194             mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
    195         } else {
    196             mDeltaYRange.set(0, 0);
    197         }
    198         mBaselineY.set(getTop(), getBottom());
    199 
    200         return anyBordersActive;
    201     }
    202 
    203     /**
    204      *  Based on the deltas, we resize the frame.
    205      */
    206     public void visualizeResizeForDelta(int deltaX, int deltaY) {
    207         mDeltaX = mDeltaXRange.clamp(deltaX);
    208         mDeltaY = mDeltaYRange.clamp(deltaY);
    209 
    210         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    211         mDeltaX = mDeltaXRange.clamp(deltaX);
    212         mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
    213         lp.x = mTempRange1.start;
    214         lp.width = mTempRange1.size();
    215 
    216         mDeltaY = mDeltaYRange.clamp(deltaY);
    217         mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
    218         lp.y = mTempRange1.start;
    219         lp.height = mTempRange1.size();
    220 
    221         resizeWidgetIfNeeded(false);
    222 
    223         // When the widget resizes in multi-window mode, the translation value changes to maintain
    224         // a center fit. These overrides ensure the resize frame always aligns with the widget view.
    225         getSnappedRectRelativeToDragLayer(sTmpRect);
    226         if (mLeftBorderActive) {
    227             lp.width = sTmpRect.width() + sTmpRect.left - lp.x;
    228         }
    229         if (mTopBorderActive) {
    230             lp.height = sTmpRect.height() + sTmpRect.top - lp.y;
    231         }
    232         if (mRightBorderActive) {
    233             lp.x = sTmpRect.left;
    234         }
    235         if (mBottomBorderActive) {
    236             lp.y = sTmpRect.top;
    237         }
    238 
    239         requestLayout();
    240     }
    241 
    242     private static int getSpanIncrement(float deltaFrac) {
    243         return Math.abs(deltaFrac) > RESIZE_THRESHOLD ? Math.round(deltaFrac) : 0;
    244     }
    245 
    246     /**
    247      *  Based on the current deltas, we determine if and how to resize the widget.
    248      */
    249     private void resizeWidgetIfNeeded(boolean onDismiss) {
    250         float xThreshold = mCellLayout.getCellWidth();
    251         float yThreshold = mCellLayout.getCellHeight();
    252 
    253         int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
    254         int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
    255 
    256         if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
    257 
    258         mDirectionVector[0] = 0;
    259         mDirectionVector[1] = 0;
    260 
    261         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
    262 
    263         int spanX = lp.cellHSpan;
    264         int spanY = lp.cellVSpan;
    265         int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
    266         int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
    267 
    268         // For each border, we bound the resizing based on the minimum width, and the maximum
    269         // expandability.
    270         mTempRange1.set(cellX, spanX + cellX);
    271         int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
    272                 hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
    273         cellX = mTempRange2.start;
    274         spanX = mTempRange2.size();
    275         if (hSpanDelta != 0) {
    276             mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
    277         }
    278 
    279         mTempRange1.set(cellY, spanY + cellY);
    280         int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
    281                 vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
    282         cellY = mTempRange2.start;
    283         spanY = mTempRange2.size();
    284         if (vSpanDelta != 0) {
    285             mDirectionVector[1] = mTopBorderActive ? -1 : 1;
    286         }
    287 
    288         if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
    289 
    290         // We always want the final commit to match the feedback, so we make sure to use the
    291         // last used direction vector when committing the resize / reorder.
    292         if (onDismiss) {
    293             mDirectionVector[0] = mLastDirectionVector[0];
    294             mDirectionVector[1] = mLastDirectionVector[1];
    295         } else {
    296             mLastDirectionVector[0] = mDirectionVector[0];
    297             mLastDirectionVector[1] = mDirectionVector[1];
    298         }
    299 
    300         if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
    301                 mDirectionVector, onDismiss)) {
    302             if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY) ) {
    303                 mStateAnnouncer.announce(
    304                         mLauncher.getString(R.string.widget_resized, spanX, spanY));
    305             }
    306 
    307             lp.tmpCellX = cellX;
    308             lp.tmpCellY = cellY;
    309             lp.cellHSpan = spanX;
    310             lp.cellVSpan = spanY;
    311             mRunningVInc += vSpanDelta;
    312             mRunningHInc += hSpanDelta;
    313 
    314             if (!onDismiss) {
    315                 updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
    316             }
    317         }
    318         mWidgetView.requestLayout();
    319     }
    320 
    321     static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
    322             int spanX, int spanY) {
    323         getWidgetSizeRanges(launcher, spanX, spanY, sTmpRect);
    324         widgetView.updateAppWidgetSize(null, sTmpRect.left, sTmpRect.top,
    325                 sTmpRect.right, sTmpRect.bottom);
    326     }
    327 
    328     public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
    329         if (sCellSize == null) {
    330             InvariantDeviceProfile inv = LauncherAppState.getIDP(context);
    331 
    332             // Initiate cell sizes.
    333             sCellSize = new Point[2];
    334             sCellSize[0] = inv.landscapeProfile.getCellSize();
    335             sCellSize[1] = inv.portraitProfile.getCellSize();
    336         }
    337 
    338         if (rect == null) {
    339             rect = new Rect();
    340         }
    341         final float density = context.getResources().getDisplayMetrics().density;
    342 
    343         // Compute landscape size
    344         int landWidth = (int) ((spanX * sCellSize[0].x) / density);
    345         int landHeight = (int) ((spanY * sCellSize[0].y) / density);
    346 
    347         // Compute portrait size
    348         int portWidth = (int) ((spanX * sCellSize[1].x) / density);
    349         int portHeight = (int) ((spanY * sCellSize[1].y) / density);
    350         rect.set(portWidth, landHeight, landWidth, portHeight);
    351         return rect;
    352     }
    353 
    354     @Override
    355     protected void onDetachedFromWindow() {
    356         super.onDetachedFromWindow();
    357 
    358         // We are done with resizing the widget. Save the widget size & position to LauncherModel
    359         resizeWidgetIfNeeded(true);
    360     }
    361 
    362     private void onTouchUp() {
    363         int xThreshold = mCellLayout.getCellWidth();
    364         int yThreshold = mCellLayout.getCellHeight();
    365 
    366         mDeltaXAddOn = mRunningHInc * xThreshold;
    367         mDeltaYAddOn = mRunningVInc * yThreshold;
    368         mDeltaX = 0;
    369         mDeltaY = 0;
    370 
    371         post(new Runnable() {
    372             @Override
    373             public void run() {
    374                 snapToWidget(true);
    375             }
    376         });
    377     }
    378 
    379     /**
    380      * Returns the rect of this view when the frame is snapped around the widget, with the bounds
    381      * relative to the {@link DragLayer}.
    382      */
    383     private void getSnappedRectRelativeToDragLayer(Rect out) {
    384         float scale = mWidgetView.getScaleToFit();
    385 
    386         mDragLayer.getViewRectRelativeToSelf(mWidgetView, out);
    387 
    388         int width = 2 * mBackgroundPadding
    389                 + (int) (scale * (out.width() - mWidgetPadding.left - mWidgetPadding.right));
    390         int height = 2 * mBackgroundPadding
    391                 + (int) (scale * (out.height() - mWidgetPadding.top - mWidgetPadding.bottom));
    392 
    393         int x = (int) (out.left - mBackgroundPadding + scale * mWidgetPadding.left);
    394         int y = (int) (out.top - mBackgroundPadding + scale * mWidgetPadding.top);
    395 
    396         out.left = x;
    397         out.top = y;
    398         out.right = out.left + width;
    399         out.bottom = out.top + height;
    400     }
    401 
    402     private void snapToWidget(boolean animate) {
    403         getSnappedRectRelativeToDragLayer(sTmpRect);
    404         int newWidth = sTmpRect.width();
    405         int newHeight = sTmpRect.height();
    406         int newX = sTmpRect.left;
    407         int newY = sTmpRect.top;
    408 
    409         // We need to make sure the frame's touchable regions lie fully within the bounds of the
    410         // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
    411         // down accordingly to provide a proper touch target.
    412         if (newY < 0) {
    413             // In this case we shift the touch region down to start at the top of the DragLayer
    414             mTopTouchRegionAdjustment = -newY;
    415         } else {
    416             mTopTouchRegionAdjustment = 0;
    417         }
    418         if (newY + newHeight > mDragLayer.getHeight()) {
    419             // In this case we shift the touch region up to end at the bottom of the DragLayer
    420             mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
    421         } else {
    422             mBottomTouchRegionAdjustment = 0;
    423         }
    424 
    425         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    426         if (!animate) {
    427             lp.width = newWidth;
    428             lp.height = newHeight;
    429             lp.x = newX;
    430             lp.y = newY;
    431             for (int i = 0; i < HANDLE_COUNT; i++) {
    432                 mDragHandles[i].setAlpha(1.0f);
    433             }
    434             requestLayout();
    435         } else {
    436             PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
    437             PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
    438                     newHeight);
    439             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
    440             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
    441             ObjectAnimator oa =
    442                     LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
    443             oa.addUpdateListener(new AnimatorUpdateListener() {
    444                 public void onAnimationUpdate(ValueAnimator animation) {
    445                     requestLayout();
    446                 }
    447             });
    448             AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
    449             set.play(oa);
    450             for (int i = 0; i < HANDLE_COUNT; i++) {
    451                 set.play(LauncherAnimUtils.ofFloat(mDragHandles[i], ALPHA, 1.0f));
    452             }
    453 
    454             set.setDuration(SNAP_DURATION);
    455             set.start();
    456         }
    457 
    458         setFocusableInTouchMode(true);
    459         requestFocus();
    460     }
    461 
    462     @Override
    463     public boolean onKey(View v, int keyCode, KeyEvent event) {
    464         // Clear the frame and give focus to the widget host view when a directional key is pressed.
    465         if (FocusLogic.shouldConsume(keyCode)) {
    466             close(false);
    467             mWidgetView.requestFocus();
    468             return true;
    469         }
    470         return false;
    471     }
    472 
    473     private boolean handleTouchDown(MotionEvent ev) {
    474         Rect hitRect = new Rect();
    475         int x = (int) ev.getX();
    476         int y = (int) ev.getY();
    477 
    478         getHitRect(hitRect);
    479         if (hitRect.contains(x, y)) {
    480             if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
    481                 mXDown = x;
    482                 mYDown = y;
    483                 return true;
    484             }
    485         }
    486         return false;
    487     }
    488 
    489     @Override
    490     public boolean onControllerTouchEvent(MotionEvent ev) {
    491         int action = ev.getAction();
    492         int x = (int) ev.getX();
    493         int y = (int) ev.getY();
    494 
    495         switch (action) {
    496             case MotionEvent.ACTION_DOWN:
    497                 return handleTouchDown(ev);
    498             case MotionEvent.ACTION_MOVE:
    499                 visualizeResizeForDelta(x - mXDown, y - mYDown);
    500                 break;
    501             case MotionEvent.ACTION_CANCEL:
    502             case MotionEvent.ACTION_UP:
    503                 visualizeResizeForDelta(x - mXDown, y - mYDown);
    504                 onTouchUp();
    505                 mXDown = mYDown = 0;
    506                 break;
    507         }
    508         return true;
    509     }
    510 
    511     @Override
    512     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
    513         if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
    514             return true;
    515         }
    516         close(false);
    517         return false;
    518     }
    519 
    520     @Override
    521     protected void handleClose(boolean animate) {
    522         mDragLayer.removeView(this);
    523     }
    524 
    525     @Override
    526     public void logActionCommand(int command) {
    527         // TODO: Log this case.
    528     }
    529 
    530     @Override
    531     protected boolean isOfType(int type) {
    532         return (type & TYPE_WIDGET_RESIZE_FRAME) != 0;
    533     }
    534 
    535     /**
    536      * A mutable class for describing the range of two int values.
    537      */
    538     private static class IntRange {
    539 
    540         public int start, end;
    541 
    542         public int clamp(int value) {
    543             return Utilities.boundToRange(value, start, end);
    544         }
    545 
    546         public void set(int s, int e) {
    547             start = s;
    548             end = e;
    549         }
    550 
    551         public int size() {
    552             return end - start;
    553         }
    554 
    555         /**
    556          * Moves either the start or end edge (but never both) by {@param delta} and  sets the
    557          * result in {@param out}
    558          */
    559         public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) {
    560             out.start = moveStart ? start + delta : start;
    561             out.end = moveEnd ? end + delta : end;
    562         }
    563 
    564         /**
    565          * Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)},
    566          * with extra conditions.
    567          * @param minSize minimum size after with the moving edge should not be shifted any further.
    568          *                For eg, if delta = -3 when moving the endEdge brings the size to less than
    569          *                minSize, only delta = -2 will applied
    570          * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
    571          * @return the amount of increase when endEdge was moves and the amount of decrease when
    572          * the start edge was moved.
    573          */
    574         public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
    575                 int minSize, int maxEnd, IntRange out) {
    576             applyDelta(moveStart, moveEnd, delta, out);
    577             if (out.start < 0) {
    578                 out.start = 0;
    579             }
    580             if (out.end > maxEnd) {
    581                 out.end = maxEnd;
    582             }
    583             if (out.size() < minSize) {
    584                 if (moveStart) {
    585                     out.start = out.end - minSize;
    586                 } else if (moveEnd) {
    587                     out.end = out.start + minSize;
    588                 }
    589             }
    590             return moveEnd ? out.size() - size() : size() - out.size();
    591         }
    592     }
    593 }
    594