1dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal/*
2dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal * Copyright (C) 2011 The Android Open Source Project
3dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal *
4dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal * Licensed under the Apache License, Version 2.0 (the "License");
5dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal * you may not use this file except in compliance with the License.
6dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal * You may obtain a copy of the License at
7dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal *
8dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal *      http://www.apache.org/licenses/LICENSE-2.0
9dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal *
10dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal * Unless required by applicable law or agreed to in writing, software
11dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal * distributed under the License is distributed on an "AS IS" BASIS,
12dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal * See the License for the specific language governing permissions and
14dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal * limitations under the License.
15dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal */
16dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
17dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalpackage com.android.launcher3;
18dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
195edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyalimport android.animation.ObjectAnimator;
205edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyalimport android.animation.PropertyValuesHolder;
21dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.content.Context;
22dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.graphics.Canvas;
23dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.util.AttributeSet;
24dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.util.Pair;
25dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.view.View;
26dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.view.ViewParent;
27dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
28dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalpublic class FocusIndicatorView extends View implements View.OnFocusChangeListener {
29dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
30dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    // It can be any number >0. The view is resized using scaleX and scaleY.
31dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    static final int DEFAULT_LAYOUT_SIZE = 100;
32dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static final float MIN_VISIBLE_ALPHA = 0.2f;
335edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal    private static final long ANIM_DURATION = 150;
34dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
35dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static final int[] sTempPos = new int[2];
36dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static final int[] sTempShift = new int[2];
37dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
38dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private final int[] mIndicatorPos = new int[2];
39dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private final int[] mTargetViewPos = new int[2];
40dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
415edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal    private ObjectAnimator mCurrentAnimation;
425edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal    private ViewAnimState mTargetState;
435edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal
44dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private View mLastFocusedView;
45dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private boolean mInitiated;
46dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
47dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private Pair<View, Boolean> mPendingCall;
48dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
49dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    public FocusIndicatorView(Context context) {
50dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        this(context, null);
51dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
52dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
53dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    public FocusIndicatorView(Context context, AttributeSet attrs) {
54dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        super(context, attrs);
55dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        setAlpha(0);
56dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        setBackgroundColor(getResources().getColor(R.color.focused_background));
57dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
58dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
59dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    @Override
60dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
61dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        super.onSizeChanged(w, h, oldw, oldh);
62dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
63dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        // Redraw if it is already showing. This avoids a bug where the height changes by a small
64dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        // amount on connecting/disconnecting a bluetooth keyboard.
65dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (mLastFocusedView != null) {
66dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            mPendingCall = Pair.create(mLastFocusedView, Boolean.TRUE);
67dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            invalidate();
68dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
69dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
70dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
71dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    @Override
72dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    public void onFocusChange(View v, boolean hasFocus) {
73dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        mPendingCall = null;
74dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (!mInitiated && (getWidth() == 0)) {
75dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            // View not yet laid out. Wait until the view is ready to be drawn, so that be can
76dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            // get the location on screen.
77dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            mPendingCall = Pair.create(v, hasFocus);
78dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            invalidate();
79dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            return;
80dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
81dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
82dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (!mInitiated) {
83dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            getLocationRelativeToParentPagedView(this, mIndicatorPos);
84dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            mInitiated = true;
85dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
86dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
87dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (hasFocus) {
88dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            int indicatorWidth = getWidth();
89dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            int indicatorHeight = getHeight();
90dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
915edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            endCurrentAnimation();
925edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            ViewAnimState nextState = new ViewAnimState();
935edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            nextState.scaleX = v.getScaleX() * v.getWidth() / indicatorWidth;
945edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            nextState.scaleY = v.getScaleY() * v.getHeight() / indicatorHeight;
95dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
96dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            getLocationRelativeToParentPagedView(v, mTargetViewPos);
975edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            nextState.x = mTargetViewPos[0] - mIndicatorPos[0] - (1 - nextState.scaleX) * indicatorWidth / 2;
985edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            nextState.y = mTargetViewPos[1] - mIndicatorPos[1] - (1 - nextState.scaleY) * indicatorHeight / 2;
99dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
100dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            if (getAlpha() > MIN_VISIBLE_ALPHA) {
1015edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                mTargetState = nextState;
1025edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this,
1035edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                        PropertyValuesHolder.ofFloat(View.ALPHA, 1),
1045edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                        PropertyValuesHolder.ofFloat(View.TRANSLATION_X, mTargetState.x),
1055edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                        PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, mTargetState.y),
1065edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                        PropertyValuesHolder.ofFloat(View.SCALE_X, mTargetState.scaleX),
1075edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                        PropertyValuesHolder.ofFloat(View.SCALE_Y, mTargetState.scaleY));
108dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            } else {
1095edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                applyState(nextState);
1105edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this,
1115edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                        PropertyValuesHolder.ofFloat(View.ALPHA, 1));
112dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            }
113dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            mLastFocusedView = v;
114dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        } else {
115dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            if (mLastFocusedView == v) {
116dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                mLastFocusedView = null;
1175edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                endCurrentAnimation();
1185edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this,
1195edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal                        PropertyValuesHolder.ofFloat(View.ALPHA, 0));
120dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            }
121dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
1225edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        if (mCurrentAnimation != null) {
1235edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            mCurrentAnimation.setDuration(ANIM_DURATION).start();
1245edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        }
1255edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal    }
1265edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal
1275edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal    private void endCurrentAnimation() {
1285edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        if (mCurrentAnimation != null) {
1295edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            mCurrentAnimation.cancel();
1305edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            mCurrentAnimation = null;
1315edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        }
1325edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        if (mTargetState != null) {
1335edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            applyState(mTargetState);
1345edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal            mTargetState = null;
1355edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        }
1365edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal    }
1375edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal
1385edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal    private void applyState(ViewAnimState state) {
1395edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        setTranslationX(state.x);
1405edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        setTranslationY(state.y);
1415edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        setScaleX(state.scaleX);
1425edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        setScaleY(state.scaleY);
143dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
144dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
145dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    @Override
146dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    protected void onDraw(Canvas canvas) {
147dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (mPendingCall != null) {
148dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            onFocusChange(mPendingCall.first, mPendingCall.second);
149dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
150dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
151dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
152dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    /**
153dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal     * Gets the location of a view relative in the window, off-setting any shift due to
154dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal     * page view scroll
155dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal     */
156dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static void getLocationRelativeToParentPagedView(View v, int[] pos) {
157dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        getPagedViewScrollShift(v, sTempShift);
158dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        v.getLocationInWindow(sTempPos);
159dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        pos[0] = sTempPos[0] + sTempShift[0];
160dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        pos[1] = sTempPos[1] + sTempShift[1];
161dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
162dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
163dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static void getPagedViewScrollShift(View child, int[] shift) {
164dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        ViewParent parent = child.getParent();
165dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (parent instanceof PagedView) {
166dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            View parentView = (View) parent;
167dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            child.getLocationInWindow(sTempPos);
168dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            shift[0] = parentView.getPaddingLeft() - sTempPos[0];
169dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            shift[1] = -(int) child.getTranslationY();
170dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        } else if (parent instanceof View) {
171dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            getPagedViewScrollShift((View) parent, shift);
172dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        } else {
173dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            shift[0] = shift[1] = 0;
174dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
175dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
1765edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal
1775edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal    private static final class ViewAnimState {
1785edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal        float x, y, scaleX, scaleY;
1795edd6bfe1862a51036cea05698caeeccd11ec6d3Sunny Goyal    }
180dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal}
181