FileDocCategorySizeDatePackage
SlidingTab.javaAPI DocAndroid 5.1 API33977Thu Mar 12 22:22:10 GMT 2015com.android.internal.widget

SlidingTab

public class SlidingTab extends android.view.ViewGroup
A special widget containing two Sliders and a threshold for each. Moving either slider beyond the threshold will cause the registered OnTriggerListener.onTrigger() to be called with whichHandle being {@link OnTriggerListener#LEFT_HANDLE} or {@link OnTriggerListener#RIGHT_HANDLE} Equivalently, selecting a tab will result in a call to {@link OnTriggerListener#onGrabbedStateChange(View, int)} with one of these two states. Releasing the tab will result in whichHandle being {@link OnTriggerListener#NO_HANDLE}.

Fields Summary
private static final String
LOG_TAG
private static final boolean
DBG
private static final int
HORIZONTAL
private static final int
VERTICAL
private static final float
THRESHOLD
private static final long
VIBRATE_SHORT
private static final long
VIBRATE_LONG
private static final int
TRACKING_MARGIN
private static final int
ANIM_DURATION
private static final int
ANIM_TARGET_TIME
private boolean
mHoldLeftOnTransition
private boolean
mHoldRightOnTransition
private static final android.media.AudioAttributes
VIBRATION_ATTRIBUTES
private OnTriggerListener
mOnTriggerListener
private int
mGrabbedState
private boolean
mTriggered
private android.os.Vibrator
mVibrator
private final float
mDensity
private final int
mOrientation
Either {@link #HORIZONTAL} or {@link #VERTICAL}.
private final Slider
mLeftSlider
private final Slider
mRightSlider
private Slider
mCurrentSlider
private boolean
mTracking
private float
mThreshold
private Slider
mOtherSlider
private boolean
mAnimating
private final android.graphics.Rect
mTmpRect
private final android.view.animation.Animation.AnimationListener
mAnimationDoneListener
Listener used to reset the view when the current animation completes.
Constructors Summary
public SlidingTab(android.content.Context context)

        this(context, null);
    
public SlidingTab(android.content.Context context, android.util.AttributeSet attrs)
Constructor used when this widget is created from a layout file.

        super(context, attrs);

        // Allocate a temporary once that can be used everywhere.
        mTmpRect = new Rect();

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingTab);
        mOrientation = a.getInt(R.styleable.SlidingTab_orientation, HORIZONTAL);
        a.recycle();

        Resources r = getResources();
        mDensity = r.getDisplayMetrics().density;
        if (DBG) log("- Density: " + mDensity);

        mLeftSlider = new Slider(this,
                R.drawable.jog_tab_left_generic,
                R.drawable.jog_tab_bar_left_generic,
                R.drawable.jog_tab_target_gray);
        mRightSlider = new Slider(this,
                R.drawable.jog_tab_right_generic,
                R.drawable.jog_tab_bar_right_generic,
                R.drawable.jog_tab_target_gray);

        // setBackgroundColor(0x80808080);
    
Methods Summary
private voidcancelGrab()

        mTracking = false;
        mTriggered = false;
        mOtherSlider.show(true);
        mCurrentSlider.reset(false);
        mCurrentSlider.hideTarget();
        mCurrentSlider = null;
        mOtherSlider = null;
        setGrabbedState(OnTriggerListener.NO_HANDLE);
    
private voiddispatchTriggerEvent(int whichHandle)
Dispatches a trigger event to listener. Ignored if a listener is not set.

param
whichHandle the handle that triggered the event.

        vibrate(VIBRATE_LONG);
        if (mOnTriggerListener != null) {
            mOnTriggerListener.onTrigger(this, whichHandle);
        }
    
private booleanisHorizontal()

        return mOrientation == HORIZONTAL;
    
private voidlog(java.lang.String msg)

        Log.d(LOG_TAG, msg);
    
private voidmoveHandle(float x, float y)

        final View handle = mCurrentSlider.tab;
        final View content = mCurrentSlider.text;
        if (isHorizontal()) {
            int deltaX = (int) x - handle.getLeft() - (handle.getWidth() / 2);
            handle.offsetLeftAndRight(deltaX);
            content.offsetLeftAndRight(deltaX);
        } else {
            int deltaY = (int) y - handle.getTop() - (handle.getHeight() / 2);
            handle.offsetTopAndBottom(deltaY);
            content.offsetTopAndBottom(deltaY);
        }
        invalidate(); // TODO: be more conservative about what we're invalidating
    
private voidonAnimationDone()

        resetView();
        mAnimating = false;
    
public booleanonInterceptTouchEvent(android.view.MotionEvent event)

        final int action = event.getAction();
        final float x = event.getX();
        final float y = event.getY();

        if (mAnimating) {
            return false;
        }

        View leftHandle = mLeftSlider.tab;
        leftHandle.getHitRect(mTmpRect);
        boolean leftHit = mTmpRect.contains((int) x, (int) y);

        View rightHandle = mRightSlider.tab;
        rightHandle.getHitRect(mTmpRect);
        boolean rightHit = mTmpRect.contains((int)x, (int) y);

        if (!mTracking && !(leftHit || rightHit)) {
            return false;
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mTracking = true;
                mTriggered = false;
                vibrate(VIBRATE_SHORT);
                if (leftHit) {
                    mCurrentSlider = mLeftSlider;
                    mOtherSlider = mRightSlider;
                    mThreshold = isHorizontal() ? THRESHOLD : 1.0f - THRESHOLD;
                    setGrabbedState(OnTriggerListener.LEFT_HANDLE);
                } else {
                    mCurrentSlider = mRightSlider;
                    mOtherSlider = mLeftSlider;
                    mThreshold = isHorizontal() ? 1.0f - THRESHOLD : THRESHOLD;
                    setGrabbedState(OnTriggerListener.RIGHT_HANDLE);
                }
                mCurrentSlider.setState(Slider.STATE_PRESSED);
                mCurrentSlider.showTarget();
                mOtherSlider.hide();
                break;
            }
        }

        return true;
    
protected voidonLayout(boolean changed, int l, int t, int r, int b)

        if (!changed) return;

        // Center the widgets in the view
        mLeftSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_LEFT : Slider.ALIGN_BOTTOM);
        mRightSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_RIGHT : Slider.ALIGN_TOP);
    
protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);

        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);

        if (DBG) {
            if (widthSpecMode == MeasureSpec.UNSPECIFIED 
                    || heightSpecMode == MeasureSpec.UNSPECIFIED) {
                Log.e("SlidingTab", "SlidingTab cannot have UNSPECIFIED MeasureSpec"
                        +"(wspec=" + widthSpecMode + ", hspec=" + heightSpecMode + ")",
                        new RuntimeException(LOG_TAG + "stack:"));
            }
        }

        mLeftSlider.measure();
        mRightSlider.measure();
        final int leftTabWidth = mLeftSlider.getTabWidth();
        final int rightTabWidth = mRightSlider.getTabWidth();
        final int leftTabHeight = mLeftSlider.getTabHeight();
        final int rightTabHeight = mRightSlider.getTabHeight();
        final int width;
        final int height;
        if (isHorizontal()) {
            width = Math.max(widthSpecSize, leftTabWidth + rightTabWidth);
            height = Math.max(leftTabHeight, rightTabHeight);
        } else {
            width = Math.max(leftTabWidth, rightTabHeight);
            height = Math.max(heightSpecSize, leftTabHeight + rightTabHeight);
        }
        setMeasuredDimension(width, height);
    
public booleanonTouchEvent(android.view.MotionEvent event)

        if (mTracking) {
            final int action = event.getAction();
            final float x = event.getX();
            final float y = event.getY();

            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    if (withinView(x, y, this) ) {
                        moveHandle(x, y);
                        float position = isHorizontal() ? x : y;
                        float target = mThreshold * (isHorizontal() ? getWidth() : getHeight());
                        boolean thresholdReached;
                        if (isHorizontal()) {
                            thresholdReached = mCurrentSlider == mLeftSlider ?
                                    position > target : position < target;
                        } else {
                            thresholdReached = mCurrentSlider == mLeftSlider ?
                                    position < target : position > target;
                        }
                        if (!mTriggered && thresholdReached) {
                            mTriggered = true;
                            mTracking = false;
                            mCurrentSlider.setState(Slider.STATE_ACTIVE);
                            boolean isLeft = mCurrentSlider == mLeftSlider;
                            dispatchTriggerEvent(isLeft ?
                                OnTriggerListener.LEFT_HANDLE : OnTriggerListener.RIGHT_HANDLE);

                            startAnimating(isLeft ? mHoldLeftOnTransition : mHoldRightOnTransition);
                            setGrabbedState(OnTriggerListener.NO_HANDLE);
                        }
                        break;
                    }
                    // Intentionally fall through - we're outside tracking rectangle

                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    cancelGrab();
                    break;
            }
        }

        return mTracking || super.onTouchEvent(event);
    
protected voidonVisibilityChanged(android.view.View changedView, int visibility)

        super.onVisibilityChanged(changedView, visibility);
        // When visibility changes and the user has a tab selected, unselect it and
        // make sure their callback gets called.
        if (changedView == this && visibility != VISIBLE
                && mGrabbedState != OnTriggerListener.NO_HANDLE) {
            cancelGrab();
        }
    
public voidreset(boolean animate)
Reset the tabs to their original state and stop any existing animation. Animate them back into place if animate is true.

param
animate

        mLeftSlider.reset(animate);
        mRightSlider.reset(animate);
        if (!animate) {
            mAnimating = false;
        }
    
private voidresetView()

        mLeftSlider.reset(false);
        mRightSlider.reset(false);
        // onLayout(true, getLeft(), getTop(), getLeft() + getWidth(), getTop() + getHeight());
    
private voidsetGrabbedState(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 voidsetHoldAfterTrigger(boolean holdLeft, boolean holdRight)

        mHoldLeftOnTransition = holdLeft;
        mHoldRightOnTransition = holdRight;
    
public voidsetLeftHintText(int resId)
Sets the left handle hint text to a given resource string.

param
resId

        if (isHorizontal()) {
            mLeftSlider.setHintText(resId);
        }
    
public voidsetLeftTabResources(int iconId, int targetId, int barId, int tabId)
Sets the left handle icon to a given resource. The resource should refer to a Drawable object, or use 0 to remove the icon.

param
iconId the resource ID of the icon drawable
param
targetId the resource of the target drawable
param
barId the resource of the bar drawable (stateful)
param
tabId the resource of the

        mLeftSlider.setIcon(iconId);
        mLeftSlider.setTarget(targetId);
        mLeftSlider.setBarBackgroundResource(barId);
        mLeftSlider.setTabBackgroundResource(tabId);
        mLeftSlider.updateDrawableStates();
    
public voidsetOnTriggerListener(com.android.internal.widget.SlidingTab$OnTriggerListener listener)
Registers a callback to be invoked when the user triggers an event.

param
listener the OnDialTriggerListener to attach to this view

        mOnTriggerListener = listener;
    
public voidsetRightHintText(int resId)
Sets the left handle hint text to a given resource string.

param
resId

        if (isHorizontal()) {
            mRightSlider.setHintText(resId);
        }
    
public voidsetRightTabResources(int iconId, int targetId, int barId, int tabId)
Sets the right handle icon to a given resource. The resource should refer to a Drawable object, or use 0 to remove the icon.

param
iconId the resource ID of the icon drawable
param
targetId the resource of the target drawable
param
barId the resource of the bar drawable (stateful)
param
tabId the resource of the

        mRightSlider.setIcon(iconId);
        mRightSlider.setTarget(targetId);
        mRightSlider.setBarBackgroundResource(barId);
        mRightSlider.setTabBackgroundResource(tabId);
        mRightSlider.updateDrawableStates();
    
public voidsetVisibility(int visibility)

        // Clear animations so sliders don't continue to animate when we show the widget again.
        if (visibility != getVisibility() && visibility == View.INVISIBLE) {
           reset(false);
        }
        super.setVisibility(visibility);
    
voidstartAnimating(boolean holdAfter)

        mAnimating = true;
        final Animation trans1;
        final Animation trans2;
        final Slider slider = mCurrentSlider;
        final Slider other = mOtherSlider;
        final int dx;
        final int dy;
        if (isHorizontal()) {
            int right = slider.tab.getRight();
            int width = slider.tab.getWidth();
            int left = slider.tab.getLeft();
            int viewWidth = getWidth();
            int holdOffset = holdAfter ? 0 : width; // how much of tab to show at the end of anim
            dx =  slider == mRightSlider ? - (right + viewWidth - holdOffset)
                    : (viewWidth - left) + viewWidth - holdOffset;
            dy = 0;
        } else {
            int top = slider.tab.getTop();
            int bottom = slider.tab.getBottom();
            int height = slider.tab.getHeight();
            int viewHeight = getHeight();
            int holdOffset = holdAfter ? 0 : height; // how much of tab to show at end of anim
            dx = 0;
            dy =  slider == mRightSlider ? (top + viewHeight - holdOffset)
                    : - ((viewHeight - bottom) + viewHeight - holdOffset);
        }
        trans1 = new TranslateAnimation(0, dx, 0, dy);
        trans1.setDuration(ANIM_DURATION);
        trans1.setInterpolator(new LinearInterpolator());
        trans1.setFillAfter(true);
        trans2 = new TranslateAnimation(0, dx, 0, dy);
        trans2.setDuration(ANIM_DURATION);
        trans2.setInterpolator(new LinearInterpolator());
        trans2.setFillAfter(true);

        trans1.setAnimationListener(new AnimationListener() {
            public void onAnimationEnd(Animation animation) {
                Animation anim;
                if (holdAfter) {
                    anim = new TranslateAnimation(dx, dx, dy, dy);
                    anim.setDuration(1000); // plenty of time for transitions
                    mAnimating = false;
                } else {
                    anim = new AlphaAnimation(0.5f, 1.0f);
                    anim.setDuration(ANIM_DURATION);
                    resetView();
                }
                anim.setAnimationListener(mAnimationDoneListener);

                /* Animation can be the same for these since the animation just holds */
                mLeftSlider.startAnimation(anim, anim);
                mRightSlider.startAnimation(anim, anim);
            }

            public void onAnimationRepeat(Animation animation) {

            }

            public void onAnimationStart(Animation animation) {

            }

        });

        slider.hideTarget();
        slider.startAnimation(trans1, trans2);
    
private synchronized voidvibrate(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 booleanwithinView(float x, float y, android.view.View view)

        return isHorizontal() && y > - TRACKING_MARGIN && y < TRACKING_MARGIN + view.getHeight()
            || !isHorizontal() && x > -TRACKING_MARGIN && x < TRACKING_MARGIN + view.getWidth();