diff --git a/res/drawable/focusable_view_bg.xml b/res/drawable/focusable_view_bg.xml deleted file mode 100644 index e156513efc..0000000000 --- a/res/drawable/focusable_view_bg.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml index 1147326f74..d193e2f6d7 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"> - - - - - - diff --git a/res/layout/all_apps_prediction_bar_icon.xml b/res/layout/all_apps_prediction_bar_icon.xml index f15aeaf75c..295b0b7083 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 8a1b7d0f3b..d95075058a 100644 --- a/res/layout/user_folder.xml +++ b/res/layout/user_folder.xml @@ -22,27 +22,14 @@ android:elevation="5dp" android:orientation="vertical" > - - - - - - - - + android:layout_height="match_parent" + android:paddingLeft="4dp" + android:paddingRight="4dp" + android:paddingTop="8dp" + launcher:pageIndicator="@+id/folder_page_indicator" /> - - - - - - - - + android:layout_height="match_parent" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingTop="8dp" + launcher:pageIndicator="@+id/folder_page_indicator" /> 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 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 e1d292cbbd..1c94950c63 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 95ab286d9b..779bd05fc9 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 81a05a23b3..4e591bb465 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 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 d8b83ad6c2..c56e4e5f99 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 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 0000000000..7672f5a79e --- /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 ALPHA = + new Property(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 SHIFT = + new Property( + 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 0000000000..9c80b0f085 --- /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 0000000000..bd5c06e5b1 --- /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(); + } + } + }; + } +}