| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| <?xml version="1.0"?> | ||
| <!DOCTYPE module PUBLIC | ||
| "-//Puppy Crawl//DTD Check Configuration 1.2//EN" | ||
| "http://www.puppycrawl.com/dtds/configuration_1_2.dtd"> | ||
|
|
||
| <module name="Checker"> | ||
| <module name="NewlineAtEndOfFile"/> | ||
| <module name="FileLength"/> | ||
| <module name="FileTabCharacter"/> | ||
|
|
||
| <!-- Trailing spaces --> | ||
| <module name="RegexpSingleline"> | ||
| <property name="format" value="\s+$"/> | ||
| <property name="message" value="Line has trailing spaces."/> | ||
| </module> | ||
|
|
||
| <module name="TreeWalker"> | ||
| <property name="cacheFile" value="${checkstyle.cache.file}"/> | ||
|
|
||
| <!-- Checks for Javadoc comments. --> | ||
| <!-- See http://checkstyle.sf.net/config_javadoc.html --> | ||
| <!--module name="JavadocMethod"/--> | ||
| <!--module name="JavadocType"/--> | ||
| <!--module name="JavadocVariable"/--> | ||
| <module name="JavadocStyle"/> | ||
|
|
||
|
|
||
| <!-- Checks for Naming Conventions. --> | ||
| <!-- See http://checkstyle.sf.net/config_naming.html --> | ||
| <module name="ConstantName"/> | ||
| <module name="LocalFinalVariableName"/> | ||
| <module name="LocalVariableName"/> | ||
| <module name="MemberName"/> | ||
| <module name="MethodName"/> | ||
| <module name="PackageName"/> | ||
| <module name="ParameterName"/> | ||
| <module name="StaticVariableName"/> | ||
| <module name="TypeName"/> | ||
|
|
||
|
|
||
| <!-- Checks for imports --> | ||
| <!-- See http://checkstyle.sf.net/config_import.html --> | ||
| <module name="AvoidStarImport"/> | ||
| <module name="IllegalImport"/> <!-- defaults to sun.* packages --> | ||
| <module name="RedundantImport"/> | ||
| <module name="UnusedImports"/> | ||
|
|
||
|
|
||
| <!-- Checks for Size Violations. --> | ||
| <!-- See http://checkstyle.sf.net/config_sizes.html --> | ||
| <module name="LineLength"> | ||
| <property name="max" value="120"/> | ||
| </module> | ||
| <module name="MethodLength"/> | ||
| <!--module name="ParameterNumber"/--> | ||
|
|
||
|
|
||
| <!-- Checks for whitespace --> | ||
| <!-- See http://checkstyle.sf.net/config_whitespace.html --> | ||
| <module name="GenericWhitespace"/> | ||
| <module name="EmptyForIteratorPad"/> | ||
| <module name="MethodParamPad"/> | ||
| <module name="NoWhitespaceAfter"/> | ||
| <module name="NoWhitespaceBefore"/> | ||
| <module name="OperatorWrap"/> | ||
| <module name="ParenPad"/> | ||
| <module name="TypecastParenPad"/> | ||
| <module name="WhitespaceAfter"/> | ||
| <module name="WhitespaceAround"/> | ||
|
|
||
|
|
||
| <!-- Modifier Checks --> | ||
| <!-- See http://checkstyle.sf.net/config_modifiers.html --> | ||
| <module name="ModifierOrder"/> | ||
| <module name="RedundantModifier"/> | ||
|
|
||
|
|
||
| <!-- Checks for blocks. You know, those {}'s --> | ||
| <!-- See http://checkstyle.sf.net/config_blocks.html --> | ||
| <!--module name="AvoidNestedBlocks"/--> | ||
| <!--module name="EmptyBlock"/--> | ||
| <module name="LeftCurly"/> | ||
| <!--module name="NeedBraces"/--> | ||
| <module name="RightCurly"/> | ||
|
|
||
|
|
||
| <!-- Checks for common coding problems --> | ||
| <!-- See http://checkstyle.sf.net/config_coding.html --> | ||
| <!--module name="AvoidInlineConditionals"/--> | ||
| <module name="CovariantEquals"/> | ||
| <!--module name="DoubleCheckedLocking"/--> | ||
| <module name="EmptyStatement"/> | ||
| <module name="EqualsAvoidNull"/> | ||
| <module name="EqualsHashCode"/> | ||
| <!--module name="HiddenField"/--> | ||
| <module name="IllegalInstantiation"/> | ||
| <!--module name="InnerAssignment"/--> | ||
| <!--module name="MagicNumber"/--> | ||
| <!--module name="MissingSwitchDefault"/--> | ||
| <module name="RedundantThrows"/> | ||
| <module name="SimplifyBooleanExpression"/> | ||
| <module name="SimplifyBooleanReturn"/> | ||
|
|
||
| <!-- Checks for class design --> | ||
| <!-- See http://checkstyle.sf.net/config_design.html --> | ||
| <!--module name="DesignForExtension"/--> | ||
| <module name="FinalClass"/> | ||
| <module name="HideUtilityClassConstructor"/> | ||
| <module name="InterfaceIsType"/> | ||
| <!--module name="VisibilityModifier"/--> | ||
|
|
||
|
|
||
| <!-- Miscellaneous other checks. --> | ||
| <!-- See http://checkstyle.sf.net/config_misc.html --> | ||
| <module name="ArrayTypeStyle"/> | ||
| <!--module name="FinalParameters"/--> | ||
| <module name="TodoComment"/> | ||
| <module name="UpperEll"/> | ||
| </module> | ||
| </module> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| package="net.simonvt.menudrawer" | ||
| android:versionCode="3" | ||
| android:versionName="2.0.1"> | ||
|
|
||
| <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" /> | ||
| </manifest> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project name="menudrawer" default="help"> | ||
|
|
||
| <!-- The local.properties file is created and updated by the 'android' tool. | ||
| It contains the path to the SDK. It should *NOT* be checked into | ||
| Version Control Systems. --> | ||
| <property file="local.properties" /> | ||
|
|
||
| <!-- The ant.properties file can be created by you. It is only edited by the | ||
| 'android' tool to add properties to it. | ||
| This is the place to change some Ant specific build properties. | ||
| Here are some properties you may want to change/update: | ||
| source.dir | ||
| The name of the source directory. Default is 'src'. | ||
| out.dir | ||
| The name of the output directory. Default is 'bin'. | ||
| For other overridable properties, look at the beginning of the rules | ||
| files in the SDK, at tools/ant/build.xml | ||
| Properties related to the SDK location or the project target should | ||
| be updated using the 'android' tool with the 'update' action. | ||
| This file is an integral part of the build system for your | ||
| application and should be checked into Version Control Systems. | ||
| --> | ||
| <property file="ant.properties" /> | ||
|
|
||
| <!-- if sdk.dir was not set from one of the property file, then | ||
| get it from the ANDROID_HOME env var. | ||
| This must be done before we load project.properties since | ||
| the proguard config can use sdk.dir --> | ||
| <property environment="env" /> | ||
| <condition property="sdk.dir" value="${env.ANDROID_HOME}"> | ||
| <isset property="env.ANDROID_HOME" /> | ||
| </condition> | ||
|
|
||
| <!-- The project.properties file is created and updated by the 'android' | ||
| tool, as well as ADT. | ||
| This contains project specific properties such as project target, and library | ||
| dependencies. Lower level build properties are stored in ant.properties | ||
| (or in .classpath for Eclipse projects). | ||
| This file is an integral part of the build system for your | ||
| application and should be checked into Version Control Systems. --> | ||
| <loadproperties srcFile="project.properties" /> | ||
|
|
||
| <!-- quick check on sdk.dir --> | ||
| <fail | ||
| message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." | ||
| unless="sdk.dir" | ||
| /> | ||
|
|
||
| <!-- | ||
| Import per project custom build rules if present at the root of the project. | ||
| This is the place to put custom intermediary targets such as: | ||
| -pre-build | ||
| -pre-compile | ||
| -post-compile (This is typically used for code obfuscation. | ||
| Compiled code location: ${out.classes.absolute.dir} | ||
| If this is not done in place, override ${out.dex.input.absolute.dir}) | ||
| -post-package | ||
| -post-build | ||
| -pre-clean | ||
| --> | ||
| <import file="custom_rules.xml" optional="true" /> | ||
|
|
||
| <!-- Import the actual build file. | ||
| To customize existing targets, there are two options: | ||
| - Customize only one target: | ||
| - copy/paste the target into this file, *before* the | ||
| <import> task. | ||
| - customize it to your needs. | ||
| - Customize the whole content of build.xml | ||
| - copy/paste the content of the rules files (minus the top node) | ||
| into this file, replacing the <import> task. | ||
| - customize to your needs. | ||
| *********************** | ||
| ****** IMPORTANT ****** | ||
| *********************** | ||
| In all cases you must update the value of version-tag below to read 'custom' instead of an integer, | ||
| in order to avoid having your file be overridden by tools such as "android update project" | ||
| --> | ||
| <!-- version-tag: 1 --> | ||
| <import file="${sdk.dir}/tools/ant/build.xml" /> | ||
|
|
||
| </project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
|
|
||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <parent> | ||
| <groupId>net.simonvt</groupId> | ||
| <artifactId>android-menudrawer-parent</artifactId> | ||
| <version>2.0.3-SNAPSHOT</version> | ||
| <relativePath>../pom.xml</relativePath> | ||
| </parent> | ||
|
|
||
| <artifactId>android-menudrawer</artifactId> | ||
| <name>Android MenuDrawer</name> | ||
| <packaging>apklib</packaging> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>com.google.android</groupId> | ||
| <artifactId>android</artifactId> | ||
| <scope>provided</scope> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| <build> | ||
| <sourceDirectory>src</sourceDirectory> | ||
|
|
||
| <plugins> | ||
| <plugin> | ||
| <groupId>com.jayway.maven.plugins.android.generation2</groupId> | ||
| <artifactId>android-maven-plugin</artifactId> | ||
| <extensions>true</extensions> | ||
| </plugin> | ||
| </plugins> | ||
| </build> | ||
| </project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # This file is automatically generated by Android Tools. | ||
| # Do not modify this file -- YOUR CHANGES WILL BE ERASED! | ||
| # | ||
| # This file must be checked in Version Control Systems. | ||
| # | ||
| # To customize properties used by the Ant build system edit | ||
| # "ant.properties", and override values to adapt the script to your | ||
| # project structure. | ||
| # | ||
| # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): | ||
| #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt | ||
|
|
||
| android.library=true | ||
| # Project target. | ||
| target=android-17 | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| <resources> | ||
|
|
||
| <!-- Reference to a style for the menu drawer. --> | ||
| <attr name="menuDrawerStyle" format="reference" /> | ||
|
|
||
| <!-- Styleables used for styling the menu drawer. --> | ||
| <declare-styleable name="MenuDrawer"> | ||
|
|
||
| <!-- Drawable to use for the background of the content. --> | ||
| <attr name="mdContentBackground" format="reference" /> | ||
|
|
||
| <!-- Drawable to use for the background of the menu. --> | ||
| <attr name="mdMenuBackground" format="reference" /> | ||
|
|
||
| <!-- The size of the menu. --> | ||
| <attr name="mdMenuSize" format="dimension" /> | ||
|
|
||
| <!-- Drawable used as indicator for the active view. --> | ||
| <attr name="mdActiveIndicator" format="reference" /> | ||
|
|
||
| <!-- Defines whether the content will have a dropshadow onto the menu. Default is true. --> | ||
| <attr name="mdDropShadowEnabled" format="boolean" /> | ||
|
|
||
| <!-- The size of the drop shadow. Default is 6dp --> | ||
| <attr name="mdDropShadowSize" format="dimension" /> | ||
|
|
||
| <!-- The color of the drop shadow. Default is #FF000000. --> | ||
| <attr name="mdDropShadowColor" format="color" /> | ||
|
|
||
| <!-- Drawable used for the drop shadow. --> | ||
| <attr name="mdDropShadow" format="reference" /> | ||
|
|
||
| <!-- The touch bezel size. --> | ||
| <attr name="mdTouchBezelSize" format="dimension" /> | ||
|
|
||
| <!-- Whether the indicator should be animated between active views. --> | ||
| <attr name="mdAllowIndicatorAnimation" format="boolean" /> | ||
|
|
||
| <!-- The maximum animation duration --> | ||
| <attr name="mdMaxAnimationDuration" format="integer" /> | ||
| </declare-styleable> | ||
|
|
||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <resources> | ||
|
|
||
| <!-- The default background of the menu. --> | ||
| <color name="md__defaultBackground">#FF555555</color> | ||
|
|
||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <resources> | ||
|
|
||
| <!-- ID used when defining the content layout in XML. --> | ||
| <item name="mdContent" type="id" /> | ||
|
|
||
| <!-- ID used when defining the menu layout in XML. --> | ||
| <item name="mdMenu" type="id" /> | ||
|
|
||
| <!-- The ID of the content container. --> | ||
| <item name="md__content" type="id" /> | ||
|
|
||
| <!-- The ID of the menu container. --> | ||
| <item name="md__menu" type="id" /> | ||
|
|
||
| <!-- The ID of the drawer. --> | ||
| <item name="md__drawer" type="id" /> | ||
|
|
||
| <!-- Used with View#setTag(int) to specify a position for the active view. --> | ||
| <item name="mdActiveViewPosition" type="id" /> | ||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <resources> | ||
|
|
||
| <style name="Widget" /> | ||
|
|
||
| <!-- Base theme for the menu drawer. --> | ||
| <style name="Widget.MenuDrawer"> | ||
| <item name="mdMenuBackground">@color/md__defaultBackground</item> | ||
| <item name="mdContentBackground">?android:attr/windowBackground</item> | ||
| </style> | ||
|
|
||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.graphics.drawable.GradientDrawable; | ||
| import android.util.AttributeSet; | ||
| import android.view.MotionEvent; | ||
|
|
||
| public class BottomDrawer extends VerticalDrawer { | ||
|
|
||
| private int mIndicatorLeft; | ||
|
|
||
| BottomDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public BottomDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public BottomDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public BottomDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| public void openMenu(boolean animate) { | ||
| animateOffsetTo(-mMenuSize, 0, animate); | ||
| } | ||
|
|
||
| @Override | ||
| public void closeMenu(boolean animate) { | ||
| animateOffsetTo(0, 0, animate); | ||
| } | ||
|
|
||
| @Override | ||
| public void setDropShadowColor(int color) { | ||
| final int endColor = color & 0x00FFFFFF; | ||
| mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, | ||
| new int[] { | ||
| color, | ||
| endColor, | ||
| }); | ||
| invalidate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onLayout(boolean changed, int l, int t, int r, int b) { | ||
| final int width = r - l; | ||
| final int height = b - t; | ||
| final int offsetPixels = (int) mOffsetPixels; | ||
| final int menuSize = mMenuSize; | ||
|
|
||
| mMenuContainer.layout(0, height - menuSize, width, height); | ||
| offsetMenu(offsetPixels); | ||
|
|
||
| if (USE_TRANSLATIONS) { | ||
| mContentContainer.layout(0, 0, width, height); | ||
| } else { | ||
| mContentContainer.layout(0, offsetPixels, width, height + offsetPixels); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Offsets the menu relative to its original position based on the position of the content. | ||
| * | ||
| * @param offsetPixels The number of pixels the content if offset. | ||
| */ | ||
| private void offsetMenu(int offsetPixels) { | ||
| if (mOffsetMenu && mMenuSize != 0) { | ||
| final int height = getHeight(); | ||
| final int menuSize = mMenuSize; | ||
| final float openRatio = (menuSize + (float) offsetPixels) / menuSize; | ||
|
|
||
| if (USE_TRANSLATIONS) { | ||
| if (offsetPixels != 0) { | ||
| final int offset = (int) (0.25f * (openRatio * menuSize)); | ||
| mMenuContainer.setTranslationY(offset); | ||
| } else { | ||
| mMenuContainer.setTranslationY(height + menuSize); | ||
| } | ||
|
|
||
| } else { | ||
| final int oldMenuTop = mMenuContainer.getTop(); | ||
| final int offsetBy = (int) (0.25f * (openRatio * menuSize)); | ||
| final int offset = height - mMenuSize + offsetBy - oldMenuTop; | ||
| mMenuContainer.offsetTopAndBottom(offset); | ||
| mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawDropShadow(Canvas canvas, int offsetPixels) { | ||
| final int width = getWidth(); | ||
| final int height = getHeight(); | ||
|
|
||
| mDropShadowDrawable.setBounds(0, height + offsetPixels, width, height + offsetPixels + mDropShadowSize); | ||
| mDropShadowDrawable.draw(canvas); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawMenuOverlay(Canvas canvas, int offsetPixels) { | ||
| final int width = getWidth(); | ||
| final int height = getHeight(); | ||
| final float openRatio = ((float) Math.abs(offsetPixels)) / mMenuSize; | ||
|
|
||
| mMenuOverlay.setBounds(0, height + offsetPixels, width, height); | ||
| mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio))); | ||
| mMenuOverlay.draw(canvas); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawIndicator(Canvas canvas, int offsetPixels) { | ||
| if (mActiveView != null && isViewDescendant(mActiveView)) { | ||
| Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition); | ||
| final int pos = position == null ? 0 : position; | ||
|
|
||
| if (pos == mActivePosition) { | ||
| final int height = getHeight(); | ||
| final int menuHeight = mMenuSize; | ||
| final int indicatorHeight = mActiveIndicator.getHeight(); | ||
|
|
||
| final float openRatio = ((float) Math.abs(offsetPixels)) / menuHeight; | ||
|
|
||
| mActiveView.getDrawingRect(mActiveRect); | ||
| offsetDescendantRectToMyCoords(mActiveView, mActiveRect); | ||
| final int indicatorWidth = mActiveIndicator.getWidth(); | ||
|
|
||
| final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio)); | ||
| final int interpolatedHeight = (int) (indicatorHeight * interpolatedRatio); | ||
|
|
||
| final int indicatorBottom = height + offsetPixels + interpolatedHeight; | ||
| final int indicatorTop = indicatorBottom - indicatorHeight; | ||
| if (mIndicatorAnimating) { | ||
| final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2); | ||
| final int startLeft = mIndicatorStartPos; | ||
| final int diff = finalLeft - startLeft; | ||
| final int startOffset = (int) (diff * mIndicatorOffset); | ||
| mIndicatorLeft = startLeft + startOffset; | ||
| } else { | ||
| mIndicatorLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2); | ||
| } | ||
|
|
||
| canvas.save(); | ||
| canvas.clipRect(mIndicatorLeft, height + offsetPixels, mIndicatorLeft + indicatorWidth, | ||
| indicatorBottom); | ||
| canvas.drawBitmap(mActiveIndicator, mIndicatorLeft, indicatorTop, null); | ||
| canvas.restore(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected int getIndicatorStartPos() { | ||
| return mIndicatorLeft; | ||
| } | ||
|
|
||
| @Override | ||
| protected void initPeekScroller() { | ||
| final int dx = -mMenuSize / 3; | ||
| mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onOffsetPixelsChanged(int offsetPixels) { | ||
| if (USE_TRANSLATIONS) { | ||
| mContentContainer.setTranslationY(offsetPixels); | ||
| offsetMenu(offsetPixels); | ||
| invalidate(); | ||
| } else { | ||
| mContentContainer.offsetTopAndBottom(offsetPixels - mContentContainer.getTop()); | ||
| offsetMenu(offsetPixels); | ||
| invalidate(); | ||
| } | ||
| } | ||
|
|
||
| ////////////////////////////////////////////////////////////////////// | ||
| // Touch handling | ||
| ////////////////////////////////////////////////////////////////////// | ||
|
|
||
| @Override | ||
| protected boolean isContentTouch(MotionEvent ev) { | ||
| return ev.getY() < getHeight() + mOffsetPixels; | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean onDownAllowDrag(MotionEvent ev) { | ||
| final int height = getHeight(); | ||
| return (!mMenuVisible && mInitialMotionY >= height - mTouchSize) | ||
| || (mMenuVisible && mInitialMotionY <= height + mOffsetPixels); | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean onMoveAllowDrag(MotionEvent ev, float diff) { | ||
| final int height = getHeight(); | ||
| return (!mMenuVisible && mInitialMotionY >= height - mTouchSize && (diff < 0)) | ||
| || (mMenuVisible && mInitialMotionY <= height + mOffsetPixels); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onMoveEvent(float dx) { | ||
| setOffsetPixels(Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize)); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onUpEvent(MotionEvent ev) { | ||
| final int offsetPixels = (int) mOffsetPixels; | ||
|
|
||
| if (mIsDragging) { | ||
| mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); | ||
| final int initialVelocity = (int) mVelocityTracker.getXVelocity(); | ||
| mLastMotionY = ev.getY(); | ||
| animateOffsetTo(mVelocityTracker.getYVelocity() < 0 ? -mMenuSize : 0, initialVelocity, | ||
| true); | ||
|
|
||
| // Close the menu when content is clicked while the menu is visible. | ||
| } else if (mMenuVisible && ev.getY() < getHeight() + offsetPixels) { | ||
| closeMenu(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.graphics.drawable.GradientDrawable; | ||
| import android.util.AttributeSet; | ||
|
|
||
| public class BottomStaticDrawer extends StaticDrawer { | ||
|
|
||
| private int mIndicatorLeft; | ||
|
|
||
| BottomStaticDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public BottomStaticDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public BottomStaticDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public BottomStaticDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| protected void initDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super.initDrawer(context, attrs, defStyle); | ||
| mPosition = Position.BOTTOM; | ||
| } | ||
|
|
||
| @Override | ||
| public void setDropShadowColor(int color) { | ||
| final int endColor = color & 0x00FFFFFF; | ||
| mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[] { | ||
| color, | ||
| endColor, | ||
| }); | ||
| invalidate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawIndicator(Canvas canvas) { | ||
| if (mActiveView != null && isViewDescendant(mActiveView)) { | ||
| Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition); | ||
| final int pos = position == null ? 0 : position; | ||
|
|
||
| if (pos == mActivePosition) { | ||
| final int height = getHeight(); | ||
| final int menuHeight = mMenuSize; | ||
| final int indicatorHeight = mActiveIndicator.getHeight(); | ||
|
|
||
| mActiveView.getDrawingRect(mActiveRect); | ||
| offsetDescendantRectToMyCoords(mActiveView, mActiveRect); | ||
| final int indicatorWidth = mActiveIndicator.getWidth(); | ||
|
|
||
| final int indicatorTop = height - menuHeight; | ||
| final int indicatorBottom = indicatorTop + indicatorHeight; | ||
| if (mIndicatorAnimating) { | ||
| final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2); | ||
| final int startLeft = mIndicatorStartPos; | ||
| final int diff = finalLeft - startLeft; | ||
| final int startOffset = (int) (diff * mIndicatorOffset); | ||
| mIndicatorLeft = startLeft + startOffset; | ||
| } else { | ||
| mIndicatorLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2); | ||
| } | ||
|
|
||
| canvas.save(); | ||
| canvas.clipRect(mIndicatorLeft, indicatorTop, mIndicatorLeft + indicatorWidth, | ||
| indicatorBottom); | ||
| canvas.drawBitmap(mActiveIndicator, mIndicatorLeft, indicatorTop, null); | ||
| canvas.restore(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected int getIndicatorStartPos() { | ||
| return mIndicatorLeft; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.util.AttributeSet; | ||
| import android.widget.FrameLayout; | ||
|
|
||
| /** | ||
| * FrameLayout which caches the hardware layer if available. | ||
| * <p/> | ||
| * If it's not posted twice the layer either wont be built on start, or it'll be built twice. | ||
| */ | ||
| public class BuildLayerFrameLayout extends FrameLayout { | ||
|
|
||
| private boolean mChanged; | ||
|
|
||
| private boolean mHardwareLayersEnabled = true; | ||
|
|
||
| private boolean mAttached; | ||
|
|
||
| private boolean mFirst = true; | ||
|
|
||
| public BuildLayerFrameLayout(Context context) { | ||
| super(context); | ||
| if (MenuDrawer.USE_TRANSLATIONS) { | ||
| setLayerType(LAYER_TYPE_HARDWARE, null); | ||
| } | ||
| } | ||
|
|
||
| public BuildLayerFrameLayout(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| if (MenuDrawer.USE_TRANSLATIONS) { | ||
| setLayerType(LAYER_TYPE_HARDWARE, null); | ||
| } | ||
| } | ||
|
|
||
| public BuildLayerFrameLayout(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| if (MenuDrawer.USE_TRANSLATIONS) { | ||
| setLayerType(LAYER_TYPE_HARDWARE, null); | ||
| } | ||
| } | ||
|
|
||
| void setHardwareLayersEnabled(boolean enabled) { | ||
| mHardwareLayersEnabled = enabled; | ||
| } | ||
|
|
||
| @Override | ||
| protected void onAttachedToWindow() { | ||
| super.onAttachedToWindow(); | ||
| mAttached = true; | ||
| } | ||
|
|
||
| @Override | ||
| protected void onDetachedFromWindow() { | ||
| super.onDetachedFromWindow(); | ||
| mAttached = false; | ||
| } | ||
|
|
||
| @Override | ||
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { | ||
| super.onSizeChanged(w, h, oldw, oldh); | ||
|
|
||
| if (MenuDrawer.USE_TRANSLATIONS && mHardwareLayersEnabled) { | ||
| post(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| mChanged = true; | ||
| invalidate(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void dispatchDraw(Canvas canvas) { | ||
| super.dispatchDraw(canvas); | ||
|
|
||
| if (mChanged && MenuDrawer.USE_TRANSLATIONS) { | ||
| post(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| if (mAttached) { | ||
| final int layerType = getLayerType(); | ||
| // If it's already a hardware layer, it'll be built anyway. | ||
| if (layerType != LAYER_TYPE_HARDWARE || mFirst) { | ||
| mFirst = false; | ||
| setLayerType(LAYER_TYPE_HARDWARE, null); | ||
| buildLayer(); | ||
| setLayerType(LAYER_TYPE_NONE, null); | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| mChanged = false; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| /* | ||
| * 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. | ||
| */ | ||
|
|
||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.content.res.Resources; | ||
| import android.graphics.Canvas; | ||
| import android.graphics.ColorFilter; | ||
| import android.graphics.Paint; | ||
| import android.graphics.PixelFormat; | ||
| import android.graphics.drawable.Drawable; | ||
|
|
||
| /** | ||
| * A specialized Drawable that fills the Canvas with a specified color. | ||
| * Note that a ColorDrawable ignores the ColorFilter. | ||
| * <p/> | ||
| * <p>It can be defined in an XML file with the <code><color></code> element.</p> | ||
| * | ||
| * @attr ref android.R.styleable#ColorDrawable_color | ||
| */ | ||
| public class ColorDrawable extends Drawable { | ||
|
|
||
| private ColorState mState; | ||
| private final Paint mPaint = new Paint(); | ||
|
|
||
| /** Creates a new black ColorDrawable. */ | ||
| public ColorDrawable() { | ||
| this(null); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new ColorDrawable with the specified color. | ||
| * | ||
| * @param color The color to draw. | ||
| */ | ||
| public ColorDrawable(int color) { | ||
| this(null); | ||
| setColor(color); | ||
| } | ||
|
|
||
| private ColorDrawable(ColorState state) { | ||
| mState = new ColorState(state); | ||
| } | ||
|
|
||
| @Override | ||
| public int getChangingConfigurations() { | ||
| return super.getChangingConfigurations() | mState.mChangingConfigurations; | ||
| } | ||
|
|
||
| @Override | ||
| public void draw(Canvas canvas) { | ||
| if ((mState.mUseColor >>> 24) != 0) { | ||
| mPaint.setColor(mState.mUseColor); | ||
| canvas.drawRect(getBounds(), mPaint); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Gets the drawable's color value. | ||
| * | ||
| * @return int The color to draw. | ||
| */ | ||
| public int getColor() { | ||
| return mState.mUseColor; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the drawable's color value. This action will clobber the results of prior calls to | ||
| * {@link #setAlpha(int)} on this object, which side-affected the underlying color. | ||
| * | ||
| * @param color The color to draw. | ||
| */ | ||
| public void setColor(int color) { | ||
| if (mState.mBaseColor != color || mState.mUseColor != color) { | ||
| invalidateSelf(); | ||
| mState.mBaseColor = mState.mUseColor = color; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Returns the alpha value of this drawable's color. | ||
| * | ||
| * @return A value between 0 and 255. | ||
| */ | ||
| public int getAlpha() { | ||
| return mState.mUseColor >>> 24; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the color's alpha value. | ||
| * | ||
| * @param alpha The alpha value to set, between 0 and 255. | ||
| */ | ||
| public void setAlpha(int alpha) { | ||
| alpha += alpha >> 7; // make it 0..256 | ||
| int baseAlpha = mState.mBaseColor >>> 24; | ||
| int useAlpha = baseAlpha * alpha >> 8; | ||
| int oldUseColor = mState.mUseColor; | ||
| mState.mUseColor = (mState.mBaseColor << 8 >>> 8) | (useAlpha << 24); | ||
| if (oldUseColor != mState.mUseColor) { | ||
| invalidateSelf(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Setting a color filter on a ColorDrawable has no effect. | ||
| * | ||
| * @param colorFilter Ignore. | ||
| */ | ||
| public void setColorFilter(ColorFilter colorFilter) { | ||
| } | ||
|
|
||
| public int getOpacity() { | ||
| switch (mState.mUseColor >>> 24) { | ||
| case 255: | ||
| return PixelFormat.OPAQUE; | ||
| case 0: | ||
| return PixelFormat.TRANSPARENT; | ||
| } | ||
| return PixelFormat.TRANSLUCENT; | ||
| } | ||
|
|
||
| @Override | ||
| public ConstantState getConstantState() { | ||
| mState.mChangingConfigurations = getChangingConfigurations(); | ||
| return mState; | ||
| } | ||
|
|
||
| static final class ColorState extends ConstantState { | ||
|
|
||
| int mBaseColor; // base color, independent of setAlpha() | ||
| int mUseColor; // basecolor modulated by setAlpha() | ||
| int mChangingConfigurations; | ||
|
|
||
| ColorState(ColorState state) { | ||
| if (state != null) { | ||
| mBaseColor = state.mBaseColor; | ||
| mUseColor = state.mUseColor; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public Drawable newDrawable() { | ||
| return new ColorDrawable(this); | ||
| } | ||
|
|
||
| @Override | ||
| public Drawable newDrawable(Resources res) { | ||
| return new ColorDrawable(this); | ||
| } | ||
|
|
||
| @Override | ||
| public int getChangingConfigurations() { | ||
| return mChangingConfigurations; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| /* | ||
| * Copyright (C) 2006 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. | ||
| */ | ||
|
|
||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.view.animation.AnimationUtils; | ||
| import android.view.animation.Interpolator; | ||
|
|
||
| /** | ||
| * This class encapsulates scrolling. The duration of the scroll | ||
| * can be passed in the constructor and specifies the maximum time that | ||
| * the scrolling animation should take. Past this time, the scrolling is | ||
| * automatically moved to its final stage and computeScrollOffset() | ||
| * will always return false to indicate that scrolling is over. | ||
| */ | ||
| public class FloatScroller { | ||
|
|
||
| private float mStart; | ||
| private float mFinal; | ||
|
|
||
| private float mCurr; | ||
| private long mStartTime; | ||
| private int mDuration; | ||
| private float mDurationReciprocal; | ||
| private float mDeltaX; | ||
| private boolean mFinished; | ||
| private Interpolator mInterpolator; | ||
|
|
||
| /** | ||
| * Create a Scroller with the specified interpolator. If the interpolator is | ||
| * null, the default (viscous) interpolator will be used. Specify whether or | ||
| * not to support progressive "flywheel" behavior in flinging. | ||
| */ | ||
| public FloatScroller(Interpolator interpolator) { | ||
| mFinished = true; | ||
| mInterpolator = interpolator; | ||
| } | ||
|
|
||
| /** | ||
| * Returns whether the scroller has finished scrolling. | ||
| * | ||
| * @return True if the scroller has finished scrolling, false otherwise. | ||
| */ | ||
| public final boolean isFinished() { | ||
| return mFinished; | ||
| } | ||
|
|
||
| /** | ||
| * Force the finished field to a particular value. | ||
| * | ||
| * @param finished The new finished value. | ||
| */ | ||
| public final void forceFinished(boolean finished) { | ||
| mFinished = finished; | ||
| } | ||
|
|
||
| /** | ||
| * Returns how long the scroll event will take, in milliseconds. | ||
| * | ||
| * @return The duration of the scroll in milliseconds. | ||
| */ | ||
| public final int getDuration() { | ||
| return mDuration; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the current offset in the scroll. | ||
| * | ||
| * @return The new offset as an absolute distance from the origin. | ||
| */ | ||
| public final float getCurr() { | ||
| return mCurr; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the start offset in the scroll. | ||
| * | ||
| * @return The start offset as an absolute distance from the origin. | ||
| */ | ||
| public final float getStart() { | ||
| return mStart; | ||
| } | ||
|
|
||
| /** | ||
| * Returns where the scroll will end. Valid only for "fling" scrolls. | ||
| * | ||
| * @return The final offset as an absolute distance from the origin. | ||
| */ | ||
| public final float getFinal() { | ||
| return mFinal; | ||
| } | ||
|
|
||
| public boolean computeScrollOffset() { | ||
| if (mFinished) { | ||
| return false; | ||
| } | ||
|
|
||
| int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime); | ||
|
|
||
| if (timePassed < mDuration) { | ||
| float x = timePassed * mDurationReciprocal; | ||
| x = mInterpolator.getInterpolation(x); | ||
| mCurr = mStart + x * mDeltaX; | ||
|
|
||
| } else { | ||
| mCurr = mFinal; | ||
| mFinished = true; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| public void startScroll(float start, float delta, int duration) { | ||
| mFinished = false; | ||
| mDuration = duration; | ||
| mStartTime = AnimationUtils.currentAnimationTimeMillis(); | ||
| mStart = start; | ||
| mFinal = start + delta; | ||
| mDeltaX = delta; | ||
| mDurationReciprocal = 1.0f / (float) mDuration; | ||
| } | ||
|
|
||
| /** | ||
| * Stops the animation. Contrary to {@link #forceFinished(boolean)}, | ||
| * aborting the animating cause the scroller to move to the final x and y | ||
| * position | ||
| * | ||
| * @see #forceFinished(boolean) | ||
| */ | ||
| public void abortAnimation() { | ||
| mCurr = mFinal; | ||
| mFinished = true; | ||
| } | ||
|
|
||
| /** | ||
| * Extend the scroll animation. This allows a running animation to scroll | ||
| * further and longer, when used with {@link #setFinal(float)}. | ||
| * | ||
| * @param extend Additional time to scroll in milliseconds. | ||
| * @see #setFinal(float) | ||
| */ | ||
| public void extendDuration(int extend) { | ||
| int passed = timePassed(); | ||
| mDuration = passed + extend; | ||
| mDurationReciprocal = 1.0f / mDuration; | ||
| mFinished = false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the time elapsed since the beginning of the scrolling. | ||
| * | ||
| * @return The elapsed time in milliseconds. | ||
| */ | ||
| public int timePassed() { | ||
| return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime); | ||
| } | ||
|
|
||
| public void setFinal(float newVal) { | ||
| mFinal = newVal; | ||
| mDeltaX = mFinal - mStart; | ||
| mFinished = false; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.util.AttributeSet; | ||
| import android.view.MotionEvent; | ||
| import android.view.VelocityTracker; | ||
|
|
||
| public abstract class HorizontalDrawer extends DraggableDrawer { | ||
|
|
||
| private static final String TAG = "HorizontalDrawer"; | ||
|
|
||
| HorizontalDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public HorizontalDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public HorizontalDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public HorizontalDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | ||
| final int widthMode = MeasureSpec.getMode(widthMeasureSpec); | ||
| final int heightMode = MeasureSpec.getMode(heightMeasureSpec); | ||
|
|
||
| if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { | ||
| throw new IllegalStateException("Must measure with an exact size"); | ||
| } | ||
|
|
||
| final int width = MeasureSpec.getSize(widthMeasureSpec); | ||
| final int height = MeasureSpec.getSize(heightMeasureSpec); | ||
|
|
||
| if (!mMenuSizeSet) mMenuSize = (int) (width * 0.8f); | ||
| if (mOffsetPixels == -1) openMenu(false); | ||
|
|
||
| final int menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize); | ||
| final int menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height); | ||
| mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec); | ||
|
|
||
| final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width); | ||
| final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height); | ||
| mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec); | ||
|
|
||
| setMeasuredDimension(width, height); | ||
|
|
||
| updateTouchAreaSize(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean onInterceptTouchEvent(MotionEvent ev) { | ||
| final int action = ev.getAction() & MotionEvent.ACTION_MASK; | ||
|
|
||
| if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) { | ||
| setOffsetPixels(0); | ||
| stopAnimation(); | ||
| endPeek(); | ||
| setDrawerState(STATE_CLOSED); | ||
| } | ||
|
|
||
| // Always intercept events over the content while menu is visible. | ||
| if (mMenuVisible && isContentTouch(ev)) return true; | ||
|
|
||
| if (mTouchMode == TOUCH_MODE_NONE) { | ||
| return false; | ||
| } | ||
|
|
||
| if (action != MotionEvent.ACTION_DOWN) { | ||
| if (mIsDragging) return true; | ||
| } | ||
|
|
||
| switch (action) { | ||
| case MotionEvent.ACTION_DOWN: { | ||
| mLastMotionX = mInitialMotionX = ev.getX(); | ||
| mLastMotionY = mInitialMotionY = ev.getY(); | ||
| final boolean allowDrag = onDownAllowDrag(ev); | ||
|
|
||
| if (allowDrag) { | ||
| setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED); | ||
| stopAnimation(); | ||
| endPeek(); | ||
| mIsDragging = false; | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| case MotionEvent.ACTION_MOVE: { | ||
| final float x = ev.getX(); | ||
| final float dx = x - mLastMotionX; | ||
| final float xDiff = Math.abs(dx); | ||
| final float y = ev.getY(); | ||
| final float yDiff = Math.abs(y - mLastMotionY); | ||
|
|
||
| if (xDiff > mTouchSlop && xDiff > yDiff) { | ||
| if (mOnInterceptMoveEventListener != null && mTouchMode == TOUCH_MODE_FULLSCREEN | ||
| && canChildScrollHorizontally(mContentContainer, false, (int) dx, (int) x, (int) y)) { | ||
| endDrag(); // Release the velocity tracker | ||
| return false; | ||
| } | ||
|
|
||
| final boolean allowDrag = onMoveAllowDrag(ev, dx); | ||
|
|
||
| if (allowDrag) { | ||
| setDrawerState(STATE_DRAGGING); | ||
| mIsDragging = true; | ||
| mLastMotionX = x; | ||
| mLastMotionY = y; | ||
| } | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| /** | ||
| * If you click really fast, an up or cancel event is delivered here. | ||
| * Just snap content to whatever is closest. | ||
| * */ | ||
| case MotionEvent.ACTION_CANCEL: | ||
| case MotionEvent.ACTION_UP: { | ||
| if (Math.abs(mOffsetPixels) > mMenuSize / 2) { | ||
| openMenu(); | ||
| } else { | ||
| closeMenu(); | ||
| } | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain(); | ||
| mVelocityTracker.addMovement(ev); | ||
|
|
||
| return mIsDragging; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean onTouchEvent(MotionEvent ev) { | ||
| if (!mMenuVisible && !mIsDragging && (mTouchMode == TOUCH_MODE_NONE)) { | ||
| return false; | ||
| } | ||
| final int action = ev.getAction() & MotionEvent.ACTION_MASK; | ||
|
|
||
| if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain(); | ||
| mVelocityTracker.addMovement(ev); | ||
|
|
||
| switch (action) { | ||
| case MotionEvent.ACTION_DOWN: { | ||
| mLastMotionX = mInitialMotionX = ev.getX(); | ||
| mLastMotionY = mInitialMotionY = ev.getY(); | ||
| final boolean allowDrag = onDownAllowDrag(ev); | ||
|
|
||
| if (allowDrag) { | ||
| stopAnimation(); | ||
| endPeek(); | ||
| startLayerTranslation(); | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| case MotionEvent.ACTION_MOVE: { | ||
| if (!mIsDragging) { | ||
| final float x = ev.getX(); | ||
| final float dx = x - mLastMotionX; | ||
| final float xDiff = Math.abs(dx); | ||
| final float y = ev.getY(); | ||
| final float yDiff = Math.abs(y - mLastMotionY); | ||
|
|
||
| if (xDiff > mTouchSlop && xDiff > yDiff) { | ||
| final boolean allowDrag = onMoveAllowDrag(ev, dx); | ||
|
|
||
| if (allowDrag) { | ||
| setDrawerState(STATE_DRAGGING); | ||
| mIsDragging = true; | ||
| mLastMotionX = x - mInitialMotionX > 0 | ||
| ? mInitialMotionX + mTouchSlop | ||
| : mInitialMotionX - mTouchSlop; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (mIsDragging) { | ||
| startLayerTranslation(); | ||
|
|
||
| final float x = ev.getX(); | ||
| final float dx = x - mLastMotionX; | ||
|
|
||
| mLastMotionX = x; | ||
| onMoveEvent(dx); | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| case MotionEvent.ACTION_CANCEL: | ||
| case MotionEvent.ACTION_UP: { | ||
| onUpEvent(ev); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.graphics.drawable.GradientDrawable; | ||
| import android.util.AttributeSet; | ||
| import android.view.MotionEvent; | ||
|
|
||
| public class LeftDrawer extends HorizontalDrawer { | ||
|
|
||
| private int mIndicatorTop; | ||
|
|
||
| LeftDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public LeftDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public LeftDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public LeftDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| public void openMenu(boolean animate) { | ||
| animateOffsetTo(mMenuSize, 0, animate); | ||
| } | ||
|
|
||
| @Override | ||
| public void closeMenu(boolean animate) { | ||
| animateOffsetTo(0, 0, animate); | ||
| } | ||
|
|
||
| @Override | ||
| public void setDropShadowColor(int color) { | ||
| final int endColor = color & 0x00FFFFFF; | ||
| mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, new int[] { | ||
| color, | ||
| endColor, | ||
| }); | ||
| invalidate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onLayout(boolean changed, int l, int t, int r, int b) { | ||
| final int width = r - l; | ||
| final int height = b - t; | ||
| final int offsetPixels = (int) mOffsetPixels; | ||
|
|
||
| mMenuContainer.layout(0, 0, mMenuSize, height); | ||
| offsetMenu(offsetPixels); | ||
|
|
||
| if (USE_TRANSLATIONS) { | ||
| mContentContainer.layout(0, 0, width, height); | ||
| } else { | ||
| mContentContainer.layout(offsetPixels, 0, width + offsetPixels, height); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Offsets the menu relative to its original position based on the position of the content. | ||
| * | ||
| * @param offsetPixels The number of pixels the content if offset. | ||
| */ | ||
| private void offsetMenu(int offsetPixels) { | ||
| if (mOffsetMenu && mMenuSize != 0) { | ||
| final int menuWidth = mMenuSize; | ||
| final float openRatio = (menuWidth - (float) offsetPixels) / menuWidth; | ||
|
|
||
| if (USE_TRANSLATIONS) { | ||
| if (offsetPixels > 0) { | ||
| final int menuLeft = (int) (0.25f * (-openRatio * menuWidth)); | ||
| mMenuContainer.setTranslationX(menuLeft); | ||
| } else { | ||
| mMenuContainer.setTranslationX(-menuWidth); | ||
| } | ||
|
|
||
| } else { | ||
| final int oldMenuLeft = mMenuContainer.getLeft(); | ||
| final int offset = (int) (0.25f * (-openRatio * menuWidth)) - oldMenuLeft; | ||
| mMenuContainer.offsetLeftAndRight(offset); | ||
| mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawDropShadow(Canvas canvas, int offsetPixels) { | ||
| final int height = getHeight(); | ||
|
|
||
| mDropShadowDrawable.setBounds(offsetPixels - mDropShadowSize, 0, offsetPixels, height); | ||
| mDropShadowDrawable.draw(canvas); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawMenuOverlay(Canvas canvas, int offsetPixels) { | ||
| final int height = getHeight(); | ||
| final float openRatio = ((float) offsetPixels) / mMenuSize; | ||
|
|
||
| mMenuOverlay.setBounds(0, 0, offsetPixels, height); | ||
| mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio))); | ||
| mMenuOverlay.draw(canvas); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawIndicator(Canvas canvas, int offsetPixels) { | ||
| if (mActiveView != null && isViewDescendant(mActiveView)) { | ||
| Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition); | ||
| final int pos = position == null ? 0 : position; | ||
|
|
||
| if (pos == mActivePosition) { | ||
| final float openRatio = ((float) offsetPixels) / mMenuSize; | ||
|
|
||
| mActiveView.getDrawingRect(mActiveRect); | ||
| offsetDescendantRectToMyCoords(mActiveView, mActiveRect); | ||
|
|
||
| final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio)); | ||
| final int interpolatedWidth = (int) (mActiveIndicator.getWidth() * interpolatedRatio); | ||
|
|
||
| if (mIndicatorAnimating) { | ||
| final int indicatorFinalTop = mActiveRect.top + ((mActiveRect.height() | ||
| - mActiveIndicator.getHeight()) / 2); | ||
| final int indicatorStartTop = mIndicatorStartPos; | ||
| final int diff = indicatorFinalTop - indicatorStartTop; | ||
| final int startOffset = (int) (diff * mIndicatorOffset); | ||
| mIndicatorTop = indicatorStartTop + startOffset; | ||
| } else { | ||
| mIndicatorTop = mActiveRect.top + ((mActiveRect.height() - mActiveIndicator.getHeight()) / 2); | ||
| } | ||
| final int right = offsetPixels; | ||
| final int left = right - interpolatedWidth; | ||
|
|
||
| canvas.save(); | ||
| canvas.clipRect(left, 0, right, getHeight()); | ||
| canvas.drawBitmap(mActiveIndicator, left, mIndicatorTop, null); | ||
| canvas.restore(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected int getIndicatorStartPos() { | ||
| return mIndicatorTop; | ||
| } | ||
|
|
||
| @Override | ||
| protected void initPeekScroller() { | ||
| final int dx = mMenuSize / 3; | ||
| mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onOffsetPixelsChanged(int offsetPixels) { | ||
| if (USE_TRANSLATIONS) { | ||
| mContentContainer.setTranslationX(offsetPixels); | ||
| offsetMenu(offsetPixels); | ||
| invalidate(); | ||
| } else { | ||
| mContentContainer.offsetLeftAndRight(offsetPixels - mContentContainer.getLeft()); | ||
| offsetMenu(offsetPixels); | ||
| invalidate(); | ||
| } | ||
| } | ||
|
|
||
| ////////////////////////////////////////////////////////////////////// | ||
| // Touch handling | ||
| ////////////////////////////////////////////////////////////////////// | ||
|
|
||
| @Override | ||
| protected boolean isContentTouch(MotionEvent ev) { | ||
| return ev.getX() > mOffsetPixels; | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean onDownAllowDrag(MotionEvent ev) { | ||
| return (!mMenuVisible && mInitialMotionX <= mTouchSize) | ||
| || (mMenuVisible && mInitialMotionX >= mOffsetPixels); | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean onMoveAllowDrag(MotionEvent ev, float diff) { | ||
| return (!mMenuVisible && mInitialMotionX <= mTouchSize && (diff > 0)) | ||
| || (mMenuVisible && mInitialMotionX >= mOffsetPixels); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onMoveEvent(float dx) { | ||
| setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize)); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onUpEvent(MotionEvent ev) { | ||
| final int offsetPixels = (int) mOffsetPixels; | ||
|
|
||
| if (mIsDragging) { | ||
| mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); | ||
| final int initialVelocity = (int) mVelocityTracker.getXVelocity(); | ||
| mLastMotionX = ev.getX(); | ||
| animateOffsetTo(mVelocityTracker.getXVelocity() > 0 ? mMenuSize : 0, initialVelocity, true); | ||
|
|
||
| // Close the menu when content is clicked while the menu is visible. | ||
| } else if (mMenuVisible && ev.getX() > offsetPixels) { | ||
| closeMenu(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.graphics.drawable.GradientDrawable; | ||
| import android.util.AttributeSet; | ||
|
|
||
| public class LeftStaticDrawer extends StaticDrawer { | ||
|
|
||
| private int mIndicatorTop; | ||
|
|
||
| LeftStaticDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public LeftStaticDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public LeftStaticDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public LeftStaticDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| protected void initDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super.initDrawer(context, attrs, defStyle); | ||
| mPosition = Position.LEFT; | ||
| } | ||
|
|
||
| @Override | ||
| public void setDropShadowColor(int color) { | ||
| final int endColor = color & 0x00FFFFFF; | ||
| mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, new int[] { | ||
| color, | ||
| endColor, | ||
| }); | ||
| invalidate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawIndicator(Canvas canvas) { | ||
| if (mActiveView != null && isViewDescendant(mActiveView)) { | ||
| Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition); | ||
| final int pos = position == null ? 0 : position; | ||
|
|
||
| if (pos == mActivePosition) { | ||
| mActiveView.getDrawingRect(mActiveRect); | ||
| offsetDescendantRectToMyCoords(mActiveView, mActiveRect); | ||
|
|
||
| if (mIndicatorAnimating) { | ||
| final int indicatorFinalTop = mActiveRect.top + ((mActiveRect.height() | ||
| - mActiveIndicator.getHeight()) / 2); | ||
| final int indicatorStartTop = mIndicatorStartPos; | ||
| final int diff = indicatorFinalTop - indicatorStartTop; | ||
| final int startOffset = (int) (diff * mIndicatorOffset); | ||
| mIndicatorTop = indicatorStartTop + startOffset; | ||
| } else { | ||
| mIndicatorTop = mActiveRect.top + ((mActiveRect.height() - mActiveIndicator.getHeight()) / 2); | ||
| } | ||
| final int right = mMenuSize; | ||
| final int left = right - mActiveIndicator.getWidth(); | ||
|
|
||
| canvas.save(); | ||
| canvas.clipRect(left, 0, right, getHeight()); | ||
| canvas.drawBitmap(mActiveIndicator, left, mIndicatorTop, null); | ||
| canvas.restore(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected int getIndicatorStartPos() { | ||
| return mIndicatorTop; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.content.Context; | ||
| import android.util.AttributeSet; | ||
| import android.view.MotionEvent; | ||
|
|
||
| /** | ||
| * FrameLayout which doesn't let touch events propagate to views positioned behind it in the view hierarchy. | ||
| */ | ||
| public class NoClickThroughFrameLayout extends BuildLayerFrameLayout { | ||
|
|
||
| public NoClickThroughFrameLayout(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public NoClickThroughFrameLayout(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public NoClickThroughFrameLayout(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean onTouchEvent(MotionEvent event) { | ||
| return true; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.view.animation.Interpolator; | ||
|
|
||
| public class PeekInterpolator implements Interpolator { | ||
|
|
||
| private static final String TAG = "PeekInterpolator"; | ||
|
|
||
| private static final SinusoidalInterpolator SINUSOIDAL_INTERPOLATOR = new SinusoidalInterpolator(); | ||
|
|
||
| @Override | ||
| public float getInterpolation(float input) { | ||
| float result; | ||
|
|
||
| if (input < 1.f / 3.f) { | ||
| result = SINUSOIDAL_INTERPOLATOR.getInterpolation(input * 3); | ||
|
|
||
| } else if (input > 2.f / 3.f) { | ||
| final float val = ((input + 1.f / 3.f) - 1.f) * 3.f; | ||
| result = 1.f - SINUSOIDAL_INTERPOLATOR.getInterpolation(val); | ||
|
|
||
| } else { | ||
| result = 1.f; | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| /** | ||
| * Enums used for positioning the drawer. | ||
| */ | ||
| public enum Position { | ||
| // Positions the drawer to the left of the content. | ||
| LEFT, | ||
|
|
||
| // Positions the drawer above the content. | ||
| TOP, | ||
|
|
||
| // Positions the drawer to the right of the content. | ||
| RIGHT, | ||
|
|
||
| // Positions the drawer below the content. | ||
| BOTTOM, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.graphics.drawable.GradientDrawable; | ||
| import android.util.AttributeSet; | ||
| import android.view.MotionEvent; | ||
|
|
||
| public class RightDrawer extends HorizontalDrawer { | ||
|
|
||
| private int mIndicatorTop; | ||
|
|
||
| RightDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public RightDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public RightDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public RightDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| public void openMenu(boolean animate) { | ||
| animateOffsetTo(-mMenuSize, 0, animate); | ||
| } | ||
|
|
||
| @Override | ||
| public void closeMenu(boolean animate) { | ||
| animateOffsetTo(0, 0, animate); | ||
| } | ||
|
|
||
| @Override | ||
| public void setDropShadowColor(int color) { | ||
| final int endColor = color & 0x00FFFFFF; | ||
| mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[] { | ||
| color, | ||
| endColor, | ||
| }); | ||
| invalidate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onLayout(boolean changed, int l, int t, int r, int b) { | ||
| final int width = r - l; | ||
| final int height = b - t; | ||
| final int offsetPixels = (int) mOffsetPixels; | ||
|
|
||
| mMenuContainer.layout(width - mMenuSize, 0, width, height); | ||
| offsetMenu(offsetPixels); | ||
|
|
||
| if (USE_TRANSLATIONS) { | ||
| mContentContainer.layout(0, 0, width, height); | ||
| } else { | ||
| mContentContainer.layout(offsetPixels, 0, width + offsetPixels, height); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Offsets the menu relative to its original position based on the position of the content. | ||
| * | ||
| * @param offsetPixels The number of pixels the content if offset. | ||
| */ | ||
| private void offsetMenu(int offsetPixels) { | ||
| if (mOffsetMenu && mMenuSize != 0) { | ||
| final int menuWidth = mMenuSize; | ||
| final float openRatio = (menuWidth + (float) offsetPixels) / menuWidth; | ||
|
|
||
| if (USE_TRANSLATIONS) { | ||
| if (offsetPixels != 0) { | ||
| final int offset = (int) (0.25f * (openRatio * menuWidth)); | ||
| mMenuContainer.setTranslationX(offset); | ||
| } else { | ||
| mMenuContainer.setTranslationX(-menuWidth); | ||
| } | ||
|
|
||
| } else { | ||
| final int width = getWidth(); | ||
| final int oldMenuRight = mMenuContainer.getRight(); | ||
| final int newRight = width + (int) (0.25f * (openRatio * menuWidth)); | ||
| final int offset = newRight - oldMenuRight; | ||
| mMenuContainer.offsetLeftAndRight(offset); | ||
| mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawDropShadow(Canvas canvas, int offsetPixels) { | ||
| final int height = getHeight(); | ||
| final int width = getWidth(); | ||
| final int left = width + offsetPixels; | ||
| final int right = left + mDropShadowSize; | ||
|
|
||
| mDropShadowDrawable.setBounds(left, 0, right, height); | ||
| mDropShadowDrawable.draw(canvas); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawMenuOverlay(Canvas canvas, int offsetPixels) { | ||
| final int height = getHeight(); | ||
| final int width = getWidth(); | ||
| final int left = width + offsetPixels; | ||
| final int right = width; | ||
| final float openRatio = ((float) Math.abs(offsetPixels)) / mMenuSize; | ||
|
|
||
| mMenuOverlay.setBounds(left, 0, right, height); | ||
| mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio))); | ||
| mMenuOverlay.draw(canvas); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawIndicator(Canvas canvas, int offsetPixels) { | ||
| if (mActiveView != null && isViewDescendant(mActiveView)) { | ||
| Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition); | ||
| final int pos = position == null ? 0 : position; | ||
|
|
||
| if (pos == mActivePosition) { | ||
| final int width = getWidth(); | ||
| final int menuWidth = mMenuSize; | ||
| final int indicatorWidth = mActiveIndicator.getWidth(); | ||
|
|
||
| final int contentRight = width + offsetPixels; | ||
| final float openRatio = ((float) Math.abs(offsetPixels)) / menuWidth; | ||
|
|
||
| mActiveView.getDrawingRect(mActiveRect); | ||
| offsetDescendantRectToMyCoords(mActiveView, mActiveRect); | ||
|
|
||
| final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio)); | ||
| final int interpolatedWidth = (int) (indicatorWidth * interpolatedRatio); | ||
|
|
||
| final int indicatorRight = contentRight + interpolatedWidth; | ||
| final int indicatorLeft = indicatorRight - indicatorWidth; | ||
|
|
||
| if (mIndicatorAnimating) { | ||
| final int indicatorFinalTop = mActiveRect.top + ((mActiveRect.height() | ||
| - mActiveIndicator.getHeight()) / 2); | ||
| final int indicatorStartTop = mIndicatorStartPos; | ||
| final int diff = indicatorFinalTop - indicatorStartTop; | ||
| final int startOffset = (int) (diff * mIndicatorOffset); | ||
| mIndicatorTop = indicatorStartTop + startOffset; | ||
| } else { | ||
| mIndicatorTop = mActiveRect.top + ((mActiveRect.height() - mActiveIndicator.getHeight()) / 2); | ||
| } | ||
|
|
||
| canvas.save(); | ||
| canvas.clipRect(contentRight, 0, indicatorRight, getHeight()); | ||
| canvas.drawBitmap(mActiveIndicator, indicatorLeft, mIndicatorTop, null); | ||
| canvas.restore(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected int getIndicatorStartPos() { | ||
| return mIndicatorTop; | ||
| } | ||
|
|
||
| @Override | ||
| protected void initPeekScroller() { | ||
| final int dx = -mMenuSize / 3; | ||
| mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onOffsetPixelsChanged(int offsetPixels) { | ||
| if (USE_TRANSLATIONS) { | ||
| mContentContainer.setTranslationX(offsetPixels); | ||
| offsetMenu(offsetPixels); | ||
| invalidate(); | ||
| } else { | ||
| mContentContainer.offsetLeftAndRight(offsetPixels - mContentContainer.getLeft()); | ||
| offsetMenu(offsetPixels); | ||
| invalidate(); | ||
| } | ||
| } | ||
|
|
||
| ////////////////////////////////////////////////////////////////////// | ||
| // Touch handling | ||
| ////////////////////////////////////////////////////////////////////// | ||
|
|
||
| @Override | ||
| protected boolean isContentTouch(MotionEvent ev) { | ||
| return ev.getX() < getWidth() + mOffsetPixels; | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean onDownAllowDrag(MotionEvent ev) { | ||
| final int width = getWidth(); | ||
| final int initialMotionX = (int) mInitialMotionX; | ||
|
|
||
| return (!mMenuVisible && initialMotionX >= width - mTouchSize) | ||
| || (mMenuVisible && initialMotionX <= width + mOffsetPixels); | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean onMoveAllowDrag(MotionEvent ev, float diff) { | ||
| final int width = getWidth(); | ||
| final int initialMotionX = (int) mInitialMotionX; | ||
|
|
||
| return (!mMenuVisible && initialMotionX >= width - mTouchSize && (diff < 0)) | ||
| || (mMenuVisible && initialMotionX <= width + mOffsetPixels); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onMoveEvent(float dx) { | ||
| final float newOffset = Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize); | ||
| setOffsetPixels(newOffset); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onUpEvent(MotionEvent ev) { | ||
| final int offsetPixels = (int) mOffsetPixels; | ||
| final int width = getWidth(); | ||
|
|
||
| if (mIsDragging) { | ||
| mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); | ||
| final int initialVelocity = (int) mVelocityTracker.getXVelocity(); | ||
| mLastMotionX = ev.getX(); | ||
| animateOffsetTo(mVelocityTracker.getXVelocity() > 0 ? 0 : -mMenuSize, initialVelocity, true); | ||
|
|
||
| // Close the menu when content is clicked while the menu is visible. | ||
| } else if (mMenuVisible && ev.getX() < width + offsetPixels) { | ||
| closeMenu(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.graphics.drawable.GradientDrawable; | ||
| import android.util.AttributeSet; | ||
|
|
||
| public class RightStaticDrawer extends StaticDrawer { | ||
|
|
||
| private int mIndicatorTop; | ||
|
|
||
| RightStaticDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public RightStaticDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public RightStaticDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public RightStaticDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| protected void initDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super.initDrawer(context, attrs, defStyle); | ||
| mPosition = Position.RIGHT; | ||
| } | ||
|
|
||
| @Override | ||
| public void setDropShadowColor(int color) { | ||
| final int endColor = color & 0x00FFFFFF; | ||
| mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[] { | ||
| color, | ||
| endColor, | ||
| }); | ||
| invalidate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawIndicator(Canvas canvas) { | ||
| if (mActiveView != null && isViewDescendant(mActiveView)) { | ||
| Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition); | ||
| final int pos = position == null ? 0 : position; | ||
|
|
||
| if (pos == mActivePosition) { | ||
| final int width = getWidth(); | ||
| final int menuWidth = mMenuSize; | ||
| final int indicatorWidth = mActiveIndicator.getWidth(); | ||
|
|
||
| final int contentRight = width - menuWidth; | ||
|
|
||
| mActiveView.getDrawingRect(mActiveRect); | ||
| offsetDescendantRectToMyCoords(mActiveView, mActiveRect); | ||
|
|
||
| final int indicatorRight = contentRight + indicatorWidth; | ||
| final int indicatorLeft = contentRight; | ||
|
|
||
| if (mIndicatorAnimating) { | ||
| final int indicatorFinalTop = mActiveRect.top + ((mActiveRect.height() | ||
| - mActiveIndicator.getHeight()) / 2); | ||
| final int indicatorStartTop = mIndicatorStartPos; | ||
| final int diff = indicatorFinalTop - indicatorStartTop; | ||
| final int startOffset = (int) (diff * mIndicatorOffset); | ||
| mIndicatorTop = indicatorStartTop + startOffset; | ||
| } else { | ||
| mIndicatorTop = mActiveRect.top + ((mActiveRect.height() - mActiveIndicator.getHeight()) / 2); | ||
| } | ||
|
|
||
| canvas.save(); | ||
| canvas.clipRect(contentRight, 0, indicatorRight, getHeight()); | ||
| canvas.drawBitmap(mActiveIndicator, indicatorLeft, mIndicatorTop, null); | ||
| canvas.restore(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected int getIndicatorStartPos() { | ||
| return mIndicatorTop; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.view.animation.Interpolator; | ||
|
|
||
| /** | ||
| * Interpolator which, when drawn from 0 to 1, looks like half a sine-wave. Used for smoother opening/closing when | ||
| * peeking at the drawer. | ||
| */ | ||
| public class SinusoidalInterpolator implements Interpolator { | ||
|
|
||
| @Override | ||
| public float getInterpolation(float input) { | ||
| return (float) (0.5f + 0.5f * Math.sin(input * Math.PI - Math.PI / 2.f)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.view.animation.Interpolator; | ||
|
|
||
| public class SmoothInterpolator implements Interpolator { | ||
|
|
||
| @Override | ||
| public float getInterpolation(float t) { | ||
| t -= 1.0f; | ||
| return t * t * t * t * t + 1.0f; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.util.AttributeSet; | ||
|
|
||
| public abstract class StaticDrawer extends MenuDrawer { | ||
|
|
||
| protected Position mPosition; | ||
|
|
||
| StaticDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public StaticDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public StaticDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public StaticDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| protected void dispatchDraw(Canvas canvas) { | ||
| super.dispatchDraw(canvas); | ||
| if (mDropShadowEnabled) drawDropShadow(canvas); | ||
| if (mActiveIndicator != null) drawIndicator(canvas); | ||
| } | ||
|
|
||
| private void drawDropShadow(Canvas canvas) { | ||
| final int width = getWidth(); | ||
| final int height = getHeight(); | ||
| final int menuSize = mMenuSize; | ||
| final int dropShadowSize = mDropShadowSize; | ||
|
|
||
| switch (mPosition) { | ||
| case LEFT: | ||
| mDropShadowDrawable.setBounds(menuSize - dropShadowSize, 0, menuSize, height); | ||
| break; | ||
|
|
||
| case TOP: | ||
| mDropShadowDrawable.setBounds(0, menuSize - dropShadowSize, width, menuSize); | ||
| break; | ||
|
|
||
| case RIGHT: | ||
| mDropShadowDrawable.setBounds(width - menuSize, 0, width - menuSize + dropShadowSize, height); | ||
| break; | ||
|
|
||
| case BOTTOM: | ||
| mDropShadowDrawable.setBounds(0, height - menuSize, width, height - menuSize + dropShadowSize); | ||
| break; | ||
| } | ||
|
|
||
| mDropShadowDrawable.draw(canvas); | ||
| } | ||
|
|
||
| protected abstract void drawIndicator(Canvas canvas); | ||
|
|
||
| @Override | ||
| protected void onLayout(boolean changed, int l, int t, int r, int b) { | ||
| final int width = r - l; | ||
| final int height = b - t; | ||
|
|
||
| switch (mPosition) { | ||
| case LEFT: | ||
| mMenuContainer.layout(0, 0, mMenuSize, height); | ||
| mContentContainer.layout(mMenuSize, 0, width, height); | ||
| break; | ||
|
|
||
| case RIGHT: | ||
| mMenuContainer.layout(width - mMenuSize, 0, width, height); | ||
| mContentContainer.layout(0, 0, width - mMenuSize, height); | ||
| break; | ||
|
|
||
| case TOP: | ||
| mMenuContainer.layout(0, 0, width, mMenuSize); | ||
| mContentContainer.layout(0, mMenuSize, width, height); | ||
| break; | ||
|
|
||
| case BOTTOM: | ||
| mMenuContainer.layout(0, height - mMenuSize, width, height); | ||
| mContentContainer.layout(0, 0, width, height - mMenuSize); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | ||
| final int widthMode = MeasureSpec.getMode(widthMeasureSpec); | ||
| final int heightMode = MeasureSpec.getMode(heightMeasureSpec); | ||
|
|
||
| if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { | ||
| throw new IllegalStateException("Must measure with an exact size"); | ||
| } | ||
|
|
||
| final int width = MeasureSpec.getSize(widthMeasureSpec); | ||
| final int height = MeasureSpec.getSize(heightMeasureSpec); | ||
|
|
||
| if (!mMenuSizeSet) mMenuSize = (int) (height * 0.25f); | ||
|
|
||
| switch (mPosition) { | ||
| case LEFT: | ||
| case RIGHT: { | ||
| final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); | ||
|
|
||
| final int menuWidth = mMenuSize; | ||
| final int menuWidthMeasureSpec = MeasureSpec.makeMeasureSpec(menuWidth, MeasureSpec.EXACTLY); | ||
|
|
||
| final int contentWidth = width - menuWidth; | ||
| final int contentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY); | ||
|
|
||
| mContentContainer.measure(contentWidthMeasureSpec, childHeightMeasureSpec); | ||
| mMenuContainer.measure(menuWidthMeasureSpec, childHeightMeasureSpec); | ||
| break; | ||
| } | ||
|
|
||
| case TOP: | ||
| case BOTTOM: { | ||
| final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); | ||
|
|
||
| final int menuHeight = mMenuSize; | ||
| final int menuHeightMeasureSpec = MeasureSpec.makeMeasureSpec(menuHeight, MeasureSpec.EXACTLY); | ||
|
|
||
| final int contentHeight = height - menuHeight; | ||
| final int contentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY); | ||
|
|
||
| mContentContainer.measure(childWidthMeasureSpec, contentHeightMeasureSpec); | ||
| mMenuContainer.measure(childWidthMeasureSpec, menuHeightMeasureSpec); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| setMeasuredDimension(width, height); | ||
| } | ||
|
|
||
| @Override | ||
| public void toggleMenu(boolean animate) { | ||
| } | ||
|
|
||
| @Override | ||
| public void openMenu(boolean animate) { | ||
| } | ||
|
|
||
| @Override | ||
| public void closeMenu(boolean animate) { | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isMenuVisible() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public void setMenuSize(int size) { | ||
| mMenuSize = size; | ||
| mMenuSizeSet = true; | ||
| requestLayout(); | ||
| invalidate(); | ||
| } | ||
|
|
||
| @Override | ||
| public void setOffsetMenuEnabled(boolean offsetMenu) { | ||
| } | ||
|
|
||
| @Override | ||
| public boolean getOffsetMenuEnabled() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public void peekDrawer() { | ||
| } | ||
|
|
||
| @Override | ||
| public void peekDrawer(long delay) { | ||
| } | ||
|
|
||
| @Override | ||
| public void peekDrawer(long startDelay, long delay) { | ||
| } | ||
|
|
||
| @Override | ||
| public void setHardwareLayerEnabled(boolean enabled) { | ||
| } | ||
|
|
||
| @Override | ||
| public int getTouchMode() { | ||
| return TOUCH_MODE_NONE; | ||
| } | ||
|
|
||
| @Override | ||
| public void setTouchMode(int mode) { | ||
| } | ||
|
|
||
| @Override | ||
| public void setTouchBezelSize(int size) { | ||
| } | ||
|
|
||
| @Override | ||
| public int getTouchBezelSize() { | ||
| return 0; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.graphics.drawable.GradientDrawable; | ||
| import android.util.AttributeSet; | ||
| import android.view.MotionEvent; | ||
|
|
||
| public class TopDrawer extends VerticalDrawer { | ||
|
|
||
| private int mIndicatorLeft; | ||
|
|
||
| TopDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public TopDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public TopDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public TopDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| public void openMenu(boolean animate) { | ||
| animateOffsetTo(mMenuSize, 0, animate); | ||
| } | ||
|
|
||
| @Override | ||
| public void closeMenu(boolean animate) { | ||
| animateOffsetTo(0, 0, animate); | ||
| } | ||
|
|
||
| @Override | ||
| public void setDropShadowColor(int color) { | ||
| final int endColor = color & 0x00FFFFFF; | ||
| mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, | ||
| new int[] { | ||
| color, | ||
| endColor, | ||
| }); | ||
| invalidate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onLayout(boolean changed, int l, int t, int r, int b) { | ||
| final int width = r - l; | ||
| final int height = b - t; | ||
| final int offsetPixels = (int) mOffsetPixels; | ||
|
|
||
| mMenuContainer.layout(0, 0, width, mMenuSize); | ||
| offsetMenu(offsetPixels); | ||
|
|
||
| if (USE_TRANSLATIONS) { | ||
| mContentContainer.layout(0, 0, width, height); | ||
| } else { | ||
| mContentContainer.layout(0, offsetPixels, width, height + offsetPixels); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Offsets the menu relative to its original position based on the position of the content. | ||
| * | ||
| * @param offsetPixels The number of pixels the content if offset. | ||
| */ | ||
| private void offsetMenu(int offsetPixels) { | ||
| if (mOffsetMenu && mMenuSize != 0) { | ||
| final int menuSize = mMenuSize; | ||
| final float openRatio = (menuSize - (float) offsetPixels) / menuSize; | ||
|
|
||
| if (USE_TRANSLATIONS) { | ||
| if (offsetPixels > 0) { | ||
| final int offset = (int) (0.25f * (-openRatio * menuSize)); | ||
| mMenuContainer.setTranslationY(offset); | ||
| } else { | ||
| mMenuContainer.setTranslationY(-menuSize); | ||
| } | ||
|
|
||
| } else { | ||
| final int oldMenuTop = mMenuContainer.getTop(); | ||
| final int offset = (int) (0.25f * (-openRatio * menuSize)) - oldMenuTop; | ||
| mMenuContainer.offsetTopAndBottom(offset); | ||
| mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawDropShadow(Canvas canvas, int offsetPixels) { | ||
| final int width = getWidth(); | ||
|
|
||
| mDropShadowDrawable.setBounds(0, offsetPixels - mDropShadowSize, width, offsetPixels); | ||
| mDropShadowDrawable.draw(canvas); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawMenuOverlay(Canvas canvas, int offsetPixels) { | ||
| final int width = getWidth(); | ||
| final float openRatio = ((float) offsetPixels) / mMenuSize; | ||
|
|
||
| mMenuOverlay.setBounds(0, 0, width, offsetPixels); | ||
| mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio))); | ||
| mMenuOverlay.draw(canvas); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawIndicator(Canvas canvas, int offsetPixels) { | ||
| if (mActiveView != null && isViewDescendant(mActiveView)) { | ||
| Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition); | ||
| final int pos = position == null ? 0 : position; | ||
|
|
||
| if (pos == mActivePosition) { | ||
| final int menuHeight = mMenuSize; | ||
| final int indicatorHeight = mActiveIndicator.getHeight(); | ||
|
|
||
| final float openRatio = ((float) offsetPixels) / menuHeight; | ||
|
|
||
| mActiveView.getDrawingRect(mActiveRect); | ||
| offsetDescendantRectToMyCoords(mActiveView, mActiveRect); | ||
| final int indicatorWidth = mActiveIndicator.getWidth(); | ||
|
|
||
| final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio)); | ||
| final int interpolatedHeight = (int) (indicatorHeight * interpolatedRatio); | ||
|
|
||
| final int indicatorTop = offsetPixels - interpolatedHeight; | ||
| if (mIndicatorAnimating) { | ||
| final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2); | ||
| final int startLeft = mIndicatorStartPos; | ||
| final int diff = finalLeft - startLeft; | ||
| final int startOffset = (int) (diff * mIndicatorOffset); | ||
| mIndicatorLeft = startLeft + startOffset; | ||
| } else { | ||
| mIndicatorLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2); | ||
| } | ||
|
|
||
| canvas.save(); | ||
| canvas.clipRect(mIndicatorLeft, indicatorTop, mIndicatorLeft + indicatorWidth, offsetPixels); | ||
| canvas.drawBitmap(mActiveIndicator, mIndicatorLeft, indicatorTop, null); | ||
| canvas.restore(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected int getIndicatorStartPos() { | ||
| return mIndicatorLeft; | ||
| } | ||
|
|
||
| @Override | ||
| protected void initPeekScroller() { | ||
| final int dx = mMenuSize / 3; | ||
| mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onOffsetPixelsChanged(int offsetPixels) { | ||
| if (USE_TRANSLATIONS) { | ||
| mContentContainer.setTranslationY(offsetPixels); | ||
| offsetMenu(offsetPixels); | ||
| invalidate(); | ||
| } else { | ||
| mContentContainer.offsetTopAndBottom(offsetPixels - mContentContainer.getTop()); | ||
| offsetMenu(offsetPixels); | ||
| invalidate(); | ||
| } | ||
| } | ||
|
|
||
| ////////////////////////////////////////////////////////////////////// | ||
| // Touch handling | ||
| ////////////////////////////////////////////////////////////////////// | ||
|
|
||
| @Override | ||
| protected boolean isContentTouch(MotionEvent ev) { | ||
| return ev.getY() > mOffsetPixels; | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean onDownAllowDrag(MotionEvent ev) { | ||
| return (!mMenuVisible && mInitialMotionY <= mTouchSize) | ||
| || (mMenuVisible && mInitialMotionY >= mOffsetPixels); | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean onMoveAllowDrag(MotionEvent ev, float diff) { | ||
| return (!mMenuVisible && mInitialMotionY <= mTouchSize && (diff > 0)) | ||
| || (mMenuVisible && mInitialMotionY >= mOffsetPixels); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onMoveEvent(float dx) { | ||
| setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize)); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onUpEvent(MotionEvent ev) { | ||
| final int offsetPixels = (int) mOffsetPixels; | ||
|
|
||
| if (mIsDragging) { | ||
| mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); | ||
| final int initialVelocity = (int) mVelocityTracker.getXVelocity(); | ||
| mLastMotionY = ev.getY(); | ||
| animateOffsetTo(mVelocityTracker.getYVelocity() > 0 ? mMenuSize : 0, initialVelocity, | ||
| true); | ||
|
|
||
| // Close the menu when content is clicked while the menu is visible. | ||
| } else if (mMenuVisible && ev.getY() > offsetPixels) { | ||
| closeMenu(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.graphics.Canvas; | ||
| import android.graphics.drawable.GradientDrawable; | ||
| import android.util.AttributeSet; | ||
|
|
||
| public class TopStaticDrawer extends StaticDrawer { | ||
|
|
||
| private int mIndicatorLeft; | ||
|
|
||
| TopStaticDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public TopStaticDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public TopStaticDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public TopStaticDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| protected void initDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super.initDrawer(context, attrs, defStyle); | ||
| mPosition = Position.TOP; | ||
| } | ||
|
|
||
| @Override | ||
| public void setDropShadowColor(int color) { | ||
| final int endColor = color & 0x00FFFFFF; | ||
| mDropShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, new int[] { | ||
| color, | ||
| endColor, | ||
| }); | ||
| invalidate(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void drawIndicator(Canvas canvas) { | ||
| if (mActiveView != null && isViewDescendant(mActiveView)) { | ||
| Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition); | ||
| final int pos = position == null ? 0 : position; | ||
|
|
||
| if (pos == mActivePosition) { | ||
| final int menuHeight = mMenuSize; | ||
| final int indicatorHeight = mActiveIndicator.getHeight(); | ||
|
|
||
| mActiveView.getDrawingRect(mActiveRect); | ||
| offsetDescendantRectToMyCoords(mActiveView, mActiveRect); | ||
| final int indicatorWidth = mActiveIndicator.getWidth(); | ||
|
|
||
| final int indicatorTop = menuHeight - indicatorHeight; | ||
| if (mIndicatorAnimating) { | ||
| final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2); | ||
| final int startLeft = mIndicatorStartPos; | ||
| final int diff = finalLeft - startLeft; | ||
| final int startOffset = (int) (diff * mIndicatorOffset); | ||
| mIndicatorLeft = startLeft + startOffset; | ||
| } else { | ||
| mIndicatorLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2); | ||
| } | ||
|
|
||
| canvas.save(); | ||
| canvas.clipRect(mIndicatorLeft, indicatorTop, mIndicatorLeft + indicatorWidth, menuHeight); | ||
| canvas.drawBitmap(mActiveIndicator, mIndicatorLeft, indicatorTop, null); | ||
| canvas.restore(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected int getIndicatorStartPos() { | ||
| return mIndicatorLeft; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| package net.simonvt.menudrawer; | ||
|
|
||
| import android.app.Activity; | ||
| import android.content.Context; | ||
| import android.util.AttributeSet; | ||
| import android.view.MotionEvent; | ||
| import android.view.VelocityTracker; | ||
|
|
||
| public abstract class VerticalDrawer extends DraggableDrawer { | ||
|
|
||
| VerticalDrawer(Activity activity, int dragMode) { | ||
| super(activity, dragMode); | ||
| } | ||
|
|
||
| public VerticalDrawer(Context context) { | ||
| super(context); | ||
| } | ||
|
|
||
| public VerticalDrawer(Context context, AttributeSet attrs) { | ||
| super(context, attrs); | ||
| } | ||
|
|
||
| public VerticalDrawer(Context context, AttributeSet attrs, int defStyle) { | ||
| super(context, attrs, defStyle); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | ||
| final int widthMode = MeasureSpec.getMode(widthMeasureSpec); | ||
| final int heightMode = MeasureSpec.getMode(heightMeasureSpec); | ||
|
|
||
| if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { | ||
| throw new IllegalStateException("Must measure with an exact size"); | ||
| } | ||
|
|
||
| final int width = MeasureSpec.getSize(widthMeasureSpec); | ||
| final int height = MeasureSpec.getSize(heightMeasureSpec); | ||
|
|
||
| if (!mMenuSizeSet) mMenuSize = (int) (height * 0.25f); | ||
| if (mOffsetPixels == -1) openMenu(false); | ||
|
|
||
| final int menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width); | ||
| final int menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize); | ||
| mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec); | ||
|
|
||
| final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width); | ||
| final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height); | ||
| mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec); | ||
|
|
||
| setMeasuredDimension(width, height); | ||
|
|
||
| updateTouchAreaSize(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean onInterceptTouchEvent(MotionEvent ev) { | ||
| final int action = ev.getAction() & MotionEvent.ACTION_MASK; | ||
|
|
||
| if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) { | ||
| setOffsetPixels(0); | ||
| stopAnimation(); | ||
| endPeek(); | ||
| setDrawerState(STATE_CLOSED); | ||
| } | ||
|
|
||
| // Always intercept events over the content while menu is visible. | ||
| if (mMenuVisible && isContentTouch(ev)) { | ||
| return true; | ||
| } | ||
|
|
||
| if (mTouchMode == TOUCH_MODE_NONE) { | ||
| return false; | ||
| } | ||
|
|
||
| if (action != MotionEvent.ACTION_DOWN) { | ||
| if (mIsDragging) { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| switch (action) { | ||
| case MotionEvent.ACTION_DOWN: { | ||
| mLastMotionX = mInitialMotionX = ev.getX(); | ||
| mLastMotionY = mInitialMotionY = ev.getY(); | ||
| final boolean allowDrag = onDownAllowDrag(ev); | ||
|
|
||
| if (allowDrag) { | ||
| setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED); | ||
| stopAnimation(); | ||
| endPeek(); | ||
| mIsDragging = false; | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| case MotionEvent.ACTION_MOVE: { | ||
| final float x = ev.getX(); | ||
| final float dx = x - mLastMotionX; | ||
| final float xDiff = Math.abs(dx); | ||
| final float y = ev.getY(); | ||
| final float dy = y - mLastMotionY; | ||
| final float yDiff = Math.abs(dy); | ||
|
|
||
| if (yDiff > mTouchSlop && yDiff > xDiff) { | ||
| if (mOnInterceptMoveEventListener != null && mTouchMode == TOUCH_MODE_FULLSCREEN | ||
| && canChildScrollVertically(mContentContainer, false, (int) dx, (int) x, (int) y)) { | ||
| endDrag(); // Release the velocity tracker | ||
| return false; | ||
| } | ||
|
|
||
| final boolean allowDrag = onMoveAllowDrag(ev, dy); | ||
|
|
||
| if (allowDrag) { | ||
| setDrawerState(STATE_DRAGGING); | ||
| mIsDragging = true; | ||
| mLastMotionX = x; | ||
| mLastMotionY = y; | ||
| } | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| /** | ||
| * If you click really fast, an up or cancel event is delivered here. Just snap content to | ||
| * whatever is closest. | ||
| */ | ||
| case MotionEvent.ACTION_CANCEL: | ||
| case MotionEvent.ACTION_UP: { | ||
| if (Math.abs(mOffsetPixels) > mMenuSize / 2) { | ||
| openMenu(); | ||
| } else { | ||
| closeMenu(); | ||
| } | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (mVelocityTracker == null) { | ||
| mVelocityTracker = VelocityTracker.obtain(); | ||
| } | ||
| mVelocityTracker.addMovement(ev); | ||
|
|
||
| return mIsDragging; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean onTouchEvent(MotionEvent ev) { | ||
| if (!mMenuVisible && !mIsDragging && (mTouchMode == TOUCH_MODE_NONE)) { | ||
| return false; | ||
| } | ||
| final int action = ev.getAction() & MotionEvent.ACTION_MASK; | ||
|
|
||
| if (mVelocityTracker == null) { | ||
| mVelocityTracker = VelocityTracker.obtain(); | ||
| } | ||
| mVelocityTracker.addMovement(ev); | ||
|
|
||
| switch (action) { | ||
| case MotionEvent.ACTION_DOWN: { | ||
| mLastMotionX = mInitialMotionX = ev.getX(); | ||
| mLastMotionY = mInitialMotionY = ev.getY(); | ||
| final boolean allowDrag = onDownAllowDrag(ev); | ||
|
|
||
| if (allowDrag) { | ||
| stopAnimation(); | ||
| endPeek(); | ||
| startLayerTranslation(); | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| case MotionEvent.ACTION_MOVE: { | ||
| if (!mIsDragging) { | ||
| final float x = ev.getX(); | ||
| final float dx = x - mLastMotionX; | ||
| final float xDiff = Math.abs(dx); | ||
| final float y = ev.getY(); | ||
| final float dy = y - mLastMotionY; | ||
| final float yDiff = Math.abs(dy); | ||
|
|
||
| if (yDiff > mTouchSlop && yDiff > xDiff) { | ||
| final boolean allowDrag = onMoveAllowDrag(ev, dy); | ||
|
|
||
| if (allowDrag) { | ||
| setDrawerState(STATE_DRAGGING); | ||
| mIsDragging = true; | ||
| mLastMotionY = y - mInitialMotionY > 0 | ||
| ? mInitialMotionY + mTouchSlop | ||
| : mInitialMotionY - mTouchSlop; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (mIsDragging) { | ||
| startLayerTranslation(); | ||
|
|
||
| final float y = ev.getY(); | ||
| final float dy = y - mLastMotionY; | ||
|
|
||
| mLastMotionY = y; | ||
| onMoveEvent(dy); | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| case MotionEvent.ACTION_CANCEL: | ||
| case MotionEvent.ACTION_UP: { | ||
| onUpEvent(ev); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| } |