New FR and HU translations, start of major overhaul
|
@ -3,7 +3,7 @@
|
||||||
package="com.thejoshwa.ultrasonic.androidapp"
|
package="com.thejoshwa.ultrasonic.androidapp"
|
||||||
a:installLocation="auto"
|
a:installLocation="auto"
|
||||||
a:versionCode="8"
|
a:versionCode="8"
|
||||||
a:versionName="1.0.1.2" >
|
a:versionName="1.0.1.3" >
|
||||||
|
|
||||||
<uses-permission a:name="android.permission.INTERNET" />
|
<uses-permission a:name="android.permission.INTERNET" />
|
||||||
<uses-permission a:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission a:name="android.permission.READ_PHONE_STATE" />
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
!.gitignore
|
||||||
|
!.travis.yml
|
||||||
|
!CHANGELOG.md
|
||||||
|
!README.md
|
||||||
|
!LICENSE
|
||||||
|
!checkstyle.xml
|
||||||
|
!pom.xml
|
||||||
|
|
||||||
|
!art/
|
||||||
|
|
||||||
|
!library/
|
||||||
|
library/*
|
||||||
|
!library/src/
|
||||||
|
!library/res/
|
||||||
|
!library/AndroidManifest.xml
|
||||||
|
!library/build.xml
|
||||||
|
!library/pom.xml
|
||||||
|
!library/project.properties
|
||||||
|
|
||||||
|
!samples/
|
||||||
|
samples/*
|
||||||
|
!samples/src/
|
||||||
|
!samples/res/
|
||||||
|
!samples/libs/
|
||||||
|
!samples/AndroidManifest.xml
|
||||||
|
!samples/build.xml
|
||||||
|
!samples/pom.xml
|
||||||
|
!samples/project.properties
|
|
@ -0,0 +1,18 @@
|
||||||
|
language: java
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- wget http://dl.google.com/android/android-sdk_r21.0.1-linux.tgz
|
||||||
|
- tar -zxf android-sdk_r21.0.1-linux.tgz
|
||||||
|
- export ANDROID_HOME=~/builds/SimonVT/android-menudrawer/android-sdk-linux
|
||||||
|
- export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
||||||
|
- TOOLS=$(android list sdk --no-ui | grep "Android SDK Platform-tools" | cut -d"-" -f1)
|
||||||
|
- android update sdk --filter "$TOOLS" --no-ui --force
|
||||||
|
- SDK=$(android list sdk --no-ui | grep ", API 16," | cut -d"-" -f1)
|
||||||
|
- android update sdk --filter "$SDK" --no-ui --force
|
||||||
|
|
||||||
|
install:
|
||||||
|
- "mvn package --quiet -DskipTests"
|
||||||
|
- "mvn verify"
|
|
@ -0,0 +1,44 @@
|
||||||
|
Change Log
|
||||||
|
==========
|
||||||
|
|
||||||
|
Version 2.0.2 *(2013-03-31)*
|
||||||
|
----------------------------
|
||||||
|
* Added listener that makes it possible to disabllow intercepting touch events over
|
||||||
|
certain views
|
||||||
|
* Added setter for the maximum animation duration
|
||||||
|
* Added getter for menu size
|
||||||
|
* Added methods that enable/disable indicator animation
|
||||||
|
* Fix: Removed log statements
|
||||||
|
* Fix: Drawing the active indicator might cause crash if the active view is not a
|
||||||
|
child of the MenuDrawer
|
||||||
|
* Fix: Crash in static drawer if no active indicator bitmap was set
|
||||||
|
|
||||||
|
Version 2.0.1 *(2013-02-12)*
|
||||||
|
----------------------------
|
||||||
|
* Indicator now animates between active views
|
||||||
|
* Fixed restoring state for right/bottom drawer
|
||||||
|
|
||||||
|
Version 2.0.0 *(2013-01-23)*
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
* Major API changes
|
||||||
|
|
||||||
|
* All classes are now in the net.simonvt.menudrawer package.
|
||||||
|
* MenuDrawerManager no longet exists. Menu is added with MenuDrawer#attach(...).
|
||||||
|
* Drawer position is now selected with Position enums instead of int constants.
|
||||||
|
* Width methods/attributes have been renamed to 'size'.
|
||||||
|
|
||||||
|
* Added top/bottom drawer.
|
||||||
|
* Added static (non-draggable, always visible) drawers.
|
||||||
|
* The touch bezel size is now configurable with MenuDrawer#setTouchBezelSize(int).
|
||||||
|
* MenuDrawer#saveState() now only required when dragging the entire window.
|
||||||
|
* Drawers can now be used in XML layouts.
|
||||||
|
* Fix: Scroller class caused conflicts with other libraries.
|
||||||
|
* Fix: No more overdraw when the drawer is closed.
|
||||||
|
* Fix: Content no longer falls behind when slowly dragging.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.0.0 *(2012-10-30)*
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Initial release.
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,96 @@
|
||||||
|
MenuDrawer
|
||||||
|
==========
|
||||||
|
|
||||||
|
A slide-out menu implementation, which allows users to navigate between views
|
||||||
|
in your app. Most commonly the menu is revealed by either dragging the edge
|
||||||
|
of the screen, or clicking the 'up' button in the action bar.
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
* The menu can be positioned along all four edges.
|
||||||
|
* Supports attaching an always visible, non-draggable menu, which is useful
|
||||||
|
on e.g. tablets.
|
||||||
|
* The menu can wrap both the content and the entire window.
|
||||||
|
* Allows the drawer to be opened by dragging the edge, the entire screen or
|
||||||
|
not at all.
|
||||||
|
* Can be used in XML layouts.
|
||||||
|
* Indicator that shows which screen is currently visible.
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
This library is very simple to use. It requires no extension of custom classes,
|
||||||
|
it's simply added to an activity by calling one of the `MenuDrawer#attach(...)`
|
||||||
|
methods.
|
||||||
|
|
||||||
|
For more examples on how to use this library, check out the sample app.
|
||||||
|
|
||||||
|
|
||||||
|
Left menu
|
||||||
|
---------
|
||||||
|
```java
|
||||||
|
public class SampleActivity extends Activity {
|
||||||
|
|
||||||
|
private MenuDrawer mDrawer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle state) {
|
||||||
|
super.onCreate(state);
|
||||||
|
mDrawer = MenuDrawer.attach(this);
|
||||||
|
mDrawer.setContentView(R.layout.activity_sample);
|
||||||
|
mDrawer.setMenuView(R.layout.menu_sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Right menu
|
||||||
|
----------
|
||||||
|
```java
|
||||||
|
public class SampleActivity extends Activity {
|
||||||
|
|
||||||
|
private MenuDrawer mDrawer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle state) {
|
||||||
|
super.onCreate(state);
|
||||||
|
mDrawer = MenuDrawer.attach(this, Position.RIGHT);
|
||||||
|
mDrawer.setContentView(R.layout.activity_sample);
|
||||||
|
mDrawer.setMenuView(R.layout.menu_sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
* Cyril Mottier for his [articles][1] on the pattern
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
Copyright 2012 Simon Vig Therkildsen
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[1]: http://android.cyrilmottier.com/?p=658
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="16"
|
||||||
|
height="33"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.48.2 r9819"
|
||||||
|
sodipodi:docname="arrow dropshadow.svg"
|
||||||
|
inkscape:export-filename="C:\Users\SimonVT\Desktop\menu_arrow_hdpi.png"
|
||||||
|
inkscape:export-xdpi="135"
|
||||||
|
inkscape:export-ydpi="135">
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<filter
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="filter3800"
|
||||||
|
x="-0.20999995"
|
||||||
|
width="1.4199999"
|
||||||
|
y="-0.084000008"
|
||||||
|
height="1.168">
|
||||||
|
<feGaussianBlur
|
||||||
|
inkscape:collect="always"
|
||||||
|
stdDeviation="1.0500001"
|
||||||
|
id="feGaussianBlur3802" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="15.839192"
|
||||||
|
inkscape:cx="-0.29262145"
|
||||||
|
inkscape:cy="18.116185"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1680"
|
||||||
|
inkscape:window-height="998"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-1019.3622)">
|
||||||
|
<path
|
||||||
|
style="fill:#1a1a1a;fill-opacity:1;fill-rule:evenodd;stroke:none;filter:url(#filter3800)"
|
||||||
|
d="M 13.999507,1050.8622 2,1035.8623 13.999517,1020.8622 c 4e-6,30.0455 0.0011,-0.01 -1e-5,30 z"
|
||||||
|
id="rect2989-7"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#e8e8e8;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||||
|
d="M 15.999508,1050.8622 4,1035.8623 15.999517,1020.8622 c 4e-6,30.0455 0.0011,-0.01 -9e-6,30 z"
|
||||||
|
id="rect2989"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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-16
|
||||||
|
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- The default background of the menu. -->
|
||||||
|
<color name="md__defaultBackground">#FF555555</color>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,672 @@
|
||||||
|
package net.simonvt.menudrawer;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.VelocityTracker;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.animation.AccelerateInterpolator;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
|
public abstract class DraggableDrawer extends MenuDrawer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key used when saving menu visibility state.
|
||||||
|
*/
|
||||||
|
private static final String STATE_MENU_VISIBLE = "net.simonvt.menudrawer.MenuDrawer.menuVisible";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolator used for stretching/retracting the active indicator.
|
||||||
|
*/
|
||||||
|
protected static final Interpolator INDICATOR_INTERPOLATOR = new AccelerateInterpolator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolator used for peeking at the drawer.
|
||||||
|
*/
|
||||||
|
private static final Interpolator PEEK_INTERPOLATOR = new PeekInterpolator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum alpha of the dark menu overlay used for dimming the menu.
|
||||||
|
*/
|
||||||
|
protected static final int MAX_MENU_OVERLAY_ALPHA = 185;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default delay from {@link #peekDrawer()} is called until first animation is run.
|
||||||
|
*/
|
||||||
|
private static final long DEFAULT_PEEK_START_DELAY = 5000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
|
||||||
|
*/
|
||||||
|
private static final long DEFAULT_PEEK_DELAY = 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The duration of the peek animation.
|
||||||
|
*/
|
||||||
|
protected static final int PEEK_DURATION = 5000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance in dp from closed position from where the drawer is considered closed with regards to touch events.
|
||||||
|
*/
|
||||||
|
private static final int CLOSE_ENOUGH = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slop before starting a drag.
|
||||||
|
*/
|
||||||
|
protected int mTouchSlop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runnable used when the peek animation is running.
|
||||||
|
*/
|
||||||
|
protected final Runnable mPeekRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
peekDrawerInvalidate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runnable used when animating the drawer open/closed.
|
||||||
|
*/
|
||||||
|
private final Runnable mDragRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
postAnimationInvalidate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current left position of the content.
|
||||||
|
*/
|
||||||
|
protected float mOffsetPixels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the drawer is currently being dragged.
|
||||||
|
*/
|
||||||
|
protected boolean mIsDragging;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial X position of a drag.
|
||||||
|
*/
|
||||||
|
protected float mInitialMotionX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial Y position of a drag.
|
||||||
|
*/
|
||||||
|
protected float mInitialMotionY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last X position of a drag.
|
||||||
|
*/
|
||||||
|
protected float mLastMotionX = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last Y position of a drag.
|
||||||
|
*/
|
||||||
|
protected float mLastMotionY = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
|
||||||
|
*/
|
||||||
|
protected long mPeekDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroller used for the peek drawer animation.
|
||||||
|
*/
|
||||||
|
protected Scroller mPeekScroller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Velocity tracker used when animating the drawer open/closed after a drag.
|
||||||
|
*/
|
||||||
|
protected VelocityTracker mVelocityTracker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum velocity allowed when animating the drawer open/closed.
|
||||||
|
*/
|
||||||
|
protected int mMaxVelocity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the menu should be offset when dragging the drawer.
|
||||||
|
*/
|
||||||
|
protected boolean mOffsetMenu = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance in px from closed position from where the drawer is considered closed with regards to touch events.
|
||||||
|
*/
|
||||||
|
protected int mCloseEnough;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runnable used for first call to {@link #startPeek()} after {@link #peekDrawer()} has been called.
|
||||||
|
*/
|
||||||
|
private Runnable mPeekStartRunnable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroller used when animating the drawer open/closed.
|
||||||
|
*/
|
||||||
|
private Scroller mScroller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}.
|
||||||
|
*/
|
||||||
|
private boolean mLayerTypeHardware;
|
||||||
|
|
||||||
|
DraggableDrawer(Activity activity, int dragMode) {
|
||||||
|
super(activity, dragMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DraggableDrawer(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DraggableDrawer(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DraggableDrawer(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);
|
||||||
|
|
||||||
|
final ViewConfiguration configuration = ViewConfiguration.get(context);
|
||||||
|
mTouchSlop = configuration.getScaledTouchSlop();
|
||||||
|
mMaxVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||||
|
|
||||||
|
mScroller = new Scroller(context, MenuDrawer.SMOOTH_INTERPOLATOR);
|
||||||
|
mPeekScroller = new Scroller(context, DraggableDrawer.PEEK_INTERPOLATOR);
|
||||||
|
|
||||||
|
mCloseEnough = dpToPx(DraggableDrawer.CLOSE_ENOUGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleMenu(boolean animate) {
|
||||||
|
if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
|
||||||
|
closeMenu(animate);
|
||||||
|
} else if (mDrawerState == STATE_CLOSED || mDrawerState == STATE_CLOSING) {
|
||||||
|
openMenu(animate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMenuVisible() {
|
||||||
|
return mMenuVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMenuSize(final int size) {
|
||||||
|
mMenuSize = size;
|
||||||
|
mMenuSizeSet = true;
|
||||||
|
if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
|
||||||
|
setOffsetPixels(mMenuSize);
|
||||||
|
}
|
||||||
|
requestLayout();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOffsetMenuEnabled(boolean offsetMenu) {
|
||||||
|
if (offsetMenu != mOffsetMenu) {
|
||||||
|
mOffsetMenu = offsetMenu;
|
||||||
|
requestLayout();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getOffsetMenuEnabled() {
|
||||||
|
return mOffsetMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void peekDrawer() {
|
||||||
|
peekDrawer(DEFAULT_PEEK_START_DELAY, DEFAULT_PEEK_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void peekDrawer(long delay) {
|
||||||
|
peekDrawer(DEFAULT_PEEK_START_DELAY, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void peekDrawer(final long startDelay, final long delay) {
|
||||||
|
if (startDelay < 0) {
|
||||||
|
throw new IllegalArgumentException("startDelay must be zero or larger.");
|
||||||
|
}
|
||||||
|
if (delay < 0) {
|
||||||
|
throw new IllegalArgumentException("delay must be zero or larger");
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCallbacks(mPeekRunnable);
|
||||||
|
removeCallbacks(mPeekStartRunnable);
|
||||||
|
|
||||||
|
mPeekDelay = delay;
|
||||||
|
mPeekStartRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
startPeek();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
postDelayed(mPeekStartRunnable, startDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHardwareLayerEnabled(boolean enabled) {
|
||||||
|
if (enabled != mHardwareLayersEnabled) {
|
||||||
|
mHardwareLayersEnabled = enabled;
|
||||||
|
mMenuContainer.setHardwareLayersEnabled(enabled);
|
||||||
|
mContentContainer.setHardwareLayersEnabled(enabled);
|
||||||
|
stopLayerTranslation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTouchMode() {
|
||||||
|
return mTouchMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTouchMode(int mode) {
|
||||||
|
if (mTouchMode != mode) {
|
||||||
|
mTouchMode = mode;
|
||||||
|
updateTouchAreaSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTouchBezelSize(int size) {
|
||||||
|
mTouchBezelSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTouchBezelSize() {
|
||||||
|
return mTouchBezelSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number of pixels the content should be offset.
|
||||||
|
*
|
||||||
|
* @param offsetPixels The number of pixels to offset the content by.
|
||||||
|
*/
|
||||||
|
protected void setOffsetPixels(float offsetPixels) {
|
||||||
|
final int oldOffset = (int) mOffsetPixels;
|
||||||
|
final int newOffset = (int) offsetPixels;
|
||||||
|
|
||||||
|
mOffsetPixels = offsetPixels;
|
||||||
|
|
||||||
|
if (newOffset != oldOffset) {
|
||||||
|
onOffsetPixelsChanged(newOffset);
|
||||||
|
mMenuVisible = newOffset != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the number of pixels the content should be offset by has changed.
|
||||||
|
*
|
||||||
|
* @param offsetPixels The number of pixels to offset the content by.
|
||||||
|
*/
|
||||||
|
protected abstract void onOffsetPixelsChanged(int offsetPixels);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If possible, set the layer type to {@link android.view.View#LAYER_TYPE_HARDWARE}.
|
||||||
|
*/
|
||||||
|
protected void startLayerTranslation() {
|
||||||
|
if (USE_TRANSLATIONS && mHardwareLayersEnabled && !mLayerTypeHardware) {
|
||||||
|
mLayerTypeHardware = true;
|
||||||
|
mContentContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||||
|
mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}, this will set it to
|
||||||
|
* {@link View#LAYER_TYPE_NONE}.
|
||||||
|
*/
|
||||||
|
private void stopLayerTranslation() {
|
||||||
|
if (mLayerTypeHardware) {
|
||||||
|
mLayerTypeHardware = false;
|
||||||
|
mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||||
|
mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the touch area based on the touch mode.
|
||||||
|
*/
|
||||||
|
protected void updateTouchAreaSize() {
|
||||||
|
if (mTouchMode == TOUCH_MODE_BEZEL) {
|
||||||
|
mTouchSize = mTouchBezelSize;
|
||||||
|
} else if (mTouchMode == TOUCH_MODE_FULLSCREEN) {
|
||||||
|
mTouchSize = getMeasuredWidth();
|
||||||
|
} else {
|
||||||
|
mTouchSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a drag has been ended.
|
||||||
|
*/
|
||||||
|
protected void endDrag() {
|
||||||
|
mIsDragging = false;
|
||||||
|
|
||||||
|
if (mVelocityTracker != null) {
|
||||||
|
mVelocityTracker.recycle();
|
||||||
|
mVelocityTracker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops ongoing animation of the drawer.
|
||||||
|
*/
|
||||||
|
protected void stopAnimation() {
|
||||||
|
removeCallbacks(mDragRunnable);
|
||||||
|
mScroller.abortAnimation();
|
||||||
|
stopLayerTranslation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a drawer animation has successfully completed.
|
||||||
|
*/
|
||||||
|
private void completeAnimation() {
|
||||||
|
mScroller.abortAnimation();
|
||||||
|
final int finalX = mScroller.getFinalX();
|
||||||
|
setOffsetPixels(finalX);
|
||||||
|
setDrawerState(finalX == 0 ? STATE_CLOSED : STATE_OPEN);
|
||||||
|
stopLayerTranslation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the drawer to the position passed.
|
||||||
|
*
|
||||||
|
* @param position The position the content is moved to.
|
||||||
|
* @param velocity Optional velocity if called by releasing a drag event.
|
||||||
|
* @param animate Whether the move is animated.
|
||||||
|
*/
|
||||||
|
protected void animateOffsetTo(int position, int velocity, boolean animate) {
|
||||||
|
endDrag();
|
||||||
|
endPeek();
|
||||||
|
|
||||||
|
final int startX = (int) mOffsetPixels;
|
||||||
|
final int dx = position - startX;
|
||||||
|
if (dx == 0 || !animate) {
|
||||||
|
setOffsetPixels(position);
|
||||||
|
setDrawerState(position == 0 ? STATE_CLOSED : STATE_OPEN);
|
||||||
|
stopLayerTranslation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int duration;
|
||||||
|
|
||||||
|
velocity = Math.abs(velocity);
|
||||||
|
if (velocity > 0) {
|
||||||
|
duration = 4 * Math.round(1000.f * Math.abs((float) dx / velocity));
|
||||||
|
} else {
|
||||||
|
duration = (int) (600.f * Math.abs((float) dx / mMenuSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
duration = Math.min(duration, mMaxAnimationDuration);
|
||||||
|
|
||||||
|
if (dx > 0) {
|
||||||
|
setDrawerState(STATE_OPENING);
|
||||||
|
mScroller.startScroll(startX, 0, dx, 0, duration);
|
||||||
|
} else {
|
||||||
|
setDrawerState(STATE_CLOSING);
|
||||||
|
mScroller.startScroll(startX, 0, dx, 0, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
startLayerTranslation();
|
||||||
|
|
||||||
|
postAnimationInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when each frame in the drawer animation should be drawn.
|
||||||
|
*/
|
||||||
|
private void postAnimationInvalidate() {
|
||||||
|
if (mScroller.computeScrollOffset()) {
|
||||||
|
final int oldX = (int) mOffsetPixels;
|
||||||
|
final int x = mScroller.getCurrX();
|
||||||
|
|
||||||
|
if (x != oldX) setOffsetPixels(x);
|
||||||
|
if (x != mScroller.getFinalX()) {
|
||||||
|
postOnAnimation(mDragRunnable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completeAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts peek drawer animation.
|
||||||
|
*/
|
||||||
|
protected void startPeek() {
|
||||||
|
initPeekScroller();
|
||||||
|
|
||||||
|
startLayerTranslation();
|
||||||
|
peekDrawerInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void initPeekScroller();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when each frame in the peek drawer animation should be drawn.
|
||||||
|
*/
|
||||||
|
private void peekDrawerInvalidate() {
|
||||||
|
if (mPeekScroller.computeScrollOffset()) {
|
||||||
|
final int oldX = (int) mOffsetPixels;
|
||||||
|
final int x = mPeekScroller.getCurrX();
|
||||||
|
if (x != oldX) setOffsetPixels(x);
|
||||||
|
|
||||||
|
if (!mPeekScroller.isFinished()) {
|
||||||
|
postOnAnimation(mPeekRunnable);
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else if (mPeekDelay > 0) {
|
||||||
|
mPeekStartRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
startPeek();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
postDelayed(mPeekStartRunnable, mPeekDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completePeek();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the peek drawer animation has successfully completed.
|
||||||
|
*/
|
||||||
|
private void completePeek() {
|
||||||
|
mPeekScroller.abortAnimation();
|
||||||
|
|
||||||
|
setOffsetPixels(0);
|
||||||
|
|
||||||
|
setDrawerState(STATE_CLOSED);
|
||||||
|
stopLayerTranslation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops ongoing peek drawer animation.
|
||||||
|
*/
|
||||||
|
protected void endPeek() {
|
||||||
|
removeCallbacks(mPeekStartRunnable);
|
||||||
|
removeCallbacks(mPeekRunnable);
|
||||||
|
stopLayerTranslation();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isCloseEnough() {
|
||||||
|
return Math.abs(mOffsetPixels) <= mCloseEnough;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the touch event occurs over the content.
|
||||||
|
*
|
||||||
|
* @param ev The motion event.
|
||||||
|
* @return True if the touch event occurred over the content, false otherwise.
|
||||||
|
*/
|
||||||
|
protected abstract boolean isContentTouch(MotionEvent ev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if dragging the content should be allowed.
|
||||||
|
*
|
||||||
|
* @param ev The motion event.
|
||||||
|
* @return True if dragging the content should be allowed, false otherwise.
|
||||||
|
*/
|
||||||
|
protected abstract boolean onDownAllowDrag(MotionEvent ev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests scrollability within child views of v given a delta of dx.
|
||||||
|
*
|
||||||
|
* @param v View to test for horizontal scrollability
|
||||||
|
* @param checkV Whether the view should be checked for draggability
|
||||||
|
* @param dx Delta scrolled in pixels
|
||||||
|
* @param x X coordinate of the active touch point
|
||||||
|
* @param y Y coordinate of the active touch point
|
||||||
|
* @return true if child views of v can be scrolled by delta of dx.
|
||||||
|
*/
|
||||||
|
protected boolean canChildScrollHorizontally(View v, boolean checkV, int dx, int x, int y) {
|
||||||
|
if (v instanceof ViewGroup) {
|
||||||
|
final ViewGroup group = (ViewGroup) v;
|
||||||
|
|
||||||
|
final int count = group.getChildCount();
|
||||||
|
// Count backwards - let topmost views consume scroll distance first.
|
||||||
|
for (int i = count - 1; i >= 0; i--) {
|
||||||
|
final View child = group.getChildAt(i);
|
||||||
|
|
||||||
|
final int childLeft = child.getLeft() + supportGetTranslationX(child);
|
||||||
|
final int childRight = child.getRight() + supportGetTranslationX(child);
|
||||||
|
final int childTop = child.getTop() + supportGetTranslationY(child);
|
||||||
|
final int childBottom = child.getBottom() + supportGetTranslationY(child);
|
||||||
|
|
||||||
|
if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
|
||||||
|
&& canChildScrollHorizontally(child, true, dx, x - childLeft, y - childTop)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests scrollability within child views of v given a delta of dx.
|
||||||
|
*
|
||||||
|
* @param v View to test for horizontal scrollability
|
||||||
|
* @param checkV Whether the view should be checked for draggability
|
||||||
|
* @param dx Delta scrolled in pixels
|
||||||
|
* @param x X coordinate of the active touch point
|
||||||
|
* @param y Y coordinate of the active touch point
|
||||||
|
* @return true if child views of v can be scrolled by delta of dx.
|
||||||
|
*/
|
||||||
|
protected boolean canChildScrollVertically(View v, boolean checkV, int dx, int x, int y) {
|
||||||
|
if (v instanceof ViewGroup) {
|
||||||
|
final ViewGroup group = (ViewGroup) v;
|
||||||
|
|
||||||
|
final int count = group.getChildCount();
|
||||||
|
// Count backwards - let topmost views consume scroll distance first.
|
||||||
|
for (int i = count - 1; i >= 0; i--) {
|
||||||
|
final View child = group.getChildAt(i);
|
||||||
|
|
||||||
|
final int childLeft = child.getLeft() + supportGetTranslationX(child);
|
||||||
|
final int childRight = child.getRight() + supportGetTranslationX(child);
|
||||||
|
final int childTop = child.getTop() + supportGetTranslationY(child);
|
||||||
|
final int childBottom = child.getBottom() + supportGetTranslationY(child);
|
||||||
|
|
||||||
|
if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
|
||||||
|
&& canChildScrollVertically(child, true, dx, x - childLeft, y - childTop)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int supportGetTranslationY(View v) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
return (int) v.getTranslationY();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int supportGetTranslationX(View v) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
return (int) v.getTranslationX();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if dragging the content should be allowed.
|
||||||
|
*
|
||||||
|
* @param ev The motion event.
|
||||||
|
* @return True if dragging the content should be allowed, false otherwise.
|
||||||
|
*/
|
||||||
|
protected abstract boolean onMoveAllowDrag(MotionEvent ev, float dx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a move event has happened while dragging the content is in progress.
|
||||||
|
*
|
||||||
|
* @param dx The X difference between the last motion event and the current motion event.
|
||||||
|
*/
|
||||||
|
protected abstract void onMoveEvent(float dx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when {@link android.view.MotionEvent#ACTION_UP} of {@link android.view.MotionEvent#ACTION_CANCEL} is
|
||||||
|
* delivered to {@link net.simonvt.menudrawer.MenuDrawer#onTouchEvent(android.view.MotionEvent)}.
|
||||||
|
*
|
||||||
|
* @param ev The motion event.
|
||||||
|
*/
|
||||||
|
protected abstract void onUpEvent(MotionEvent ev);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
|
super.dispatchDraw(canvas);
|
||||||
|
final int offsetPixels = (int) mOffsetPixels;
|
||||||
|
|
||||||
|
if (offsetPixels != 0) drawMenuOverlay(canvas, offsetPixels);
|
||||||
|
if (mDropShadowEnabled) drawDropShadow(canvas, offsetPixels);
|
||||||
|
if (mActiveIndicator != null) drawIndicator(canvas, offsetPixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the content drop shadow should be drawn.
|
||||||
|
*
|
||||||
|
* @param canvas The canvas on which to draw.
|
||||||
|
* @param offsetPixels Value in pixels indicating the offset.
|
||||||
|
*/
|
||||||
|
protected abstract void drawDropShadow(Canvas canvas, int offsetPixels);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the menu overlay should be drawn.
|
||||||
|
*
|
||||||
|
* @param canvas The canvas on which to draw.
|
||||||
|
* @param offsetPixels Value in pixels indicating the offset.
|
||||||
|
*/
|
||||||
|
protected abstract void drawMenuOverlay(Canvas canvas, int offsetPixels);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the active indicator should be drawn.
|
||||||
|
*
|
||||||
|
* @param canvas The canvas on which to draw.
|
||||||
|
* @param offsetPixels Value in pixels indicating the offset.
|
||||||
|
*/
|
||||||
|
protected abstract void drawIndicator(Canvas canvas, int offsetPixels);
|
||||||
|
|
||||||
|
void saveState(Bundle state) {
|
||||||
|
final boolean menuVisible = mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING;
|
||||||
|
state.putBoolean(STATE_MENU_VISIBLE, menuVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restoreState(Parcelable in) {
|
||||||
|
super.restoreState(in);
|
||||||
|
Bundle state = (Bundle) in;
|
||||||
|
final boolean menuOpen = state.getBoolean(STATE_MENU_VISIBLE);
|
||||||
|
if (menuOpen) {
|
||||||
|
openMenu(false);
|
||||||
|
} else {
|
||||||
|
setOffsetPixels(0);
|
||||||
|
}
|
||||||
|
mDrawerState = menuOpen ? STATE_OPEN : STATE_CLOSED;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,505 @@
|
||||||
|
/*
|
||||||
|
* 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.content.Context;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.FloatMath;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
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 Scroller {
|
||||||
|
private int mMode;
|
||||||
|
|
||||||
|
private int mStartX;
|
||||||
|
private int mStartY;
|
||||||
|
private int mFinalX;
|
||||||
|
private int mFinalY;
|
||||||
|
|
||||||
|
private int mMinX;
|
||||||
|
private int mMaxX;
|
||||||
|
private int mMinY;
|
||||||
|
private int mMaxY;
|
||||||
|
|
||||||
|
private int mCurrX;
|
||||||
|
private int mCurrY;
|
||||||
|
private long mStartTime;
|
||||||
|
private int mDuration;
|
||||||
|
private float mDurationReciprocal;
|
||||||
|
private float mDeltaX;
|
||||||
|
private float mDeltaY;
|
||||||
|
private boolean mFinished;
|
||||||
|
private Interpolator mInterpolator;
|
||||||
|
private boolean mFlywheel;
|
||||||
|
|
||||||
|
private float mVelocity;
|
||||||
|
|
||||||
|
private static final int DEFAULT_DURATION = 250;
|
||||||
|
private static final int SCROLL_MODE = 0;
|
||||||
|
private static final int FLING_MODE = 1;
|
||||||
|
|
||||||
|
private static final float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
|
||||||
|
private static final float ALPHA = 800; // pixels / seconds
|
||||||
|
private static final float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
|
||||||
|
private static final float END_TENSION = 1.0f - START_TENSION;
|
||||||
|
private static final int NB_SAMPLES = 100;
|
||||||
|
private static final float[] SPLINE = new float[NB_SAMPLES + 1];
|
||||||
|
|
||||||
|
private float mDeceleration;
|
||||||
|
private final float mPpi;
|
||||||
|
|
||||||
|
static {
|
||||||
|
float xMin = 0.0f;
|
||||||
|
for (int i = 0; i <= NB_SAMPLES; i++) {
|
||||||
|
final float t = (float) i / NB_SAMPLES;
|
||||||
|
float xMax = 1.0f;
|
||||||
|
float x, tx, coef;
|
||||||
|
while (true) {
|
||||||
|
x = xMin + (xMax - xMin) / 2.0f;
|
||||||
|
coef = 3.0f * x * (1.0f - x);
|
||||||
|
tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
|
||||||
|
if (Math.abs(tx - t) < 1E-5) break;
|
||||||
|
if (tx > t) xMax = x;
|
||||||
|
else xMin = x;
|
||||||
|
}
|
||||||
|
final float d = coef + x * x * x;
|
||||||
|
SPLINE[i] = d;
|
||||||
|
}
|
||||||
|
SPLINE[NB_SAMPLES] = 1.0f;
|
||||||
|
|
||||||
|
// This controls the viscous fluid effect (how much of it)
|
||||||
|
sViscousFluidScale = 8.0f;
|
||||||
|
// must be set to 1.0 (used in viscousFluid())
|
||||||
|
sViscousFluidNormalize = 1.0f;
|
||||||
|
sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float sViscousFluidScale;
|
||||||
|
private static float sViscousFluidNormalize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Scroller with the default duration and interpolator.
|
||||||
|
*/
|
||||||
|
public Scroller(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Scroller with the specified interpolator. If the interpolator is
|
||||||
|
* null, the default (viscous) interpolator will be used. "Flywheel" behavior will
|
||||||
|
* be in effect for apps targeting Honeycomb or newer.
|
||||||
|
*/
|
||||||
|
public Scroller(Context context, Interpolator interpolator) {
|
||||||
|
this(context, interpolator,
|
||||||
|
context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Scroller(Context context, Interpolator interpolator, boolean flywheel) {
|
||||||
|
mFinished = true;
|
||||||
|
mInterpolator = interpolator;
|
||||||
|
mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
|
||||||
|
mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
|
||||||
|
mFlywheel = flywheel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of friction applied to flings. The default value
|
||||||
|
* is {@link android.view.ViewConfiguration#getScrollFriction}.
|
||||||
|
*
|
||||||
|
* @param friction A scalar dimension-less value representing the coefficient of
|
||||||
|
* friction.
|
||||||
|
*/
|
||||||
|
public final void setFriction(float friction) {
|
||||||
|
mDeceleration = computeDeceleration(friction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float computeDeceleration(float friction) {
|
||||||
|
return SensorManager.GRAVITY_EARTH // g (m/s^2)
|
||||||
|
* 39.37f // inch/meter
|
||||||
|
* mPpi // pixels per inch
|
||||||
|
* friction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 X offset in the scroll.
|
||||||
|
*
|
||||||
|
* @return The new X offset as an absolute distance from the origin.
|
||||||
|
*/
|
||||||
|
public final int getCurrX() {
|
||||||
|
return mCurrX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current Y offset in the scroll.
|
||||||
|
*
|
||||||
|
* @return The new Y offset as an absolute distance from the origin.
|
||||||
|
*/
|
||||||
|
public final int getCurrY() {
|
||||||
|
return mCurrY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current velocity.
|
||||||
|
*
|
||||||
|
* @return The original velocity less the deceleration. Result may be
|
||||||
|
* negative.
|
||||||
|
*/
|
||||||
|
public float getCurrVelocity() {
|
||||||
|
return mVelocity - mDeceleration * timePassed() / 2000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start X offset in the scroll.
|
||||||
|
*
|
||||||
|
* @return The start X offset as an absolute distance from the origin.
|
||||||
|
*/
|
||||||
|
public final int getStartX() {
|
||||||
|
return mStartX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start Y offset in the scroll.
|
||||||
|
*
|
||||||
|
* @return The start Y offset as an absolute distance from the origin.
|
||||||
|
*/
|
||||||
|
public final int getStartY() {
|
||||||
|
return mStartY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||||
|
*
|
||||||
|
* @return The final X offset as an absolute distance from the origin.
|
||||||
|
*/
|
||||||
|
public final int getFinalX() {
|
||||||
|
return mFinalX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||||
|
*
|
||||||
|
* @return The final Y offset as an absolute distance from the origin.
|
||||||
|
*/
|
||||||
|
public final int getFinalY() {
|
||||||
|
return mFinalY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this when you want to know the new location. If it returns true,
|
||||||
|
* the animation is not yet finished. loc will be altered to provide the
|
||||||
|
* new location.
|
||||||
|
*/
|
||||||
|
public boolean computeScrollOffset() {
|
||||||
|
if (mFinished) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
|
||||||
|
|
||||||
|
if (timePassed < mDuration) {
|
||||||
|
switch (mMode) {
|
||||||
|
case SCROLL_MODE:
|
||||||
|
float x = timePassed * mDurationReciprocal;
|
||||||
|
|
||||||
|
if (mInterpolator == null)
|
||||||
|
x = viscousFluid(x);
|
||||||
|
else
|
||||||
|
x = mInterpolator.getInterpolation(x);
|
||||||
|
|
||||||
|
mCurrX = mStartX + Math.round(x * mDeltaX);
|
||||||
|
mCurrY = mStartY + Math.round(x * mDeltaY);
|
||||||
|
break;
|
||||||
|
case FLING_MODE:
|
||||||
|
final float t = (float) timePassed / mDuration;
|
||||||
|
final int index = (int) (NB_SAMPLES * t);
|
||||||
|
final float tInf = (float) index / NB_SAMPLES;
|
||||||
|
final float tSup = (float) (index + 1) / NB_SAMPLES;
|
||||||
|
final float dInf = SPLINE[index];
|
||||||
|
final float dSup = SPLINE[index + 1];
|
||||||
|
final float distanceCoef = dInf + (t - tInf) / (tSup - tInf) * (dSup - dInf);
|
||||||
|
|
||||||
|
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
|
||||||
|
// Pin to mMinX <= mCurrX <= mMaxX
|
||||||
|
mCurrX = Math.min(mCurrX, mMaxX);
|
||||||
|
mCurrX = Math.max(mCurrX, mMinX);
|
||||||
|
|
||||||
|
mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
|
||||||
|
// Pin to mMinY <= mCurrY <= mMaxY
|
||||||
|
mCurrY = Math.min(mCurrY, mMaxY);
|
||||||
|
mCurrY = Math.max(mCurrY, mMinY);
|
||||||
|
|
||||||
|
if (mCurrX == mFinalX && mCurrY == mFinalY) {
|
||||||
|
mFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mCurrX = mFinalX;
|
||||||
|
mCurrY = mFinalY;
|
||||||
|
mFinished = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start scrolling by providing a starting point and the distance to travel.
|
||||||
|
* The scroll will use the default value of 250 milliseconds for the
|
||||||
|
* duration.
|
||||||
|
*
|
||||||
|
* @param startX Starting horizontal scroll offset in pixels. Positive
|
||||||
|
* numbers will scroll the content to the left.
|
||||||
|
* @param startY Starting vertical scroll offset in pixels. Positive numbers
|
||||||
|
* will scroll the content up.
|
||||||
|
* @param dx Horizontal distance to travel. Positive numbers will scroll the
|
||||||
|
* content to the left.
|
||||||
|
* @param dy Vertical distance to travel. Positive numbers will scroll the
|
||||||
|
* content up.
|
||||||
|
*/
|
||||||
|
public void startScroll(int startX, int startY, int dx, int dy) {
|
||||||
|
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start scrolling by providing a starting point and the distance to travel.
|
||||||
|
*
|
||||||
|
* @param startX Starting horizontal scroll offset in pixels. Positive
|
||||||
|
* numbers will scroll the content to the left.
|
||||||
|
* @param startY Starting vertical scroll offset in pixels. Positive numbers
|
||||||
|
* will scroll the content up.
|
||||||
|
* @param dx Horizontal distance to travel. Positive numbers will scroll the
|
||||||
|
* content to the left.
|
||||||
|
* @param dy Vertical distance to travel. Positive numbers will scroll the
|
||||||
|
* content up.
|
||||||
|
* @param duration Duration of the scroll in milliseconds.
|
||||||
|
*/
|
||||||
|
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
|
||||||
|
mMode = SCROLL_MODE;
|
||||||
|
mFinished = false;
|
||||||
|
mDuration = duration;
|
||||||
|
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||||
|
mStartX = startX;
|
||||||
|
mStartY = startY;
|
||||||
|
mFinalX = startX + dx;
|
||||||
|
mFinalY = startY + dy;
|
||||||
|
mDeltaX = dx;
|
||||||
|
mDeltaY = dy;
|
||||||
|
mDurationReciprocal = 1.0f / (float) mDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start scrolling based on a fling gesture. The distance travelled will
|
||||||
|
* depend on the initial velocity of the fling.
|
||||||
|
*
|
||||||
|
* @param startX Starting point of the scroll (X)
|
||||||
|
* @param startY Starting point of the scroll (Y)
|
||||||
|
* @param velocityX Initial velocity of the fling (X) measured in pixels per
|
||||||
|
* second.
|
||||||
|
* @param velocityY Initial velocity of the fling (Y) measured in pixels per
|
||||||
|
* second
|
||||||
|
* @param minX Minimum X value. The scroller will not scroll past this
|
||||||
|
* point.
|
||||||
|
* @param maxX Maximum X value. The scroller will not scroll past this
|
||||||
|
* point.
|
||||||
|
* @param minY Minimum Y value. The scroller will not scroll past this
|
||||||
|
* point.
|
||||||
|
* @param maxY Maximum Y value. The scroller will not scroll past this
|
||||||
|
* point.
|
||||||
|
*/
|
||||||
|
public void fling(int startX, int startY, int velocityX, int velocityY,
|
||||||
|
int minX, int maxX, int minY, int maxY) {
|
||||||
|
// Continue a scroll or fling in progress
|
||||||
|
if (mFlywheel && !mFinished) {
|
||||||
|
float oldVel = getCurrVelocity();
|
||||||
|
|
||||||
|
float dx = (float) (mFinalX - mStartX);
|
||||||
|
float dy = (float) (mFinalY - mStartY);
|
||||||
|
float hyp = FloatMath.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
float ndx = dx / hyp;
|
||||||
|
float ndy = dy / hyp;
|
||||||
|
|
||||||
|
float oldVelocityX = ndx * oldVel;
|
||||||
|
float oldVelocityY = ndy * oldVel;
|
||||||
|
if (Math.signum(velocityX) == Math.signum(oldVelocityX)
|
||||||
|
&& Math.signum(velocityY) == Math.signum(oldVelocityY)) {
|
||||||
|
velocityX += oldVelocityX;
|
||||||
|
velocityY += oldVelocityY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mMode = FLING_MODE;
|
||||||
|
mFinished = false;
|
||||||
|
|
||||||
|
float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
|
||||||
|
|
||||||
|
mVelocity = velocity;
|
||||||
|
final double l = Math.log(START_TENSION * velocity / ALPHA);
|
||||||
|
mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
|
||||||
|
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||||
|
mStartX = startX;
|
||||||
|
mStartY = startY;
|
||||||
|
|
||||||
|
float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
|
||||||
|
float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
|
||||||
|
|
||||||
|
int totalDistance =
|
||||||
|
(int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
|
||||||
|
|
||||||
|
mMinX = minX;
|
||||||
|
mMaxX = maxX;
|
||||||
|
mMinY = minY;
|
||||||
|
mMaxY = maxY;
|
||||||
|
|
||||||
|
mFinalX = startX + Math.round(totalDistance * coeffX);
|
||||||
|
// Pin to mMinX <= mFinalX <= mMaxX
|
||||||
|
mFinalX = Math.min(mFinalX, mMaxX);
|
||||||
|
mFinalX = Math.max(mFinalX, mMinX);
|
||||||
|
|
||||||
|
mFinalY = startY + Math.round(totalDistance * coeffY);
|
||||||
|
// Pin to mMinY <= mFinalY <= mMaxY
|
||||||
|
mFinalY = Math.min(mFinalY, mMaxY);
|
||||||
|
mFinalY = Math.max(mFinalY, mMinY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float viscousFluid(float x) {
|
||||||
|
x *= sViscousFluidScale;
|
||||||
|
if (x < 1.0f) {
|
||||||
|
x -= (1.0f - (float) Math.exp(-x));
|
||||||
|
} else {
|
||||||
|
float start = 0.36787944117f; // 1/e == exp(-1)
|
||||||
|
x = 1.0f - (float) Math.exp(1.0f - x);
|
||||||
|
x = start + x * (1.0f - start);
|
||||||
|
}
|
||||||
|
x *= sViscousFluidNormalize;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
mCurrX = mFinalX;
|
||||||
|
mCurrY = mFinalY;
|
||||||
|
mFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the scroll animation. This allows a running animation to scroll
|
||||||
|
* further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
|
||||||
|
*
|
||||||
|
* @param extend Additional time to scroll in milliseconds.
|
||||||
|
* @see #setFinalX(int)
|
||||||
|
* @see #setFinalY(int)
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the final position (X) for this scroller.
|
||||||
|
*
|
||||||
|
* @param newX The new X offset as an absolute distance from the origin.
|
||||||
|
* @see #extendDuration(int)
|
||||||
|
* @see #setFinalY(int)
|
||||||
|
*/
|
||||||
|
public void setFinalX(int newX) {
|
||||||
|
mFinalX = newX;
|
||||||
|
mDeltaX = mFinalX - mStartX;
|
||||||
|
mFinished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the final position (Y) for this scroller.
|
||||||
|
*
|
||||||
|
* @param newY The new Y offset as an absolute distance from the origin.
|
||||||
|
* @see #extendDuration(int)
|
||||||
|
* @see #setFinalX(int)
|
||||||
|
*/
|
||||||
|
public void setFinalY(int newY) {
|
||||||
|
mFinalY = newY;
|
||||||
|
mDeltaY = mFinalY - mStartY;
|
||||||
|
mFinished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public boolean isScrollingInDirection(float xvel, float yvel) {
|
||||||
|
return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX)
|
||||||
|
&& Math.signum(yvel) == Math.signum(mFinalY - mStartY);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?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>org.sonatype.oss</groupId>
|
||||||
|
<artifactId>oss-parent</artifactId>
|
||||||
|
<version>7</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>net.simonvt</groupId>
|
||||||
|
<artifactId>android-menudrawer-parent</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<version>2.0.3-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<name>Android MenuDrawer (Parent)</name>
|
||||||
|
<description>A menu drawer implementation which allows dragging of both the content, and the entire window.</description>
|
||||||
|
<url>https://github.com/SimonVT/android-menudrawer</url>
|
||||||
|
<inceptionYear>2012</inceptionYear>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>library</module>
|
||||||
|
<module>samples</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<url>http://github.com/SimonVT/android-menudrawer/</url>
|
||||||
|
<connection>scm:git:git://github.com/SimonVT/android-menudrawer.git</connection>
|
||||||
|
<developerConnection>scm:git:git@github.com:SimonVT/android-menudrawer.git</developerConnection>
|
||||||
|
<tag>HEAD</tag>
|
||||||
|
</scm>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>Apache License Version 2.0</name>
|
||||||
|
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||||
|
<distribution>repo</distribution>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<issueManagement>
|
||||||
|
<system>GitHub Issues</system>
|
||||||
|
<url>https://github.com/SimonVT/android-menudrawer/issues</url>
|
||||||
|
</issueManagement>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
|
||||||
|
<java.version>1.6</java.version>
|
||||||
|
<android.version>4.1.1.4</android.version>
|
||||||
|
<android.platform>16</android.platform>
|
||||||
|
|
||||||
|
<android-support.version>r7</android-support.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.android</groupId>
|
||||||
|
<artifactId>android</artifactId>
|
||||||
|
<version>${android.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
|
||||||
|
<artifactId>android-maven-plugin</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
<configuration>
|
||||||
|
<sdk>
|
||||||
|
<platform>${android.platform}</platform>
|
||||||
|
</sdk>
|
||||||
|
</configuration>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-release-plugin</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
|
<configuration>
|
||||||
|
<autoVersionSubmodules>true</autoVersionSubmodules>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||||
|
<version>2.9.1</version>
|
||||||
|
<configuration>
|
||||||
|
<failsOnError>true</failsOnError>
|
||||||
|
<!-- Relative to module directory. -->
|
||||||
|
<configLocation>../checkstyle.xml</configLocation>
|
||||||
|
<consoleOutput>true</consoleOutput>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>checkstyle</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="net.simonvt.menudrawer.samples"
|
||||||
|
android:versionCode="3"
|
||||||
|
android:versionName="2.0.1">
|
||||||
|
|
||||||
|
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="17" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:theme="@style/SampleTheme">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="SamplesActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleTop" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity android:name="net.simonvt.menudrawer.samples.WindowSample" />
|
||||||
|
<activity android:name="net.simonvt.menudrawer.samples.ContentSample" />
|
||||||
|
<activity android:name="net.simonvt.menudrawer.samples.ListActivitySample" />
|
||||||
|
<activity android:name="net.simonvt.menudrawer.samples.ActionBarOverlaySample" />
|
||||||
|
<activity android:name="net.simonvt.menudrawer.samples.ViewPagerSample" />
|
||||||
|
<activity
|
||||||
|
android:name="net.simonvt.menudrawer.samples.RightMenuSample"
|
||||||
|
android:theme="@style/SampleTheme.RightDrawer" />
|
||||||
|
<activity
|
||||||
|
android:name="net.simonvt.menudrawer.samples.TopMenuSample"
|
||||||
|
android:theme="@style/SampleTheme.TopDrawer">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.simonvt.menudrawer.samples.SamplesActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="net.simonvt.menudrawer.samples.BottomMenuSample"
|
||||||
|
android:theme="@style/SampleTheme.BottomDrawer" />
|
||||||
|
<activity
|
||||||
|
android:name="net.simonvt.menudrawer.samples.LayoutSample"
|
||||||
|
android:theme="@style/SampleTheme.TopDrawer" />
|
||||||
|
<activity android:name="net.simonvt.menudrawer.samples.StaticDrawerSample" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project name="menudrawersamples" 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>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?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-sample</artifactId>
|
||||||
|
<name>Android MenuDrawer Sample</name>
|
||||||
|
<packaging>apk</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.android</groupId>
|
||||||
|
<artifactId>android</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.simonvt</groupId>
|
||||||
|
<artifactId>android-menudrawer</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<type>apklib</type>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.android</groupId>
|
||||||
|
<artifactId>support-v4</artifactId>
|
||||||
|
<version>${android-support.version}</version>
|
||||||
|
</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>
|
|
@ -0,0 +1,15 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Project target.
|
||||||
|
target=android-16
|
||||||
|
android.library.reference.1=../library
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 148 B |
After Width: | Height: | Size: 173 B |
After Width: | Height: | Size: 191 B |
After Width: | Height: | Size: 154 B |
After Width: | Height: | Size: 159 B |
After Width: | Height: | Size: 189 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 891 B |
After Width: | Height: | Size: 904 B |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 131 B |
After Width: | Height: | Size: 165 B |
After Width: | Height: | Size: 171 B |
After Width: | Height: | Size: 151 B |
After Width: | Height: | Size: 158 B |
After Width: | Height: | Size: 172 B |
After Width: | Height: | Size: 869 B |
After Width: | Height: | Size: 887 B |
After Width: | Height: | Size: 855 B |
After Width: | Height: | Size: 835 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 184 B |
After Width: | Height: | Size: 179 B |
After Width: | Height: | Size: 203 B |
After Width: | Height: | Size: 158 B |
After Width: | Height: | Size: 163 B |
After Width: | Height: | Size: 190 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<transition xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/md__list_pressed_holo_dark" />
|
||||||
|
<item android:drawable="@drawable/md__list_longpressed_holo" />
|
||||||
|
</transition>
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2010 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item android:state_window_focused="false" android:drawable="@color/md__transparent" />
|
||||||
|
|
||||||
|
<!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
|
||||||
|
<item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/md__list_selector_disabled_holo_dark" />
|
||||||
|
<item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/md__list_selector_disabled_holo_dark" />
|
||||||
|
<item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/md__list_selector_background_transition_holo_dark" />
|
||||||
|
<item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/md__list_selector_background_transition_holo_dark" />
|
||||||
|
<item android:state_focused="true" android:drawable="@drawable/md__list_focused_holo" />
|
||||||
|
</selector>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sample_bottommenu" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contentText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sample_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contentText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/seekBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<net.simonvt.menudrawer.TopDrawer xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/drawer"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:mdMenuSize="64dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@id/mdMenu"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="10dp" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/item1"
|
||||||
|
style="@style/MenuDrawer.Widget.Title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawableLeft="@drawable/ic_action_refresh_dark"
|
||||||
|
android:onClick="onDrawerItemClick"
|
||||||
|
android:tag="Item no. 1"
|
||||||
|
android:text="Item 1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/item2"
|
||||||
|
style="@style/MenuDrawer.Widget.Title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawableLeft="@drawable/ic_action_select_all_dark"
|
||||||
|
android:onClick="onDrawerItemClick"
|
||||||
|
android:tag="Item no. 2"
|
||||||
|
android:text="Item 2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@id/mdContent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sample_layout" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contentText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</net.simonvt.menudrawer.TopDrawer>
|