Java程序  |  185行  |  6.72 KB

/*
 * 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 {@param parent}, 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();
        shift[0] += child.getLeft();
        shift[1] += child.getTop();
        if (parent instanceof PagedView) {
            PagedView page = (PagedView) parent;
            shift[0] -= page.getScrollForPage(page.indexOfChild(child));
        }

        if (parent != commonParent) {
            computeLocationRelativeToParentHelper(parent, commonParent, shift);
        }
    }

    @Thunk static final class ViewAnimState {
        float x, y, scaleX, scaleY;
    }
}