FocusIndicatorView.java revision dcbcc86353e9ed52daac87f292aece667cd0ac71
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher3;
18
19import android.content.Context;
20import android.graphics.Canvas;
21import android.util.AttributeSet;
22import android.util.Pair;
23import android.view.View;
24import android.view.ViewParent;
25
26public class FocusIndicatorView extends View implements View.OnFocusChangeListener {
27
28    // It can be any number >0. The view is resized using scaleX and scaleY.
29    static final int DEFAULT_LAYOUT_SIZE = 100;
30    private static final float MIN_VISIBLE_ALPHA = 0.2f;
31
32    private static final int[] sTempPos = new int[2];
33    private static final int[] sTempShift = new int[2];
34
35    private final int[] mIndicatorPos = new int[2];
36    private final int[] mTargetViewPos = new int[2];
37
38    private View mLastFocusedView;
39    private boolean mInitiated;
40
41    private Pair<View, Boolean> mPendingCall;
42
43    public FocusIndicatorView(Context context) {
44        this(context, null);
45    }
46
47    public FocusIndicatorView(Context context, AttributeSet attrs) {
48        super(context, attrs);
49        setAlpha(0);
50        setBackgroundColor(getResources().getColor(R.color.focused_background));
51    }
52
53    @Override
54    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
55        super.onSizeChanged(w, h, oldw, oldh);
56
57        // Redraw if it is already showing. This avoids a bug where the height changes by a small
58        // amount on connecting/disconnecting a bluetooth keyboard.
59        if (mLastFocusedView != null) {
60            mPendingCall = Pair.create(mLastFocusedView, Boolean.TRUE);
61            invalidate();
62        }
63    }
64
65    @Override
66    public void onFocusChange(View v, boolean hasFocus) {
67        mPendingCall = null;
68        if (!mInitiated && (getWidth() == 0)) {
69            // View not yet laid out. Wait until the view is ready to be drawn, so that be can
70            // get the location on screen.
71            mPendingCall = Pair.create(v, hasFocus);
72            invalidate();
73            return;
74        }
75
76        if (!mInitiated) {
77            getLocationRelativeToParentPagedView(this, mIndicatorPos);
78            mInitiated = true;
79        }
80
81        if (hasFocus) {
82            int indicatorWidth = getWidth();
83            int indicatorHeight = getHeight();
84
85            float scaleX = v.getScaleX() * v.getWidth() / indicatorWidth;
86            float scaleY = v.getScaleY() * v.getHeight() / indicatorHeight;
87
88            getLocationRelativeToParentPagedView(v, mTargetViewPos);
89            float x = mTargetViewPos[0] - mIndicatorPos[0] - (1 - scaleX) * indicatorWidth / 2;
90            float y = mTargetViewPos[1] - mIndicatorPos[1] - (1 - scaleY) * indicatorHeight / 2;
91
92            if (getAlpha() > MIN_VISIBLE_ALPHA) {
93                animate()
94                .translationX(x)
95                .translationY(y)
96                .scaleX(scaleX)
97                .scaleY(scaleY)
98                .alpha(1);
99            } else {
100                setTranslationX(x);
101                setTranslationY(y);
102                setScaleX(scaleX);
103                setScaleY(scaleY);
104                animate().alpha(1);
105            }
106            mLastFocusedView = v;
107        } else {
108            if (mLastFocusedView == v) {
109                mLastFocusedView = null;
110                animate().alpha(0);
111            }
112        }
113    }
114
115    @Override
116    protected void onDraw(Canvas canvas) {
117        if (mPendingCall != null) {
118            onFocusChange(mPendingCall.first, mPendingCall.second);
119        }
120    }
121
122    /**
123     * Gets the location of a view relative in the window, off-setting any shift due to
124     * page view scroll
125     */
126    private static void getLocationRelativeToParentPagedView(View v, int[] pos) {
127        getPagedViewScrollShift(v, sTempShift);
128        v.getLocationInWindow(sTempPos);
129        pos[0] = sTempPos[0] + sTempShift[0];
130        pos[1] = sTempPos[1] + sTempShift[1];
131    }
132
133    private static void getPagedViewScrollShift(View child, int[] shift) {
134        ViewParent parent = child.getParent();
135        if (parent instanceof PagedView) {
136            View parentView = (View) parent;
137            child.getLocationInWindow(sTempPos);
138            shift[0] = parentView.getPaddingLeft() - sTempPos[0];
139            shift[1] = -(int) child.getTranslationY();
140        } else if (parent instanceof View) {
141            getPagedViewScrollShift((View) parent, shift);
142        } else {
143            shift[0] = shift[1] = 0;
144        }
145    }
146}
147