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