Advertisement
Guest User

SemiClosedSlidingDrawer.java

a guest
Apr 2nd, 2012
2,571
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 30.76 KB | None | 0 0
  1. /*
  2.  * Copyright (C) 2008 The Android Open Source Project
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *      http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16.  
  17. package se.smartrefill.view;
  18.  
  19. import java.lang.reflect.Method;
  20.  
  21. import se.smartrefill.app.x.R;
  22. import android.content.Context;
  23. import android.content.res.TypedArray;
  24. import android.graphics.Bitmap;
  25. import android.graphics.Canvas;
  26. import android.graphics.Rect;
  27. import android.os.Handler;
  28. import android.os.Message;
  29. import android.os.SystemClock;
  30. import android.util.AttributeSet;
  31. import android.view.MotionEvent;
  32. import android.view.SoundEffectConstants;
  33. import android.view.VelocityTracker;
  34. import android.view.View;
  35. import android.view.ViewGroup;
  36. import android.view.accessibility.AccessibilityEvent;
  37.  
  38. /**
  39.  * This is a modified version of {@link SlidingDrawer} that supports the use
  40.  * case to show a part of the content also when the drawer is closed/collapsed.
  41.  * Use the 'semiClosedContentSize' attribute to specify how much of the content
  42.  * (preferably in dips) that should be visible in closed/collapsed mode.
  43.  *
  44.  * SlidingDrawer hides content out of the screen and allows the user to drag a
  45.  * handle to bring the content on screen. SlidingDrawer can be used vertically
  46.  * or horizontally.
  47.  *
  48.  * A special widget composed of two children views: the handle, that the users
  49.  * drags, and the content, attached to the handle and dragged with it.
  50.  *
  51.  * SlidingDrawer should be used as an overlay inside layouts. This means
  52.  * SlidingDrawer should only be used inside of a FrameLayout or a RelativeLayout
  53.  * for instance. The size of the SlidingDrawer defines how much space the
  54.  * content will occupy once slid out so SlidingDrawer should usually use
  55.  * fill_parent for both its dimensions.
  56.  *
  57.  * Inside an XML layout, SlidingDrawer must define the id of the handle and of
  58.  * the content:
  59.  *
  60.  * <pre class="prettyprint">
  61.  * &lt;SlidingDrawer
  62.  *     android:id="@+id/drawer"
  63.  *     android:layout_width="fill_parent"
  64.  *     android:layout_height="fill_parent"
  65.  *
  66.  *     android:handle="@+id/handle"
  67.  *     android:content="@+id/content"&gt;
  68.  *
  69.  *     &lt;ImageView
  70.  *         android:id="@id/handle"
  71.  *         android:layout_width="88dip"
  72.  *         android:layout_height="44dip" /&gt;
  73.  *
  74.  *     &lt;GridView
  75.  *         android:id="@id/content"
  76.  *         android:layout_width="fill_parent"
  77.  *         android:layout_height="fill_parent" /&gt;
  78.  *
  79.  * &lt;/SlidingDrawer&gt;
  80.  * </pre>
  81.  *
  82.  * @attr ref android.R.styleable#SlidingDrawer_content
  83.  * @attr ref android.R.styleable#SlidingDrawer_handle
  84.  * @attr ref android.R.styleable#SlidingDrawer_topOffset
  85.  * @attr ref android.R.styleable#SlidingDrawer_bottomOffset
  86.  * @attr ref android.R.styleable#SlidingDrawer_orientation
  87.  * @attr ref android.R.styleable#SlidingDrawer_allowSingleTap
  88.  * @attr ref android.R.styleable#SlidingDrawer_animateOnClick
  89.  */
  90. public class SemiClosedSlidingDrawer
  91.     extends ViewGroup
  92. {
  93.     public static final int ORIENTATION_HORIZONTAL = 0;
  94.     public static final int ORIENTATION_VERTICAL = 1;
  95.  
  96.     private static final int TAP_THRESHOLD = 6;
  97.     private static final float MAXIMUM_TAP_VELOCITY = 100.0f;
  98.     private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
  99.     private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
  100.     private static final float MAXIMUM_ACCELERATION = 2000.0f;
  101.     private static final int VELOCITY_UNITS = 1000;
  102.     private static final int MSG_ANIMATE = 1000;
  103.     private static final int ANIMATION_FRAME_DURATION = 1000 / 60;
  104.  
  105.     private static final int EXPANDED_FULL_OPEN = -10001;
  106.     private static final int COLLAPSED_SEMI_CLOSED = -10003;
  107.  
  108.     private final int mHandleId;
  109.     private final int mContentId;
  110.  
  111.     private View mHandle;
  112.     private View mContent;
  113.  
  114.     private final Rect mFrame = new Rect();
  115.     private final Rect mInvalidate = new Rect();
  116.     private boolean mTracking;
  117.     private boolean mLocked;
  118.  
  119.     private VelocityTracker mVelocityTracker;
  120.  
  121.     private boolean mVertical;
  122.     private boolean mExpanded;
  123.     private int mBottomOffset;
  124.     private int mTopOffset;
  125.     private int mHandleHeight;
  126.     private int mHandleWidth;
  127.  
  128.     private int mSemiClosedContentSize;
  129.  
  130.     private OnDrawerOpenListener mOnDrawerOpenListener;
  131.     private OnDrawerCloseListener mOnDrawerCloseListener;
  132.     private OnDrawerScrollListener mOnDrawerScrollListener;
  133.  
  134.     private final Handler mHandler = new SlidingHandler();
  135.     private float mAnimatedAcceleration;
  136.     private float mAnimatedVelocity;
  137.     private float mAnimationPosition;
  138.     private long mAnimationLastTime;
  139.     private long mCurrentAnimationTime;
  140.     private int mTouchDelta;
  141.     private boolean mAnimating;
  142.     private boolean mAllowSingleTap;
  143.     private boolean mAnimateOnClick;
  144.  
  145.     private final int mTapThreshold;
  146.     private final int mMaximumTapVelocity;
  147.     private final int mMaximumMinorVelocity;
  148.     private final int mMaximumMajorVelocity;
  149.     private final int mMaximumAcceleration;
  150.     private final int mVelocityUnits;
  151.  
  152.     /**
  153.      * Callback invoked when the drawer is opened.
  154.      */
  155.     public static interface OnDrawerOpenListener
  156.     {
  157.         /**
  158.          * Invoked when the drawer becomes fully open.
  159.          */
  160.         public void onDrawerOpened();
  161.     }
  162.  
  163.     /**
  164.      * Callback invoked when the drawer is closed.
  165.      */
  166.     public static interface OnDrawerCloseListener
  167.     {
  168.         /**
  169.          * Invoked when the drawer becomes fully closed.
  170.          */
  171.         public void onDrawerClosed();
  172.     }
  173.  
  174.     /**
  175.      * Callback invoked when the drawer is scrolled.
  176.      */
  177.     public static interface OnDrawerScrollListener
  178.     {
  179.         /**
  180.          * Invoked when the user starts dragging/flinging the drawer's handle.
  181.          */
  182.         public void onScrollStarted();
  183.  
  184.         /**
  185.          * Invoked when the user stops dragging/flinging the drawer's handle.
  186.          */
  187.         public void onScrollEnded();
  188.     }
  189.  
  190.     /**
  191.      * Creates a new SlidingDrawer from a specified set of attributes defined in
  192.      * XML.
  193.      *
  194.      * @param context
  195.      *            The application's environment.
  196.      * @param attrs
  197.      *            The attributes defined in XML.
  198.      */
  199.     public SemiClosedSlidingDrawer(Context context, AttributeSet attrs)
  200.     {
  201.         this(context, attrs, 0);
  202.     }
  203.  
  204.     /**
  205.      * Creates a new SlidingDrawer from a specified set of attributes defined in
  206.      * XML.
  207.      *
  208.      * @param context
  209.      *            The application's environment.
  210.      * @param attrs
  211.      *            The attributes defined in XML.
  212.      * @param defStyle
  213.      *            The style to apply to this widget.
  214.      */
  215.     public SemiClosedSlidingDrawer(Context context, AttributeSet attrs, int defStyle)
  216.     {
  217.         super(context, attrs, defStyle);
  218.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SemiClosedSlidingDrawer, defStyle, 0);
  219.  
  220.         String orientation = a.getString(R.styleable.SemiClosedSlidingDrawer_orientation);
  221.         mVertical = (orientation != null && orientation.equals("vertical"));
  222.         mBottomOffset = (int) a.getDimension(R.styleable.SemiClosedSlidingDrawer_bottomOffset, 0.0f);
  223.         mTopOffset = (int) a.getDimension(R.styleable.SemiClosedSlidingDrawer_topOffset, 0.0f);
  224.         mAllowSingleTap = a.getBoolean(R.styleable.SemiClosedSlidingDrawer_allowSingleTap, true);
  225.         mAnimateOnClick = a.getBoolean(R.styleable.SemiClosedSlidingDrawer_animateOnClick, true);
  226.         mSemiClosedContentSize = (int) a.getDimension(R.styleable.SemiClosedSlidingDrawer_semiClosedContentSize, 0.0f);
  227.  
  228.         int handleId = a.getResourceId(R.styleable.SemiClosedSlidingDrawer_handle, 0);
  229.         if (handleId == 0)
  230.         {
  231.             throw new IllegalArgumentException("The handle attribute is required and must refer " + "to a valid child.");
  232.         }
  233.  
  234.         int contentId = a.getResourceId(R.styleable.SemiClosedSlidingDrawer_content, 0);
  235.         if (contentId == 0)
  236.         {
  237.             throw new IllegalArgumentException("The content attribute is required and must refer "
  238.                             + "to a valid child.");
  239.         }
  240.  
  241.         if (handleId == contentId)
  242.         {
  243.             throw new IllegalArgumentException("The content and handle attributes must refer "
  244.                             + "to different children.");
  245.         }
  246.  
  247.         mHandleId = handleId;
  248.         mContentId = contentId;
  249.  
  250.         final float density = getResources().getDisplayMetrics().density;
  251.         mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
  252.         mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
  253.         mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
  254.         mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
  255.         mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
  256.         mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);
  257.  
  258.         a.recycle();
  259.  
  260.         setAlwaysDrawnWithCacheEnabled(false);
  261.     }
  262.  
  263.     @Override
  264.     protected void onFinishInflate()
  265.     {
  266.         mHandle = findViewById(mHandleId);
  267.         if (mHandle == null)
  268.         {
  269.             throw new IllegalArgumentException("The handle attribute is must refer to an" + " existing child.");
  270.         }
  271.         mHandle.setOnClickListener(new DrawerToggler());
  272.  
  273.         mContent = findViewById(mContentId);
  274.         if (mContent == null)
  275.         {
  276.             throw new IllegalArgumentException("The content attribute is must refer to an" + " existing child.");
  277.         }
  278.         mContent.setVisibility(View.VISIBLE);
  279.     }
  280.  
  281.     @Override
  282.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  283.     {
  284.         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
  285.         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
  286.  
  287.         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
  288.         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
  289.  
  290.         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED)
  291.         {
  292.             throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
  293.         }
  294.  
  295.         final View handle = mHandle;
  296.         measureChild(handle, widthMeasureSpec, heightMeasureSpec);
  297.  
  298.         if (mVertical)
  299.         {
  300.             int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
  301.             mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),
  302.                             MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
  303.         }
  304.         else
  305.         {
  306.             int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
  307.             mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
  308.                             MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
  309.         }
  310.  
  311.         setMeasuredDimension(widthSpecSize, heightSpecSize);
  312.     }
  313.  
  314.     @Override
  315.     protected void dispatchDraw(Canvas canvas)
  316.     {
  317.         final long drawingTime = getDrawingTime();
  318.         final View handle = mHandle;
  319.         final boolean isVertical = mVertical;
  320.  
  321.         drawChild(canvas, handle, drawingTime);
  322.  
  323.         if (mTracking || mAnimating || !mExpanded)
  324.         {
  325.             final Bitmap cache = mContent.getDrawingCache();
  326.             if (cache != null)
  327.             {
  328.                 if (isVertical)
  329.                 {
  330.                     canvas.drawBitmap(cache, 0, handle.getBottom(), null);
  331.                 }
  332.                 else
  333.                 {
  334.                     canvas.drawBitmap(cache, handle.getRight(), 0, null);
  335.                 }
  336.             }
  337.             else
  338.             {
  339.                 canvas.save();
  340.                 canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset, isVertical ? handle.getTop()
  341.                                 - mTopOffset : 0);
  342.                 drawChild(canvas, mContent, drawingTime);
  343.                 canvas.restore();
  344.             }
  345.         }
  346.         else if (mExpanded)
  347.         {
  348.             drawChild(canvas, mContent, drawingTime);
  349.         }
  350.     }
  351.  
  352.     @Override
  353.     protected void onLayout(boolean changed, int l, int t, int r, int b)
  354.     {
  355.         if (mTracking)
  356.         {
  357.             return;
  358.         }
  359.  
  360.         final int width = r - l;
  361.         final int height = b - t;
  362.  
  363.         final View handle = mHandle;
  364.  
  365.         int childWidth = handle.getMeasuredWidth();
  366.         int childHeight = handle.getMeasuredHeight();
  367.  
  368.         int childLeft;
  369.         int childTop;
  370.  
  371.         final View content = mContent;
  372.  
  373.         if (mVertical)
  374.         {
  375.             // [SEMI-CLOSED] Change so that drawer is laid out semi-closed in
  376.             // collapsed mode.
  377.             childLeft = (width - childWidth) / 2;
  378.             childTop = mExpanded ? mTopOffset : height - childHeight + mBottomOffset - mSemiClosedContentSize;
  379.  
  380.             content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(),
  381.                             mTopOffset + childHeight + content.getMeasuredHeight());
  382.         }
  383.         else
  384.         {
  385.             // [SEMI-CLOSED] Change so that drawer is laid out semi-closed in
  386.             // collapsed mode.
  387.             childLeft = mExpanded ? mTopOffset : width - childWidth + mBottomOffset - mSemiClosedContentSize;
  388.             childTop = (height - childHeight) / 2;
  389.  
  390.             content.layout(mTopOffset + childWidth, 0, mTopOffset + childWidth + content.getMeasuredWidth(),
  391.                             content.getMeasuredHeight());
  392.         }
  393.  
  394.         handle.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
  395.         mHandleHeight = handle.getHeight();
  396.         mHandleWidth = handle.getWidth();
  397.     }
  398.  
  399.     @Override
  400.     public boolean onInterceptTouchEvent(MotionEvent event)
  401.     {
  402.         if (mLocked)
  403.         {
  404.             return false;
  405.         }
  406.  
  407.         final int action = event.getAction();
  408.  
  409.         float x = event.getX();
  410.         float y = event.getY();
  411.  
  412.         final Rect frame = mFrame;
  413.         final View handle = mHandle;
  414.        
  415.         handle.getHitRect(frame);
  416.  
  417.         if (!mTracking && !frame.contains((int) x, (int) y))
  418.         {
  419.             return false;
  420.         }
  421.  
  422.         if (action == MotionEvent.ACTION_DOWN)
  423.         {
  424.             mTracking = true;
  425.  
  426.             handle.setPressed(true);
  427.             // Must be called before prepareTracking()
  428.             prepareContent();
  429.  
  430.             // Must be called after prepareContent()
  431.             if (mOnDrawerScrollListener != null)
  432.             {
  433.                 mOnDrawerScrollListener.onScrollStarted();
  434.             }
  435.  
  436.             if (mVertical)
  437.             {
  438.                 final int top = mHandle.getTop();
  439.                 mTouchDelta = (int) y - top;
  440.                 prepareTracking(top);
  441.             }
  442.             else
  443.             {
  444.                 final int left = mHandle.getLeft();
  445.                 mTouchDelta = (int) x - left;
  446.                 prepareTracking(left);
  447.             }
  448.             mVelocityTracker.addMovement(event);
  449.         }
  450.  
  451.         return true;
  452.     }
  453.  
  454.     @Override
  455.     public boolean onTouchEvent(MotionEvent event)
  456.     {
  457.         if (mLocked)
  458.         {
  459.             return true;
  460.         }
  461.  
  462.         if (mTracking)
  463.         {
  464.             mVelocityTracker.addMovement(event);
  465.             final int action = event.getAction();
  466.             switch (action) {
  467.             case MotionEvent.ACTION_MOVE:
  468.                 moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
  469.                 break;
  470.             case MotionEvent.ACTION_UP:
  471.             case MotionEvent.ACTION_CANCEL: {
  472.                 final VelocityTracker velocityTracker = mVelocityTracker;
  473.                 velocityTracker.computeCurrentVelocity(mVelocityUnits);
  474.  
  475.                 float yVelocity = velocityTracker.getYVelocity();
  476.                 float xVelocity = velocityTracker.getXVelocity();
  477.                 boolean negative;
  478.  
  479.                 final boolean vertical = mVertical;
  480.                 if (vertical)
  481.                 {
  482.                     negative = yVelocity < 0;
  483.                     if (xVelocity < 0)
  484.                     {
  485.                         xVelocity = -xVelocity;
  486.                     }
  487.                     if (xVelocity > mMaximumMinorVelocity)
  488.                     {
  489.                         xVelocity = mMaximumMinorVelocity;
  490.                     }
  491.                 }
  492.                 else
  493.                 {
  494.                     negative = xVelocity < 0;
  495.                     if (yVelocity < 0)
  496.                     {
  497.                         yVelocity = -yVelocity;
  498.                     }
  499.                     if (yVelocity > mMaximumMinorVelocity)
  500.                     {
  501.                         yVelocity = mMaximumMinorVelocity;
  502.                     }
  503.                 }
  504.  
  505.                 float velocity = (float) Math.hypot(xVelocity, yVelocity);
  506.                 if (negative)
  507.                 {
  508.                     velocity = -velocity;
  509.                 }
  510.  
  511.                 final int top = mHandle.getTop();
  512.                 final int left = mHandle.getLeft();
  513.  
  514.                 if (Math.abs(velocity) < mMaximumTapVelocity)
  515.                 {
  516.                     // [SEMI-CLOSED] Remove width of visible content part here.
  517.                     if (vertical ? (mExpanded && top < mTapThreshold + mTopOffset)
  518.                                     || (!mExpanded && top > mBottomOffset + getBottom() - getTop() - mHandleHeight
  519.                                                     - -mSemiClosedContentSize - mTapThreshold)
  520.                                     : (mExpanded && left < mTapThreshold + mTopOffset)
  521.                                                     || (!mExpanded && left > mBottomOffset + getRight() - getLeft()
  522.                                                                     - mHandleWidth - mSemiClosedContentSize
  523.                                                                     - mTapThreshold))
  524.                     {
  525.  
  526.                         if (mAllowSingleTap)
  527.                         {
  528.                             playSoundEffect(SoundEffectConstants.CLICK);
  529.  
  530.                             if (mExpanded)
  531.                             {
  532.                                 animateClose(vertical ? top : left);
  533.                             }
  534.                             else
  535.                             {
  536.                                 animateOpen(vertical ? top : left);
  537.                             }
  538.                         }
  539.                         else
  540.                         {
  541.                             performFling(vertical ? top : left, velocity, false);
  542.                         }
  543.  
  544.                     }
  545.                     else
  546.                     {
  547.                         performFling(vertical ? top : left, velocity, false);
  548.                     }
  549.                 }
  550.                 else
  551.                 {
  552.                     performFling(vertical ? top : left, velocity, false);
  553.                 }
  554.             }
  555.                 break;
  556.             }
  557.         }
  558.  
  559.         return mTracking || mAnimating || super.onTouchEvent(event);
  560.     }
  561.  
  562.     private void animateClose(int position)
  563.     {
  564.         prepareTracking(position);
  565.         performFling(position, mMaximumAcceleration, true);
  566.     }
  567.  
  568.     private void animateOpen(int position)
  569.     {
  570.         prepareTracking(position);
  571.         performFling(position, -mMaximumAcceleration, true);
  572.     }
  573.  
  574.     private void performFling(int position, float velocity, boolean always)
  575.     {
  576.         mAnimationPosition = position;
  577.         mAnimatedVelocity = velocity;
  578.  
  579.         if (mExpanded)
  580.         {
  581.             if (always
  582.                             || (velocity > mMaximumMajorVelocity || (position > mTopOffset
  583.                                             + (mVertical ? mHandleHeight : mHandleWidth) && velocity > -mMaximumMajorVelocity)))
  584.             {
  585.                 // We are expanded, but they didn't move sufficiently to cause
  586.                 // us to retract. Animate back to the expanded position.
  587.                 mAnimatedAcceleration = mMaximumAcceleration;
  588.                 if (velocity < 0)
  589.                 {
  590.                     mAnimatedVelocity = 0;
  591.                 }
  592.             }
  593.             else
  594.             {
  595.                 // We are expanded and are now going to animate away.
  596.                 mAnimatedAcceleration = -mMaximumAcceleration;
  597.                 if (velocity > 0)
  598.                 {
  599.                     mAnimatedVelocity = 0;
  600.                 }
  601.             }
  602.         }
  603.         else
  604.         {
  605.             if (!always
  606.                             && (velocity > mMaximumMajorVelocity || (position > (mVertical ? getHeight() : getWidth()) / 2 && velocity > -mMaximumMajorVelocity)))
  607.             {
  608.                 // We are collapsed, and they moved enough to allow us to
  609.                 // expand.
  610.                 mAnimatedAcceleration = mMaximumAcceleration;
  611.                 if (velocity < 0)
  612.                 {
  613.                     mAnimatedVelocity = 0;
  614.                 }
  615.             }
  616.             else
  617.             {
  618.                 // We are collapsed, but they didn't move sufficiently to cause
  619.                 // us to retract. Animate back to the collapsed position.
  620.                 mAnimatedAcceleration = -mMaximumAcceleration;
  621.                 if (velocity > 0)
  622.                 {
  623.                     mAnimatedVelocity = 0;
  624.                 }
  625.             }
  626.         }
  627.  
  628.         long now = SystemClock.uptimeMillis();
  629.         mAnimationLastTime = now;
  630.         mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
  631.         mAnimating = true;
  632.         mHandler.removeMessages(MSG_ANIMATE);
  633.         mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
  634.         stopTracking();
  635.     }
  636.  
  637.     private void prepareTracking(int position)
  638.     {
  639.         mTracking = true;
  640.         mVelocityTracker = VelocityTracker.obtain();
  641.         boolean opening = !mExpanded;
  642.         if (opening)
  643.         {
  644.             mAnimatedAcceleration = mMaximumAcceleration;
  645.             mAnimatedVelocity = mMaximumMajorVelocity;
  646.             // [SEMI-CLOSED] Remove width of visible content part here.
  647.             mAnimationPosition = mBottomOffset + (mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth)
  648.                             - mSemiClosedContentSize;
  649.             moveHandle((int) mAnimationPosition);
  650.             mAnimating = true;
  651.             mHandler.removeMessages(MSG_ANIMATE);
  652.             long now = SystemClock.uptimeMillis();
  653.             mAnimationLastTime = now;
  654.             mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
  655.             mAnimating = true;
  656.         }
  657.         else
  658.         {
  659.             if (mAnimating)
  660.             {
  661.                 mAnimating = false;
  662.                 mHandler.removeMessages(MSG_ANIMATE);
  663.             }
  664.             moveHandle(position);
  665.         }
  666.     }
  667.  
  668.     private void moveHandle(int position)
  669.     {
  670.         final View handle = mHandle;
  671.  
  672.         if (mVertical)
  673.         {
  674.             if (position == EXPANDED_FULL_OPEN)
  675.             {
  676.                 handle.offsetTopAndBottom(mTopOffset - handle.getTop());
  677.                 invalidate();
  678.             }
  679.             // [SEMI-CLOSED] Remove width of visible content part here.
  680.             else if (position == COLLAPSED_SEMI_CLOSED)
  681.             {
  682.                 handle.offsetTopAndBottom(mBottomOffset + getBottom() - getTop() - mHandleHeight
  683.                                 - mSemiClosedContentSize - handle.getTop());
  684.                 invalidate();
  685.             }
  686.             else
  687.             {
  688.                 final int top = handle.getTop();
  689.                 int deltaY = position - top;
  690.                 if (position < mTopOffset)
  691.                 {
  692.                     deltaY = mTopOffset - top;
  693.                 }
  694.                 // [SEMI-CLOSED] Remove width of visible content part here ...
  695.                 else if (deltaY > mBottomOffset + getBottom() - getTop() - mHandleHeight - mSemiClosedContentSize - top)
  696.                 {
  697.                     // [SEMI_CLOSED] ... and here.
  698.                     deltaY = mBottomOffset + getBottom() - getTop() - mHandleHeight - mSemiClosedContentSize - top;
  699.                 }
  700.                 handle.offsetTopAndBottom(deltaY);
  701.  
  702.                 final Rect frame = mFrame;
  703.                 final Rect region = mInvalidate;
  704.  
  705.                 handle.getHitRect(frame);
  706.                 region.set(frame);
  707.  
  708.                 region.union(frame.left, frame.top - deltaY, frame.right, frame.bottom - deltaY);
  709.                 region.union(0, frame.bottom - deltaY, getWidth(), frame.bottom - deltaY + mContent.getHeight());
  710.  
  711.                 invalidate(region);
  712.             }
  713.         }
  714.         else
  715.         {
  716.             if (position == EXPANDED_FULL_OPEN)
  717.             {
  718.                 handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
  719.                 invalidate();
  720.             }
  721.             // [SEMI-CLOSED] Remove width of visible content part here.
  722.             else if (position == COLLAPSED_SEMI_CLOSED)
  723.             {
  724.                 handle.offsetLeftAndRight(mBottomOffset + getRight() - getLeft() - mHandleWidth
  725.                                 - mSemiClosedContentSize - handle.getLeft());
  726.                 invalidate();
  727.             }
  728.             else
  729.             {
  730.                 final int left = handle.getLeft();
  731.                 int deltaX = position - left;
  732.                 if (position < mTopOffset)
  733.                 {
  734.                     deltaX = mTopOffset - left;
  735.                 }
  736.                 // [SEMI-CLOSED] Remove width of visible content part here ...
  737.                 else if (deltaX > mBottomOffset + getRight() - getLeft() - mHandleWidth - mSemiClosedContentSize - left)
  738.                 {
  739.                     // [SEMI-CLOSED] ... and here.
  740.                     deltaX = mBottomOffset + getRight() - getLeft() - mHandleWidth - mSemiClosedContentSize - left;
  741.                 }
  742.                 handle.offsetLeftAndRight(deltaX);
  743.  
  744.                 final Rect frame = mFrame;
  745.                 final Rect region = mInvalidate;
  746.  
  747.                 handle.getHitRect(frame);
  748.                 region.set(frame);
  749.  
  750.                 region.union(frame.left - deltaX, frame.top, frame.right - deltaX, frame.bottom);
  751.                 region.union(frame.right - deltaX, 0, frame.right - deltaX + mContent.getWidth(), getHeight());
  752.  
  753.                 invalidate(region);
  754.             }
  755.         }
  756.     }
  757.  
  758.     private void prepareContent()
  759.     {
  760.         if (mAnimating)
  761.         {
  762.             return;
  763.         }
  764.  
  765.         // Something changed in the content, we need to honor the layout request
  766.         // before creating the cached bitmap
  767.         final View content = mContent;
  768.         if (content.isLayoutRequested())
  769.         {
  770.             if (mVertical)
  771.             {
  772.                 final int childHeight = mHandleHeight;
  773.                 int height = getBottom() - getTop() - childHeight - mTopOffset;
  774.                 content.measure(MeasureSpec.makeMeasureSpec(getRight() - getLeft(), MeasureSpec.EXACTLY),
  775.                                 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
  776.                 content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(), mTopOffset + childHeight
  777.                                 + content.getMeasuredHeight());
  778.             }
  779.             else
  780.             {
  781.                 final int childWidth = mHandle.getWidth();
  782.                 int width = getRight() - getLeft() - childWidth - mTopOffset;
  783.                 content.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
  784.                                 MeasureSpec.makeMeasureSpec(getBottom() - getTop(), MeasureSpec.EXACTLY));
  785.                 content.layout(childWidth + mTopOffset, 0, mTopOffset + childWidth + content.getMeasuredWidth(),
  786.                                 content.getMeasuredHeight());
  787.             }
  788.         }
  789.         // Try only once... we should really loop but it's not a big deal
  790.         // if the draw was cancelled, it will only be temporary anyway
  791.         content.getViewTreeObserver().dispatchOnPreDraw();
  792.         if (!isViewHardwareAccelerated(content))
  793.         {
  794.             content.buildDrawingCache();
  795.         }
  796.  
  797.         content.setVisibility(View.GONE);
  798.     }
  799.    
  800.     private boolean isViewHardwareAccelerated(View view)
  801.     {
  802.         // View.isHardwareAccelerated() is available from API level 11 (and up).
  803.         try
  804.         {
  805.             Method isHardwareAcceleratedMethod = view.getClass().getDeclaredMethod("isHardwareAccelerated");
  806.             if (isHardwareAcceleratedMethod != null)
  807.             {
  808.                 Boolean result = (Boolean) isHardwareAcceleratedMethod.invoke(view);
  809.                 return result != null && result;
  810.             }
  811.         }
  812.         catch (Exception e)
  813.         {
  814.             // Can't do anything here.
  815.         }
  816.         return false;
  817.     }
  818.  
  819.     private void stopTracking()
  820.     {
  821.         mHandle.setPressed(false);
  822.         mTracking = false;
  823.  
  824.         if (mOnDrawerScrollListener != null)
  825.         {
  826.             mOnDrawerScrollListener.onScrollEnded();
  827.         }
  828.  
  829.         if (mVelocityTracker != null)
  830.         {
  831.             mVelocityTracker.recycle();
  832.             mVelocityTracker = null;
  833.         }
  834.     }
  835.  
  836.     private void doAnimation()
  837.     {
  838.         if (mAnimating)
  839.         {
  840.             incrementAnimation();
  841.             if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1)
  842.             {
  843.                 mAnimating = false;
  844.                 closeDrawer();
  845.             }
  846.             else if (mAnimationPosition < mTopOffset)
  847.             {
  848.                 mAnimating = false;
  849.                 openDrawer();
  850.             }
  851.             else
  852.             {
  853.                 moveHandle((int) mAnimationPosition);
  854.                 mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
  855.                 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
  856.             }
  857.         }
  858.     }
  859.  
  860.     private void incrementAnimation()
  861.     {
  862.         long now = SystemClock.uptimeMillis();
  863.         float t = (now - mAnimationLastTime) / 1000.0f; // ms -> s
  864.         final float position = mAnimationPosition;
  865.         final float v = mAnimatedVelocity; // px/s
  866.         final float a = mAnimatedAcceleration; // px/s/s
  867.         mAnimationPosition = position + (v * t) + (0.5f * a * t * t); // px
  868.         mAnimatedVelocity = v + (a * t); // px/s
  869.         mAnimationLastTime = now; // ms
  870.     }
  871.  
  872.     /**
  873.      * Toggles the drawer open and close. Takes effect immediately.
  874.      *
  875.      * @see #open()
  876.      * @see #close()
  877.      * @see #animateClose()
  878.      * @see #animateOpen()
  879.      * @see #animateToggle()
  880.      */
  881.     public void toggle()
  882.     {
  883.         if (!mExpanded)
  884.         {
  885.             openDrawer();
  886.         }
  887.         else
  888.         {
  889.             closeDrawer();
  890.         }
  891.         invalidate();
  892.         requestLayout();
  893.     }
  894.  
  895.     /**
  896.      * Toggles the drawer open and close with an animation.
  897.      *
  898.      * @see #open()
  899.      * @see #close()
  900.      * @see #animateClose()
  901.      * @see #animateOpen()
  902.      * @see #toggle()
  903.      */
  904.     public void animateToggle()
  905.     {
  906.         if (!mExpanded)
  907.         {
  908.             animateOpen();
  909.         }
  910.         else
  911.         {
  912.             animateClose();
  913.         }
  914.     }
  915.  
  916.     /**
  917.      * Opens the drawer immediately.
  918.      *
  919.      * @see #toggle()
  920.      * @see #close()
  921.      * @see #animateOpen()
  922.      */
  923.     public void open()
  924.     {
  925.         openDrawer();
  926.         invalidate();
  927.         requestLayout();
  928.  
  929.         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
  930.     }
  931.  
  932.     /**
  933.      * Closes the drawer immediately.
  934.      *
  935.      * @see #toggle()
  936.      * @see #open()
  937.      * @see #animateClose()
  938.      */
  939.     public void close()
  940.     {
  941.         closeDrawer();
  942.         invalidate();
  943.         requestLayout();
  944.     }
  945.  
  946.     /**
  947.      * Closes the drawer with an animation.
  948.      *
  949.      * @see #close()
  950.      * @see #open()
  951.      * @see #animateOpen()
  952.      * @see #animateToggle()
  953.      * @see #toggle()
  954.      */
  955.     public void animateClose()
  956.     {
  957.         prepareContent();
  958.         final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
  959.         if (scrollListener != null)
  960.         {
  961.             scrollListener.onScrollStarted();
  962.         }
  963.         animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft());
  964.  
  965.         if (scrollListener != null)
  966.         {
  967.             scrollListener.onScrollEnded();
  968.         }
  969.     }
  970.  
  971.     /**
  972.      * Opens the drawer with an animation.
  973.      *
  974.      * @see #close()
  975.      * @see #open()
  976.      * @see #animateClose()
  977.      * @see #animateToggle()
  978.      * @see #toggle()
  979.      */
  980.     public void animateOpen()
  981.     {
  982.         prepareContent();
  983.         final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
  984.         if (scrollListener != null)
  985.         {
  986.             scrollListener.onScrollStarted();
  987.         }
  988.         animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());
  989.  
  990.         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
  991.  
  992.         if (scrollListener != null)
  993.         {
  994.             scrollListener.onScrollEnded();
  995.         }
  996.     }
  997.  
  998.     private void closeDrawer()
  999.     {
  1000.         moveHandle(COLLAPSED_SEMI_CLOSED);
  1001.         mContent.setVisibility(View.GONE);
  1002.         mContent.destroyDrawingCache();
  1003.  
  1004.         if (!mExpanded)
  1005.         {
  1006.             return;
  1007.         }
  1008.  
  1009.         mExpanded = false;
  1010.         if (mOnDrawerCloseListener != null)
  1011.         {
  1012.             mOnDrawerCloseListener.onDrawerClosed();
  1013.         }
  1014.     }
  1015.  
  1016.     private void openDrawer()
  1017.     {
  1018.         moveHandle(EXPANDED_FULL_OPEN);
  1019.         mContent.setVisibility(View.VISIBLE);
  1020.  
  1021.         if (mExpanded)
  1022.         {
  1023.             return;
  1024.         }
  1025.  
  1026.         mExpanded = true;
  1027.  
  1028.         if (mOnDrawerOpenListener != null)
  1029.         {
  1030.             mOnDrawerOpenListener.onDrawerOpened();
  1031.         }
  1032.     }
  1033.  
  1034.     /**
  1035.      * Sets the listener that receives a notification when the drawer becomes
  1036.      * open.
  1037.      *
  1038.      * @param onDrawerOpenListener
  1039.      *            The listener to be notified when the drawer is opened.
  1040.      */
  1041.     public void setOnDrawerOpenListener(OnDrawerOpenListener onDrawerOpenListener)
  1042.     {
  1043.         mOnDrawerOpenListener = onDrawerOpenListener;
  1044.     }
  1045.  
  1046.     /**
  1047.      * Sets the listener that receives a notification when the drawer becomes
  1048.      * close.
  1049.      *
  1050.      * @param onDrawerCloseListener
  1051.      *            The listener to be notified when the drawer is closed.
  1052.      */
  1053.     public void setOnDrawerCloseListener(OnDrawerCloseListener onDrawerCloseListener)
  1054.     {
  1055.         mOnDrawerCloseListener = onDrawerCloseListener;
  1056.     }
  1057.  
  1058.     /**
  1059.      * Sets the listener that receives a notification when the drawer starts or
  1060.      * ends a scroll. A fling is considered as a scroll. A fling will also
  1061.      * trigger a drawer opened or drawer closed event.
  1062.      *
  1063.      * @param onDrawerScrollListener
  1064.      *            The listener to be notified when scrolling starts or stops.
  1065.      */
  1066.     public void setOnDrawerScrollListener(OnDrawerScrollListener onDrawerScrollListener)
  1067.     {
  1068.         mOnDrawerScrollListener = onDrawerScrollListener;
  1069.     }
  1070.  
  1071.     /**
  1072.      * Returns the handle of the drawer.
  1073.      *
  1074.      * @return The View reprenseting the handle of the drawer, identified by the
  1075.      *         "handle" id in XML.
  1076.      */
  1077.     public View getHandle()
  1078.     {
  1079.         return mHandle;
  1080.     }
  1081.  
  1082.     /**
  1083.      * Returns the content of the drawer.
  1084.      *
  1085.      * @return The View reprenseting the content of the drawer, identified by
  1086.      *         the "content" id in XML.
  1087.      */
  1088.     public View getContent()
  1089.     {
  1090.         return mContent;
  1091.     }
  1092.  
  1093.     /**
  1094.      * Unlocks the SlidingDrawer so that touch events are processed.
  1095.      *
  1096.      * @see #lock()
  1097.      */
  1098.     public void unlock()
  1099.     {
  1100.         mLocked = false;
  1101.     }
  1102.  
  1103.     /**
  1104.      * Locks the SlidingDrawer so that touch events are ignores.
  1105.      *
  1106.      * @see #unlock()
  1107.      */
  1108.     public void lock()
  1109.     {
  1110.         mLocked = true;
  1111.     }
  1112.  
  1113.     /**
  1114.      * Indicates whether the drawer is currently fully opened.
  1115.      *
  1116.      * @return True if the drawer is opened, false otherwise.
  1117.      */
  1118.     public boolean isOpened()
  1119.     {
  1120.         return mExpanded;
  1121.     }
  1122.  
  1123.     /**
  1124.      * Indicates whether the drawer is scrolling or flinging.
  1125.      *
  1126.      * @return True if the drawer is scroller or flinging, false otherwise.
  1127.      */
  1128.     public boolean isMoving()
  1129.     {
  1130.         return mTracking || mAnimating;
  1131.     }
  1132.  
  1133.     private class DrawerToggler
  1134.         implements OnClickListener
  1135.     {
  1136.         public void onClick(View v)
  1137.         {
  1138.             if (mLocked)
  1139.             {
  1140.                 return;
  1141.             }
  1142.             // mAllowSingleTap isn't relevant here; you're *always*
  1143.             // allowed to open/close the drawer by clicking with the
  1144.             // trackball.
  1145.  
  1146.             if (mAnimateOnClick)
  1147.             {
  1148.                 animateToggle();
  1149.             }
  1150.             else
  1151.             {
  1152.                 toggle();
  1153.             }
  1154.         }
  1155.     }
  1156.  
  1157.     private class SlidingHandler
  1158.         extends Handler
  1159.     {
  1160.         public void handleMessage(Message m)
  1161.         {
  1162.             switch (m.what) {
  1163.             case MSG_ANIMATE:
  1164.                 doAnimation();
  1165.                 break;
  1166.             }
  1167.         }
  1168.     }
  1169. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement