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.Rect;
     12 import android.view.Gravity;
     13 import android.widget.FrameLayout;
     14 import android.widget.ImageView;
     15 
     16 import com.android.launcher3.R;
     17 
     18 public class AppWidgetResizeFrame extends FrameLayout {
     19     private LauncherAppWidgetHostView mWidgetView;
     20     private CellLayout mCellLayout;
     21     private DragLayer mDragLayer;
     22     private ImageView mLeftHandle;
     23     private ImageView mRightHandle;
     24     private ImageView mTopHandle;
     25     private ImageView mBottomHandle;
     26 
     27     private boolean mLeftBorderActive;
     28     private boolean mRightBorderActive;
     29     private boolean mTopBorderActive;
     30     private boolean mBottomBorderActive;
     31 
     32     private int mWidgetPaddingLeft;
     33     private int mWidgetPaddingRight;
     34     private int mWidgetPaddingTop;
     35     private int mWidgetPaddingBottom;
     36 
     37     private int mBaselineWidth;
     38     private int mBaselineHeight;
     39     private int mBaselineX;
     40     private int mBaselineY;
     41     private int mResizeMode;
     42 
     43     private int mRunningHInc;
     44     private int mRunningVInc;
     45     private int mMinHSpan;
     46     private int mMinVSpan;
     47     private int mDeltaX;
     48     private int mDeltaY;
     49     private int mDeltaXAddOn;
     50     private int mDeltaYAddOn;
     51 
     52     private int mBackgroundPadding;
     53     private int mTouchTargetWidth;
     54 
     55     private int mTopTouchRegionAdjustment = 0;
     56     private int mBottomTouchRegionAdjustment = 0;
     57 
     58     int[] mDirectionVector = new int[2];
     59     int[] mLastDirectionVector = new int[2];
     60     int[] mTmpPt = new int[2];
     61 
     62     final int SNAP_DURATION = 150;
     63     final int BACKGROUND_PADDING = 24;
     64     final float DIMMED_HANDLE_ALPHA = 0f;
     65     final float RESIZE_THRESHOLD = 0.66f;
     66 
     67     private static Rect mTmpRect = new Rect();
     68 
     69     public static final int LEFT = 0;
     70     public static final int TOP = 1;
     71     public static final int RIGHT = 2;
     72     public static final int BOTTOM = 3;
     73 
     74     private Launcher mLauncher;
     75 
     76     public AppWidgetResizeFrame(Context context,
     77             LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
     78 
     79         super(context);
     80         mLauncher = (Launcher) context;
     81         mCellLayout = cellLayout;
     82         mWidgetView = widgetView;
     83         mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
     84         mDragLayer = dragLayer;
     85 
     86         final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
     87         int[] result = Launcher.getMinSpanForWidget(mLauncher, info);
     88         mMinHSpan = result[0];
     89         mMinVSpan = result[1];
     90 
     91         setBackgroundResource(R.drawable.widget_resize_frame_holo);
     92         setPadding(0, 0, 0, 0);
     93 
     94         LayoutParams lp;
     95         mLeftHandle = new ImageView(context);
     96         mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left);
     97         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
     98                 Gravity.LEFT | Gravity.CENTER_VERTICAL);
     99         addView(mLeftHandle, lp);
    100 
    101         mRightHandle = new ImageView(context);
    102         mRightHandle.setImageResource(R.drawable.widget_resize_handle_right);
    103         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
    104                 Gravity.RIGHT | Gravity.CENTER_VERTICAL);
    105         addView(mRightHandle, lp);
    106 
    107         mTopHandle = new ImageView(context);
    108         mTopHandle.setImageResource(R.drawable.widget_resize_handle_top);
    109         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
    110                 Gravity.CENTER_HORIZONTAL | Gravity.TOP);
    111         addView(mTopHandle, lp);
    112 
    113         mBottomHandle = new ImageView(context);
    114         mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom);
    115         lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
    116                 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
    117         addView(mBottomHandle, lp);
    118 
    119         Rect p = AppWidgetHostView.getDefaultPaddingForWidget(context,
    120                 widgetView.getAppWidgetInfo().provider, null);
    121         mWidgetPaddingLeft = p.left;
    122         mWidgetPaddingTop = p.top;
    123         mWidgetPaddingRight = p.right;
    124         mWidgetPaddingBottom = p.bottom;
    125 
    126         if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
    127             mTopHandle.setVisibility(GONE);
    128             mBottomHandle.setVisibility(GONE);
    129         } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
    130             mLeftHandle.setVisibility(GONE);
    131             mRightHandle.setVisibility(GONE);
    132         }
    133 
    134         final float density = mLauncher.getResources().getDisplayMetrics().density;
    135         mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
    136         mTouchTargetWidth = 2 * mBackgroundPadding;
    137 
    138         // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
    139         // cells (same if not resized, or different) will be marked as occupied when the resize
    140         // frame is dismissed.
    141         mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
    142     }
    143 
    144     public boolean beginResizeIfPointInRegion(int x, int y) {
    145         boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
    146         boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
    147 
    148         mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
    149         mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
    150         mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
    151         mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
    152                 && verticalActive;
    153 
    154         boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
    155                 || mTopBorderActive || mBottomBorderActive;
    156 
    157         mBaselineWidth = getMeasuredWidth();
    158         mBaselineHeight = getMeasuredHeight();
    159         mBaselineX = getLeft();
    160         mBaselineY = getTop();
    161 
    162         if (anyBordersActive) {
    163             mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
    164             mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
    165             mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
    166             mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
    167         }
    168         return anyBordersActive;
    169     }
    170 
    171     /**
    172      *  Here we bound the deltas such that the frame cannot be stretched beyond the extents
    173      *  of the CellLayout, and such that the frame's borders can't cross.
    174      */
    175     public void updateDeltas(int deltaX, int deltaY) {
    176         if (mLeftBorderActive) {
    177             mDeltaX = Math.max(-mBaselineX, deltaX);
    178             mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
    179         } else if (mRightBorderActive) {
    180             mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
    181             mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
    182         }
    183 
    184         if (mTopBorderActive) {
    185             mDeltaY = Math.max(-mBaselineY, deltaY);
    186             mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
    187         } else if (mBottomBorderActive) {
    188             mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
    189             mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
    190         }
    191     }
    192 
    193     public void visualizeResizeForDelta(int deltaX, int deltaY) {
    194         visualizeResizeForDelta(deltaX, deltaY, false);
    195     }
    196 
    197     /**
    198      *  Based on the deltas, we resize the frame, and, if needed, we resize the widget.
    199      */
    200     private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
    201         updateDeltas(deltaX, deltaY);
    202         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    203 
    204         if (mLeftBorderActive) {
    205             lp.x = mBaselineX + mDeltaX;
    206             lp.width = mBaselineWidth - mDeltaX;
    207         } else if (mRightBorderActive) {
    208             lp.width = mBaselineWidth + mDeltaX;
    209         }
    210 
    211         if (mTopBorderActive) {
    212             lp.y = mBaselineY + mDeltaY;
    213             lp.height = mBaselineHeight - mDeltaY;
    214         } else if (mBottomBorderActive) {
    215             lp.height = mBaselineHeight + mDeltaY;
    216         }
    217 
    218         resizeWidgetIfNeeded(onDismiss);
    219         requestLayout();
    220     }
    221 
    222     /**
    223      *  Based on the current deltas, we determine if and how to resize the widget.
    224      */
    225     private void resizeWidgetIfNeeded(boolean onDismiss) {
    226         int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
    227         int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
    228 
    229         int deltaX = mDeltaX + mDeltaXAddOn;
    230         int deltaY = mDeltaY + mDeltaYAddOn;
    231 
    232         float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc;
    233         float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc;
    234 
    235         int hSpanInc = 0;
    236         int vSpanInc = 0;
    237         int cellXInc = 0;
    238         int cellYInc = 0;
    239 
    240         int countX = mCellLayout.getCountX();
    241         int countY = mCellLayout.getCountY();
    242 
    243         if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
    244             hSpanInc = Math.round(hSpanIncF);
    245         }
    246         if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
    247             vSpanInc = Math.round(vSpanIncF);
    248         }
    249 
    250         if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
    251 
    252 
    253         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
    254 
    255         int spanX = lp.cellHSpan;
    256         int spanY = lp.cellVSpan;
    257         int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
    258         int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
    259 
    260         int hSpanDelta = 0;
    261         int vSpanDelta = 0;
    262 
    263         // For each border, we bound the resizing based on the minimum width, and the maximum
    264         // expandability.
    265         if (mLeftBorderActive) {
    266             cellXInc = Math.max(-cellX, hSpanInc);
    267             cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
    268             hSpanInc *= -1;
    269             hSpanInc = Math.min(cellX, hSpanInc);
    270             hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
    271             hSpanDelta = -hSpanInc;
    272 
    273         } else if (mRightBorderActive) {
    274             hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc);
    275             hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
    276             hSpanDelta = hSpanInc;
    277         }
    278 
    279         if (mTopBorderActive) {
    280             cellYInc = Math.max(-cellY, vSpanInc);
    281             cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
    282             vSpanInc *= -1;
    283             vSpanInc = Math.min(cellY, vSpanInc);
    284             vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
    285             vSpanDelta = -vSpanInc;
    286         } else if (mBottomBorderActive) {
    287             vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc);
    288             vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
    289             vSpanDelta = vSpanInc;
    290         }
    291 
    292         mDirectionVector[0] = 0;
    293         mDirectionVector[1] = 0;
    294         // Update the widget's dimensions and position according to the deltas computed above
    295         if (mLeftBorderActive || mRightBorderActive) {
    296             spanX += hSpanInc;
    297             cellX += cellXInc;
    298             if (hSpanDelta != 0) {
    299                 mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
    300             }
    301         }
    302 
    303         if (mTopBorderActive || mBottomBorderActive) {
    304             spanY += vSpanInc;
    305             cellY += cellYInc;
    306             if (vSpanDelta != 0) {
    307                 mDirectionVector[1] = mTopBorderActive ? -1 : 1;
    308             }
    309         }
    310 
    311         if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
    312 
    313         // We always want the final commit to match the feedback, so we make sure to use the
    314         // last used direction vector when committing the resize / reorder.
    315         if (onDismiss) {
    316             mDirectionVector[0] = mLastDirectionVector[0];
    317             mDirectionVector[1] = mLastDirectionVector[1];
    318         } else {
    319             mLastDirectionVector[0] = mDirectionVector[0];
    320             mLastDirectionVector[1] = mDirectionVector[1];
    321         }
    322 
    323         if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
    324                 mDirectionVector, onDismiss)) {
    325             lp.tmpCellX = cellX;
    326             lp.tmpCellY = cellY;
    327             lp.cellHSpan = spanX;
    328             lp.cellVSpan = spanY;
    329             mRunningVInc += vSpanDelta;
    330             mRunningHInc += hSpanDelta;
    331             if (!onDismiss) {
    332                 updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
    333             }
    334         }
    335         mWidgetView.requestLayout();
    336     }
    337 
    338     static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
    339             int spanX, int spanY) {
    340 
    341         getWidgetSizeRanges(launcher, spanX, spanY, mTmpRect);
    342         widgetView.updateAppWidgetSize(null, mTmpRect.left, mTmpRect.top,
    343                 mTmpRect.right, mTmpRect.bottom);
    344     }
    345 
    346     static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) {
    347         if (rect == null) {
    348             rect = new Rect();
    349         }
    350         Rect landMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.LANDSCAPE);
    351         Rect portMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.PORTRAIT);
    352         final float density = launcher.getResources().getDisplayMetrics().density;
    353 
    354         // Compute landscape size
    355         int cellWidth = landMetrics.left;
    356         int cellHeight = landMetrics.top;
    357         int widthGap = landMetrics.right;
    358         int heightGap = landMetrics.bottom;
    359         int landWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
    360         int landHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
    361 
    362         // Compute portrait size
    363         cellWidth = portMetrics.left;
    364         cellHeight = portMetrics.top;
    365         widthGap = portMetrics.right;
    366         heightGap = portMetrics.bottom;
    367         int portWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
    368         int portHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
    369         rect.set(portWidth, landHeight, landWidth, portHeight);
    370         return rect;
    371     }
    372 
    373     /**
    374      * This is the final step of the resize. Here we save the new widget size and position
    375      * to LauncherModel and animate the resize frame.
    376      */
    377     public void commitResize() {
    378         resizeWidgetIfNeeded(true);
    379         requestLayout();
    380     }
    381 
    382     public void onTouchUp() {
    383         int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
    384         int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
    385 
    386         mDeltaXAddOn = mRunningHInc * xThreshold;
    387         mDeltaYAddOn = mRunningVInc * yThreshold;
    388         mDeltaX = 0;
    389         mDeltaY = 0;
    390 
    391         post(new Runnable() {
    392             @Override
    393             public void run() {
    394                 snapToWidget(true);
    395             }
    396         });
    397     }
    398 
    399     public void snapToWidget(boolean animate) {
    400         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    401         int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft -
    402                 mWidgetPaddingRight;
    403         int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop -
    404                 mWidgetPaddingBottom;
    405 
    406         mTmpPt[0] = mWidgetView.getLeft();
    407         mTmpPt[1] = mWidgetView.getTop();
    408         mDragLayer.getDescendantCoordRelativeToSelf(mCellLayout.getShortcutsAndWidgets(), mTmpPt);
    409 
    410         int newX = mTmpPt[0] - mBackgroundPadding + mWidgetPaddingLeft;
    411         int newY = mTmpPt[1] - mBackgroundPadding + mWidgetPaddingTop;
    412 
    413         // We need to make sure the frame's touchable regions lie fully within the bounds of the
    414         // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
    415         // down accordingly to provide a proper touch target.
    416         if (newY < 0) {
    417             // In this case we shift the touch region down to start at the top of the DragLayer
    418             mTopTouchRegionAdjustment = -newY;
    419         } else {
    420             mTopTouchRegionAdjustment = 0;
    421         }
    422         if (newY + newHeight > mDragLayer.getHeight()) {
    423             // In this case we shift the touch region up to end at the bottom of the DragLayer
    424             mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
    425         } else {
    426             mBottomTouchRegionAdjustment = 0;
    427         }
    428 
    429         if (!animate) {
    430             lp.width = newWidth;
    431             lp.height = newHeight;
    432             lp.x = newX;
    433             lp.y = newY;
    434             mLeftHandle.setAlpha(1.0f);
    435             mRightHandle.setAlpha(1.0f);
    436             mTopHandle.setAlpha(1.0f);
    437             mBottomHandle.setAlpha(1.0f);
    438             requestLayout();
    439         } else {
    440             PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
    441             PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
    442                     newHeight);
    443             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
    444             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
    445             ObjectAnimator oa =
    446                     LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
    447             ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, "alpha", 1.0f);
    448             ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, "alpha", 1.0f);
    449             ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, "alpha", 1.0f);
    450             ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, "alpha", 1.0f);
    451             oa.addUpdateListener(new AnimatorUpdateListener() {
    452                 public void onAnimationUpdate(ValueAnimator animation) {
    453                     requestLayout();
    454                 }
    455             });
    456             AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
    457             if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
    458                 set.playTogether(oa, topOa, bottomOa);
    459             } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
    460                 set.playTogether(oa, leftOa, rightOa);
    461             } else {
    462                 set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
    463             }
    464 
    465             set.setDuration(SNAP_DURATION);
    466             set.start();
    467         }
    468     }
    469 }
    470