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