WaveViewpublic class WaveView extends android.view.View implements ValueAnimator.AnimatorUpdateListenerA special widget containing a center and outer ring. Moving the center ring to the outer ring
causes an event that can be caught by implementing OnTriggerListener. |
Fields Summary |
---|
private static final String | TAG | private static final boolean | DBG | private static final int | WAVE_COUNT | private static final long | VIBRATE_SHORT | private static final long | VIBRATE_LONG | private static final int | STATE_RESET_LOCK | private static final int | STATE_READY | private static final int | STATE_START_ATTEMPT | private static final int | STATE_ATTEMPTING | private static final int | STATE_UNLOCK_ATTEMPT | private static final int | STATE_UNLOCK_SUCCESS | private static final long | DURATION | private static final long | FINAL_DURATION | private static final long | RING_DELAY | private static final long | FINAL_DELAY | private static final long | SHORT_DELAY | private static final long | WAVE_DURATION | private static final long | RESET_TIMEOUT | private static final long | DELAY_INCREMENT | private static final long | DELAY_INCREMENT2 | private static final long | WAVE_DELAY | private static final float | GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLEDThe scale by which to multiply the unlock handle width to compute the radius
in which it can be grabbed when accessibility is disabled. | private static final float | GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLEDThe scale by which to multiply the unlock handle width to compute the radius
in which it can be grabbed when accessibility is enabled (more generous). | private static final android.media.AudioAttributes | VIBRATION_ATTRIBUTES | private android.os.Vibrator | mVibrator | private OnTriggerListener | mOnTriggerListener | private ArrayList | mDrawables | private ArrayList | mLightWaves | private boolean | mFingerDown | private float | mRingRadius | private int | mSnapRadius | private int | mWaveCount | private long | mWaveTimerDelay | private int | mCurrentWave | private float | mLockCenterX | private float | mLockCenterY | private float | mMouseX | private float | mMouseY | private DrawableHolder | mUnlockRing | private DrawableHolder | mUnlockDefault | private DrawableHolder | mUnlockHalo | private int | mLockState | private int | mGrabbedState | private boolean | mWavesRunning | private boolean | mFinishWaves | private final Runnable | mLockTimerActions | private final Runnable | mAddWaveAction |
Constructors Summary |
---|
public WaveView(android.content.Context context)
this(context, null);
| public WaveView(android.content.Context context, android.util.AttributeSet attrs)
super(context, attrs);
// TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WaveView);
// mOrientation = a.getInt(R.styleable.WaveView_orientation, HORIZONTAL);
// a.recycle();
initDrawables();
|
Methods Summary |
---|
private void | announceUnlockHandle()Announces the unlock handle if accessibility is enabled.
setContentDescription(mContext.getString(R.string.description_target_unlock_tablet));
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
setContentDescription(null);
| android.graphics.drawable.BitmapDrawable | createDrawable(int resId)
Resources res = getResources();
Bitmap bitmap = BitmapFactory.decodeResource(res, resId);
return new BitmapDrawable(res, bitmap);
| private void | dispatchTriggerEvent(int whichHandle)Dispatches a trigger event to listener. Ignored if a listener is not set.
vibrate(VIBRATE_LONG);
if (mOnTriggerListener != null) {
mOnTriggerListener.onTrigger(this, whichHandle);
}
| private float | getScaledGrabHandleRadius()
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mUnlockHalo.getWidth();
} else {
return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED * mUnlockHalo.getWidth();
}
| protected int | getSuggestedMinimumHeight()
// View should be large enough to contain the unlock ring + halo
return mUnlockRing.getHeight() + mUnlockHalo.getHeight();
| protected int | getSuggestedMinimumWidth()
// View should be large enough to contain the unlock ring + halo
return mUnlockRing.getWidth() + mUnlockHalo.getWidth();
| private void | initDrawables()
mUnlockRing = new DrawableHolder(createDrawable(R.drawable.unlock_ring));
mUnlockRing.setX(mLockCenterX);
mUnlockRing.setY(mLockCenterY);
mUnlockRing.setScaleX(0.1f);
mUnlockRing.setScaleY(0.1f);
mUnlockRing.setAlpha(0.0f);
mDrawables.add(mUnlockRing);
mUnlockDefault = new DrawableHolder(createDrawable(R.drawable.unlock_default));
mUnlockDefault.setX(mLockCenterX);
mUnlockDefault.setY(mLockCenterY);
mUnlockDefault.setScaleX(0.1f);
mUnlockDefault.setScaleY(0.1f);
mUnlockDefault.setAlpha(0.0f);
mDrawables.add(mUnlockDefault);
mUnlockHalo = new DrawableHolder(createDrawable(R.drawable.unlock_halo));
mUnlockHalo.setX(mLockCenterX);
mUnlockHalo.setY(mLockCenterY);
mUnlockHalo.setScaleX(0.1f);
mUnlockHalo.setScaleY(0.1f);
mUnlockHalo.setAlpha(0.0f);
mDrawables.add(mUnlockHalo);
BitmapDrawable wave = createDrawable(R.drawable.unlock_wave);
for (int i = 0; i < mWaveCount; i++) {
DrawableHolder holder = new DrawableHolder(wave);
mLightWaves.add(holder);
holder.setAlpha(0.0f);
}
| public void | onAnimationUpdate(android.animation.ValueAnimator animation)
invalidate();
| protected void | onDraw(android.graphics.Canvas canvas)
waveUpdateFrame(mMouseX, mMouseY, mFingerDown);
for (int i = 0; i < mDrawables.size(); ++i) {
mDrawables.get(i).draw(canvas);
}
for (int i = 0; i < mLightWaves.size(); ++i) {
mLightWaves.get(i).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);
}
return super.onHoverEvent(event);
| protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthSpecMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSpecSize, getSuggestedMinimumWidth());
} else if (widthSpecMode == MeasureSpec.EXACTLY) {
width = widthSpecSize;
} else {
width = getSuggestedMinimumWidth();
}
if (heightSpecMode == MeasureSpec.AT_MOST) {
height = Math.min(heightSpecSize, getSuggestedMinimumWidth());
} else if (heightSpecMode == MeasureSpec.EXACTLY) {
height = heightSpecSize;
} else {
height = getSuggestedMinimumHeight();
}
setMeasuredDimension(width, height);
| protected void | onSizeChanged(int w, int h, int oldw, int oldh)
mLockCenterX = 0.5f * w;
mLockCenterY = 0.5f * h;
super.onSizeChanged(w, h, oldw, oldh);
| public boolean | onTouchEvent(android.view.MotionEvent event)
final int action = event.getAction();
mMouseX = event.getX();
mMouseY = event.getY();
boolean handled = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
removeCallbacks(mLockTimerActions);
mFingerDown = true;
tryTransitionToStartAttemptState(event);
handled = true;
break;
case MotionEvent.ACTION_MOVE:
tryTransitionToStartAttemptState(event);
handled = true;
break;
case MotionEvent.ACTION_UP:
if (DBG) Log.v(TAG, "ACTION_UP");
mFingerDown = false;
postDelayed(mLockTimerActions, RESET_TIMEOUT);
setGrabbedState(OnTriggerListener.NO_HANDLE);
// Normally the state machine is driven by user interaction causing redraws.
// However, when there's no more user interaction and no running animations,
// the state machine stops advancing because onDraw() never gets called.
// The following ensures we advance to the next state in this case,
// either STATE_UNLOCK_ATTEMPT or STATE_RESET_LOCK.
waveUpdateFrame(mMouseX, mMouseY, mFingerDown);
handled = true;
break;
case MotionEvent.ACTION_CANCEL:
mFingerDown = false;
handled = true;
break;
}
invalidate();
return handled ? true : super.onTouchEvent(event);
| public void | reset()
if (DBG) Log.v(TAG, "reset() : resets state to STATE_RESET_LOCK");
mLockState = STATE_RESET_LOCK;
invalidate();
| private void | setGrabbedState(int newState)Sets the current grabbed state, and dispatches a grabbed state change
event to our listener.
if (newState != mGrabbedState) {
mGrabbedState = newState;
if (mOnTriggerListener != null) {
mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState);
}
}
| public void | setOnTriggerListener(com.android.internal.widget.WaveView$OnTriggerListener listener)Registers a callback to be invoked when the user triggers an event.
mOnTriggerListener = listener;
| private void | tryTransitionToStartAttemptState(android.view.MotionEvent event)Tries to transition to start attempt state.
final float dx = event.getX() - mUnlockHalo.getX();
final float dy = event.getY() - mUnlockHalo.getY();
float dist = (float) Math.hypot(dx, dy);
if (dist <= getScaledGrabHandleRadius()) {
setGrabbedState(OnTriggerListener.CENTER_HANDLE);
if (mLockState == STATE_READY) {
mLockState = STATE_START_ATTEMPT;
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
announceUnlockHandle();
}
}
}
| private synchronized void | vibrate(long duration)Triggers haptic feedback.
final boolean hapticEnabled = Settings.System.getIntForUser(
mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
UserHandle.USER_CURRENT) != 0;
if (hapticEnabled) {
if (mVibrator == null) {
mVibrator = (android.os.Vibrator) getContext()
.getSystemService(Context.VIBRATOR_SERVICE);
}
mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES);
}
| private void | waveUpdateFrame(float mouseX, float mouseY, boolean fingerDown)
double distX = mouseX - mLockCenterX;
double distY = mouseY - mLockCenterY;
int dragDistance = (int) Math.ceil(Math.hypot(distX, distY));
double touchA = Math.atan2(distX, distY);
float ringX = (float) (mLockCenterX + mRingRadius * Math.sin(touchA));
float ringY = (float) (mLockCenterY + mRingRadius * Math.cos(touchA));
switch (mLockState) {
case STATE_RESET_LOCK:
if (DBG) Log.v(TAG, "State RESET_LOCK");
mWaveTimerDelay = WAVE_DELAY;
for (int i = 0; i < mLightWaves.size(); i++) {
DrawableHolder holder = mLightWaves.get(i);
holder.addAnimTo(300, 0, "alpha", 0.0f, false);
}
for (int i = 0; i < mLightWaves.size(); i++) {
mLightWaves.get(i).startAnimations(this);
}
mUnlockRing.addAnimTo(DURATION, 0, "x", mLockCenterX, true);
mUnlockRing.addAnimTo(DURATION, 0, "y", mLockCenterY, true);
mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 0.1f, true);
mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 0.1f, true);
mUnlockRing.addAnimTo(DURATION, 0, "alpha", 0.0f, true);
mUnlockDefault.removeAnimationFor("x");
mUnlockDefault.removeAnimationFor("y");
mUnlockDefault.removeAnimationFor("scaleX");
mUnlockDefault.removeAnimationFor("scaleY");
mUnlockDefault.removeAnimationFor("alpha");
mUnlockDefault.setX(mLockCenterX);
mUnlockDefault.setY(mLockCenterY);
mUnlockDefault.setScaleX(0.1f);
mUnlockDefault.setScaleY(0.1f);
mUnlockDefault.setAlpha(0.0f);
mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
mUnlockHalo.removeAnimationFor("x");
mUnlockHalo.removeAnimationFor("y");
mUnlockHalo.removeAnimationFor("scaleX");
mUnlockHalo.removeAnimationFor("scaleY");
mUnlockHalo.removeAnimationFor("alpha");
mUnlockHalo.setX(mLockCenterX);
mUnlockHalo.setY(mLockCenterY);
mUnlockHalo.setScaleX(0.1f);
mUnlockHalo.setScaleY(0.1f);
mUnlockHalo.setAlpha(0.0f);
mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "x", mLockCenterX, true);
mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "y", mLockCenterY, true);
mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
removeCallbacks(mLockTimerActions);
mLockState = STATE_READY;
break;
case STATE_READY:
if (DBG) Log.v(TAG, "State READY");
mWaveTimerDelay = WAVE_DELAY;
break;
case STATE_START_ATTEMPT:
if (DBG) Log.v(TAG, "State START_ATTEMPT");
mUnlockDefault.removeAnimationFor("x");
mUnlockDefault.removeAnimationFor("y");
mUnlockDefault.removeAnimationFor("scaleX");
mUnlockDefault.removeAnimationFor("scaleY");
mUnlockDefault.removeAnimationFor("alpha");
mUnlockDefault.setX(mLockCenterX + 182);
mUnlockDefault.setY(mLockCenterY);
mUnlockDefault.setScaleX(0.1f);
mUnlockDefault.setScaleY(0.1f);
mUnlockDefault.setAlpha(0.0f);
mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, false);
mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, false);
mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, false);
mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 1.0f, true);
mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 1.0f, true);
mUnlockRing.addAnimTo(DURATION, 0, "alpha", 1.0f, true);
mLockState = STATE_ATTEMPTING;
break;
case STATE_ATTEMPTING:
if (DBG) Log.v(TAG, "State ATTEMPTING (fingerDown = " + fingerDown + ")");
if (dragDistance > mSnapRadius) {
mFinishWaves = true; // don't start any more waves.
if (fingerDown) {
mUnlockHalo.addAnimTo(0, 0, "x", ringX, true);
mUnlockHalo.addAnimTo(0, 0, "y", ringY, true);
mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
} else {
if (DBG) Log.v(TAG, "up detected, moving to STATE_UNLOCK_ATTEMPT");
mLockState = STATE_UNLOCK_ATTEMPT;
}
} else {
// If waves have stopped, we need to kick them off again...
if (!mWavesRunning) {
mWavesRunning = true;
mFinishWaves = false;
// mWaveTimerDelay = WAVE_DELAY;
postDelayed(mAddWaveAction, mWaveTimerDelay);
}
mUnlockHalo.addAnimTo(0, 0, "x", mouseX, true);
mUnlockHalo.addAnimTo(0, 0, "y", mouseY, true);
mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
}
break;
case STATE_UNLOCK_ATTEMPT:
if (DBG) Log.v(TAG, "State UNLOCK_ATTEMPT");
if (dragDistance > mSnapRadius) {
for (int n = 0; n < mLightWaves.size(); n++) {
DrawableHolder wave = mLightWaves.get(n);
long delay = 1000L*(6 + n - mCurrentWave)/10L;
wave.addAnimTo(FINAL_DURATION, delay, "x", ringX, true);
wave.addAnimTo(FINAL_DURATION, delay, "y", ringY, true);
wave.addAnimTo(FINAL_DURATION, delay, "scaleX", 0.1f, true);
wave.addAnimTo(FINAL_DURATION, delay, "scaleY", 0.1f, true);
wave.addAnimTo(FINAL_DURATION, delay, "alpha", 0.0f, true);
}
for (int i = 0; i < mLightWaves.size(); i++) {
mLightWaves.get(i).startAnimations(this);
}
mUnlockRing.addAnimTo(FINAL_DURATION, 0, "x", ringX, false);
mUnlockRing.addAnimTo(FINAL_DURATION, 0, "y", ringY, false);
mUnlockRing.addAnimTo(FINAL_DURATION, 0, "scaleX", 0.1f, false);
mUnlockRing.addAnimTo(FINAL_DURATION, 0, "scaleY", 0.1f, false);
mUnlockRing.addAnimTo(FINAL_DURATION, 0, "alpha", 0.0f, false);
mUnlockRing.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false);
mUnlockDefault.removeAnimationFor("x");
mUnlockDefault.removeAnimationFor("y");
mUnlockDefault.removeAnimationFor("scaleX");
mUnlockDefault.removeAnimationFor("scaleY");
mUnlockDefault.removeAnimationFor("alpha");
mUnlockDefault.setX(ringX);
mUnlockDefault.setY(ringY);
mUnlockDefault.setScaleX(0.1f);
mUnlockDefault.setScaleY(0.1f);
mUnlockDefault.setAlpha(0.0f);
mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "x", ringX, true);
mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "y", ringY, true);
mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "scaleX", 1.0f, true);
mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "scaleY", 1.0f, true);
mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "alpha", 1.0f, true);
mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false);
mUnlockHalo.addAnimTo(FINAL_DURATION, 0, "x", ringX, false);
mUnlockHalo.addAnimTo(FINAL_DURATION, 0, "y", ringY, false);
mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false);
removeCallbacks(mLockTimerActions);
postDelayed(mLockTimerActions, RESET_TIMEOUT);
dispatchTriggerEvent(OnTriggerListener.CENTER_HANDLE);
mLockState = STATE_UNLOCK_SUCCESS;
} else {
mLockState = STATE_RESET_LOCK;
}
break;
case STATE_UNLOCK_SUCCESS:
if (DBG) Log.v(TAG, "State UNLOCK_SUCCESS");
removeCallbacks(mAddWaveAction);
break;
default:
if (DBG) Log.v(TAG, "Unknown state " + mLockState);
break;
}
mUnlockDefault.startAnimations(this);
mUnlockHalo.startAnimations(this);
mUnlockRing.startAnimations(this);
|
|