GlowPadViewpublic class GlowPadView extends android.view.View A re-usable widget containing a center, outer ring and wave animation. |
Fields Summary |
---|
private static final String | TAG | private static final boolean | DEBUG | private static final int | STATE_IDLE | private static final int | STATE_START | private static final int | STATE_FIRST_TOUCH | private static final int | STATE_TRACKING | private static final int | STATE_SNAP | private static final int | STATE_FINISH | private static final float | SNAP_MARGIN_DEFAULT | private static final int | WAVE_ANIMATION_DURATION | private static final int | RETURN_TO_HOME_DELAY | private static final int | RETURN_TO_HOME_DURATION | private static final int | HIDE_ANIMATION_DELAY | private static final int | HIDE_ANIMATION_DURATION | private static final int | SHOW_ANIMATION_DURATION | private static final int | SHOW_ANIMATION_DELAY | private static final int | INITIAL_SHOW_HANDLE_DURATION | private static final int | REVEAL_GLOW_DELAY | private static final int | REVEAL_GLOW_DURATION | private static final float | TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED | private static final float | TARGET_SCALE_EXPANDED | private static final float | TARGET_SCALE_COLLAPSED | private static final float | RING_SCALE_EXPANDED | private static final float | RING_SCALE_COLLAPSED | private static final android.media.AudioAttributes | VIBRATION_ATTRIBUTES | private ArrayList | mTargetDrawables | private AnimationBundle | mWaveAnimations | private AnimationBundle | mTargetAnimations | private AnimationBundle | mGlowAnimations | private ArrayList | mTargetDescriptions | private ArrayList | mDirectionDescriptions | private OnTriggerListener | mOnTriggerListener | private TargetDrawable | mHandleDrawable | private TargetDrawable | mOuterRing | private android.os.Vibrator | mVibrator | private int | mFeedbackCount | private int | mVibrationDuration | private int | mGrabbedState | private int | mActiveTarget | private float | mGlowRadius | private float | mWaveCenterX | private float | mWaveCenterY | private int | mMaxTargetHeight | private int | mMaxTargetWidth | private float | mRingScaleFactor | private boolean | mAllowScaling | private float | mOuterRadius | private float | mSnapMargin | private float | mFirstItemOffset | private boolean | mMagneticTargets | private boolean | mDragging | private int | mNewTargetResources | private android.animation.Animator.AnimatorListener | mResetListener | private android.animation.Animator.AnimatorListener | mResetListenerWithPing | private android.animation.ValueAnimator.AnimatorUpdateListener | mUpdateListener | private boolean | mAnimatingTargets | private android.animation.Animator.AnimatorListener | mTargetUpdateListener | private int | mTargetResourceId | private int | mTargetDescriptionsResourceId | private int | mDirectionDescriptionsResourceId | private boolean | mAlwaysTrackFinger | private int | mHorizontalInset | private int | mVerticalInset | private int | mGravity | private boolean | mInitialLayout | private Tweener | mBackgroundAnimator | private PointCloud | mPointCloud | private float | mInnerRadius | private int | mPointerId |
Constructors Summary |
---|
public GlowPadView(android.content.Context context)
this(context, null);
| public GlowPadView(android.content.Context context, android.util.AttributeSet attrs)
super(context, attrs);
Resources res = context.getResources();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GlowPadView);
mInnerRadius = a.getDimension(R.styleable.GlowPadView_innerRadius, mInnerRadius);
mOuterRadius = a.getDimension(R.styleable.GlowPadView_outerRadius, mOuterRadius);
mSnapMargin = a.getDimension(R.styleable.GlowPadView_snapMargin, mSnapMargin);
mFirstItemOffset = (float) Math.toRadians(
a.getFloat(R.styleable.GlowPadView_firstItemOffset,
(float) Math.toDegrees(mFirstItemOffset)));
mVibrationDuration = a.getInt(R.styleable.GlowPadView_vibrationDuration,
mVibrationDuration);
mFeedbackCount = a.getInt(R.styleable.GlowPadView_feedbackCount,
mFeedbackCount);
mAllowScaling = a.getBoolean(R.styleable.GlowPadView_allowScaling, false);
TypedValue handle = a.peekValue(R.styleable.GlowPadView_handleDrawable);
mHandleDrawable = new TargetDrawable(res, handle != null ? handle.resourceId : 0);
mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
mOuterRing = new TargetDrawable(res,
getResourceId(a, R.styleable.GlowPadView_outerRingDrawable));
mAlwaysTrackFinger = a.getBoolean(R.styleable.GlowPadView_alwaysTrackFinger, false);
mMagneticTargets = a.getBoolean(R.styleable.GlowPadView_magneticTargets, mMagneticTargets);
int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable);
Drawable pointDrawable = pointId != 0 ? context.getDrawable(pointId) : null;
mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f);
mPointCloud = new PointCloud(pointDrawable);
mPointCloud.makePointCloud(mInnerRadius, mOuterRadius);
mPointCloud.glowManager.setRadius(mGlowRadius);
TypedValue outValue = new TypedValue();
// Read array of target drawables
if (a.getValue(R.styleable.GlowPadView_targetDrawables, outValue)) {
internalSetTargetResources(outValue.resourceId);
}
if (mTargetDrawables == null || mTargetDrawables.size() == 0) {
throw new IllegalStateException("Must specify at least one target drawable");
}
// Read array of target descriptions
if (a.getValue(R.styleable.GlowPadView_targetDescriptions, outValue)) {
final int resourceId = outValue.resourceId;
if (resourceId == 0) {
throw new IllegalStateException("Must specify target descriptions");
}
setTargetDescriptionsResourceId(resourceId);
}
// Read array of direction descriptions
if (a.getValue(R.styleable.GlowPadView_directionDescriptions, outValue)) {
final int resourceId = outValue.resourceId;
if (resourceId == 0) {
throw new IllegalStateException("Must specify direction descriptions");
}
setDirectionDescriptionsResourceId(resourceId);
}
mGravity = a.getInt(R.styleable.GlowPadView_gravity, Gravity.TOP);
a.recycle();
setVibrateEnabled(mVibrationDuration > 0);
assignDefaultsIfNeeded();
|
Methods Summary |
---|
private void | announceTargets()
StringBuilder utterance = new StringBuilder();
final int targetCount = mTargetDrawables.size();
for (int i = 0; i < targetCount; i++) {
String targetDescription = getTargetDescription(i);
String directionDescription = getDirectionDescription(i);
if (!TextUtils.isEmpty(targetDescription)
&& !TextUtils.isEmpty(directionDescription)) {
String text = String.format(directionDescription, targetDescription);
utterance.append(text);
}
}
if (utterance.length() > 0) {
announceForAccessibility(utterance.toString());
}
| private void | assignDefaultsIfNeeded()
if (mOuterRadius == 0.0f) {
mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
}
if (mSnapMargin == 0.0f) {
mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics());
}
if (mInnerRadius == 0.0f) {
mInnerRadius = mHandleDrawable.getWidth() / 10.0f;
}
| private void | computeInsets(int dx, int dy)
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
mHorizontalInset = 0;
break;
case Gravity.RIGHT:
mHorizontalInset = dx;
break;
case Gravity.CENTER_HORIZONTAL:
default:
mHorizontalInset = dx / 2;
break;
}
switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
mVerticalInset = 0;
break;
case Gravity.BOTTOM:
mVerticalInset = dy;
break;
case Gravity.CENTER_VERTICAL:
default:
mVerticalInset = dy / 2;
break;
}
| private float | computeScaleFactor(int desiredWidth, int desiredHeight, int actualWidth, int actualHeight)Given the desired width and height of the ring and the allocated width and height, compute
how much we need to scale the ring.
// Return unity if scaling is not allowed.
if (!mAllowScaling) return 1f;
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
float scaleX = 1f;
float scaleY = 1f;
// We use the gravity as a cue for whether we want to scale on a particular axis.
// We only scale to fit horizontally if we're not pinned to the left or right. Likewise,
// we only scale to fit vertically if we're not pinned to the top or bottom. In these
// cases, we want the ring to hang off the side or top/bottom, respectively.
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
case Gravity.RIGHT:
break;
case Gravity.CENTER_HORIZONTAL:
default:
if (desiredWidth > actualWidth) {
scaleX = (1f * actualWidth - mMaxTargetWidth) /
(desiredWidth - mMaxTargetWidth);
}
break;
}
switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
case Gravity.BOTTOM:
break;
case Gravity.CENTER_VERTICAL:
default:
if (desiredHeight > actualHeight) {
scaleY = (1f * actualHeight - mMaxTargetHeight) /
(desiredHeight - mMaxTargetHeight);
}
break;
}
return Math.min(scaleX, scaleY);
| private void | deactivateTargets()
final int count = mTargetDrawables.size();
for (int i = 0; i < count; i++) {
TargetDrawable target = mTargetDrawables.get(i);
target.setState(TargetDrawable.STATE_INACTIVE);
}
mActiveTarget = -1;
| private void | dispatchOnFinishFinalAnimation()
if (mOnTriggerListener != null) {
mOnTriggerListener.onFinishFinalAnimation();
}
| private void | dispatchTriggerEvent(int whichTarget)Dispatches a trigger event to listener. Ignored if a listener is not set.
vibrate();
if (mOnTriggerListener != null) {
mOnTriggerListener.onTrigger(this, whichTarget);
}
| private float | dist2(float dx, float dy)
return dx*dx + dy*dy;
| private void | doFinish()
final int activeTarget = mActiveTarget;
final boolean targetHit = activeTarget != -1;
if (targetHit) {
if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit);
highlightSelected(activeTarget);
// Inform listener of any active targets. Typically only one will be active.
hideGlow(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener);
dispatchTriggerEvent(activeTarget);
if (!mAlwaysTrackFinger) {
// Force ring and targets to finish animation to final expanded state
mTargetAnimations.stop();
}
} else {
// Animate handle back to the center based on current state.
hideGlow(HIDE_ANIMATION_DURATION, 0, 0.0f, mResetListenerWithPing);
hideTargets(true, false);
}
setGrabbedState(OnTriggerListener.NO_HANDLE);
| private void | dump()
Log.v(TAG, "Outer Radius = " + mOuterRadius);
Log.v(TAG, "SnapMargin = " + mSnapMargin);
Log.v(TAG, "FeedbackCount = " + mFeedbackCount);
Log.v(TAG, "VibrationDuration = " + mVibrationDuration);
Log.v(TAG, "GlowRadius = " + mGlowRadius);
Log.v(TAG, "WaveCenterX = " + mWaveCenterX);
Log.v(TAG, "WaveCenterY = " + mWaveCenterY);
| private float | getAngle(float alpha, int i)
return mFirstItemOffset + alpha * i;
| private java.lang.String | getDirectionDescription(int index)
if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) {
mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId);
if (mTargetDrawables.size() != mDirectionDescriptions.size()) {
Log.w(TAG, "The number of target drawables must be"
+ " equal to the number of direction descriptions.");
return null;
}
}
return mDirectionDescriptions.get(index);
| public int | getDirectionDescriptionsResourceId()Gets the resource id specifying the target direction descriptions.
return mDirectionDescriptionsResourceId;
| private int | getResourceId(android.content.res.TypedArray a, int id)
TypedValue tv = a.peekValue(id);
return tv == null ? 0 : tv.resourceId;
| public int | getResourceIdForTarget(int index)
final TargetDrawable drawable = mTargetDrawables.get(index);
return drawable == null ? 0 : drawable.getResourceId();
| private float | getRingHeight()
return mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius);
| private float | getRingWidth()
return mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius);
| private float | getScaledGlowRadiusSquared()
final float scaledTapRadius;
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mGlowRadius;
} else {
scaledTapRadius = mGlowRadius;
}
return square(scaledTapRadius);
| protected int | getScaledSuggestedMinimumHeight()This gets the suggested height accounting for the ring's scale factor.
return (int) (mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius)
+ mMaxTargetHeight);
| protected int | getScaledSuggestedMinimumWidth()This gets the suggested width accounting for the ring's scale factor.
return (int) (mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius)
+ mMaxTargetWidth);
| private float | getSliceAngle()
return (float) (-2.0f * Math.PI / mTargetDrawables.size());
| protected int | getSuggestedMinimumHeight()
// View should be large enough to contain the unlock ring + target and
// target drawable on either edge
return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight);
| protected int | getSuggestedMinimumWidth()
// View should be large enough to contain the background + handle and
// target drawable on either edge.
return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth);
| private java.lang.String | getTargetDescription(int index)
if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) {
mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId);
if (mTargetDrawables.size() != mTargetDescriptions.size()) {
Log.w(TAG, "The number of target drawables must be"
+ " equal to the number of target descriptions.");
return null;
}
}
return mTargetDescriptions.get(index);
| public int | getTargetDescriptionsResourceId()Gets the resource id specifying the target descriptions for accessibility.
return mTargetDescriptionsResourceId;
| public int | getTargetPosition(int resourceId)Gets the position of a target in the array that matches the given resource.
for (int i = 0; i < mTargetDrawables.size(); i++) {
final TargetDrawable target = mTargetDrawables.get(i);
if (target.getResourceId() == resourceId) {
return i; // should never be more than one match
}
}
return -1;
| public int | getTargetResourceId()
return mTargetResourceId;
| private void | handleCancel(android.view.MotionEvent event)
if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL");
// Drop the active target if canceled.
mActiveTarget = -1;
int actionIndex = event.findPointerIndex(mPointerId);
actionIndex = actionIndex == -1 ? 0 : actionIndex;
switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
| private void | handleDown(android.view.MotionEvent event)
int actionIndex = event.getActionIndex();
float eventX = event.getX(actionIndex);
float eventY = event.getY(actionIndex);
switchToState(STATE_START, eventX, eventY);
if (!trySwitchToFirstTouchState(eventX, eventY)) {
mDragging = false;
} else {
mPointerId = event.getPointerId(actionIndex);
updateGlowPosition(eventX, eventY);
}
| private void | handleMove(android.view.MotionEvent event)
int activeTarget = -1;
final int historySize = event.getHistorySize();
ArrayList<TargetDrawable> targets = mTargetDrawables;
int ntargets = targets.size();
float x = 0.0f;
float y = 0.0f;
float activeAngle = 0.0f;
int actionIndex = event.findPointerIndex(mPointerId);
if (actionIndex == -1) {
return; // no data for this pointer
}
for (int k = 0; k < historySize + 1; k++) {
float eventX = k < historySize ? event.getHistoricalX(actionIndex, k)
: event.getX(actionIndex);
float eventY = k < historySize ? event.getHistoricalY(actionIndex, k)
: event.getY(actionIndex);
// tx and ty are relative to wave center
float tx = eventX - mWaveCenterX;
float ty = eventY - mWaveCenterY;
float touchRadius = (float) Math.sqrt(dist2(tx, ty));
final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f;
float limitX = tx * scale;
float limitY = ty * scale;
double angleRad = Math.atan2(-ty, tx);
if (!mDragging) {
trySwitchToFirstTouchState(eventX, eventY);
}
if (mDragging) {
// For multiple targets, snap to the one that matches
final float snapRadius = mRingScaleFactor * mOuterRadius - mSnapMargin;
final float snapDistance2 = snapRadius * snapRadius;
// Find first target in range
for (int i = 0; i < ntargets; i++) {
TargetDrawable target = targets.get(i);
double targetMinRad = mFirstItemOffset + (i - 0.5) * 2 * Math.PI / ntargets;
double targetMaxRad = mFirstItemOffset + (i + 0.5) * 2 * Math.PI / ntargets;
if (target.isEnabled()) {
boolean angleMatches =
(angleRad > targetMinRad && angleRad <= targetMaxRad) ||
(angleRad + 2 * Math.PI > targetMinRad &&
angleRad + 2 * Math.PI <= targetMaxRad) ||
(angleRad - 2 * Math.PI > targetMinRad &&
angleRad - 2 * Math.PI <= targetMaxRad);
if (angleMatches && (dist2(tx, ty) > snapDistance2)) {
activeTarget = i;
activeAngle = (float) -angleRad;
}
}
}
}
x = limitX;
y = limitY;
}
if (!mDragging) {
return;
}
if (activeTarget != -1) {
switchToState(STATE_SNAP, x,y);
updateGlowPosition(x, y);
} else {
switchToState(STATE_TRACKING, x, y);
updateGlowPosition(x, y);
}
if (mActiveTarget != activeTarget) {
// Defocus the old target
if (mActiveTarget != -1) {
TargetDrawable target = targets.get(mActiveTarget);
if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
target.setState(TargetDrawable.STATE_INACTIVE);
}
if (mMagneticTargets) {
updateTargetPosition(mActiveTarget, mWaveCenterX, mWaveCenterY);
}
}
// Focus the new target
if (activeTarget != -1) {
TargetDrawable target = targets.get(activeTarget);
if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
target.setState(TargetDrawable.STATE_FOCUSED);
}
if (mMagneticTargets) {
updateTargetPosition(activeTarget, mWaveCenterX, mWaveCenterY, activeAngle);
}
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
String targetContentDescription = getTargetDescription(activeTarget);
announceForAccessibility(targetContentDescription);
}
}
}
mActiveTarget = activeTarget;
| private void | handleUp(android.view.MotionEvent event)
if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
int actionIndex = event.getActionIndex();
if (event.getPointerId(actionIndex) == mPointerId) {
switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
}
| private void | hideGlow(int duration, int delay, float finalAlpha, android.animation.Animator.AnimatorListener finishListener)
mGlowAnimations.cancel();
mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
"ease", Ease.Quart.easeOut,
"delay", delay,
"alpha", finalAlpha,
"x", 0.0f,
"y", 0.0f,
"onUpdate", mUpdateListener,
"onComplete", finishListener));
mGlowAnimations.start();
| private void | hideTargets(boolean animate, boolean expanded)
mTargetAnimations.cancel();
// Note: these animations should complete at the same time so that we can swap out
// the target assets asynchronously from the setTargetResources() call.
mAnimatingTargets = animate;
final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
final float targetScale = expanded ?
TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED;
final int length = mTargetDrawables.size();
final TimeInterpolator interpolator = Ease.Cubic.easeOut;
for (int i = 0; i < length; i++) {
TargetDrawable target = mTargetDrawables.get(i);
target.setState(TargetDrawable.STATE_INACTIVE);
mTargetAnimations.add(Tweener.to(target, duration,
"ease", interpolator,
"alpha", 0.0f,
"scaleX", targetScale,
"scaleY", targetScale,
"delay", delay,
"onUpdate", mUpdateListener));
}
float ringScaleTarget = expanded ?
RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED;
ringScaleTarget *= mRingScaleFactor;
mTargetAnimations.add(Tweener.to(mOuterRing, duration,
"ease", interpolator,
"alpha", 0.0f,
"scaleX", ringScaleTarget,
"scaleY", ringScaleTarget,
"delay", delay,
"onUpdate", mUpdateListener,
"onComplete", mTargetUpdateListener));
mTargetAnimations.start();
| private void | hideUnselected(int active)
for (int i = 0; i < mTargetDrawables.size(); i++) {
if (i != active) {
mTargetDrawables.get(i).setAlpha(0.0f);
}
}
| private void | highlightSelected(int activeTarget)
// Highlight the given target and fade others
mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
hideUnselected(activeTarget);
| private void | internalSetTargetResources(int resourceId)
final ArrayList<TargetDrawable> targets = loadDrawableArray(resourceId);
mTargetDrawables = targets;
mTargetResourceId = resourceId;
int maxWidth = mHandleDrawable.getWidth();
int maxHeight = mHandleDrawable.getHeight();
final int count = targets.size();
for (int i = 0; i < count; i++) {
TargetDrawable target = targets.get(i);
maxWidth = Math.max(maxWidth, target.getWidth());
maxHeight = Math.max(maxHeight, target.getHeight());
}
if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) {
mMaxTargetWidth = maxWidth;
mMaxTargetHeight = maxHeight;
requestLayout(); // required to resize layout and call updateTargetPositions()
} else {
updateTargetPositions(mWaveCenterX, mWaveCenterY);
updatePointCloudPosition(mWaveCenterX, mWaveCenterY);
}
| private java.util.ArrayList | loadDescriptions(int resourceId)
TypedArray array = getContext().getResources().obtainTypedArray(resourceId);
final int count = array.length();
ArrayList<String> targetContentDescriptions = new ArrayList<String>(count);
for (int i = 0; i < count; i++) {
String contentDescription = array.getString(i);
targetContentDescriptions.add(contentDescription);
}
array.recycle();
return targetContentDescriptions;
| private java.util.ArrayList | loadDrawableArray(int resourceId)
Resources res = getContext().getResources();
TypedArray array = res.obtainTypedArray(resourceId);
final int count = array.length();
ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count);
for (int i = 0; i < count; i++) {
TypedValue value = array.peekValue(i);
TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0);
drawables.add(target);
}
array.recycle();
return drawables;
| protected void | onDraw(android.graphics.Canvas canvas)
mPointCloud.draw(canvas);
mOuterRing.draw(canvas);
final int ntargets = mTargetDrawables.size();
for (int i = 0; i < ntargets; i++) {
TargetDrawable target = mTargetDrawables.get(i);
if (target != null) {
target.draw(canvas);
}
}
mHandleDrawable.draw(canvas);
| public boolean | onHoverEvent(android.view.MotionEvent event)
if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
event.setAction(MotionEvent.ACTION_DOWN);
break;
case MotionEvent.ACTION_HOVER_MOVE:
event.setAction(MotionEvent.ACTION_MOVE);
break;
case MotionEvent.ACTION_HOVER_EXIT:
event.setAction(MotionEvent.ACTION_UP);
break;
}
onTouchEvent(event);
event.setAction(action);
}
super.onHoverEvent(event);
return true;
| protected void | onLayout(boolean changed, int left, int top, int right, int bottom)
super.onLayout(changed, left, top, right, bottom);
final int width = right - left;
final int height = bottom - top;
// Target placement width/height. This puts the targets on the greater of the ring
// width or the specified outer radius.
final float placementWidth = getRingWidth();
final float placementHeight = getRingHeight();
float newWaveCenterX = mHorizontalInset
+ Math.max(width, mMaxTargetWidth + placementWidth) / 2;
float newWaveCenterY = mVerticalInset
+ Math.max(height, + mMaxTargetHeight + placementHeight) / 2;
if (mInitialLayout) {
stopAndHideWaveAnimation();
hideTargets(false, false);
mInitialLayout = false;
}
mOuterRing.setPositionX(newWaveCenterX);
mOuterRing.setPositionY(newWaveCenterY);
mPointCloud.setScale(mRingScaleFactor);
mHandleDrawable.setPositionX(newWaveCenterX);
mHandleDrawable.setPositionY(newWaveCenterY);
updateTargetPositions(newWaveCenterX, newWaveCenterY);
updatePointCloudPosition(newWaveCenterX, newWaveCenterY);
updateGlowPosition(newWaveCenterX, newWaveCenterY);
mWaveCenterX = newWaveCenterX;
mWaveCenterY = newWaveCenterY;
if (DEBUG) dump();
| protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
final int minimumWidth = getSuggestedMinimumWidth();
final int minimumHeight = getSuggestedMinimumHeight();
int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
mRingScaleFactor = computeScaleFactor(minimumWidth, minimumHeight,
computedWidth, computedHeight);
int scaledWidth = getScaledSuggestedMinimumWidth();
int scaledHeight = getScaledSuggestedMinimumHeight();
computeInsets(computedWidth - scaledWidth, computedHeight - scaledHeight);
setMeasuredDimension(computedWidth, computedHeight);
| public boolean | onTouchEvent(android.view.MotionEvent event)
final int action = event.getActionMasked();
boolean handled = false;
switch (action) {
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_DOWN:
if (DEBUG) Log.v(TAG, "*** DOWN ***");
handleDown(event);
handleMove(event);
handled = true;
break;
case MotionEvent.ACTION_MOVE:
if (DEBUG) Log.v(TAG, "*** MOVE ***");
handleMove(event);
handled = true;
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
if (DEBUG) Log.v(TAG, "*** UP ***");
handleMove(event);
handleUp(event);
handled = true;
break;
case MotionEvent.ACTION_CANCEL:
if (DEBUG) Log.v(TAG, "*** CANCEL ***");
handleMove(event);
handleCancel(event);
handled = true;
break;
}
invalidate();
return handled ? true : super.onTouchEvent(event);
| public void | ping()Starts wave animation.
if (mFeedbackCount > 0) {
boolean doWaveAnimation = true;
final AnimationBundle waveAnimations = mWaveAnimations;
// Don't do a wave if there's already one in progress
if (waveAnimations.size() > 0 && waveAnimations.get(0).animator.isRunning()) {
long t = waveAnimations.get(0).animator.getCurrentPlayTime();
if (t < WAVE_ANIMATION_DURATION/2) {
doWaveAnimation = false;
}
}
if (doWaveAnimation) {
startWaveAnimation();
}
}
| private boolean | replaceTargetDrawables(android.content.res.Resources res, int existingResourceId, int newResourceId)
if (existingResourceId == 0 || newResourceId == 0) {
return false;
}
boolean result = false;
final ArrayList<TargetDrawable> drawables = mTargetDrawables;
final int size = drawables.size();
for (int i = 0; i < size; i++) {
final TargetDrawable target = drawables.get(i);
if (target != null && target.getResourceId() == existingResourceId) {
target.setDrawable(res, newResourceId);
result = true;
}
}
if (result) {
requestLayout(); // in case any given drawable's size changes
}
return result;
| public boolean | replaceTargetDrawablesIfPresent(android.content.ComponentName component, java.lang.String name, int existingResId)Searches the given package for a resource to use to replace the Drawable on the
target with the given resource id
if (existingResId == 0) return false;
boolean replaced = false;
if (component != null) {
try {
PackageManager packageManager = mContext.getPackageManager();
// Look for the search icon specified in the activity meta-data
Bundle metaData = packageManager.getActivityInfo(
component, PackageManager.GET_META_DATA).metaData;
if (metaData != null) {
int iconResId = metaData.getInt(name);
if (iconResId != 0) {
Resources res = packageManager.getResourcesForActivity(component);
replaced = replaceTargetDrawables(res, existingResId, iconResId);
}
}
} catch (NameNotFoundException e) {
Log.w(TAG, "Failed to swap drawable; "
+ component.flattenToShortString() + " not found", e);
} catch (Resources.NotFoundException nfe) {
Log.w(TAG, "Failed to swap drawable from "
+ component.flattenToShortString(), nfe);
}
}
if (!replaced) {
// Restore the original drawable
replaceTargetDrawables(mContext.getResources(), existingResId, existingResId);
}
return replaced;
| public void | reset(boolean animate)Resets the widget to default state and cancels all animation. If animate is 'true', will
animate objects into place. Otherwise, objects will snap back to place.
mGlowAnimations.stop();
mTargetAnimations.stop();
startBackgroundAnimation(0, 0.0f);
stopAndHideWaveAnimation();
hideTargets(animate, false);
hideGlow(0, 0, 0.0f, null);
Tweener.reset();
| private int | resolveMeasured(int measureSpec, int desired)
int result = 0;
int specSize = MeasureSpec.getSize(measureSpec);
switch (MeasureSpec.getMode(measureSpec)) {
case MeasureSpec.UNSPECIFIED:
result = desired;
break;
case MeasureSpec.AT_MOST:
result = Math.min(specSize, desired);
break;
case MeasureSpec.EXACTLY:
default:
result = specSize;
}
return result;
| public void | resumeAnimations()
mWaveAnimations.setSuspended(false);
mTargetAnimations.setSuspended(false);
mGlowAnimations.setSuspended(false);
mWaveAnimations.start();
mTargetAnimations.start();
mGlowAnimations.start();
| public void | setDirectionDescriptionsResourceId(int resourceId)Sets the resource id specifying the target direction descriptions for accessibility.
mDirectionDescriptionsResourceId = resourceId;
if (mDirectionDescriptions != null) {
mDirectionDescriptions.clear();
}
| public void | setEnableTarget(int resourceId, boolean enabled)
for (int i = 0; i < mTargetDrawables.size(); i++) {
final TargetDrawable target = mTargetDrawables.get(i);
if (target.getResourceId() == resourceId) {
target.setEnabled(enabled);
break; // should never be more than one match
}
}
| private void | setGrabbedState(int newState)Sets the current grabbed state, and dispatches a grabbed state change
event to our listener.
if (newState != mGrabbedState) {
if (newState != OnTriggerListener.NO_HANDLE) {
vibrate();
}
mGrabbedState = newState;
if (mOnTriggerListener != null) {
if (newState == OnTriggerListener.NO_HANDLE) {
mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE);
} else {
mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE);
}
mOnTriggerListener.onGrabbedStateChange(this, newState);
}
}
| public void | setOnTriggerListener(com.android.internal.widget.multiwaveview.GlowPadView$OnTriggerListener listener)
mOnTriggerListener = listener;
| public void | setTargetDescriptionsResourceId(int resourceId)Sets the resource id specifying the target descriptions for accessibility.
mTargetDescriptionsResourceId = resourceId;
if (mTargetDescriptions != null) {
mTargetDescriptions.clear();
}
| public void | setTargetResources(int resourceId)Loads an array of drawables from the given resourceId.
if (mAnimatingTargets) {
// postpone this change until we return to the initial state
mNewTargetResources = resourceId;
} else {
internalSetTargetResources(resourceId);
}
| public void | setVibrateEnabled(boolean enabled)Enable or disable vibrate on touch.
if (enabled && mVibrator == null) {
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
} else {
mVibrator = null;
}
| private void | showGlow(int duration, int delay, float finalAlpha, android.animation.Animator.AnimatorListener finishListener)
mGlowAnimations.cancel();
mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
"ease", Ease.Cubic.easeIn,
"delay", delay,
"alpha", finalAlpha,
"onUpdate", mUpdateListener,
"onComplete", finishListener));
mGlowAnimations.start();
| private void | showTargets(boolean animate)
mTargetAnimations.stop();
mAnimatingTargets = animate;
final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
final int duration = animate ? SHOW_ANIMATION_DURATION : 0;
final int length = mTargetDrawables.size();
for (int i = 0; i < length; i++) {
TargetDrawable target = mTargetDrawables.get(i);
target.setState(TargetDrawable.STATE_INACTIVE);
mTargetAnimations.add(Tweener.to(target, duration,
"ease", Ease.Cubic.easeOut,
"alpha", 1.0f,
"scaleX", 1.0f,
"scaleY", 1.0f,
"delay", delay,
"onUpdate", mUpdateListener));
}
float ringScale = mRingScaleFactor * RING_SCALE_EXPANDED;
mTargetAnimations.add(Tweener.to(mOuterRing, duration,
"ease", Ease.Cubic.easeOut,
"alpha", 1.0f,
"scaleX", ringScale,
"scaleY", ringScale,
"delay", delay,
"onUpdate", mUpdateListener,
"onComplete", mTargetUpdateListener));
mTargetAnimations.start();
| private float | square(float d)
return d * d;
| private void | startBackgroundAnimation(int duration, float alpha)
final Drawable background = getBackground();
if (mAlwaysTrackFinger && background != null) {
if (mBackgroundAnimator != null) {
mBackgroundAnimator.animator.cancel();
}
mBackgroundAnimator = Tweener.to(background, duration,
"ease", Ease.Cubic.easeIn,
"alpha", (int)(255.0f * alpha),
"delay", SHOW_ANIMATION_DELAY);
mBackgroundAnimator.animator.start();
}
| private void | startWaveAnimation()
mWaveAnimations.cancel();
mPointCloud.waveManager.setAlpha(1.0f);
mPointCloud.waveManager.setRadius(mHandleDrawable.getWidth()/2.0f);
mWaveAnimations.add(Tweener.to(mPointCloud.waveManager, WAVE_ANIMATION_DURATION,
"ease", Ease.Quad.easeOut,
"delay", 0,
"radius", 2.0f * mOuterRadius,
"onUpdate", mUpdateListener,
"onComplete",
new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animator) {
mPointCloud.waveManager.setRadius(0.0f);
mPointCloud.waveManager.setAlpha(0.0f);
}
}));
mWaveAnimations.start();
| private void | stopAndHideWaveAnimation()
mWaveAnimations.cancel();
mPointCloud.waveManager.setAlpha(0.0f);
| public void | suspendAnimations()
mWaveAnimations.setSuspended(true);
mTargetAnimations.setSuspended(true);
mGlowAnimations.setSuspended(true);
| private void | switchToState(int state, float x, float y)
switch (state) {
case STATE_IDLE:
deactivateTargets();
hideGlow(0, 0, 0.0f, null);
startBackgroundAnimation(0, 0.0f);
mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
mHandleDrawable.setAlpha(1.0f);
break;
case STATE_START:
startBackgroundAnimation(0, 0.0f);
break;
case STATE_FIRST_TOUCH:
mHandleDrawable.setAlpha(0.0f);
deactivateTargets();
showTargets(true);
startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f);
setGrabbedState(OnTriggerListener.CENTER_HANDLE);
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
announceTargets();
}
break;
case STATE_TRACKING:
mHandleDrawable.setAlpha(0.0f);
showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 1.0f, null);
break;
case STATE_SNAP:
// TODO: Add transition states (see list_selector_background_transition.xml)
mHandleDrawable.setAlpha(0.0f);
showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 0.0f, null);
break;
case STATE_FINISH:
doFinish();
break;
}
| private boolean | trySwitchToFirstTouchState(float x, float y)
final float tx = x - mWaveCenterX;
final float ty = y - mWaveCenterY;
if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledGlowRadiusSquared()) {
if (DEBUG) Log.v(TAG, "** Handle HIT");
switchToState(STATE_FIRST_TOUCH, x, y);
updateGlowPosition(tx, ty);
mDragging = true;
return true;
}
return false;
| private void | updateGlowPosition(float x, float y)
float dx = x - mOuterRing.getX();
float dy = y - mOuterRing.getY();
dx *= 1f / mRingScaleFactor;
dy *= 1f / mRingScaleFactor;
mPointCloud.glowManager.setX(mOuterRing.getX() + dx);
mPointCloud.glowManager.setY(mOuterRing.getY() + dy);
| private void | updatePointCloudPosition(float centerX, float centerY)
mPointCloud.setCenter(centerX, centerY);
| private void | updateTargetPosition(int i, float centerX, float centerY)
final float angle = getAngle(getSliceAngle(), i);
updateTargetPosition(i, centerX, centerY, angle);
| private void | updateTargetPosition(int i, float centerX, float centerY, float angle)
final float placementRadiusX = getRingWidth() / 2;
final float placementRadiusY = getRingHeight() / 2;
if (i >= 0) {
ArrayList<TargetDrawable> targets = mTargetDrawables;
final TargetDrawable targetIcon = targets.get(i);
targetIcon.setPositionX(centerX);
targetIcon.setPositionY(centerY);
targetIcon.setX(placementRadiusX * (float) Math.cos(angle));
targetIcon.setY(placementRadiusY * (float) Math.sin(angle));
}
| private void | updateTargetPositions(float centerX, float centerY)
updateTargetPositions(centerX, centerY, false);
| private void | updateTargetPositions(float centerX, float centerY, boolean skipActive)
final int size = mTargetDrawables.size();
final float alpha = getSliceAngle();
// Reposition the target drawables if the view changed.
for (int i = 0; i < size; i++) {
if (!skipActive || i != mActiveTarget) {
updateTargetPosition(i, centerX, centerY, getAngle(alpha, i));
}
}
| private void | vibrate()
final boolean hapticEnabled = Settings.System.getIntForUser(
mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
UserHandle.USER_CURRENT) != 0;
if (mVibrator != null && hapticEnabled) {
mVibrator.vibrate(mVibrationDuration, VIBRATION_ATTRIBUTES);
}
|
|