FocusIndicatorView.java revision dcbcc86353e9ed52daac87f292aece667cd0ac71
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
19dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.content.Context;
20dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.graphics.Canvas;
21dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.util.AttributeSet;
22dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.util.Pair;
23dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.view.View;
24dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalimport android.view.ViewParent;
25dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
26dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyalpublic class FocusIndicatorView extends View implements View.OnFocusChangeListener {
27dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
28dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    // It can be any number >0. The view is resized using scaleX and scaleY.
29dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    static final int DEFAULT_LAYOUT_SIZE = 100;
30dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static final float MIN_VISIBLE_ALPHA = 0.2f;
31dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
32dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static final int[] sTempPos = new int[2];
33dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static final int[] sTempShift = new int[2];
34dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
35dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private final int[] mIndicatorPos = new int[2];
36dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private final int[] mTargetViewPos = new int[2];
37dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
38dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private View mLastFocusedView;
39dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private boolean mInitiated;
40dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
41dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private Pair<View, Boolean> mPendingCall;
42dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
43dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    public FocusIndicatorView(Context context) {
44dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        this(context, null);
45dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
46dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
47dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    public FocusIndicatorView(Context context, AttributeSet attrs) {
48dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        super(context, attrs);
49dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        setAlpha(0);
50dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        setBackgroundColor(getResources().getColor(R.color.focused_background));
51dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
52dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
53dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    @Override
54dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
55dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        super.onSizeChanged(w, h, oldw, oldh);
56dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
57dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        // Redraw if it is already showing. This avoids a bug where the height changes by a small
58dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        // amount on connecting/disconnecting a bluetooth keyboard.
59dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (mLastFocusedView != null) {
60dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            mPendingCall = Pair.create(mLastFocusedView, Boolean.TRUE);
61dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            invalidate();
62dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
63dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
64dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
65dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    @Override
66dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    public void onFocusChange(View v, boolean hasFocus) {
67dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        mPendingCall = null;
68dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (!mInitiated && (getWidth() == 0)) {
69dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            // View not yet laid out. Wait until the view is ready to be drawn, so that be can
70dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            // get the location on screen.
71dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            mPendingCall = Pair.create(v, hasFocus);
72dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            invalidate();
73dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            return;
74dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
75dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
76dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (!mInitiated) {
77dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            getLocationRelativeToParentPagedView(this, mIndicatorPos);
78dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            mInitiated = true;
79dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
80dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
81dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (hasFocus) {
82dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            int indicatorWidth = getWidth();
83dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            int indicatorHeight = getHeight();
84dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
85dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            float scaleX = v.getScaleX() * v.getWidth() / indicatorWidth;
86dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            float scaleY = v.getScaleY() * v.getHeight() / indicatorHeight;
87dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
88dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            getLocationRelativeToParentPagedView(v, mTargetViewPos);
89dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            float x = mTargetViewPos[0] - mIndicatorPos[0] - (1 - scaleX) * indicatorWidth / 2;
90dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            float y = mTargetViewPos[1] - mIndicatorPos[1] - (1 - scaleY) * indicatorHeight / 2;
91dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
92dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            if (getAlpha() > MIN_VISIBLE_ALPHA) {
93dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                animate()
94dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                .translationX(x)
95dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                .translationY(y)
96dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                .scaleX(scaleX)
97dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                .scaleY(scaleY)
98dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                .alpha(1);
99dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            } else {
100dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                setTranslationX(x);
101dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                setTranslationY(y);
102dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                setScaleX(scaleX);
103dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                setScaleY(scaleY);
104dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                animate().alpha(1);
105dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            }
106dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            mLastFocusedView = v;
107dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        } else {
108dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            if (mLastFocusedView == v) {
109dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                mLastFocusedView = null;
110dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal                animate().alpha(0);
111dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            }
112dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
113dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
114dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
115dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    @Override
116dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    protected void onDraw(Canvas canvas) {
117dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (mPendingCall != null) {
118dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            onFocusChange(mPendingCall.first, mPendingCall.second);
119dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
120dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
121dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
122dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    /**
123dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal     * Gets the location of a view relative in the window, off-setting any shift due to
124dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal     * page view scroll
125dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal     */
126dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static void getLocationRelativeToParentPagedView(View v, int[] pos) {
127dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        getPagedViewScrollShift(v, sTempShift);
128dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        v.getLocationInWindow(sTempPos);
129dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        pos[0] = sTempPos[0] + sTempShift[0];
130dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        pos[1] = sTempPos[1] + sTempShift[1];
131dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
132dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal
133dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    private static void getPagedViewScrollShift(View child, int[] shift) {
134dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        ViewParent parent = child.getParent();
135dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        if (parent instanceof PagedView) {
136dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            View parentView = (View) parent;
137dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            child.getLocationInWindow(sTempPos);
138dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            shift[0] = parentView.getPaddingLeft() - sTempPos[0];
139dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            shift[1] = -(int) child.getTranslationY();
140dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        } else if (parent instanceof View) {
141dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            getPagedViewScrollShift((View) parent, shift);
142dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        } else {
143dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal            shift[0] = shift[1] = 0;
144dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal        }
145dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal    }
146dcbcc86353e9ed52daac87f292aece667cd0ac71Sunny Goyal}
147