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