`

四个方向上的SlidingDrawer

阅读更多
http://blog.sephiroth.it/2011/03/29/widget-slidingdrawer-top-to-bottom/

/*
 * Copyright (C) 2008 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.
 * 
 * Modifications by: Alessandro Crugnola
 */

package it.sephiroth.demo.slider.widget;

import it.sephiroth.demo.slider.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;


public class MultiDirectionSlidingDrawer extends ViewGroup {
	
	public static final int				ORIENTATION_RTL		= 0;
	public static final int				ORIENTATION_BTT		= 1;
	public static final int				ORIENTATION_LTR		= 2;
	public static final int				ORIENTATION_TTB		= 3;
	
	private static final int			TAP_THRESHOLD					= 6;
	private static final float			MAXIMUM_TAP_VELOCITY			= 100.0f;
	private static final float			MAXIMUM_MINOR_VELOCITY		= 150.0f;
	private static final float			MAXIMUM_MAJOR_VELOCITY		= 200.0f;
	private static final float			MAXIMUM_ACCELERATION			= 2000.0f;
	private static final int			VELOCITY_UNITS					= 1000;
	private static final int			MSG_ANIMATE						= 1000;
	private static final int			ANIMATION_FRAME_DURATION	= 1000 / 60;
	
	private static final int			EXPANDED_FULL_OPEN			= -10001;
	private static final int			COLLAPSED_FULL_CLOSED		= -10002;
	
	private final int						mHandleId;
	private final int						mContentId;
	
	private View							mHandle;
	private View							mContent;
	
	private final Rect					mFrame							= new Rect();
	private final Rect					mInvalidate						= new Rect();
	private boolean						mTracking;
	private boolean						mLocked;
	
	private VelocityTracker				mVelocityTracker;
	
	private boolean						mInvert;
	private boolean						mVertical;
	private boolean						mExpanded;
	private int								mBottomOffset;
	private int								mTopOffset;
	private int								mHandleHeight;
	private int								mHandleWidth;
	
	private OnDrawerOpenListener		mOnDrawerOpenListener;
	private OnDrawerCloseListener		mOnDrawerCloseListener;
	private OnDrawerScrollListener	mOnDrawerScrollListener;
	
	private final Handler				mHandler							= new SlidingHandler();
	private float							mAnimatedAcceleration;
	private float							mAnimatedVelocity;
	private float							mAnimationPosition;
	private long							mAnimationLastTime;
	private long							mCurrentAnimationTime;
	private int								mTouchDelta;
	private boolean						mAnimating;
	private boolean						mAllowSingleTap;
	private boolean						mAnimateOnClick;
	
	private final int						mTapThreshold;
	private final int						mMaximumTapVelocity;
	private int								mMaximumMinorVelocity;
	private int								mMaximumMajorVelocity;
	private int								mMaximumAcceleration;
	private final int						mVelocityUnits;
	
	/**
	 * Callback invoked when the drawer is opened.
	 */
	public static interface OnDrawerOpenListener {
		
		/**
		 * Invoked when the drawer becomes fully open.
		 */
		public void onDrawerOpened();
	}
	
	/**
	 * Callback invoked when the drawer is closed.
	 */
	public static interface OnDrawerCloseListener {
		
		/**
		 * Invoked when the drawer becomes fully closed.
		 */
		public void onDrawerClosed();
	}
	
	/**
	 * Callback invoked when the drawer is scrolled.
	 */
	public static interface OnDrawerScrollListener {
		
		/**
		 * Invoked when the user starts dragging/flinging the drawer's handle.
		 */
		public void onScrollStarted();
		
		/**
		 * Invoked when the user stops dragging/flinging the drawer's handle.
		 */
		public void onScrollEnded();
	}
	
	/**
	 * Creates a new SlidingDrawer from a specified set of attributes defined in
	 * XML.
	 * 
	 * @param context
	 *           The application's environment.
	 * @param attrs
	 *           The attributes defined in XML.
	 */
	public MultiDirectionSlidingDrawer( Context context, AttributeSet attrs )
	{
		this( context, attrs, 0 );
	}
	
	/**
	 * Creates a new SlidingDrawer from a specified set of attributes defined in
	 * XML.
	 * 
	 * @param context
	 *           The application's environment.
	 * @param attrs
	 *           The attributes defined in XML.
	 * @param defStyle
	 *           The style to apply to this widget.
	 */
	public MultiDirectionSlidingDrawer( Context context, AttributeSet attrs, int defStyle )
	{
		super( context, attrs, defStyle );
		TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.MultiDirectionSlidingDrawer, defStyle, 0 );
		
		int orientation = a.getInt( R.styleable.MultiDirectionSlidingDrawer_direction, ORIENTATION_BTT );
		mVertical = ( orientation == ORIENTATION_BTT || orientation == ORIENTATION_TTB );
		mBottomOffset = (int)a.getDimension( R.styleable.MultiDirectionSlidingDrawer_bottomOffset, 0.0f );
		mTopOffset = (int)a.getDimension( R.styleable.MultiDirectionSlidingDrawer_topOffset, 0.0f );
		mAllowSingleTap = a.getBoolean( R.styleable.MultiDirectionSlidingDrawer_allowSingleTap, true );
		mAnimateOnClick = a.getBoolean( R.styleable.MultiDirectionSlidingDrawer_animateOnClick, true );
		mInvert = ( orientation == ORIENTATION_TTB || orientation == ORIENTATION_LTR );
		
		int handleId = a.getResourceId( R.styleable.MultiDirectionSlidingDrawer_handle, 0 );
		if ( handleId == 0 ) { throw new IllegalArgumentException( "The handle attribute is required and must refer "
				+ "to a valid child." ); }
		
		int contentId = a.getResourceId( R.styleable.MultiDirectionSlidingDrawer_content, 0 );
		if ( contentId == 0 ) { throw new IllegalArgumentException( "The content attribute is required and must refer "
				+ "to a valid child." ); }
		
		if ( handleId == contentId ) { throw new IllegalArgumentException( "The content and handle attributes must refer "
				+ "to different children." ); }
		mHandleId = handleId;
		mContentId = contentId;
		
		final float density = getResources().getDisplayMetrics().density;
		mTapThreshold = (int)( TAP_THRESHOLD * density + 0.5f );
		mMaximumTapVelocity = (int)( MAXIMUM_TAP_VELOCITY * density + 0.5f );
		mMaximumMinorVelocity = (int)( MAXIMUM_MINOR_VELOCITY * density + 0.5f );
		mMaximumMajorVelocity = (int)( MAXIMUM_MAJOR_VELOCITY * density + 0.5f );
		mMaximumAcceleration = (int)( MAXIMUM_ACCELERATION * density + 0.5f );
		mVelocityUnits = (int)( VELOCITY_UNITS * density + 0.5f );
		
		if( mInvert ) {
			mMaximumAcceleration = -mMaximumAcceleration;
			mMaximumMajorVelocity = -mMaximumMajorVelocity;
			mMaximumMinorVelocity = -mMaximumMinorVelocity;
		}
		
		a.recycle();
		setAlwaysDrawnWithCacheEnabled( false );
	}
	
	@Override
	protected void onFinishInflate()
	{
		mHandle = findViewById( mHandleId );
		if ( mHandle == null ) { throw new IllegalArgumentException( "The handle attribute is must refer to an" + " existing child." ); }
		mHandle.setOnClickListener( new DrawerToggler() );
		
		mContent = findViewById( mContentId );
		if ( mContent == null ) { throw new IllegalArgumentException( "The content attribute is must refer to an"
				+ " existing child." ); }
		mContent.setVisibility( View.GONE );
	}
	
	@Override
	protected void onMeasure( 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 ( widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED ) { throw new RuntimeException(
				"SlidingDrawer cannot have UNSPECIFIED dimensions" ); }
		
		final View handle = mHandle;
		measureChild( handle, widthMeasureSpec, heightMeasureSpec );
		
		if ( mVertical ) {
			int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
			mContent.measure( MeasureSpec.makeMeasureSpec( widthSpecSize, MeasureSpec.EXACTLY ), MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY ) );
		} else {
			int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
			mContent.measure( MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY ), MeasureSpec.makeMeasureSpec( heightSpecSize, MeasureSpec.EXACTLY ) );
		}
		
		setMeasuredDimension( widthSpecSize, heightSpecSize );
	}
	
	@Override
	protected void dispatchDraw( Canvas canvas )
	{
		final long drawingTime = getDrawingTime();
		final View handle = mHandle;
		final boolean isVertical = mVertical;
		
		drawChild( canvas, handle, drawingTime );
		
		if ( mTracking || mAnimating ) {
			final Bitmap cache = mContent.getDrawingCache();
			if ( cache != null ) {
				if ( isVertical ) {
					if( mInvert ) {
						canvas.drawBitmap( cache, 0, handle.getTop() - (getBottom() - getTop()) + mHandleHeight, null );
					} else {
						canvas.drawBitmap( cache, 0, handle.getBottom(), null );
					}
				} else {
					canvas.drawBitmap( cache, mInvert ? handle.getLeft() - cache.getWidth() : handle.getRight(), 0, null );
				}
			} else {
				canvas.save();
				if( mInvert ) {
					canvas.translate( isVertical ? 0 : handle.getLeft() - mTopOffset - mContent.getMeasuredWidth(), isVertical ? handle.getTop() - mTopOffset - mContent.getMeasuredHeight() : 0 );
				} else {
					canvas.translate( isVertical ? 0 : handle.getLeft() - mTopOffset, isVertical ? handle.getTop() - mTopOffset : 0 );
				}
				drawChild( canvas, mContent, drawingTime );
				canvas.restore();
			}
			invalidate();
		} else if ( mExpanded ) {
			drawChild( canvas, mContent, drawingTime );
		}
	}
	
	public static final String	LOG_TAG	= "Sliding";
	
	@Override
	protected void onLayout( boolean changed, int l, int t, int r, int b )
	{
		if ( mTracking ) { return; }
		
		final int width = r - l;
		final int height = b - t;
		
		final View handle = mHandle;
		
		int handleWidth = handle.getMeasuredWidth();
		int handleHeight = handle.getMeasuredHeight();
		
		Log.d( LOG_TAG, "handleHeight: " + handleHeight );
		
		int handleLeft;
		int handleTop;
		
		final View content = mContent;
		
		if ( mVertical ) {
			handleLeft = ( width - handleWidth ) / 2;
			if ( mInvert ) {
				Log.d( LOG_TAG, "content.layout(1)" );
				handleTop = mExpanded ? height - mBottomOffset - handleHeight : mTopOffset;
				content.layout( 0, mTopOffset, content.getMeasuredWidth(), mTopOffset + content.getMeasuredHeight() );
			} else {
				handleTop = mExpanded ? mTopOffset : height - handleHeight + mBottomOffset;
				content.layout( 0, mTopOffset + handleHeight, content.getMeasuredWidth(), mTopOffset + handleHeight + content.getMeasuredHeight() );
			}
		} else {
			handleTop = ( height - handleHeight ) / 2;
			if( mInvert ) {
				handleLeft = mExpanded ? width - mBottomOffset - handleWidth : mTopOffset;
				content.layout( mTopOffset, 0, mTopOffset + content.getMeasuredWidth(), content.getMeasuredHeight() );
			} else {
				handleLeft = mExpanded ? mTopOffset : width - handleWidth + mBottomOffset;
				content.layout( mTopOffset + handleWidth, 0, mTopOffset + handleWidth + content.getMeasuredWidth(), content.getMeasuredHeight() );
			}
		}
		
		handle.layout( handleLeft, handleTop, handleLeft + handleWidth, handleTop + handleHeight );
		mHandleHeight = handle.getHeight();
		mHandleWidth = handle.getWidth();
	}
	
	@Override
	public boolean onInterceptTouchEvent( MotionEvent event )
	{
		if ( mLocked ) { return false; }
		
		final int action = event.getAction();
		
		float x = event.getX();
		float y = event.getY();
		
		final Rect frame = mFrame;
		final View handle = mHandle;
		
		handle.getHitRect( frame );
		if ( !mTracking && !frame.contains( (int)x, (int)y ) ) { return false; }
		
		if ( action == MotionEvent.ACTION_DOWN ) {
			mTracking = true;
			
			handle.setPressed( true );
			// Must be called before prepareTracking()
			prepareContent();
			
			// Must be called after prepareContent()
			if ( mOnDrawerScrollListener != null ) {
				mOnDrawerScrollListener.onScrollStarted();
			}
			
			if ( mVertical ) {
				final int top = mHandle.getTop();
				mTouchDelta = (int)y - top;
				prepareTracking( top );
			} else {
				final int left = mHandle.getLeft();
				mTouchDelta = (int)x - left;
				prepareTracking( left );
			}
			mVelocityTracker.addMovement( event );
		}
		
		return true;
	}
	
	@Override
	public boolean onTouchEvent( MotionEvent event )
	{
		if ( mLocked ) { return true; }
		
		if ( mTracking ) {
			mVelocityTracker.addMovement( event );
			final int action = event.getAction();
			switch ( action ) {
				case MotionEvent.ACTION_MOVE:
					moveHandle( (int)( mVertical ? event.getY() : event.getX() ) - mTouchDelta );
					break;
				case MotionEvent.ACTION_UP:
				case MotionEvent.ACTION_CANCEL: {
					final VelocityTracker velocityTracker = mVelocityTracker;
					velocityTracker.computeCurrentVelocity( mVelocityUnits );
					
					float yVelocity = velocityTracker.getYVelocity();
					float xVelocity = velocityTracker.getXVelocity();
					boolean negative;
					
					final boolean vertical = mVertical;
					if ( vertical ) {
						negative = yVelocity < 0;
						if ( xVelocity < 0 ) {
							xVelocity = -xVelocity;
						}
						// fix by Maciej Ciemięga.
						if ( (!mInvert && xVelocity > mMaximumMinorVelocity) || (mInvert && xVelocity < mMaximumMinorVelocity) ) {
							xVelocity = mMaximumMinorVelocity;
						}
					} else {
						negative = xVelocity < 0;
						if ( yVelocity < 0 ) {
							yVelocity = -yVelocity;
						}
						// fix by Maciej Ciemięga.
						if ( (!mInvert && yVelocity > mMaximumMinorVelocity) || (mInvert && yVelocity < mMaximumMinorVelocity) ) {
							yVelocity = mMaximumMinorVelocity;
						}
					}
					
					float velocity = (float)Math.hypot( xVelocity, yVelocity );
					if ( negative ) {
						velocity = -velocity;
					}
					
					final int handleTop = mHandle.getTop();
					final int handleLeft = mHandle.getLeft();
					final int handleBottom = mHandle.getBottom();
					final int handleRight = mHandle.getRight();
					
					if ( Math.abs( velocity ) < mMaximumTapVelocity ) {
						boolean c1;
						boolean c2;
						boolean c3;
						boolean c4;
						
						if( mInvert ) {
							c1 = ( mExpanded && (getBottom() - handleBottom ) < mTapThreshold + mBottomOffset );
							c2 = ( !mExpanded && handleTop < mTopOffset + mHandleHeight - mTapThreshold );
							c3 = ( mExpanded && (getRight() - handleRight ) < mTapThreshold + mBottomOffset );
							c4 = ( !mExpanded && handleLeft > mTopOffset + mHandleWidth + mTapThreshold );
						} else {
							c1 = ( mExpanded && handleTop < mTapThreshold + mTopOffset );
							c2 = ( !mExpanded && handleTop > mBottomOffset + getBottom() - getTop() - mHandleHeight - mTapThreshold );
							c3 = ( mExpanded && handleLeft < mTapThreshold + mTopOffset );
							c4 = ( !mExpanded && handleLeft > mBottomOffset + getRight() - getLeft() - mHandleWidth - mTapThreshold );
						}
						
						Log.d( LOG_TAG, "ACTION_UP: " + "c1: " + c1 + ", c2: " + c2 + ", c3: " + c3 + ", c4: " + c4 );
						
						if ( vertical ? c1 || c2 : c3 || c4 ) {
							
							if ( mAllowSingleTap ) {
								playSoundEffect( SoundEffectConstants.CLICK );
								
								if ( mExpanded ) {
									animateClose( vertical ? handleTop : handleLeft );
								} else {
									animateOpen( vertical ? handleTop : handleLeft );
								}
							} else {
								performFling( vertical ? handleTop : handleLeft, velocity, false );
							}
						} else {
							performFling( vertical ? handleTop : handleLeft, velocity, false );
						}
					} else {
						performFling( vertical ? handleTop : handleLeft, velocity, false );
					}
				}
					break;
			}
		}
		
		return mTracking || mAnimating || super.onTouchEvent( event );
	}
	
	private void animateClose( int position )
	{
		prepareTracking( position );
		performFling( position, mMaximumAcceleration, true );
	}
	
	private void animateOpen( int position )
	{
		prepareTracking( position );
		performFling( position, -mMaximumAcceleration, true );
	}
	
	private void performFling( int position, float velocity, boolean always )
	{
		mAnimationPosition = position;
		mAnimatedVelocity = velocity;
		
		boolean c1;
		boolean c2;
		boolean c3;
		
		if ( mExpanded ) 
		{
			int bottom = mVertical ? getBottom() : getRight();
			int handleHeight = mVertical ? mHandleHeight : mHandleWidth;
			
			Log.d( LOG_TAG, "position: " + position + ", velocity: " + velocity + ", mMaximumMajorVelocity: " + mMaximumMajorVelocity );
			c1 = mInvert ? velocity < mMaximumMajorVelocity : velocity > mMaximumMajorVelocity;
			c2 = mInvert ? ( bottom - (position + handleHeight) ) + mBottomOffset > handleHeight : position > mTopOffset + ( mVertical ? mHandleHeight : mHandleWidth );
			c3 = mInvert ? velocity < -mMaximumMajorVelocity : velocity > -mMaximumMajorVelocity;
			Log.d( LOG_TAG, "EXPANDED. c1: " + c1 + ", c2: " + c2 + ", c3: " + c3 );
			if ( always || ( c1 || ( c2 && c3 ) ) ) {
				// We are expanded, So animate to CLOSE!
				mAnimatedAcceleration = mMaximumAcceleration;
				if( mInvert )
				{
					if ( velocity > 0 ) {
						mAnimatedVelocity = 0;
					}
				} else {
					if ( velocity < 0 ) {
						mAnimatedVelocity = 0;
					}
				}
			} else {
				// We are expanded, but they didn't move sufficiently to cause
				// us to retract. Animate back to the expanded position. so animate BACK to expanded!
				mAnimatedAcceleration = -mMaximumAcceleration;
				
				if( mInvert ) {
					if ( velocity < 0 ) {
						mAnimatedVelocity = 0;
					}
				} else {
					if ( velocity > 0 ) {
						mAnimatedVelocity = 0;
					}
				}
			}
		} else {
			
			// WE'RE COLLAPSED
			
			c1 = mInvert ? velocity < mMaximumMajorVelocity : velocity > mMaximumMajorVelocity;
			c2 = mInvert ? ( position < ( mVertical ? getHeight() : getWidth() ) / 2 ) : ( position > ( mVertical ? getHeight() : getWidth() ) / 2 );
			c3 = mInvert ? velocity < -mMaximumMajorVelocity : velocity > -mMaximumMajorVelocity;
			
			Log.d( LOG_TAG, "COLLAPSED. position: " + position + ", velocity: " + velocity + ", mMaximumMajorVelocity: " + mMaximumMajorVelocity );
			Log.d( LOG_TAG, "COLLAPSED. always: " + always + ", c1: " + c1 + ", c2: " + c2 + ", c3: " + c3 );
			
			if ( !always && ( c1 || ( c2 && c3 ) ) ) {
				mAnimatedAcceleration = mMaximumAcceleration;
				
				if( mInvert ) {
					if ( velocity > 0 ) {
						mAnimatedVelocity = 0;
					}
				} else {
					if ( velocity < 0 ) {
						mAnimatedVelocity = 0;
					}
				}
			} else {
				mAnimatedAcceleration = -mMaximumAcceleration;
				
				if( mInvert ) {
					if ( velocity < 0 ) {
						mAnimatedVelocity = 0;
					}
				} else {
					if ( velocity > 0 ) {
						mAnimatedVelocity = 0;
					}
				}
			}
		}
		
		long now = SystemClock.uptimeMillis();
		mAnimationLastTime = now;
		mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
		mAnimating = true;
		mHandler.removeMessages( MSG_ANIMATE );
		mHandler.sendMessageAtTime( mHandler.obtainMessage( MSG_ANIMATE ), mCurrentAnimationTime );
		stopTracking();
	}
	
	private void prepareTracking( int position )
	{
		mTracking = true;
		mVelocityTracker = VelocityTracker.obtain();
		boolean opening = !mExpanded;
		
		if ( opening ) {
			mAnimatedAcceleration = mMaximumAcceleration;
			mAnimatedVelocity = mMaximumMajorVelocity;
			if( mInvert )
				mAnimationPosition = mTopOffset;
			else
				mAnimationPosition = mBottomOffset + ( mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth );
			moveHandle( (int)mAnimationPosition );
			mAnimating = true;
			mHandler.removeMessages( MSG_ANIMATE );
			long now = SystemClock.uptimeMillis();
			mAnimationLastTime = now;
			mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
			mAnimating = true;
		} else {
			if ( mAnimating ) {
				mAnimating = false;
				mHandler.removeMessages( MSG_ANIMATE );
			}
			moveHandle( position );
		}
	}
	
	private void moveHandle( int position )
	{
		final View handle = mHandle;
		
		if ( mVertical ) {
			if ( position == EXPANDED_FULL_OPEN ) {
				if( mInvert )
					handle.offsetTopAndBottom( mBottomOffset + getBottom() - getTop() - mHandleHeight );
				else
					handle.offsetTopAndBottom( mTopOffset - handle.getTop() );
				invalidate();
			} else if ( position == COLLAPSED_FULL_CLOSED ) {
				if( mInvert ) {
					handle.offsetTopAndBottom( mTopOffset - handle.getTop() );
				} else {
					handle.offsetTopAndBottom( mBottomOffset + getBottom() - getTop() - mHandleHeight - handle.getTop() );
				}
				invalidate();
			} else 
			{
				final int top = handle.getTop();
				int deltaY = position - top;
				if ( position < mTopOffset ) {
					deltaY = mTopOffset - top;
				} else if ( deltaY > mBottomOffset + getBottom() - getTop() - mHandleHeight - top ) {
					deltaY = mBottomOffset + getBottom() - getTop() - mHandleHeight - top;
				}
				
				handle.offsetTopAndBottom( deltaY );
				
				final Rect frame = mFrame;
				final Rect region = mInvalidate;
				
				handle.getHitRect( frame );
				region.set( frame );
				
				region.union( frame.left, frame.top - deltaY, frame.right, frame.bottom - deltaY );
				region.union( 0, frame.bottom - deltaY, getWidth(), frame.bottom - deltaY + mContent.getHeight() );
				
				invalidate( region );
			}
		} else {
			if ( position == EXPANDED_FULL_OPEN ) {
				if( mInvert )
					handle.offsetLeftAndRight( mBottomOffset + getRight() - getLeft() - mHandleWidth );
				else
					handle.offsetLeftAndRight( mTopOffset - handle.getLeft() );
				invalidate();
			} else if ( position == COLLAPSED_FULL_CLOSED ) {
				if( mInvert )
					handle.offsetLeftAndRight( mTopOffset - handle.getLeft() );
				else
					handle.offsetLeftAndRight( mBottomOffset + getRight() - getLeft() - mHandleWidth - handle.getLeft() );
				invalidate();
			} else {
				final int left = handle.getLeft();
				int deltaX = position - left;
				if ( position < mTopOffset ) {
					deltaX = mTopOffset - left;
				} else if ( deltaX > mBottomOffset + getRight() - getLeft() - mHandleWidth - left ) {
					deltaX = mBottomOffset + getRight() - getLeft() - mHandleWidth - left;
				}
				handle.offsetLeftAndRight( deltaX );
				
				final Rect frame = mFrame;
				final Rect region = mInvalidate;
				
				handle.getHitRect( frame );
				region.set( frame );
				
				region.union( frame.left - deltaX, frame.top, frame.right - deltaX, frame.bottom );
				region.union( frame.right - deltaX, 0, frame.right - deltaX + mContent.getWidth(), getHeight() );
				
				invalidate( region );
			}
		}
	}
	
	private void prepareContent()
	{
		if ( mAnimating ) { return; }
		
		// Something changed in the content, we need to honor the layout request
		// before creating the cached bitmap
		final View content = mContent;
		if ( content.isLayoutRequested() ) {
			
			if ( mVertical ) {
				final int handleHeight = mHandleHeight;
				int height = getBottom() - getTop() - handleHeight - mTopOffset;
				content.measure( MeasureSpec.makeMeasureSpec( getRight() - getLeft(), MeasureSpec.EXACTLY ), MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY ) );
				
				Log.d( LOG_TAG, "content.layout(2)" );
				
				if ( mInvert ) 
					content.layout( 0, mTopOffset, content.getMeasuredWidth(), mTopOffset + content.getMeasuredHeight() );
				else 
					content.layout( 0, mTopOffset + handleHeight, content.getMeasuredWidth(), mTopOffset + handleHeight + content.getMeasuredHeight() );
			
			} else {
				
				final int handleWidth = mHandle.getWidth();
				int width = getRight() - getLeft() - handleWidth - mTopOffset;
				content.measure( MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY ), MeasureSpec.makeMeasureSpec( getBottom() - getTop(), MeasureSpec.EXACTLY ) );
				
				if( mInvert )
					content.layout( mTopOffset, 0, mTopOffset + content.getMeasuredWidth(), content.getMeasuredHeight() );
				else
					content.layout( handleWidth + mTopOffset, 0, mTopOffset + handleWidth + content.getMeasuredWidth(), content.getMeasuredHeight() );
			}
		}
		// Try only once... we should really loop but it's not a big deal
		// if the draw was cancelled, it will only be temporary anyway
		content.getViewTreeObserver().dispatchOnPreDraw();
		content.buildDrawingCache();
		
		content.setVisibility( View.GONE );
	}
	
	private void stopTracking()
	{
		mHandle.setPressed( false );
		mTracking = false;
		
		if ( mOnDrawerScrollListener != null ) {
			mOnDrawerScrollListener.onScrollEnded();
		}
		
		if ( mVelocityTracker != null ) {
			mVelocityTracker.recycle();
			mVelocityTracker = null;
		}
	}
	
	private void doAnimation()
	{
		if ( mAnimating ) {
			incrementAnimation();
			
			if( mInvert )
			{
				if ( mAnimationPosition < mTopOffset ) {
					mAnimating = false;
					closeDrawer();
				} else if ( mAnimationPosition >= mTopOffset + ( mVertical ? getHeight() : getWidth() ) - 1 ) {
					mAnimating = false;
					openDrawer();
				} else {
					moveHandle( (int)mAnimationPosition );
					mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
					mHandler.sendMessageAtTime( mHandler.obtainMessage( MSG_ANIMATE ), mCurrentAnimationTime );
				}				
			} else {
				if ( mAnimationPosition >= mBottomOffset + ( mVertical ? getHeight() : getWidth() ) - 1 ) {
					mAnimating = false;
					closeDrawer();
				} else if ( mAnimationPosition < mTopOffset ) {
					mAnimating = false;
					openDrawer();
				} else {
					moveHandle( (int)mAnimationPosition );
					mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
					mHandler.sendMessageAtTime( mHandler.obtainMessage( MSG_ANIMATE ), mCurrentAnimationTime );
				}
			}
		}
	}
	
	private void incrementAnimation()
	{
		long now = SystemClock.uptimeMillis();
		float t = ( now - mAnimationLastTime ) / 1000.0f; // ms -> s
		final float position = mAnimationPosition;
		final float v = mAnimatedVelocity; // px/s
		final float a = mInvert ? mAnimatedAcceleration : mAnimatedAcceleration; // px/s/s
		mAnimationPosition = position + ( v * t ) + ( 0.5f * a * t * t ); // px
		mAnimatedVelocity = v + ( a * t ); // px/s
		mAnimationLastTime = now; // ms
	}
	
	/**
	 * Toggles the drawer open and close. Takes effect immediately.
	 * 
	 * @see #open()
	 * @see #close()
	 * @see #animateClose()
	 * @see #animateOpen()
	 * @see #animateToggle()
	 */
	public void toggle()
	{
		if ( !mExpanded ) {
			openDrawer();
		} else {
			closeDrawer();
		}
		invalidate();
		requestLayout();
	}
	
	/**
	 * Toggles the drawer open and close with an animation.
	 * 
	 * @see #open()
	 * @see #close()
	 * @see #animateClose()
	 * @see #animateOpen()
	 * @see #toggle()
	 */
	public void animateToggle()
	{
		if ( !mExpanded ) {
			animateOpen();
		} else {
			animateClose();
		}
	}
	
	/**
	 * Opens the drawer immediately.
	 * 
	 * @see #toggle()
	 * @see #close()
	 * @see #animateOpen()
	 */
	public void open()
	{
		openDrawer();
		invalidate();
		requestLayout();
		
		sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED );
	}
	
	/**
	 * Closes the drawer immediately.
	 * 
	 * @see #toggle()
	 * @see #open()
	 * @see #animateClose()
	 */
	public void close()
	{
		closeDrawer();
		invalidate();
		requestLayout();
	}
	
	/**
	 * Closes the drawer with an animation.
	 * 
	 * @see #close()
	 * @see #open()
	 * @see #animateOpen()
	 * @see #animateToggle()
	 * @see #toggle()
	 */
	public void animateClose()
	{
		prepareContent();
		final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
		if ( scrollListener != null ) {
			scrollListener.onScrollStarted();
		}
		animateClose( mVertical ? mHandle.getTop() : mHandle.getLeft() );
		
		if ( scrollListener != null ) {
			scrollListener.onScrollEnded();
		}
	}
	
	/**
	 * Opens the drawer with an animation.
	 * 
	 * @see #close()
	 * @see #open()
	 * @see #animateClose()
	 * @see #animateToggle()
	 * @see #toggle()
	 */
	public void animateOpen()
	{
		prepareContent();
		final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
		if ( scrollListener != null ) {
			scrollListener.onScrollStarted();
		}
		animateOpen( mVertical ? mHandle.getTop() : mHandle.getLeft() );
		
		sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED );
		
		if ( scrollListener != null ) {
			scrollListener.onScrollEnded();
		}
	}
	
	private void closeDrawer()
	{
		moveHandle( COLLAPSED_FULL_CLOSED );
		mContent.setVisibility( View.GONE );
		mContent.destroyDrawingCache();
		
		if ( !mExpanded ) { return; }
		
		mExpanded = false;
		if ( mOnDrawerCloseListener != null ) {
			mOnDrawerCloseListener.onDrawerClosed();
		}
	}
	
	private void openDrawer()
	{
		moveHandle( EXPANDED_FULL_OPEN );
		mContent.setVisibility( View.VISIBLE );
		
		if ( mExpanded ) { return; }
		
		mExpanded = true;
		
		if ( mOnDrawerOpenListener != null ) {
			mOnDrawerOpenListener.onDrawerOpened();
		}
	}
	
	/**
	 * Sets the listener that receives a notification when the drawer becomes
	 * open.
	 * 
	 * @param onDrawerOpenListener
	 *           The listener to be notified when the drawer is opened.
	 */
	public void setOnDrawerOpenListener( OnDrawerOpenListener onDrawerOpenListener )
	{
		mOnDrawerOpenListener = onDrawerOpenListener;
	}
	
	/**
	 * Sets the listener that receives a notification when the drawer becomes
	 * close.
	 * 
	 * @param onDrawerCloseListener
	 *           The listener to be notified when the drawer is closed.
	 */
	public void setOnDrawerCloseListener( OnDrawerCloseListener onDrawerCloseListener )
	{
		mOnDrawerCloseListener = onDrawerCloseListener;
	}
	
	/**
	 * Sets the listener that receives a notification when the drawer starts or
	 * ends a scroll. A fling is considered as a scroll. A fling will also
	 * trigger a drawer opened or drawer closed event.
	 * 
	 * @param onDrawerScrollListener
	 *           The listener to be notified when scrolling starts or stops.
	 */
	public void setOnDrawerScrollListener( OnDrawerScrollListener onDrawerScrollListener )
	{
		mOnDrawerScrollListener = onDrawerScrollListener;
	}
	
	/**
	 * Returns the handle of the drawer.
	 * 
	 * @return The View reprenseting the handle of the drawer, identified by the
	 *         "handle" id in XML.
	 */
	public View getHandle()
	{
		return mHandle;
	}
	
	/**
	 * Returns the content of the drawer.
	 * 
	 * @return The View reprenseting the content of the drawer, identified by the
	 *         "content" id in XML.
	 */
	public View getContent()
	{
		return mContent;
	}
	
	/**
	 * Unlocks the SlidingDrawer so that touch events are processed.
	 * 
	 * @see #lock()
	 */
	public void unlock()
	{
		mLocked = false;
	}
	
	/**
	 * Locks the SlidingDrawer so that touch events are ignores.
	 * 
	 * @see #unlock()
	 */
	public void lock()
	{
		mLocked = true;
	}
	
	/**
	 * Indicates whether the drawer is currently fully opened.
	 * 
	 * @return True if the drawer is opened, false otherwise.
	 */
	public boolean isOpened()
	{
		return mExpanded;
	}
	
	/**
	 * Indicates whether the drawer is scrolling or flinging.
	 * 
	 * @return True if the drawer is scroller or flinging, false otherwise.
	 */
	public boolean isMoving()
	{
		return mTracking || mAnimating;
	}
	
	private class DrawerToggler implements OnClickListener {
		
		public void onClick( View v )
		{
			if ( mLocked ) { return; }
			// mAllowSingleTap isn't relevant here; you're *always*
			// allowed to open/close the drawer by clicking with the
			// trackball.
			
			if ( mAnimateOnClick ) {
				animateToggle();
			} else {
				toggle();
			}
		}
	}
	
	private class SlidingHandler extends Handler {
		
		public void handleMessage( Message m )
		{
			switch ( m.what ) {
				case MSG_ANIMATE:
					doAnimation();
					break;
			}
		}
	}
}



package it.sephiroth.demo.slider;

import it.sephiroth.demo.slider.widget.MultiDirectionSlidingDrawer;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;

public class MainActivity extends Activity {

	Button mCloseButton;
	Button mOpenButton;
	MultiDirectionSlidingDrawer mDrawer;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature( Window.FEATURE_NO_TITLE );
        setContentView(R.layout.main);
        
        mCloseButton.setOnClickListener( new OnClickListener() {
			@Override
			public void onClick( View v )
			{
				mDrawer.animateClose();
			}
		});
        
        mOpenButton.setOnClickListener( new OnClickListener() {
			
			@Override
			public void onClick( View v )
			{
				if( !mDrawer.isOpened() )
					mDrawer.animateOpen();
			}
		});
    }
    
    @Override
   public void onContentChanged()
   {
   	super.onContentChanged();
   	mCloseButton = (Button) findViewById( R.id.button_close );
   	mOpenButton = (Button) findViewById( R.id.button_open );
   	mDrawer = (MultiDirectionSlidingDrawer) findViewById( R.id.drawer );
   }
}


<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout android:layout_height="fill_parent" android:layout_width="fill_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android">
<Button android:layout_height="wrap_content" android:layout_width="100dp" android:visibility="gone" android:layout_centerInParent="true" android:text="@string/open" android:id="@+id/button_open"/>
<it.sephiroth.demo.slider.widget.MultiDirectionSlidingDrawer android:layout_height="wrap_content" android:layout_width="fill_parent" android:id="@+id/drawer" my:content="@+id/content" my:handle="@+id/handle" my:direction="topToBottom" xmlns:my="http://schemas.android.com/apk/res/it.sephiroth.demo.slider">
<include android:id="@id/content" layout="@layout/pen_content"/>
<ImageView android:layout_height="40px" android:layout_width="wrap_content" android:id="@id/handle" android:src="@drawable/sliding_drawer_handle_bottom"/> </it.sephiroth.demo.slider.widget.MultiDirectionSlidingDrawer> 
</RelativeLayout>
分享到:
评论
6 楼 glmxzaj 2014-10-11  
怎么content内容大小不会改变呢  一直是fillpartent
5 楼 postxx 2013-11-08  
下滑后 第一个页面的按钮还能点击,怎么屏蔽呀
4 楼 zw6296149 2013-07-23  
djjackylei 写道
zzheng.wang 写道
左到右点击无效的  530行  改成负的就行   感谢楼主分享

你好,能请教一下具体是哪句代码吗?因为行数方面可能不太一样


543      Log.d( LOG_TAG, "COLLAPSED. always: " + always + ", c1: "      545      + c1 + ", c2: " + c2 + ", c3: " + c3 ); 
545        if ( !always && ( c1 || ( c2 && c3 ) ) ) { 
546             mAnimatedAcceleration = mMaximumAcceleration; 
 
本文546行的    mAnimatedAcceleration = mMaximumAcceleration; 
改为          mAnimatedAcceleration = -mMaximumAcceleration;

就可以实现 左-右的handle点击 打开了..- -刚测试出来的 ...希望能看到^^               
3 楼 djjackylei 2013-05-08  
zzheng.wang 写道
左到右点击无效的  530行  改成负的就行   感谢楼主分享

你好,能请教一下具体是哪句代码吗?因为行数方面可能不太一样
2 楼 zzheng.wang 2013-04-26  
左到右点击无效的  530行  改成负的就行   感谢楼主分享
1 楼 djjackylei 2013-04-12  
左-右方向的handle点击无效,拖动才能出来,不知道楼主是否有解决办法

相关推荐

    四个方向的SlidingDrawer实例

    在这个"四个方向的SlidingDrawer实例"中,我们将深入探讨如何创建一个能从上下左右四个方向滑出的抽屉效果,并为初学者提供学习指导。 首先,SlidingDrawer主要由两部分组成:handle(手柄)和content(内容区域)...

    自定义四个方向上的SlidingDrawer(抽屉效果)

    本文将详细介绍如何自定义一个可以支持四个方向——上、下、左、右滑动的SlidingDrawer。 首先,理解SlidingDrawer的基本结构和工作原理至关重要。SlidingDrawer由两部分组成:handle(手柄)和content(内容区域)...

    SlidingDrawer(抽屉)

    SlidingDrawer有四个主要属性需要设置: 1. `android:handle`:这是抽屉的手柄,通常是一个ImageView或Button,用户可以通过点击或拖动它来打开或关闭抽屉。 2. `android:content`:定义抽屉的内容区域,可以包含...

    SlidingDrawer侧滑抽屉

    **四、SlidingDrawer的替代方案** 由于Android SDK 21(Lollipop)开始,SlidingDrawer已被弃用,推荐使用其他组件来实现类似功能: 1. ** CoordinatorLayout + AppBarLayout + CollapsingToolbarLayout**:适用于...

    四个方向的抽屉控件

    本篇将深入探讨“四个方向的抽屉控件”的实现和相关知识点。 一、抽屉控件的基本概念 1. 抽屉控件(Drawer):在UI设计中,抽屉通常以滑动的形式出现,可以从屏幕边缘滑出,提供导航、设置或其他操作选项。在...

    Android控件之SlidingDrawer(滑动式抽屉)详解与实例分享

    滑动式抽屉(SlidingDrawer)是Android平台上一个独特的控件,它提供了一种隐藏和显示内容的方式,常用于创建类似抽屉的交互体验。在Android 1.5版本的模拟器中,进入应用程序列表时所展示的效果就是SlidingDrawer的...

    android 多方向抽屉

    标题"android 多方向抽屉"暗示我们将探讨的不只是传统的侧滑抽屉,而是支持上下左右多个方向滑动的抽屉效果。这种设计可以为应用提供更丰富的用户体验,使用户能够从四个不同的方向触发隐藏内容。 描述中的"android...

    各个方向弹出边菜单.zip

    这种菜单通常从屏幕的左侧或右侧滑出,但此资源可能包含了一些不寻常的设计,允许菜单从上、下、左、右四个方向弹出。下面将详细讲解如何在Android中实现这种多功能的边菜单及其涉及的技术点。 1. **Sliding Drawer...

    android抽屉

    SlidingDrawer组件提供了一种方式来隐藏屏幕上的内容,当用户需要时,可以通过拖动手柄将其显示出来。这个组件可以沿着垂直或水平方向滑动,由两个主要部分组成:一个是可以拖动的手柄View,另一个是隐藏的内容View...

    Android提高之多方向抽屉实现方法

    它可以轻松地实现上下左右四个方向的抽屉效果,不仅支持简单的左右滑动,还支持上下滑动,使得开发者在设计界面时有更多的选择。在使用Panel控件的过程中,需要注意的一个问题是在某些版本中可能出现抽屉“闪烁”的...

    DemoSlidingDrawer

    SlidingDrawer是Android SDK提供的一种滑动抽屉控件,它允许开发者在屏幕上添加一个可以从屏幕边缘滑出的抽屉,通常用于隐藏或显示额外的功能或信息。抽屉可以水平或垂直滑动,且支持设置抽屉的把手,用户可以通过...

    android抽屉[收集].pdf

    在Android应用开发中,抽屉(SlidingDrawer)是一个常用组件,它允许开发者将内容以抽屉的形式隐藏在屏幕边缘,通过一个可拖动的手柄(handle)来展示或隐藏这些内容。抽屉通常用于创建导航菜单或者隐藏不常用但重要...

    android 抽屉功能

    早期的Android版本中,存在一个叫做`SlidingDrawer`的组件,它提供了类似的功能。但随着Material Design设计语言的引入,`SlidingDrawer`已被废弃,取而代之的是`Navigation Drawer`。`Navigation Drawer`更符合现代...

Global site tag (gtag.js) - Google Analytics