| /* |
| * 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.animation.ObjectAnimator; |
| import android.animation.PropertyValuesHolder; |
| import android.content.Context; |
| import android.graphics.Canvas; |
| import android.util.AttributeSet; |
| import android.util.Pair; |
| import android.view.View; |
| |
| import com.android.launcher3.util.Thunk; |
| |
| 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 long ANIM_DURATION = 150; |
| |
| private final int[] mIndicatorPos = new int[2]; |
| private final int[] mTargetViewPos = new int[2]; |
| |
| private ObjectAnimator mCurrentAnimation; |
| private ViewAnimState mTargetState; |
| |
| private View mLastFocusedView; |
| private boolean mInitiated; |
| |
| private Pair<View, Boolean> 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) { |
| // The parent view should always the a parent of the target view. |
| computeLocationRelativeToParent(this, (View) getParent(), mIndicatorPos); |
| mInitiated = true; |
| } |
| |
| if (hasFocus) { |
| int indicatorWidth = getWidth(); |
| int indicatorHeight = getHeight(); |
| |
| endCurrentAnimation(); |
| ViewAnimState nextState = new ViewAnimState(); |
| nextState.scaleX = v.getScaleX() * v.getWidth() / indicatorWidth; |
| nextState.scaleY = v.getScaleY() * v.getHeight() / indicatorHeight; |
| |
| computeLocationRelativeToParent(v, (View) getParent(), mTargetViewPos); |
| nextState.x = mTargetViewPos[0] - mIndicatorPos[0] - (1 - nextState.scaleX) * indicatorWidth / 2; |
| nextState.y = mTargetViewPos[1] - mIndicatorPos[1] - (1 - nextState.scaleY) * indicatorHeight / 2; |
| |
| if (getAlpha() > MIN_VISIBLE_ALPHA) { |
| mTargetState = nextState; |
| mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this, |
| PropertyValuesHolder.ofFloat(View.ALPHA, 1), |
| PropertyValuesHolder.ofFloat(View.TRANSLATION_X, mTargetState.x), |
| PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, mTargetState.y), |
| PropertyValuesHolder.ofFloat(View.SCALE_X, mTargetState.scaleX), |
| PropertyValuesHolder.ofFloat(View.SCALE_Y, mTargetState.scaleY)); |
| } else { |
| applyState(nextState); |
| mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this, |
| PropertyValuesHolder.ofFloat(View.ALPHA, 1)); |
| } |
| mLastFocusedView = v; |
| } else { |
| if (mLastFocusedView == v) { |
| mLastFocusedView = null; |
| endCurrentAnimation(); |
| mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this, |
| PropertyValuesHolder.ofFloat(View.ALPHA, 0)); |
| } |
| } |
| if (mCurrentAnimation != null) { |
| mCurrentAnimation.setDuration(ANIM_DURATION).start(); |
| } |
| } |
| |
| private void endCurrentAnimation() { |
| if (mCurrentAnimation != null) { |
| mCurrentAnimation.cancel(); |
| mCurrentAnimation = null; |
| } |
| if (mTargetState != null) { |
| applyState(mTargetState); |
| mTargetState = null; |
| } |
| } |
| |
| private void applyState(ViewAnimState state) { |
| setTranslationX(state.x); |
| setTranslationY(state.y); |
| setScaleX(state.scaleX); |
| setScaleY(state.scaleY); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| if (mPendingCall != null) { |
| onFocusChange(mPendingCall.first, mPendingCall.second); |
| } |
| } |
| |
| /** |
| * Computes the location of a view relative to {@link #mCommonParent}, off-setting |
| * any shift due to page view scroll. |
| * @param pos an array of two integers in which to hold the coordinates |
| */ |
| private static void computeLocationRelativeToParent(View v, View parent, int[] pos) { |
| pos[0] = pos[1] = 0; |
| computeLocationRelativeToParentHelper(v, parent, pos); |
| |
| // If a view is scaled, its position will also shift accordingly. For optimization, only |
| // consider this for the last node. |
| pos[0] += (1 - v.getScaleX()) * v.getWidth() / 2; |
| pos[1] += (1 - v.getScaleY()) * v.getHeight() / 2; |
| } |
| |
| private static void computeLocationRelativeToParentHelper(View child, |
| View commonParent, int[] shift) { |
| View parent = (View) child.getParent(); |
| if (parent instanceof PagedView) { |
| child = ((PagedView) parent).getPageAt(0); |
| } |
| |
| shift[0] += child.getLeft(); |
| shift[1] += child.getTop(); |
| |
| if (parent != commonParent) { |
| computeLocationRelativeToParentHelper(parent, commonParent, shift); |
| } |
| } |
| |
| @Thunk static final class ViewAnimState { |
| float x, y, scaleX, scaleY; |
| } |
| } |