diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2016-05-09 20:43:21 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2016-06-08 15:00:09 -0700 |
commit | 3333b0ced8e6743c41909f6f6b916f1f9ec5a004 (patch) | |
tree | 7fd735821e3393a0dfe8691087f58ebbaa9dafae | |
parent | ab06999a70c773ffbc0aadaf938e0e90f8ca09b2 (diff) | |
download | android_packages_apps_Trebuchet-3333b0ced8e6743c41909f6f6b916f1f9ec5a004.tar.gz android_packages_apps_Trebuchet-3333b0ced8e6743c41909f6f6b916f1f9ec5a004.tar.bz2 android_packages_apps_Trebuchet-3333b0ced8e6743c41909f6f6b916f1f9ec5a004.zip |
Unifying focus indicator handling for workspace and all-apps
Adding an abstract FocusIndicatorHelper based on FocusIndicatorView
which draws the background instead of using a dummy view.
Change-Id: Id560195323d2ddad8fcd77ba675cf3f4fd4a94ab
18 files changed, 438 insertions, 313 deletions
diff --git a/res/drawable/focusable_view_bg.xml b/res/drawable/focusable_view_bg.xml deleted file mode 100644 index e156513ef..000000000 --- a/res/drawable/focusable_view_bg.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2011 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - - <item android:state_focused="true"> - <shape android:shape="rectangle"> - <solid android:color="@color/focused_background" /> - </shape> - </item> - -</selector>
\ No newline at end of file diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml index 1147326f7..d193e2f6d 100644 --- a/res/layout-land/launcher.xml +++ b/res/layout-land/launcher.xml @@ -30,11 +30,6 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <com.android.launcher3.FocusIndicatorView - android:id="@+id/focus_indicator" - android:layout_width="52dp" - android:layout_height="52dp" /> - <!-- The workspace contains 5 screens of cells --> <!-- DO NOT CHANGE THE ID --> <com.android.launcher3.Workspace diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml index fed99f331..527ed54ec 100644 --- a/res/layout-port/launcher.xml +++ b/res/layout-port/launcher.xml @@ -31,11 +31,6 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <com.android.launcher3.FocusIndicatorView - android:id="@+id/focus_indicator" - android:layout_width="52dp" - android:layout_height="52dp" /> - <!-- The workspace contains 5 screens of cells --> <!-- DO NOT CHANGE THE ID --> <com.android.launcher3.Workspace diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml index 23e673cbf..184e688f3 100644 --- a/res/layout-sw720dp/launcher.xml +++ b/res/layout-sw720dp/launcher.xml @@ -30,11 +30,6 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <com.android.launcher3.FocusIndicatorView - android:id="@+id/focus_indicator" - android:layout_width="52dp" - android:layout_height="52dp" /> - <!-- The workspace contains 5 screens of cells --> <!-- DO NOT CHANGE THE ID --> <com.android.launcher3.Workspace diff --git a/res/layout/all_apps_icon.xml b/res/layout/all_apps_icon.xml index bb95c5fb8..3836fed89 100644 --- a/res/layout/all_apps_icon.xml +++ b/res/layout/all_apps_icon.xml @@ -24,6 +24,5 @@ android:paddingTop="@dimen/all_apps_icon_top_bottom_padding" android:paddingBottom="@dimen/all_apps_icon_top_bottom_padding" android:focusable="true" - android:background="@drawable/focusable_view_bg" launcher:iconDisplay="all_apps" /> diff --git a/res/layout/all_apps_prediction_bar_icon.xml b/res/layout/all_apps_prediction_bar_icon.xml index f15aeaf75..295b0b708 100644 --- a/res/layout/all_apps_prediction_bar_icon.xml +++ b/res/layout/all_apps_prediction_bar_icon.xml @@ -24,6 +24,5 @@ android:paddingTop="@dimen/all_apps_prediction_icon_top_padding" android:paddingBottom="@dimen/all_apps_prediction_icon_bottom_padding" android:focusable="true" - android:background="@drawable/focusable_view_bg" launcher:iconDisplay="all_apps" /> diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml index 8a1b7d0f3..d95075058 100644 --- a/res/layout/user_folder.xml +++ b/res/layout/user_folder.xml @@ -22,27 +22,14 @@ android:elevation="5dp" android:orientation="vertical" > - <FrameLayout - android:id="@+id/folder_content_wrapper" + <com.android.launcher3.folder.FolderPagedView + android:id="@+id/folder_content" android:layout_width="match_parent" - android:layout_height="match_parent" > - - <!-- Actual size of the indicator doesn't matter as it is scaled to match the view size --> - - <com.android.launcher3.FocusIndicatorView - android:id="@+id/focus_indicator" - android:layout_width="20dp" - android:layout_height="20dp" /> - - <com.android.launcher3.folder.FolderPagedView - android:id="@+id/folder_content" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingLeft="4dp" - android:paddingRight="4dp" - android:paddingTop="8dp" - launcher:pageIndicator="@+id/folder_page_indicator" /> - </FrameLayout> + android:layout_height="match_parent" + android:paddingLeft="4dp" + android:paddingRight="4dp" + android:paddingTop="8dp" + launcher:pageIndicator="@+id/folder_page_indicator" /> <LinearLayout android:id="@+id/folder_footer" diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml index de1316ebb..bb6bd765b 100644 --- a/res/layout/user_folder_icon_normalized.xml +++ b/res/layout/user_folder_icon_normalized.xml @@ -22,27 +22,14 @@ android:elevation="5dp" android:orientation="vertical" > - <FrameLayout - android:id="@+id/folder_content_wrapper" + <com.android.launcher3.folder.FolderPagedView + android:id="@+id/folder_content" android:layout_width="match_parent" - android:layout_height="match_parent" > - - <!-- Actual size of the indicator doesn't matter as it is scaled to match the view size --> - - <com.android.launcher3.FocusIndicatorView - android:id="@+id/focus_indicator" - android:layout_width="20dp" - android:layout_height="20dp" /> - - <com.android.launcher3.folder.FolderPagedView - android:id="@+id/folder_content" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingLeft="8dp" - android:paddingRight="8dp" - android:paddingTop="8dp" - launcher:pageIndicator="@+id/folder_page_indicator" /> - </FrameLayout> + android:layout_height="match_parent" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingTop="8dp" + launcher:pageIndicator="@+id/folder_page_indicator" /> <LinearLayout android:id="@+id/folder_footer" diff --git a/src/com/android/launcher3/FocusIndicatorView.java b/src/com/android/launcher3/FocusIndicatorView.java deleted file mode 100644 index a835d9969..000000000 --- a/src/com/android/launcher3/FocusIndicatorView.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3; - -import android.animation.Animator; -import android.animation.PropertyValuesHolder; -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.Pair; -import android.view.View; - -import com.android.launcher3.util.Thunk; - -public class FocusIndicatorView extends View implements View.OnFocusChangeListener { - - // It can be any number >0. The view is resized using scaleX and scaleY. - static final int DEFAULT_LAYOUT_SIZE = 100; - - private static final float MIN_VISIBLE_ALPHA = 0.2f; - private static final long ANIM_DURATION = 150; - - private final int[] mIndicatorPos = new int[2]; - private final int[] mTargetViewPos = new int[2]; - - private Animator mCurrentAnimation; - private ViewAnimState mTargetState; - - private View mLastFocusedView; - private boolean mInitiated; - private final OnFocusChangeListener mHideIndicatorOnFocusListener; - - private Pair<View, Boolean> mPendingCall; - - public FocusIndicatorView(Context context) { - this(context, null); - } - - public FocusIndicatorView(Context context, AttributeSet attrs) { - super(context, attrs); - setAlpha(0); - setBackgroundColor(getResources().getColor(R.color.focused_background)); - - mHideIndicatorOnFocusListener = new OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - endCurrentAnimation(); - setAlpha(0); - } - } - }; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - - // Redraw if it is already showing. This avoids a bug where the height changes by a small - // amount on connecting/disconnecting a bluetooth keyboard. - if (mLastFocusedView != null) { - mPendingCall = Pair.create(mLastFocusedView, Boolean.TRUE); - invalidate(); - } - } - - /** - * Sets the alpha of this FocusIndicatorView to 0 when a view with this listener receives focus. - */ - public View.OnFocusChangeListener getHideIndicatorOnFocusListener() { - return mHideIndicatorOnFocusListener; - } - - @Override - public void onFocusChange(View v, boolean hasFocus) { - mPendingCall = null; - if (!mInitiated && (getWidth() == 0)) { - // View not yet laid out. Wait until the view is ready to be drawn, so that be can - // get the location on screen. - mPendingCall = Pair.create(v, hasFocus); - invalidate(); - return; - } - - if (!mInitiated) { - // The parent view should always the a parent of the target view. - computeLocationRelativeToParent(this, (View) getParent(), mIndicatorPos); - mInitiated = true; - } - - if (hasFocus) { - int indicatorWidth = getWidth(); - int indicatorHeight = getHeight(); - - endCurrentAnimation(); - ViewAnimState nextState = new ViewAnimState(); - nextState.scaleX = v.getScaleX() * v.getWidth() / indicatorWidth; - nextState.scaleY = v.getScaleY() * v.getHeight() / indicatorHeight; - - computeLocationRelativeToParent(v, (View) getParent(), mTargetViewPos); - nextState.x = mTargetViewPos[0] - mIndicatorPos[0] - (1 - nextState.scaleX) * indicatorWidth / 2; - nextState.y = mTargetViewPos[1] - mIndicatorPos[1] - (1 - nextState.scaleY) * indicatorHeight / 2; - - if (getAlpha() > MIN_VISIBLE_ALPHA) { - mTargetState = nextState; - mCurrentAnimation = new LauncherViewPropertyAnimator(this) - .alpha(1) - .translationX(mTargetState.x) - .translationY(mTargetState.y) - .scaleX(mTargetState.scaleX) - .scaleY(mTargetState.scaleY); - } else { - applyState(nextState); - mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this, - PropertyValuesHolder.ofFloat(View.ALPHA, 1)); - } - mLastFocusedView = v; - } else { - if (mLastFocusedView == v) { - mLastFocusedView = null; - endCurrentAnimation(); - mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this, - PropertyValuesHolder.ofFloat(View.ALPHA, 0)); - } - } - if (mCurrentAnimation != null) { - mCurrentAnimation.setDuration(ANIM_DURATION).start(); - } - } - - private void endCurrentAnimation() { - if (mCurrentAnimation != null) { - mCurrentAnimation.cancel(); - mCurrentAnimation = null; - } - if (mTargetState != null) { - applyState(mTargetState); - mTargetState = null; - } - } - - private void applyState(ViewAnimState state) { - setTranslationX(state.x); - setTranslationY(state.y); - setScaleX(state.scaleX); - setScaleY(state.scaleY); - } - - @Override - protected void onDraw(Canvas canvas) { - if (mPendingCall != null) { - onFocusChange(mPendingCall.first, mPendingCall.second); - } - } - - /** - * Computes the location of a view relative to {@param parent}, off-setting - * any shift due to page view scroll. - * @param pos an array of two integers in which to hold the coordinates - */ - private static void computeLocationRelativeToParent(View v, View parent, int[] pos) { - pos[0] = pos[1] = 0; - computeLocationRelativeToParentHelper(v, parent, pos); - - // If a view is scaled, its position will also shift accordingly. For optimization, only - // consider this for the last node. - pos[0] += (1 - v.getScaleX()) * v.getWidth() / 2; - pos[1] += (1 - v.getScaleY()) * v.getHeight() / 2; - } - - private static void computeLocationRelativeToParentHelper(View child, - View commonParent, int[] shift) { - View parent = (View) child.getParent(); - shift[0] += child.getLeft(); - shift[1] += child.getTop(); - if (parent instanceof PagedView) { - PagedView page = (PagedView) parent; - shift[0] -= page.getScrollForPage(page.indexOfChild(child)); - } - - if (parent != commonParent) { - computeLocationRelativeToParentHelper(parent, commonParent, shift); - } - } - - @Thunk static final class ViewAnimState { - float x, y, scaleX, scaleY; - } -} diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index e1d292cbb..1c94950c6 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -107,6 +107,7 @@ import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dynamicui.ExtractedColors; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.WidgetsModel; @@ -363,7 +364,7 @@ public class Launcher extends Activity private UserEventDispatcher mUserEventDispatcher; - public FocusIndicatorView mFocusHandler; + public ViewGroupFocusHelper mFocusHandler; private boolean mRotationEnabled = false; @Thunk void setOrientation() { @@ -1340,8 +1341,9 @@ public class Launcher extends Activity */ private void setupViews() { mLauncherView = findViewById(R.id.launcher); - mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator); mDragLayer = (DragLayer) findViewById(R.id.drag_layer); + mFocusHandler = mDragLayer.getFocusIndicatorHelper(); + mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); mPageIndicator = (PageIndicatorLine) mDragLayer.findViewById(R.id.page_indicator); diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 95ab286d9..779bd05fc 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -52,6 +52,7 @@ import com.android.launcher3.LauncherTransitionable; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; +import com.android.launcher3.keyboard.FocusedItemDecorator; import com.android.launcher3.util.ComponentKey; import java.nio.charset.Charset; @@ -311,6 +312,10 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mAppsRecyclerView.addItemDecoration(mItemDecoration); } + FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView); + mAppsRecyclerView.addItemDecoration(focusedItemDecorator); + mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener()); + // Precalculate the prediction icon and normal icon sizes LayoutInflater layoutInflater = LayoutInflater.from(getContext()); final int widthMeasureSpec = MeasureSpec.makeMeasureSpec( diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 81a05a23b..4e591bb46 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -33,6 +33,7 @@ import android.support.v7.widget.RecyclerView; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnFocusChangeListener; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; @@ -348,6 +349,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. private BindViewCallback mBindViewCallback; private AllAppsSearchBarController mSearchController; + private OnFocusChangeListener mIconFocusListener; // The text to show when there are no search results and no market search handler. private String mEmptySearchMessage; @@ -412,6 +414,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. } } + public void setIconFocusListener(OnFocusChangeListener focusListener) { + mIconFocusListener = focusListener; + } + /** * Sets the last search query that was made, used to show when there are no results and to also * seed the intent for searching the market. @@ -461,26 +467,17 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. switch (viewType) { case SECTION_BREAK_VIEW_TYPE: return new ViewHolder(new View(parent.getContext())); - case ICON_VIEW_TYPE: { - BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( - R.layout.all_apps_icon, parent, false); - icon.setOnTouchListener(mTouchListener); - icon.setOnClickListener(mIconClickListener); - icon.setOnLongClickListener(mIconLongClickListener); - icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext()) - .getLongPressTimeout()); - icon.setFocusable(true); - return new ViewHolder(icon); - } + case ICON_VIEW_TYPE: case PREDICTION_ICON_VIEW_TYPE: { BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( - R.layout.all_apps_prediction_bar_icon, parent, false); + viewType == ICON_VIEW_TYPE ? R.layout.all_apps_icon : + R.layout.all_apps_prediction_bar_icon, parent, false); icon.setOnTouchListener(mTouchListener); icon.setOnClickListener(mIconClickListener); icon.setOnLongClickListener(mIconLongClickListener); icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext()) .getLongPressTimeout()); - icon.setFocusable(true); + icon.setOnFocusChangeListener(mIconFocusListener); return new ViewHolder(icon); } case EMPTY_SEARCH_VIEW_TYPE: diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 472da44ac..765ad64e9 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -61,6 +61,7 @@ import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.TouchController; @@ -111,6 +112,8 @@ public class DragLayer extends InsettableFrameLayout { // Related to adjacent page hints private final Rect mScrollChildPosition = new Rect(); + private final ViewGroupFocusHelper mFocusIndicatorHelper; + private boolean mInScrollArea; private boolean mShowPageHints; private Drawable mLeftHoverDrawable; @@ -144,6 +147,7 @@ public class DragLayer extends InsettableFrameLayout { mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active); mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active); mIsRtl = Utilities.isRtl(res); + mFocusIndicatorHelper = new ViewGroupFocusHelper(this); } public void setup(Launcher launcher, DragController dragController, @@ -157,6 +161,10 @@ public class DragLayer extends InsettableFrameLayout { onAccessibilityStateChanged(isAccessibilityEnabled); } + public ViewGroupFocusHelper getFocusIndicatorHelper() { + return mFocusIndicatorHelper; + } + @Override public boolean dispatchKeyEvent(KeyEvent event) { return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); @@ -966,6 +974,7 @@ public class DragLayer extends InsettableFrameLayout { canvas.restore(); } + mFocusIndicatorHelper.draw(canvas); super.dispatchDraw(canvas); } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index af9370773..93238de86 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -98,7 +98,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList private static final String TAG = "Launcher.Folder"; /** - * We avoid measuring {@link #mContentWrapper} with a 0 width or height, as this + * We avoid measuring {@link #mContent} with a 0 width or height, as this * results in CellLayout being measured as UNSPECIFIED, which it does not support. */ private static final int MIN_CONTENT_DIMEN = 5; @@ -147,7 +147,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList @Thunk FolderIcon mFolderIcon; @Thunk FolderPagedView mContent; - @Thunk View mContentWrapper; public ExtendedEditText mFolderName; private PageIndicatorDots mPageIndicator; @@ -226,7 +225,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList @Override protected void onFinishInflate() { super.onFinishInflate(); - mContentWrapper = findViewById(R.id.folder_content_wrapper); mContent = (FolderPagedView) findViewById(R.id.folder_content); mContent.setFolder(this); @@ -562,8 +560,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList reveal.setDuration(mMaterialExpandDuration); reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); - mContentWrapper.setAlpha(0f); - Animator iconsAlpha = ObjectAnimator.ofFloat(mContentWrapper, "alpha", 0f, 1f); + mContent.setAlpha(0f); + Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f); iconsAlpha.setDuration(mMaterialExpandDuration); iconsAlpha.setStartDelay(mMaterialExpandStagger); iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); @@ -581,12 +579,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList openFolderAnim = anim; - mContentWrapper.setLayerType(LAYER_TYPE_HARDWARE, null); + mContent.setLayerType(LAYER_TYPE_HARDWARE, null); mFooter.setLayerType(LAYER_TYPE_HARDWARE, null); onCompleteRunnable = new Runnable() { @Override public void run() { - mContentWrapper.setLayerType(LAYER_TYPE_NONE, null); + mContent.setLayerType(LAYER_TYPE_NONE, null); mFooter.setLayerType(LAYER_TYPE_NONE, null); } }; @@ -1122,7 +1120,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY); mContent.setFixedSize(contentWidth, contentHeight); - mContentWrapper.measure(contentAreaWidthSpec, contentAreaHeightSpec); + mContent.measure(contentAreaWidthSpec, contentAreaHeightSpec); if (mContent.getChildCount() > 0) { int cellIconGap = (mContent.getPageAt(0).getCellWidth() diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index d8b83ad6c..c56e4e5f9 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -18,6 +18,7 @@ package com.android.launcher3.folder; import android.annotation.SuppressLint; import android.content.Context; +import android.graphics.Canvas; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -30,7 +31,6 @@ import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener; -import com.android.launcher3.FocusIndicatorView; import com.android.launcher3.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; @@ -45,6 +45,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.pageindicators.PageIndicator; +import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -73,6 +74,7 @@ public class FolderPagedView extends PagedView { private final LayoutInflater mInflater; private final IconCache mIconCache; + private final ViewGroupFocusHelper mFocusIndicatorHelper; @Thunk final HashMap<View, Runnable> mPendingAnimations = new HashMap<>(); @@ -90,7 +92,6 @@ public class FolderPagedView extends PagedView { private int mGridCountY; private Folder mFolder; - private FocusIndicatorView mFocusIndicatorView; private PagedFolderKeyEventListener mKeyListener; private PageIndicator mPageIndicator; @@ -112,11 +113,11 @@ public class FolderPagedView extends PagedView { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); setEdgeGlowColor(getResources().getColor(R.color.folder_edge_effect_color)); + mFocusIndicatorHelper = new ViewGroupFocusHelper(this); } public void setFolder(Folder folder) { mFolder = folder; - mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator); mKeyListener = new PagedFolderKeyEventListener(folder); mPageIndicator = (PageIndicator) folder.findViewById(R.id.folder_page_indicator); } @@ -162,6 +163,12 @@ public class FolderPagedView extends PagedView { } } + @Override + protected void dispatchDraw(Canvas canvas) { + mFocusIndicatorHelper.draw(canvas); + super.dispatchDraw(canvas); + } + /** * Binds items to the layout. * @return list of items that could not be bound, probably because we hit the max size limit. @@ -226,7 +233,7 @@ public class FolderPagedView extends PagedView { textView.applyFromShortcutInfo(item, mIconCache); textView.setOnClickListener(mFolder); textView.setOnLongClickListener(mFolder); - textView.setOnFocusChangeListener(mFocusIndicatorView); + textView.setOnFocusChangeListener(mFocusIndicatorHelper); textView.setOnKeyListener(mKeyListener); textView.setLayoutParams(new CellLayout.LayoutParams( diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java new file mode 100644 index 000000000..7672f5a79 --- /dev/null +++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.keyboard; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.RectEvaluator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Build.VERSION_CODES; +import android.util.Property; +import android.view.View; +import android.view.View.OnFocusChangeListener; + +import com.android.launcher3.R; + +/** + * A helper class to draw background of a focused view. + */ +@TargetApi(VERSION_CODES.LOLLIPOP) +public abstract class FocusIndicatorHelper implements + OnFocusChangeListener, AnimatorUpdateListener { + + private static final float MIN_VISIBLE_ALPHA = 0.2f; + private static final long ANIM_DURATION = 150; + + public static final Property<FocusIndicatorHelper, Float> ALPHA = + new Property<FocusIndicatorHelper, Float>(Float.TYPE, "alpha") { + @Override + public void set(FocusIndicatorHelper object, Float value) { + object.setAlpha(value); + } + + @Override + public Float get(FocusIndicatorHelper object) { + return object.mAlpha; + } + }; + + public static final Property<FocusIndicatorHelper, Float> SHIFT = + new Property<FocusIndicatorHelper, Float>( + Float.TYPE, "shift") { + + @Override + public void set(FocusIndicatorHelper object, Float value) { + object.mShift = value; + } + + @Override + public Float get(FocusIndicatorHelper object) { + return object.mShift; + } + }; + + private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect()); + private static final Rect sTempRect1 = new Rect(); + private static final Rect sTempRect2 = new Rect(); + + private final View mContainer; + private final Paint mPaint; + private final int mMaxAlpha; + + private final Rect mDirtyRect = new Rect(); + private boolean mIsDirty = false; + + private View mLastFocusedView; + + private View mCurrentView; + private View mTargetView; + /** + * The fraction indicating the position of the focusRect between {@link #mCurrentView} + * & {@link #mTargetView} + */ + private float mShift; + + private ObjectAnimator mCurrentAnimation; + private float mAlpha; + + public FocusIndicatorHelper(View container) { + mContainer = container; + + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + int color = container.getResources().getColor(R.color.focused_background); + mMaxAlpha = Color.alpha(color); + mPaint.setColor(0xFF000000 | color); + + setAlpha(0); + mShift = 0; + } + + protected void setAlpha(float alpha) { + mAlpha = alpha; + mPaint.setAlpha((int) (mAlpha * mMaxAlpha)); + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + invalidateDirty(); + } + + protected void invalidateDirty() { + if (mIsDirty) { + mContainer.invalidate(mDirtyRect); + mIsDirty = false; + } + + Rect newRect = getDrawRect(); + if (newRect != null) { + mContainer.invalidate(newRect); + } + } + + public void draw(Canvas c) { + if (mAlpha > 0) { + Rect newRect = getDrawRect(); + if (newRect != null) { + mDirtyRect.set(newRect); + c.drawRect(mDirtyRect, mPaint); + mIsDirty = true; + } + } + } + + private Rect getDrawRect() { + if (mCurrentView != null) { + viewToRect(mCurrentView, sTempRect1); + + if (mShift > 0 && mTargetView != null) { + viewToRect(mTargetView, sTempRect2); + return RECT_EVALUATOR.evaluate(mShift, sTempRect1, sTempRect2); + } else { + return sTempRect1; + } + } + return null; + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + endCurrentAnimation(); + + if (mAlpha > MIN_VISIBLE_ALPHA) { + mTargetView = v; + + mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this, + PropertyValuesHolder.ofFloat(ALPHA, 1), + PropertyValuesHolder.ofFloat(SHIFT, 1)); + mCurrentAnimation.addListener(new ViewSetListener(v, true)); + } else { + setCurrentView(v); + + mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this, + PropertyValuesHolder.ofFloat(ALPHA, 1)); + } + + mLastFocusedView = v; + } else { + if (mLastFocusedView == v) { + mLastFocusedView = null; + endCurrentAnimation(); + mCurrentAnimation = ObjectAnimator.ofPropertyValuesHolder(this, + PropertyValuesHolder.ofFloat(ALPHA, 0)); + mCurrentAnimation.addListener(new ViewSetListener(null, false)); + } + } + + // invalidate once + invalidateDirty(); + + mLastFocusedView = hasFocus ? v : null; + if (mCurrentAnimation != null) { + mCurrentAnimation.addUpdateListener(this); + mCurrentAnimation.setDuration(ANIM_DURATION).start(); + } + } + + protected void endCurrentAnimation() { + if (mCurrentAnimation != null) { + mCurrentAnimation.cancel(); + mCurrentAnimation = null; + } + } + + protected void setCurrentView(View v) { + mCurrentView = v; + mShift = 0; + mTargetView = null; + } + + /** + * Gets the position of {@param v} relative to {@link #mContainer}. + */ + public abstract void viewToRect(View v, Rect outRect); + + private class ViewSetListener extends AnimatorListenerAdapter { + private final View mViewToSet; + private final boolean mCallOnCancel; + private boolean mCalled = false; + + public ViewSetListener(View v, boolean callOnCancel) { + mViewToSet = v; + mCallOnCancel = callOnCancel; + } + + @Override + public void onAnimationCancel(Animator animation) { + if (!mCallOnCancel) { + mCalled = true; + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (!mCalled) { + setCurrentView(mViewToSet); + mCalled = true; + } + } + } +} diff --git a/src/com/android/launcher3/keyboard/FocusedItemDecorator.java b/src/com/android/launcher3/keyboard/FocusedItemDecorator.java new file mode 100644 index 000000000..9c80b0f08 --- /dev/null +++ b/src/com/android/launcher3/keyboard/FocusedItemDecorator.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.keyboard; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.ItemDecoration; +import android.support.v7.widget.RecyclerView.State; +import android.view.View; +import android.view.View.OnFocusChangeListener; + +/** + * {@link ItemDecoration} for drawing and animating focused view background. + */ +public class FocusedItemDecorator extends ItemDecoration { + + private FocusIndicatorHelper mHelper; + + public FocusedItemDecorator(View container) { + mHelper = new FocusIndicatorHelper(container) { + + @Override + public void viewToRect(View v, Rect outRect) { + outRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); + } + }; + } + + public OnFocusChangeListener getFocusListener() { + return mHelper; + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, State state) { + mHelper.draw(c); + } +} diff --git a/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java b/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java new file mode 100644 index 000000000..bd5c06e5b --- /dev/null +++ b/src/com/android/launcher3/keyboard/ViewGroupFocusHelper.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.keyboard; + +import android.graphics.Rect; +import android.view.View; +import android.view.View.OnFocusChangeListener; + +import com.android.launcher3.PagedView; + +/** + * {@link FocusIndicatorHelper} for a generic view group. + */ +public class ViewGroupFocusHelper extends FocusIndicatorHelper { + + private final View mContainer; + + public ViewGroupFocusHelper(View container) { + super(container); + mContainer = container; + } + + @Override + public void viewToRect(View v, Rect outRect) { + outRect.left = 0; + outRect.top = 0; + + computeLocationRelativeToContainer(v, outRect); + + // If a view is scaled, its position will also shift accordingly. For optimization, only + // consider this for the last node. + outRect.left += (1 - v.getScaleX()) * v.getWidth() / 2; + outRect.top += (1 - v.getScaleY()) * v.getHeight() / 2; + + outRect.right = outRect.left + (int) (v.getScaleX() * v.getWidth()); + outRect.bottom = outRect.top + (int) (v.getScaleY() * v.getHeight()); + } + + private void computeLocationRelativeToContainer(View child, Rect outRect) { + View parent = (View) child.getParent(); + outRect.left += child.getLeft(); + outRect.top += child.getTop(); + + if (parent != mContainer) { + if (parent instanceof PagedView) { + PagedView page = (PagedView) parent; + outRect.left -= page.getScrollForPage(page.indexOfChild(child)); + } + + computeLocationRelativeToContainer(parent, outRect); + } + } + + /** + * Sets the alpha of this FocusIndicatorHelper to 0 when a view with this listener + * receives focus. + */ + public View.OnFocusChangeListener getHideIndicatorOnFocusListener() { + return new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + endCurrentAnimation(); + setCurrentView(null); + setAlpha(0); + invalidateDirty(); + } + } + }; + } +} |