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="/blog_article/@drawable/sliding_drawer_handle_bottom/index.html"/> </it.sephiroth.demo.slider.widget.MultiDirectionSlidingDrawer> </RelativeLayout>
http://www.joysin.net/network/protocol/wireshark-capture-tcp-establishment-and-disconnect.html
Wireshark抓包分析TCP的建立与断开过程
一、TCP建立连接
说明:在此图中HostA充当客户端角色,HostB充当服务器角色。
TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN,ACK。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。
第一次握手:建立连接时,HostA发送SYN包(SEQ=a)到HostB,并进入SYN_SEND状态,等待HostB确认。
第二次握手:HostB收到SYN包后,必须确认HostA的SYN(ACK=a+1),同时自己也送一个SYN包(SEQ=b),即SYN+ACK包,此时HostB进入SYN_RECV状态。
第三次握手:HostA收到HostB的SYN+ACK包,向HostB发送确认包ACK(ACK=b+1),此包发送完毕,HostA和HostB进入入Established状态,完成三次握手。
抓包验证:
第一次握手:
第二次握手:
第三次握手:
二、TCP断开连接
当数据传输完毕后,需要经过四次握手来断开TCP连接,其步骤如下:
抓包验证:
第一次握手:
第二、三次握手:
第四次握手:
实验完毕!!!!!!!!!!!!!
Joysin
2010.11.30
一、FAT12软盘格式
软盘格式如图1:
每个扇区是512字节,512B*2880=1.44MB
图 1 软盘(1.44MB,FAT12)
1、引导扇区占512字节,开启后加载这里的512个字节代码,不能把操作系统写在里面,因为太小了。
2、FAT1,FAT2两者都是一样的,各占9个扇区,如下图。之所以从00000200开始是因为引导扇区占了512字节。
由簇号寻找在表中的FAT项,由于第0个和第1个FAT项始终不用,所以最小的簇号为2,簇号为2对应的FAT项为FFF,簇号为3对应的FAT项为008,FAT项的值代表的是文件下一个簇号,但如果值大于或等于0xFF8,则表示当前簇已经是本文件的最后一个簇。如果值为0xFF7,表示它是一个坏簇。
3、根目录区,存放着根目录条目,每个条目占32个字节,如下图:
由每个条目就能知道文件的名字和条目对应的开始簇号。扇区号=簇号-2,根目录区的扇区大小如下图
RootDirSectors=(224*32+512-1)/ 512=14,共224个根目录条目,每个长度是32字节,每个扇区为512字节。
4、数据区
其实起始扇区为19+14+簇号-2
二、int 13h中断
对于1.44MB的软盘来讲,总共有两面(磁头号0和1),每面有80个柱面(0-79),每个柱面有18个扇区。软盘的容量的由来:2×80×18×512=1.44MB,扇区号是这样分配的,0柱面,0磁头是第一个扇区,0柱面,1磁头是第二个扇区,1柱面,0磁头是第三个扇区,1柱面,1磁头是第四个扇区。
三、boot.asm代码详解如下:
根目录读取一个扇区到内存0900h:0100位置,遍历此扇区的16个根目录,看是否有LOADER BIN,如果没找到,再读取一个扇区到内存0900h:0100位置,循环刚才的动作,直到14个扇区全部查找完毕;如果找到了,那么取该目录的开始簇号,①根据开始簇号取得他在数据区的扇区号然后读入内存0900h:0100处;然后根据开始簇号取得它在FAT1中的扇区号,然后读入内存08FF:0000处,一般读两个扇区;根据偏移计算FAT项的值,如果为FFF则结束,如果为008,转到①继续执行。0900:0000-0900:0100为堆栈区域。内存分配图如下:
代码如下:
;%define _BOOT_DEBUG_ ; 做 Boot Sector 时一定将此行注释掉!将此行打开后用 nasm Boot.asm -o Boot.com 做成一个.COM文件易于调试
%ifdef _BOOT_DEBUG_
org 0100h ; 调试状态, 做成 .COM 文件, 可调试
%else
org 07c00h ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行
%endif
;================================================================================================
%ifdef _BOOT_DEBUG_
BaseOfStack equ 0100h ; 调试状态下堆栈基地址(栈底, 从这个位置向低地址生长)
%else
BaseOfStack equ 07c00h ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)
%endif
;equ为伪指令,编译时候就变成对应的代码,本身不占用空间
BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址
OffsetOfLoader equ 0100h ; LOADER.BIN 被加载到的位置 ---- 偏移地址
RootDirSectors equ 14 ; 根目录占用14个扇区 根据BPB_RootEntCnt计算出来的
SectorNoOfRootDirectory equ 19 ; 根目录的第一个扇区号,每个根目录项占32个字节
SectorNoOfFAT1 equ 1 ; FAT1 的第一个扇区号 = BPB_RsvdSecCnt
DeltaSectorNo equ 17 ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2
; 文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo
;================================================================================================
jmp short LABEL_START ; Start to boot.
nop ; 这个 nop 不可少
;一个磁道有18个扇区,一个扇区有512字节
; 下面是 FAT12 磁盘的头
BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节
BPB_BytsPerSec DW 512 ; 每扇区字节数 ;重要
BPB_SecPerClus DB 1 ; 每簇多少扇区
BPB_RsvdSecCnt DW 1 ; Boot 记录占用多少扇区 ;所以FAT的第一个扇区为1
BPB_NumFATs DB 2 ; 共有多少 FAT 表
BPB_RootEntCnt DW 224 ; 根目录文件数最大值 ;计算扇区个数时候用到
BPB_TotSec16 DW 2880 ; 逻辑扇区总数
BPB_Media DB 0xF0 ; 媒体描述符
BPB_FATSz16 DW 9 ; 每FAT扇区数
BPB_SecPerTrk DW 18 ; 每磁道扇区数 ;重要
BPB_NumHeads DW 2 ; 磁头数(面数) ;由扇区求柱面,磁头,扇区时候用到
BPB_HiddSec DD 0 ; 隐藏扇区数
BPB_TotSec32 DD 0 ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数
BS_DrvNum DB 0 ; 中断 13 的驱动器号 ;后来赋值给dl
BS_Reserved1 DB 0 ; 未使用
BS_BootSig DB 29h ; 扩展引导标记 (29h)
BS_VolID DD 0 ; 卷序列号
BS_VolLab DB 'OrangeS0.02'; 卷标, 必须 11 个字节
BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节
LABEL_START:
mov ax, cs ;cs=0000h
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack;上面的地方没有代码,可以自由存数据
; 清屏
mov ax, 0600h ; AH = 6, AL = 0h
mov bx, 0700h ; 黑底白字(BL = 07h)
mov cx, 0 ; 左上角: (0, 0)
mov dx, 0184fh ; 右下角: (80, 50)
int 10h ; int 10h
mov dh, 0 ; "Booting "
call DispStr ;int 10号暂时不考虑,功能显示字符串,因为有很多种方式
xor ah, ah ; ┓
xor dl, dl ; ┣ 软驱复位
int 13h ; ┛
; 下面在 A 盘的根目录寻找 LOADER.BIN
mov word [wSectorNo], SectorNoOfRootDirectory ;19
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
cmp word [wRootDirSizeForLoop], 0 ; ┓ 14
jz LABEL_NO_LOADERBIN ; ┣ 判断根目录区是不是已经读完
dec word [wRootDirSizeForLoop] ; ┛ 如果读完表示没有找到 LOADER.BIN
mov ax, BaseOfLoader ;09000h
mov es, ax ; es <- BaseOfLoader
mov bx, OffsetOfLoader ; 0100h bx <- OffsetOfLoader 于是, es:bx = BaseOfLoader:OffsetOfLoader
mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号
mov cl, 1
call ReadSector
mov si, LoaderFileName ; ds:si -> "LOADER BIN"
mov di, OffsetOfLoader ; es:di -> BaseOfLoader:0100,,正好指向根目录项的文件名属性
cld
mov dx, 10h ;因为一个扇区最多有512/32=16个根目录
LABEL_SEARCH_FOR_LOADERBIN:
cmp dx, 0 ; 循环次数控制,
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ;如果已经读完这个扇区的所有根目录,就跳到下一个扇区。
dec dx ; 就跳到这个扇区的下一个根目录
mov cx, 11 ;因为根目录的DIR_Name有11个字节
LABEL_CMP_FILENAME:
cmp cx, 0
jz LABEL_FILENAME_FOUND ; 如果比较了 11 个字符都相等, 表示找到
dec cx
lodsb ; ds:si -> al
cmp al, byte [es:di]
jz LABEL_GO_ON
jmp LABEL_DIFFERENT ; 只要发现不一样的字符就表明本 DirectoryEntry 不是我们要找的 LOADER.BIN
LABEL_GO_ON:
inc di
jmp LABEL_CMP_FILENAME ; 继续循环
LABEL_DIFFERENT:
and di, 0FFE0h ; di &= E0 为了让它指向本条目开头
add di, 20h ; di += 20h 下一个目录条目 20h=32个字节
mov si, LoaderFileName
jmp LABEL_SEARCH_FOR_LOADERBIN;
LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
add word [wSectorNo], 1
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN
LABEL_NO_LOADERBIN:
mov dh, 2 ; "No LOADER."
call DispStr ; 显示字符串
%ifdef _BOOT_DEBUG_
mov ax, 4c00h ; ┓
int 21h ; ┛没有找到 LOADER.BIN, 回到 DOS
%else
jmp $ ; 没有找到 LOADER.BIN, 死循环在这里
%endif
LABEL_FILENAME_FOUND: ; 找到 LOADER.BIN 后便来到这里继续
mov ax, RootDirSectors
and di, 0FFE0h ; di -> 当前条目的开始
add di, 01Ah ; 取得此条目对应的开始簇号的偏移
mov cx, word [es:di] ;2
push cx ; 保存此 Sector 在 FAT 中的序号
add cx, ax ;2+14
add cx, DeltaSectorNo ; 19+14+簇号-2
mov ax, BaseOfLoader
mov es, ax ;
mov bx, OffsetOfLoader ;
mov ax, cx ; ax=34
LABEL_GOON_LOADING_FILE:
push ax ; `.
push bx ; |
mov ah, 0Eh ; | 每读一个扇区就在 "Booting " 后面
mov al, '.' ; | 打一个点, 形成这样的效果:
mov bl, 0Fh ; | Booting ......
int 10h ; |
pop bx ; |
pop ax ; /
mov cl, 1 ;扇区号为34,读一个扇区,到09000h:0100h
call ReadSector
pop ax ; 取出此 Sector 在 FAT 中的序号
call GetFATEntry ;或者FAT项的值
cmp ax, 0FFFh
jz LABEL_FILE_LOADED
push ax ; 保存 Sector 在 FAT 中的序号
mov dx, RootDirSectors
add ax, dx ;ax+14
add ax, DeltaSectorNo ;ax+14+17
add bx, [BPB_BytsPerSec];0100h+200h,又读取一个扇区
jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:
mov dh, 1 ; "Ready."
call DispStr ; 显示字符串
; *****************************************************************************************************
jmp BaseOfLoader:OffsetOfLoader ; 这一句正式跳转到已加载到内
; 存中的 LOADER.BIN 的开始处,
; 开始执行 LOADER.BIN 的代码。
; Boot Sector 的使命到此结束
; *****************************************************************************************************
;============================================================================
;变量
;----------------------------------------
wRootDirSizeForLoop: dw RootDirSectors ; 因为要用到这个存储单元,不是单一的用14这个数字,Root Directory 占用的扇区数, 在循环中会递减至零.
wSectorNo: dw 0 ; 要读取的扇区号
bOdd: db 0 ; 奇数还是偶数
;============================================================================
;字符串
;----------------------------------------
LoaderFileName: db "LOADER BIN", 0 ; LOADER.BIN 之文件名
; 为简化代码, 下面每个字符串的长度均为 MessageLength
MessageLength equ 9
BootMessage: db "Booting "; 9字节, 不够则用空格补齐. 序号 0
Message1: db "Ready. "; 9字节, 不够则用空格补齐. 序号 1
Message2: db "No LOADER"; 9字节, 不够则用空格补齐. 序号 2
;============================================================================
;----------------------------------------
; 函数名: DispStr
;----------------------------------------
; 作用:
; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
DispStr:
mov ax, MessageLength
mul dh
add ax, BootMessage
mov bp, ax ; ┓
mov ax, ds ; ┣ ES:BP = 串地址
mov es, ax ; ┛
mov cx, MessageLength ; CX = 串长度
mov ax, 01301h ; AH = 13, AL = 01h
mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h)
mov dl, 0
int 10h ; int 10h
ret
;----------------------------------------
; 函数名: ReadSector
;----------------------------------------
; 作用:
; 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中
ReadSector:
; -----------------------------------
; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
; -----------------------------------
; 设扇区号为 x
; ┌ 柱面号 = y >> 1
; x ┌ 商 y ┤
; -------------- => ┤ └ 磁头号 = y & 1
; 每磁道扇区数 │
; └ 余 z => 起始扇区号 = z + 1
push bp ;因为要用到bp
mov bp, sp ;
sub esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]
mov byte [bp-2], cl ;大家奇怪为什么不直接push呢,因为如果直接push,那么则不能直接取出来数据
push bx ; 保存 bx,因为要做除数
mov bl, [BPB_SecPerTrk] ; bl: 除数为18
div bl ; y 在 al 中, z 在 ah 中
inc ah ; z ++
mov cl, ah ; cl <- 起始扇区号
mov dh, al ; dh <- y
shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
mov ch, al ; ch <- 柱面号
and dh, 1 ; dh & 1 = 磁头号
pop bx ; 恢复 bx
; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘)
.GoOnReading:
mov ah, 2 ; 读
mov al, byte [bp-2] ; 读 al 个扇区
int 13h
jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止
add esp, 2 ;由于前面辟出了两个字节,保存要读的扇区数
pop bp
ret
;----------------------------------------
; 函数名: GetFATEntry
;----------------------------------------
; 作用:
; 找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
; 需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx
GetFATEntry:
push es
push bx
push ax
mov ax, BaseOfLoader; `.
sub ax, 0100h ; | 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT;9000-0100=8F00,90000和8F000之前差4K
mov es, ax ; /
pop ax ;ax=2
mov byte [bOdd], 0
mov bx, 3
mul bx ; ax=6;
mov bx, 2
div bx ;ax=3(商);dx=0(余数),本质是ax*1.5,为了求偏移
cmp dx, 0
jz LABEL_EVEN
mov byte [bOdd], 1 ;如果为1,说明是奇数
LABEL_EVEN:;偶数
; 现在 ax 中是 FATEntry 在 FAT 中的偏移量,下面来
; 计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
xor dx, dx
mov bx, [BPB_BytsPerSec] :512
div bx ; dx:ax / BPB_BytsPerSec
; ax <- 商 (FATEntry 所在的扇区相对于 FAT 的扇区号) 结果是0号扇区
; dx <- 余数 (FATEntry 在扇区内的偏移) 结果是3号偏移
push dx
mov bx, 0 ; bx <- 0 于是, es:bx = (BaseOfLoader - 100):00
add ax, SectorNoOfFAT1 ; 引导扇区占一个扇区,此句之后的 ax 就是 FATEntry 所在的扇区号
mov cl, 2
call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界
; 发生错误, 因为一个 FATEntry 可能跨越两个扇区
pop dx
add bx, dx ;偏移为3
mov ax, [es:bx] ; 如前面的图所说,ax=8FFFh
cmp byte [bOdd], 1
jnz LABEL_EVEN_2
shr ax, 4 ;如果偏移为4,上步得到ax=008Fh,右移后ax=0008h,与后还为0008h
LABEL_EVEN_2:
and ax, 0FFFh ;不等于跳到此处,ax=0FFFh
LABEL_GET_FAT_ENRY_OK:
pop bx
pop es
ret
;----------------------------------------
times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw 0xaa55 ; 结束标志