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