| package com.android.launcher2; |
| |
| import android.animation.AnimatorSet; |
| import android.animation.ObjectAnimator; |
| import android.animation.PropertyValuesHolder; |
| import android.animation.ValueAnimator; |
| import android.animation.ValueAnimator.AnimatorUpdateListener; |
| import android.appwidget.AppWidgetHostView; |
| import android.appwidget.AppWidgetProviderInfo; |
| import android.content.Context; |
| import android.graphics.Rect; |
| import android.view.Gravity; |
| import android.widget.FrameLayout; |
| import android.widget.ImageView; |
| |
| import com.android.launcher.R; |
| |
| public class AppWidgetResizeFrame extends FrameLayout { |
| |
| private ItemInfo mItemInfo; |
| private LauncherAppWidgetHostView mWidgetView; |
| private CellLayout mCellLayout; |
| private DragLayer mDragLayer; |
| private Workspace mWorkspace; |
| private ImageView mLeftHandle; |
| private ImageView mRightHandle; |
| private ImageView mTopHandle; |
| private ImageView mBottomHandle; |
| |
| private boolean mLeftBorderActive; |
| private boolean mRightBorderActive; |
| private boolean mTopBorderActive; |
| private boolean mBottomBorderActive; |
| |
| private int mWidgetPaddingLeft; |
| private int mWidgetPaddingRight; |
| private int mWidgetPaddingTop; |
| private int mWidgetPaddingBottom; |
| |
| private int mBaselineWidth; |
| private int mBaselineHeight; |
| private int mBaselineX; |
| private int mBaselineY; |
| private int mResizeMode; |
| |
| private int mRunningHInc; |
| private int mRunningVInc; |
| private int mMinHSpan; |
| private int mMinVSpan; |
| private int mDeltaX; |
| private int mDeltaY; |
| |
| private int mBackgroundPadding; |
| private int mTouchTargetWidth; |
| |
| private int mExpandability[] = new int[4]; |
| |
| final int SNAP_DURATION = 150; |
| final int BACKGROUND_PADDING = 24; |
| final float DIMMED_HANDLE_ALPHA = 0f; |
| final float RESIZE_THRESHOLD = 0.66f; |
| |
| public static final int LEFT = 0; |
| public static final int TOP = 1; |
| public static final int RIGHT = 2; |
| public static final int BOTTOM = 3; |
| |
| private Launcher mLauncher; |
| |
| public AppWidgetResizeFrame(Context context, ItemInfo itemInfo, |
| LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) { |
| |
| super(context); |
| mLauncher = (Launcher) context; |
| mItemInfo = itemInfo; |
| mCellLayout = cellLayout; |
| mWidgetView = widgetView; |
| mResizeMode = widgetView.getAppWidgetInfo().resizeMode; |
| mDragLayer = dragLayer; |
| mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace); |
| |
| final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo(); |
| int[] result = mLauncher.getMinSpanForWidget(info, null); |
| mMinHSpan = result[0]; |
| mMinVSpan = result[1]; |
| |
| setBackgroundResource(R.drawable.widget_resize_frame_holo); |
| setPadding(0, 0, 0, 0); |
| |
| LayoutParams lp; |
| mLeftHandle = new ImageView(context); |
| mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left); |
| lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, |
| Gravity.LEFT | Gravity.CENTER_VERTICAL); |
| addView(mLeftHandle, lp); |
| |
| mRightHandle = new ImageView(context); |
| mRightHandle.setImageResource(R.drawable.widget_resize_handle_right); |
| lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, |
| Gravity.RIGHT | Gravity.CENTER_VERTICAL); |
| addView(mRightHandle, lp); |
| |
| mTopHandle = new ImageView(context); |
| mTopHandle.setImageResource(R.drawable.widget_resize_handle_top); |
| lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, |
| Gravity.CENTER_HORIZONTAL | Gravity.TOP); |
| addView(mTopHandle, lp); |
| |
| mBottomHandle = new ImageView(context); |
| mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom); |
| lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, |
| Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); |
| addView(mBottomHandle, lp); |
| |
| Rect p = AppWidgetHostView.getDefaultPaddingForWidget(context, |
| widgetView.getAppWidgetInfo().provider, null); |
| mWidgetPaddingLeft = p.left; |
| mWidgetPaddingTop = p.top; |
| mWidgetPaddingRight = p.right; |
| mWidgetPaddingBottom = p.bottom; |
| |
| if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { |
| mTopHandle.setVisibility(GONE); |
| mBottomHandle.setVisibility(GONE); |
| } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) { |
| mLeftHandle.setVisibility(GONE); |
| mRightHandle.setVisibility(GONE); |
| } |
| |
| final float density = mLauncher.getResources().getDisplayMetrics().density; |
| mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING); |
| mTouchTargetWidth = 2 * mBackgroundPadding; |
| } |
| |
| public boolean beginResizeIfPointInRegion(int x, int y) { |
| boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0; |
| boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0; |
| mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive; |
| mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive; |
| mTopBorderActive = (y < mTouchTargetWidth) && verticalActive; |
| mBottomBorderActive = (y > getHeight() - mTouchTargetWidth) && verticalActive; |
| |
| boolean anyBordersActive = mLeftBorderActive || mRightBorderActive |
| || mTopBorderActive || mBottomBorderActive; |
| |
| mBaselineWidth = getMeasuredWidth(); |
| mBaselineHeight = getMeasuredHeight(); |
| mBaselineX = getLeft(); |
| mBaselineY = getTop(); |
| mRunningHInc = 0; |
| mRunningVInc = 0; |
| |
| if (anyBordersActive) { |
| mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); |
| mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA); |
| mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); |
| mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA); |
| } |
| mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability); |
| |
| return anyBordersActive; |
| } |
| |
| /** |
| * Here we bound the deltas such that the frame cannot be stretched beyond the extents |
| * of the CellLayout, and such that the frame's borders can't cross. |
| */ |
| public void updateDeltas(int deltaX, int deltaY) { |
| if (mLeftBorderActive) { |
| mDeltaX = Math.max(-mBaselineX, deltaX); |
| mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX); |
| } else if (mRightBorderActive) { |
| mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX); |
| mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX); |
| } |
| |
| if (mTopBorderActive) { |
| mDeltaY = Math.max(-mBaselineY, deltaY); |
| mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY); |
| } else if (mBottomBorderActive) { |
| mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY); |
| mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY); |
| } |
| } |
| |
| /** |
| * Based on the deltas, we resize the frame, and, if needed, we resize the widget. |
| */ |
| public void visualizeResizeForDelta(int deltaX, int deltaY) { |
| updateDeltas(deltaX, deltaY); |
| DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); |
| |
| if (mLeftBorderActive) { |
| lp.x = mBaselineX + mDeltaX; |
| lp.width = mBaselineWidth - mDeltaX; |
| } else if (mRightBorderActive) { |
| lp.width = mBaselineWidth + mDeltaX; |
| } |
| |
| if (mTopBorderActive) { |
| lp.y = mBaselineY + mDeltaY; |
| lp.height = mBaselineHeight - mDeltaY; |
| } else if (mBottomBorderActive) { |
| lp.height = mBaselineHeight + mDeltaY; |
| } |
| |
| resizeWidgetIfNeeded(); |
| requestLayout(); |
| } |
| |
| /** |
| * Based on the current deltas, we determine if and how to resize the widget. |
| */ |
| private void resizeWidgetIfNeeded() { |
| int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); |
| int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); |
| |
| float hSpanIncF = 1.0f * mDeltaX / xThreshold - mRunningHInc; |
| float vSpanIncF = 1.0f * mDeltaY / yThreshold - mRunningVInc; |
| |
| int hSpanInc = 0; |
| int vSpanInc = 0; |
| int cellXInc = 0; |
| int cellYInc = 0; |
| |
| if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) { |
| hSpanInc = Math.round(hSpanIncF); |
| } |
| if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) { |
| vSpanInc = Math.round(vSpanIncF); |
| } |
| |
| if (hSpanInc == 0 && vSpanInc == 0) return; |
| |
| // Before we change the widget, we clear the occupied cells associated with it. |
| // The new set of occupied cells is marked below, once the layout params are updated. |
| mCellLayout.markCellsAsUnoccupiedForView(mWidgetView); |
| |
| CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams(); |
| |
| // For each border, we bound the resizing based on the minimum width, and the maximum |
| // expandability. |
| if (mLeftBorderActive) { |
| cellXInc = Math.max(-mExpandability[LEFT], hSpanInc); |
| cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc); |
| hSpanInc *= -1; |
| hSpanInc = Math.min(mExpandability[LEFT], hSpanInc); |
| hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); |
| mRunningHInc -= hSpanInc; |
| } else if (mRightBorderActive) { |
| hSpanInc = Math.min(mExpandability[RIGHT], hSpanInc); |
| hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); |
| mRunningHInc += hSpanInc; |
| } |
| |
| if (mTopBorderActive) { |
| cellYInc = Math.max(-mExpandability[TOP], vSpanInc); |
| cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc); |
| vSpanInc *= -1; |
| vSpanInc = Math.min(mExpandability[TOP], vSpanInc); |
| vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); |
| mRunningVInc -= vSpanInc; |
| } else if (mBottomBorderActive) { |
| vSpanInc = Math.min(mExpandability[BOTTOM], vSpanInc); |
| vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); |
| mRunningVInc += vSpanInc; |
| } |
| |
| // Update the widget's dimensions and position according to the deltas computed above |
| if (mLeftBorderActive || mRightBorderActive) { |
| lp.cellHSpan += hSpanInc; |
| lp.cellX += cellXInc; |
| } |
| |
| if (mTopBorderActive || mBottomBorderActive) { |
| lp.cellVSpan += vSpanInc; |
| lp.cellY += cellYInc; |
| } |
| |
| // Update the expandability array, as we have changed the widget's size. |
| mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability); |
| |
| // Update the cells occupied by this widget |
| mCellLayout.markCellsAsOccupiedForView(mWidgetView); |
| mWidgetView.requestLayout(); |
| } |
| |
| /** |
| * This is the final step of the resize. Here we save the new widget size and position |
| * to LauncherModel and animate the resize frame. |
| */ |
| public void commitResizeForDelta(int deltaX, int deltaY) { |
| visualizeResizeForDelta(deltaX, deltaY); |
| |
| CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams(); |
| LauncherModel.resizeItemInDatabase(getContext(), mItemInfo, lp.cellX, lp.cellY, |
| lp.cellHSpan, lp.cellVSpan); |
| mWidgetView.requestLayout(); |
| |
| // Once our widget resizes (hence the post), we want to snap the resize frame to it |
| post(new Runnable() { |
| public void run() { |
| snapToWidget(true); |
| } |
| }); |
| } |
| |
| public void snapToWidget(boolean animate) { |
| final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); |
| int xOffset = mCellLayout.getLeft() + mCellLayout.getPaddingLeft() - mWorkspace.getScrollX(); |
| int yOffset = mCellLayout.getTop() + mCellLayout.getPaddingTop() - mWorkspace.getScrollY(); |
| |
| int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft - |
| mWidgetPaddingRight; |
| int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop - |
| mWidgetPaddingBottom; |
| |
| int newX = mWidgetView.getLeft() - mBackgroundPadding + xOffset + mWidgetPaddingLeft; |
| int newY = mWidgetView.getTop() - mBackgroundPadding + yOffset + mWidgetPaddingTop; |
| |
| // We need to make sure the frame stays within the bounds of the CellLayout |
| if (newY < 0) { |
| newHeight -= -newY; |
| newY = 0; |
| } |
| if (newY + newHeight > mDragLayer.getHeight()) { |
| newHeight -= newY + newHeight - mDragLayer.getHeight(); |
| } |
| |
| if (!animate) { |
| lp.width = newWidth; |
| lp.height = newHeight; |
| lp.x = newX; |
| lp.y = newY; |
| mLeftHandle.setAlpha(1.0f); |
| mRightHandle.setAlpha(1.0f); |
| mTopHandle.setAlpha(1.0f); |
| mBottomHandle.setAlpha(1.0f); |
| requestLayout(); |
| } else { |
| PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth); |
| PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height, |
| newHeight); |
| PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX); |
| PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY); |
| ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); |
| ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f); |
| ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f); |
| ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f); |
| ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f); |
| oa.addUpdateListener(new AnimatorUpdateListener() { |
| public void onAnimationUpdate(ValueAnimator animation) { |
| requestLayout(); |
| } |
| }); |
| AnimatorSet set = new AnimatorSet(); |
| if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) { |
| set.playTogether(oa, topOa, bottomOa); |
| } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { |
| set.playTogether(oa, leftOa, rightOa); |
| } else { |
| set.playTogether(oa, leftOa, rightOa, topOa, bottomOa); |
| } |
| |
| set.setDuration(SNAP_DURATION); |
| set.start(); |
| } |
| } |
| } |