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