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.AppWidgetProviderInfo;
      9 import android.content.Context;
     10 import android.content.res.Resources;
     11 import android.view.Gravity;
     12 import android.widget.FrameLayout;
     13 import android.widget.ImageView;
     14 
     15 import com.android.launcher.R;
     16 
     17 public class AppWidgetResizeFrame extends FrameLayout {
     18 
     19     private ItemInfo mItemInfo;
     20     private LauncherAppWidgetHostView mWidgetView;
     21     private CellLayout mCellLayout;
     22     private DragLayer mDragLayer;
     23     private Workspace mWorkspace;
     24     private ImageView mLeftHandle;
     25     private ImageView mRightHandle;
     26     private ImageView mTopHandle;
     27     private ImageView mBottomHandle;
     28 
     29     private boolean mLeftBorderActive;
     30     private boolean mRightBorderActive;
     31     private boolean mTopBorderActive;
     32     private boolean mBottomBorderActive;
     33 
     34     private int mWidgetPaddingLeft;
     35     private int mWidgetPaddingRight;
     36     private int mWidgetPaddingTop;
     37     private int mWidgetPaddingBottom;
     38 
     39     private int mBaselineWidth;
     40     private int mBaselineHeight;
     41     private int mBaselineX;
     42     private int mBaselineY;
     43     private int mResizeMode;
     44 
     45     private int mRunningHInc;
     46     private int mRunningVInc;
     47     private int mMinHSpan;
     48     private int mMinVSpan;
     49     private int mDeltaX;
     50     private int mDeltaY;
     51 
     52     private int mBackgroundPadding;
     53     private int mTouchTargetWidth;
     54 
     55     private int mExpandability[] = new int[4];
     56 
     57     final int SNAP_DURATION = 150;
     58     final int BACKGROUND_PADDING = 24;
     59     final float DIMMED_HANDLE_ALPHA = 0f;
     60     final float RESIZE_THRESHOLD = 0.66f;
     61 
     62     public static final int LEFT = 0;
     63     public static final int TOP = 1;
     64     public static final int RIGHT = 2;
     65     public static final int BOTTOM = 3;
     66 
     67     private Launcher mLauncher;
     68 
     69     public AppWidgetResizeFrame(Context context, ItemInfo itemInfo,
     70             LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
     71 
     72         super(context);
     73         mLauncher = (Launcher) context;
     74         mItemInfo = itemInfo;
     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 = mLauncher.getMinResizeSpanForWidget(info, null);
     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         Launcher.Padding p = mLauncher.getPaddingForWidget(widgetView.getAppWidgetInfo().provider);
    115         mWidgetPaddingLeft = p.left;
    116         mWidgetPaddingTop = p.top;
    117         mWidgetPaddingRight = p.right;
    118         mWidgetPaddingBottom = p.bottom;
    119 
    120         if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
    121             mTopHandle.setVisibility(GONE);
    122             mBottomHandle.setVisibility(GONE);
    123         } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
    124             mLeftHandle.setVisibility(GONE);
    125             mRightHandle.setVisibility(GONE);
    126         }
    127 
    128         final float density = mLauncher.getResources().getDisplayMetrics().density;
    129         mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
    130         mTouchTargetWidth = 2 * mBackgroundPadding;
    131     }
    132 
    133     public boolean beginResizeIfPointInRegion(int x, int y) {
    134         boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
    135         boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
    136         mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
    137         mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
    138         mTopBorderActive = (y < mTouchTargetWidth) && verticalActive;
    139         mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive;
    140 
    141         boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
    142                 || mTopBorderActive || mBottomBorderActive;
    143 
    144         mBaselineWidth = getMeasuredWidth();
    145         mBaselineHeight = getMeasuredHeight();
    146         mBaselineX = getLeft();
    147         mBaselineY = getTop();
    148         mRunningHInc = 0;
    149         mRunningVInc = 0;
    150 
    151         if (anyBordersActive) {
    152             mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
    153             mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
    154             mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
    155             mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
    156         }
    157         mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
    158 
    159         return anyBordersActive;
    160     }
    161 
    162     /**
    163      *  Here we bound the deltas such that the frame cannot be stretched beyond the extents
    164      *  of the CellLayout, and such that the frame's borders can't cross.
    165      */
    166     public void updateDeltas(int deltaX, int deltaY) {
    167         if (mLeftBorderActive) {
    168             mDeltaX = Math.max(-mBaselineX, deltaX);
    169             mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
    170         } else if (mRightBorderActive) {
    171             mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
    172             mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
    173         }
    174 
    175         if (mTopBorderActive) {
    176             mDeltaY = Math.max(-mBaselineY, deltaY);
    177             mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
    178         } else if (mBottomBorderActive) {
    179             mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
    180             mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
    181         }
    182     }
    183 
    184     /**
    185      *  Based on the deltas, we resize the frame, and, if needed, we resize the widget.
    186      */
    187     public void visualizeResizeForDelta(int deltaX, int deltaY) {
    188         updateDeltas(deltaX, deltaY);
    189         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    190 
    191         if (mLeftBorderActive) {
    192             lp.x = mBaselineX + mDeltaX;
    193             lp.width = mBaselineWidth - mDeltaX;
    194         } else if (mRightBorderActive) {
    195             lp.width = mBaselineWidth + mDeltaX;
    196         }
    197 
    198         if (mTopBorderActive) {
    199             lp.y = mBaselineY + mDeltaY;
    200             lp.height = mBaselineHeight - mDeltaY;
    201         } else if (mBottomBorderActive) {
    202             lp.height = mBaselineHeight + mDeltaY;
    203         }
    204 
    205         resizeWidgetIfNeeded();
    206         requestLayout();
    207     }
    208 
    209     /**
    210      *  Based on the current deltas, we determine if and how to resize the widget.
    211      */
    212     private void resizeWidgetIfNeeded() {
    213         int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
    214         int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
    215 
    216         float hSpanIncF = 1.0f * mDeltaX / xThreshold - mRunningHInc;
    217         float vSpanIncF = 1.0f * mDeltaY / yThreshold - mRunningVInc;
    218 
    219         int hSpanInc = 0;
    220         int vSpanInc = 0;
    221         int cellXInc = 0;
    222         int cellYInc = 0;
    223 
    224         if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
    225             hSpanInc = Math.round(hSpanIncF);
    226         }
    227         if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
    228             vSpanInc = Math.round(vSpanIncF);
    229         }
    230 
    231         if (hSpanInc == 0 && vSpanInc == 0) return;
    232 
    233         // Before we change the widget, we clear the occupied cells associated with it.
    234         // The new set of occupied cells is marked below, once the layout params are updated.
    235         mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
    236 
    237         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
    238 
    239         // For each border, we bound the resizing based on the minimum width, and the maximum
    240         // expandability.
    241         if (mLeftBorderActive) {
    242             cellXInc = Math.max(-mExpandability[LEFT], hSpanInc);
    243             cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
    244             hSpanInc *= -1;
    245             hSpanInc = Math.min(mExpandability[LEFT], hSpanInc);
    246             hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
    247             mRunningHInc -= hSpanInc;
    248         } else if (mRightBorderActive) {
    249             hSpanInc = Math.min(mExpandability[RIGHT], hSpanInc);
    250             hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
    251             mRunningHInc += hSpanInc;
    252         }
    253 
    254         if (mTopBorderActive) {
    255             cellYInc = Math.max(-mExpandability[TOP], vSpanInc);
    256             cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
    257             vSpanInc *= -1;
    258             vSpanInc = Math.min(mExpandability[TOP], vSpanInc);
    259             vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
    260             mRunningVInc -= vSpanInc;
    261         } else if (mBottomBorderActive) {
    262             vSpanInc = Math.min(mExpandability[BOTTOM], vSpanInc);
    263             vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
    264             mRunningVInc += vSpanInc;
    265         }
    266 
    267         // Update the widget's dimensions and position according to the deltas computed above
    268         if (mLeftBorderActive || mRightBorderActive) {
    269             lp.cellHSpan += hSpanInc;
    270             lp.cellX += cellXInc;
    271         }
    272 
    273         if (mTopBorderActive || mBottomBorderActive) {
    274             lp.cellVSpan += vSpanInc;
    275             lp.cellY += cellYInc;
    276         }
    277 
    278         // Update the expandability array, as we have changed the widget's size.
    279         mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability);
    280 
    281         // Update the cells occupied by this widget
    282         mCellLayout.markCellsAsOccupiedForView(mWidgetView);
    283         mWidgetView.requestLayout();
    284     }
    285 
    286     /**
    287      * This is the final step of the resize. Here we save the new widget size and position
    288      * to LauncherModel and animate the resize frame.
    289      */
    290     public void commitResizeForDelta(int deltaX, int deltaY) {
    291         visualizeResizeForDelta(deltaX, deltaY);
    292 
    293         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
    294         LauncherModel.resizeItemInDatabase(getContext(), mItemInfo, lp.cellX, lp.cellY,
    295                 lp.cellHSpan, lp.cellVSpan);
    296         mWidgetView.requestLayout();
    297 
    298         // Once our widget resizes (hence the post), we want to snap the resize frame to it
    299         post(new Runnable() {
    300             public void run() {
    301                 snapToWidget(true);
    302             }
    303         });
    304     }
    305 
    306     public void snapToWidget(boolean animate) {
    307         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
    308         int xOffset = mCellLayout.getLeft() + mCellLayout.getPaddingLeft() - mWorkspace.getScrollX();
    309         int yOffset = mCellLayout.getTop() + mCellLayout.getPaddingTop() - mWorkspace.getScrollY();
    310 
    311         int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft -
    312                 mWidgetPaddingRight;
    313         int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop -
    314                 mWidgetPaddingBottom;
    315 
    316         int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft;
    317         int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop;
    318 
    319         // We need to make sure the frame stays within the bounds of the CellLayout
    320         if (newY < 0) {
    321             newHeight -= -newY;
    322             newY = 0;
    323         }
    324         if (newY + newHeight > mDragLayer.getHeight()) {
    325             newHeight -= newY + newHeight - mDragLayer.getHeight();
    326         }
    327 
    328         if (!animate) {
    329             lp.width = newWidth;
    330             lp.height = newHeight;
    331             lp.x = newX;
    332             lp.y = newY;
    333             mLeftHandle.setAlpha(1.0f);
    334             mRightHandle.setAlpha(1.0f);
    335             mTopHandle.setAlpha(1.0f);
    336             mBottomHandle.setAlpha(1.0f);
    337             requestLayout();
    338         } else {
    339             PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
    340             PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
    341                     newHeight);
    342             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
    343             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
    344             ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
    345             ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f);
    346             ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f);
    347             ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f);
    348             ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f);
    349             oa.addUpdateListener(new AnimatorUpdateListener() {
    350                 public void onAnimationUpdate(ValueAnimator animation) {
    351                     requestLayout();
    352                 }
    353             });
    354             AnimatorSet set = new AnimatorSet();
    355             if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
    356                 set.playTogether(oa, topOa, bottomOa);
    357             } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
    358                 set.playTogether(oa, leftOa, rightOa);
    359             } else {
    360                 set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
    361             }
    362 
    363             set.setDuration(SNAP_DURATION);
    364             set.start();
    365         }
    366     }
    367 }
    368