/* * 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.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.util.Pair; import android.view.View; import android.view.ViewParent; 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 int[] sTempPos = new int[2]; private static final int[] sTempShift = new int[2]; private final int[] mIndicatorPos = new int[2]; private final int[] mTargetViewPos = new int[2]; private View mLastFocusedView; private boolean mInitiated; 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)); } @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(); } } @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) { getLocationRelativeToParentPagedView(this, mIndicatorPos); mInitiated = true; } if (hasFocus) { int indicatorWidth = getWidth(); int indicatorHeight = getHeight(); float scaleX = v.getScaleX() * v.getWidth() / indicatorWidth; float scaleY = v.getScaleY() * v.getHeight() / indicatorHeight; getLocationRelativeToParentPagedView(v, mTargetViewPos); float x = mTargetViewPos[0] - mIndicatorPos[0] - (1 - scaleX) * indicatorWidth / 2; float y = mTargetViewPos[1] - mIndicatorPos[1] - (1 - scaleY) * indicatorHeight / 2; if (getAlpha() > MIN_VISIBLE_ALPHA) { animate() .translationX(x) .translationY(y) .scaleX(scaleX) .scaleY(scaleY) .alpha(1); } else { setTranslationX(x); setTranslationY(y); setScaleX(scaleX); setScaleY(scaleY); animate().alpha(1); } mLastFocusedView = v; } else { if (mLastFocusedView == v) { mLastFocusedView = null; animate().alpha(0); } } } @Override protected void onDraw(Canvas canvas) { if (mPendingCall != null) { onFocusChange(mPendingCall.first, mPendingCall.second); } } /** * Gets the location of a view relative in the window, off-setting any shift due to * page view scroll */ private static void getLocationRelativeToParentPagedView(View v, int[] pos) { getPagedViewScrollShift(v, sTempShift); v.getLocationInWindow(sTempPos); pos[0] = sTempPos[0] + sTempShift[0]; pos[1] = sTempPos[1] + sTempShift[1]; } private static void getPagedViewScrollShift(View child, int[] shift) { ViewParent parent = child.getParent(); if (parent instanceof PagedView) { View parentView = (View) parent; child.getLocationInWindow(sTempPos); shift[0] = parentView.getPaddingLeft() - sTempPos[0]; shift[1] = -(int) child.getTranslationY(); } else if (parent instanceof View) { getPagedViewScrollShift((View) parent, shift); } else { shift[0] = shift[1] = 0; } } }