├── .gitignore ├── LICENSE ├── README.md ├── SlidingTabs ├── AndroidManifest.xml ├── ic_launcher-web.png ├── libs │ └── android-support-v4.jar ├── proguard-project.txt ├── project.properties ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ ├── ic_launcher.png │ │ ├── tab_bckg_left.9.png │ │ └── tab_bckg_right.9.png │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ ├── layout │ │ ├── activity_main.xml │ │ ├── tab_left.xml │ │ └── tab_right.xml │ ├── values-v11 │ │ └── styles.xml │ ├── values-v14 │ │ └── styles.xml │ └── values │ │ ├── attrs.xml │ │ ├── strings.xml │ │ └── styles.xml └── src │ └── com │ └── mobifactor │ └── slidingtabs │ ├── MainActivity.java │ └── MySlidingDrawer.java └── screen.png /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Intellij project files 26 | *.iml 27 | *.ipr 28 | *.iws 29 | .idea/ 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Sliding Up Tabs 2 | =========== 3 | 4 | Extension of standard Android SlidingDrawer class, which allows you to place the tabs at the bottom of screen staggered. 5 | 6 | - **Author:** Michael Nekrasov 7 | - **Website:** [http://mobifactor.ru](http://www.mobifactor.ru/) 8 | - **Example App:** [Google Play](https://play.google.com/store/apps/details?id=com.mobifactor.mig) 9 | 10 | 11 | -------------------------------------------------------------------------------- /SlidingTabs/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /SlidingTabs/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miklen326/slidingtabs/b4f9b9fc8a2e596602b8dd60efc1dc6f89ad95eb/SlidingTabs/ic_launcher-web.png -------------------------------------------------------------------------------- /SlidingTabs/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miklen326/slidingtabs/b4f9b9fc8a2e596602b8dd60efc1dc6f89ad95eb/SlidingTabs/libs/android-support-v4.jar -------------------------------------------------------------------------------- /SlidingTabs/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /SlidingTabs/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | -------------------------------------------------------------------------------- /SlidingTabs/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miklen326/slidingtabs/b4f9b9fc8a2e596602b8dd60efc1dc6f89ad95eb/SlidingTabs/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /SlidingTabs/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miklen326/slidingtabs/b4f9b9fc8a2e596602b8dd60efc1dc6f89ad95eb/SlidingTabs/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /SlidingTabs/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miklen326/slidingtabs/b4f9b9fc8a2e596602b8dd60efc1dc6f89ad95eb/SlidingTabs/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /SlidingTabs/res/drawable-xhdpi/tab_bckg_left.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miklen326/slidingtabs/b4f9b9fc8a2e596602b8dd60efc1dc6f89ad95eb/SlidingTabs/res/drawable-xhdpi/tab_bckg_left.9.png -------------------------------------------------------------------------------- /SlidingTabs/res/drawable-xhdpi/tab_bckg_right.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miklen326/slidingtabs/b4f9b9fc8a2e596602b8dd60efc1dc6f89ad95eb/SlidingTabs/res/drawable-xhdpi/tab_bckg_right.9.png -------------------------------------------------------------------------------- /SlidingTabs/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miklen326/slidingtabs/b4f9b9fc8a2e596602b8dd60efc1dc6f89ad95eb/SlidingTabs/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /SlidingTabs/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /SlidingTabs/res/layout/tab_left.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | 30 | 31 | 32 | 39 | 40 | 47 | 48 | 56 | 57 | 65 | 66 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /SlidingTabs/res/layout/tab_right.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | 30 | 31 | 32 | 39 | 40 | 47 | 48 | 56 | 57 | 65 | 66 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /SlidingTabs/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /SlidingTabs/res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /SlidingTabs/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SlidingTabs/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sliding Tabs 4 | Tab 5 | 6 | 7 | -------------------------------------------------------------------------------- /SlidingTabs/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SlidingTabs/src/com/mobifactor/slidingtabs/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mobifactor.slidingtabs; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import android.os.Bundle; 7 | import android.app.Activity; 8 | import android.content.Context; 9 | import android.util.TypedValue; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup.LayoutParams; 13 | import android.widget.RelativeLayout; 14 | import android.widget.TextView; 15 | 16 | public class MainActivity extends Activity { 17 | 18 | private List mTabs; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | mTabs = new ArrayList(); 26 | 27 | for (int i = 0; i < 4; i++) { 28 | MySlidingDrawer slidingDrawer = createTab(i); 29 | TextView title = (TextView) slidingDrawer.getHandle().findViewById( 30 | R.id.title); 31 | title.setText(getString(R.string.tab_name) + " " + (i + 1)); 32 | } 33 | 34 | layoutTabs(); 35 | } 36 | 37 | @Override 38 | public void onBackPressed() { 39 | if (mTabs != null) { 40 | for (MySlidingDrawer tab : mTabs) { 41 | if (tab.isOpened()) { 42 | tab.close(); 43 | return; 44 | } 45 | } 46 | } 47 | super.onBackPressed(); 48 | } 49 | 50 | @Override 51 | protected void onStart() { 52 | super.onStart(); 53 | } 54 | 55 | public MySlidingDrawer createTab(final int index) { 56 | RelativeLayout rootLayout = (RelativeLayout) findViewById(R.id.root); 57 | LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 58 | final MySlidingDrawer slidingDrawer; 59 | if (index % 2 == 0) { 60 | slidingDrawer = (MySlidingDrawer) inflater.inflate( 61 | R.layout.tab_left, null); 62 | } else { 63 | slidingDrawer = (MySlidingDrawer) inflater.inflate( 64 | R.layout.tab_right, null); 65 | } 66 | 67 | RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( 68 | LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 69 | params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); 70 | rootLayout.addView(slidingDrawer, params); 71 | mTabs.add(slidingDrawer); 72 | 73 | slidingDrawer 74 | .setOnDrawerScrollListener(new MySlidingDrawer.OnDrawerScrollListener() { 75 | @Override 76 | public void onScrollStarted() { 77 | for (MySlidingDrawer tab : mTabs) { 78 | if (!slidingDrawer.equals(tab) && tab.isOpened()) 79 | tab.close(); 80 | } 81 | } 82 | 83 | @Override 84 | public void onScrollEnded() { 85 | } 86 | }); 87 | 88 | slidingDrawer.getContent().setClickable(true); 89 | return slidingDrawer; 90 | } 91 | 92 | public void layoutTabs() { 93 | final int count = mTabs.size(); 94 | int offset = 0; 95 | for (int i = 0; i < count; i++) { 96 | MySlidingDrawer slidingDrawer = mTabs.get(count - i - 1); 97 | slidingDrawer.setmBottomOffset((int) TypedValue.applyDimension( 98 | TypedValue.COMPLEX_UNIT_DIP, -offset, getResources() 99 | .getDisplayMetrics())); 100 | View contentView = slidingDrawer.findViewById(R.id.content); 101 | int padding = (int) TypedValue.applyDimension( 102 | TypedValue.COMPLEX_UNIT_DIP, 10, getResources() 103 | .getDisplayMetrics()); 104 | contentView.setPadding( 105 | padding, 106 | padding, 107 | padding, 108 | padding 109 | + (int) TypedValue.applyDimension( 110 | TypedValue.COMPLEX_UNIT_DIP, offset 111 | + (((i + 1) % 2 == 0) ? 30 : 10), 112 | getResources().getDisplayMetrics())); 113 | offset += ((i + 1) % 2 == 0) ? 35 : 10; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /SlidingTabs/src/com/mobifactor/slidingtabs/MySlidingDrawer.java: -------------------------------------------------------------------------------- 1 | package com.mobifactor.slidingtabs; 2 | 3 | import java.lang.ref.WeakReference; 4 | 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Bitmap; 8 | import android.graphics.Canvas; 9 | import android.graphics.Paint; 10 | import android.graphics.Paint.Style; 11 | import android.graphics.Rect; 12 | import android.os.Handler; 13 | import android.os.Message; 14 | import android.os.SystemClock; 15 | import android.util.AttributeSet; 16 | import android.view.MotionEvent; 17 | import android.view.SoundEffectConstants; 18 | import android.view.VelocityTracker; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | import android.view.accessibility.AccessibilityEvent; 22 | import android.view.accessibility.AccessibilityNodeInfo; 23 | 24 | public class MySlidingDrawer extends ViewGroup { 25 | public static final int ORIENTATION_HORIZONTAL = 0; 26 | public static final int ORIENTATION_VERTICAL = 1; 27 | 28 | private static final int TAP_THRESHOLD = 6; 29 | private static final float MAXIMUM_TAP_VELOCITY = 100.0f; 30 | private static final float MAXIMUM_MINOR_VELOCITY = 150.0f; 31 | private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f; 32 | private static final float MAXIMUM_ACCELERATION = 2000.0f; 33 | private static final int VELOCITY_UNITS = 1000; 34 | private static final int MSG_ANIMATE = 1000; 35 | private static final int ANIMATION_FRAME_DURATION = 1000 / 60; 36 | 37 | private static final int EXPANDED_FULL_OPEN = -10001; 38 | private static final int COLLAPSED_FULL_CLOSED = -10002; 39 | 40 | private final int mHandleId; 41 | private final int mContentId; 42 | 43 | private View mHandle; 44 | private View mContent; 45 | 46 | private final Rect mFrame = new Rect(); 47 | private final Rect mInvalidate = new Rect(); 48 | private boolean mTracking; 49 | private boolean mLocked; 50 | 51 | private VelocityTracker mVelocityTracker; 52 | 53 | private boolean mVertical; 54 | private boolean mExpanded; 55 | private int mBottomOffset; 56 | private int mTopOffset; 57 | private int mHandleHeight; 58 | private int mHandleWidth; 59 | 60 | private OnDrawerOpenListener mOnDrawerOpenListener; 61 | private OnDrawerCloseListener mOnDrawerCloseListener; 62 | private OnDrawerScrollListener mOnDrawerScrollListener; 63 | 64 | private final Handler mHandler = new SlidingHandler(this); 65 | private float mAnimatedAcceleration; 66 | private float mAnimatedVelocity; 67 | private float mAnimationPosition; 68 | private long mAnimationLastTime; 69 | private long mCurrentAnimationTime; 70 | private int mTouchDelta; 71 | private boolean mAnimating; 72 | private boolean mAllowSingleTap; 73 | private boolean mAnimateOnClick; 74 | 75 | private final int mTapThreshold; 76 | private final int mMaximumTapVelocity; 77 | private final int mMaximumMinorVelocity; 78 | private final int mMaximumMajorVelocity; 79 | private final int mMaximumAcceleration; 80 | private final int mVelocityUnits; 81 | 82 | private final boolean mRightTab; 83 | 84 | private final int mBackgroundColor; 85 | 86 | /** 87 | * Callback invoked when the drawer is opened. 88 | */ 89 | public static interface OnDrawerOpenListener { 90 | /** 91 | * Invoked when the drawer becomes fully open. 92 | */ 93 | public void onDrawerOpened(); 94 | } 95 | 96 | /** 97 | * Callback invoked when the drawer is closed. 98 | */ 99 | public static interface OnDrawerCloseListener { 100 | /** 101 | * Invoked when the drawer becomes fully closed. 102 | */ 103 | public void onDrawerClosed(); 104 | } 105 | 106 | /** 107 | * Callback invoked when the drawer is scrolled. 108 | */ 109 | public static interface OnDrawerScrollListener { 110 | /** 111 | * Invoked when the user starts dragging/flinging the drawer's handle. 112 | */ 113 | public void onScrollStarted(); 114 | 115 | /** 116 | * Invoked when the user stops dragging/flinging the drawer's handle. 117 | */ 118 | public void onScrollEnded(); 119 | } 120 | 121 | /** 122 | * Creates a new SlidingDrawer from a specified set of attributes defined in XML. 123 | * 124 | * @param context The application's environment. 125 | * @param attrs The attributes defined in XML. 126 | */ 127 | public MySlidingDrawer(Context context, AttributeSet attrs) { 128 | this(context, attrs, 0); 129 | } 130 | 131 | /** 132 | * Creates a new SlidingDrawer from a specified set of attributes defined in XML. 133 | * 134 | * @param context The application's environment. 135 | * @param attrs The attributes defined in XML. 136 | * @param defStyle The style to apply to this widget. 137 | */ 138 | public MySlidingDrawer(Context context, AttributeSet attrs, int defStyle) { 139 | super(context, attrs, defStyle); 140 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MySlidingDrawer, defStyle, 0); 141 | 142 | mVertical = true; 143 | mBottomOffset = (int) a.getDimension(R.styleable.MySlidingDrawer_bottomOffset, 0.0f); 144 | mTopOffset = (int) a.getDimension(R.styleable.MySlidingDrawer_topOffset, 0.0f); 145 | mAllowSingleTap = true; 146 | mAnimateOnClick = a.getBoolean(R.styleable.MySlidingDrawer_animateOnClick, true); 147 | mRightTab = a.getBoolean(R.styleable.MySlidingDrawer_rightTab, true); 148 | mBackgroundColor = a.getColor(R.styleable.MySlidingDrawer_backgroundColor, 0); 149 | 150 | int handleId = a.getResourceId(R.styleable.MySlidingDrawer_handle, 0); 151 | if (handleId == 0) { 152 | throw new IllegalArgumentException("The handle attribute is required and must refer " 153 | + "to a valid child."); 154 | } 155 | 156 | int contentId = a.getResourceId(R.styleable.MySlidingDrawer_content, 0); 157 | if (contentId == 0) { 158 | throw new IllegalArgumentException("The content attribute is required and must refer " 159 | + "to a valid child."); 160 | } 161 | 162 | if (handleId == contentId) { 163 | throw new IllegalArgumentException("The content and handle attributes must refer " 164 | + "to different children."); 165 | } 166 | 167 | mHandleId = handleId; 168 | mContentId = contentId; 169 | 170 | final float density = getResources().getDisplayMetrics().density; 171 | mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f); 172 | mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f); 173 | mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f); 174 | mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f); 175 | mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f); 176 | mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f); 177 | 178 | a.recycle(); 179 | 180 | setAlwaysDrawnWithCacheEnabled(false); 181 | } 182 | 183 | public void setmBottomOffset(int mBottomOffset) { 184 | this.mBottomOffset = mBottomOffset; 185 | } 186 | 187 | @Override 188 | protected void onFinishInflate() { 189 | mHandle = findViewById(mHandleId); 190 | if (mHandle == null) { 191 | throw new IllegalArgumentException("The handle attribute is must refer to an" 192 | + " existing child."); 193 | } 194 | mHandle.setOnClickListener(new DrawerToggler()); 195 | 196 | mContent = findViewById(mContentId); 197 | if (mContent == null) { 198 | throw new IllegalArgumentException("The content attribute is must refer to an" 199 | + " existing child."); 200 | } 201 | mContent.setVisibility(View.GONE); 202 | } 203 | 204 | @Override 205 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 206 | 207 | int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 208 | int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 209 | 210 | int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 211 | int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 212 | 213 | if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { 214 | throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions"); 215 | } 216 | 217 | final View handle = getHandle(); 218 | final View content = getContent(); 219 | measureChild(handle, widthMeasureSpec, heightMeasureSpec); 220 | 221 | if (mVertical) { 222 | int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset; 223 | content.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, heightSpecMode)); 224 | heightSpecSize = handle.getMeasuredHeight() + mTopOffset + content.getMeasuredHeight(); 225 | widthSpecSize = content.getMeasuredWidth(); 226 | if (handle.getMeasuredWidth() > widthSpecSize) widthSpecSize = handle.getMeasuredWidth(); 227 | } 228 | else { 229 | int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset; 230 | getContent().measure(MeasureSpec.makeMeasureSpec(width, widthSpecMode), heightMeasureSpec); 231 | widthSpecSize = handle.getMeasuredWidth() + mTopOffset + content.getMeasuredWidth(); 232 | heightSpecSize = content.getMeasuredHeight(); 233 | if (handle.getMeasuredHeight() > heightSpecSize) heightSpecSize = handle.getMeasuredHeight(); 234 | } 235 | 236 | setMeasuredDimension(widthSpecSize, heightSpecSize); 237 | } 238 | /* 239 | @Override 240 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 241 | int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 242 | int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 243 | 244 | int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 245 | int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 246 | 247 | if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { 248 | throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions"); 249 | } 250 | 251 | final View handle = mHandle; 252 | measureChild(handle, widthMeasureSpec, heightMeasureSpec); 253 | 254 | if (mVertical) { 255 | int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset; 256 | mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY), 257 | MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 258 | } else { 259 | int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset; 260 | mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 261 | MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY)); 262 | } 263 | 264 | setMeasuredDimension(widthSpecSize, heightSpecSize); 265 | }*/ 266 | 267 | @Override 268 | protected void dispatchDraw(Canvas canvas) { 269 | final long drawingTime = getDrawingTime(); 270 | final View handle = mHandle; 271 | final boolean isVertical = mVertical; 272 | 273 | drawChild(canvas, handle, drawingTime); 274 | 275 | if (mTracking || mAnimating) { 276 | final Bitmap cache = mContent.getDrawingCache(); 277 | if (cache != null) { 278 | if (isVertical) { 279 | canvas.drawBitmap(cache, 0, handle.getBottom(), null); 280 | } else { 281 | canvas.drawBitmap(cache, handle.getRight(), 0, null); 282 | } 283 | } else { 284 | canvas.save(); 285 | canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset, 286 | isVertical ? handle.getTop() - mTopOffset : 0); 287 | drawChild(canvas, mContent, drawingTime); 288 | canvas.restore(); 289 | } 290 | } else if (mExpanded) { 291 | drawChild(canvas, mContent, drawingTime); 292 | } else { 293 | Paint paint2 = new Paint(); 294 | paint2.setColor(mBackgroundColor); 295 | paint2.setStyle(Style.FILL); 296 | if (isVertical) { 297 | canvas.drawRect(0, handle.getBottom(), mContent.getRight(), mContent.getBottom(), paint2); 298 | } else { 299 | } 300 | } 301 | } 302 | 303 | @Override 304 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 305 | if (mTracking) { 306 | return; 307 | } 308 | 309 | final int width = r - l; 310 | final int height = b - t; 311 | 312 | final View handle = mHandle; 313 | 314 | int childWidth = handle.getMeasuredWidth(); 315 | int childHeight = handle.getMeasuredHeight(); 316 | 317 | int childLeft; 318 | int childTop; 319 | 320 | final View content = mContent; 321 | 322 | if (mVertical) { 323 | childLeft = (width - childWidth) / 2; 324 | childTop = mExpanded ? mTopOffset : height - childHeight + mBottomOffset; 325 | 326 | content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(), 327 | mTopOffset + childHeight + content.getMeasuredHeight()); 328 | } else { 329 | childLeft = mExpanded ? mTopOffset : width - childWidth + mBottomOffset; 330 | childTop = (height - childHeight) / 2; 331 | 332 | content.layout(mTopOffset + childWidth, 0, 333 | mTopOffset + childWidth + content.getMeasuredWidth(), 334 | content.getMeasuredHeight()); 335 | } 336 | 337 | handle.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); 338 | mHandleHeight = handle.getHeight(); 339 | mHandleWidth = handle.getWidth(); 340 | } 341 | 342 | @Override 343 | public boolean dispatchTouchEvent(MotionEvent event) { 344 | float x = event.getX(); 345 | float y = event.getY(); 346 | 347 | final Rect frame = mFrame; 348 | final View handle = mHandle; 349 | 350 | handle.getHitRect(frame); 351 | if (y <= frame.bottom && y >= frame.top) { 352 | if (mRightTab) { 353 | if (x < frame.right / 2) 354 | return false; 355 | } else { 356 | if (x > frame.right / 2) 357 | return false; 358 | } 359 | } 360 | return super.dispatchTouchEvent(event); 361 | } 362 | 363 | @Override 364 | public boolean onInterceptTouchEvent(MotionEvent event) { 365 | if (mLocked) { 366 | return false; 367 | } 368 | 369 | final int action = event.getAction(); 370 | 371 | float x = event.getX(); 372 | float y = event.getY(); 373 | 374 | final Rect frame = mFrame; 375 | final View handle = mHandle; 376 | 377 | handle.getHitRect(frame); 378 | if (!mTracking && !frame.contains((int) x, (int) y)) { 379 | return false; 380 | } 381 | 382 | if (action == MotionEvent.ACTION_DOWN) { 383 | mTracking = true; 384 | 385 | handle.setPressed(true); 386 | // Must be called before prepareTracking() 387 | prepareContent(); 388 | 389 | // Must be called after prepareContent() 390 | if (mOnDrawerScrollListener != null) { 391 | mOnDrawerScrollListener.onScrollStarted(); 392 | } 393 | 394 | if (mVertical) { 395 | final int top = mHandle.getTop(); 396 | mTouchDelta = (int) y - top; 397 | prepareTracking(top); 398 | } else { 399 | final int left = mHandle.getLeft(); 400 | mTouchDelta = (int) x - left; 401 | prepareTracking(left); 402 | } 403 | mVelocityTracker.addMovement(event); 404 | } 405 | 406 | return true; 407 | } 408 | 409 | @Override 410 | public boolean onTouchEvent(MotionEvent event) { 411 | if (mLocked) { 412 | return true; 413 | } 414 | 415 | if (mTracking) { 416 | mVelocityTracker.addMovement(event); 417 | final int action = event.getAction(); 418 | switch (action) { 419 | case MotionEvent.ACTION_MOVE: 420 | moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta); 421 | break; 422 | case MotionEvent.ACTION_UP: 423 | case MotionEvent.ACTION_CANCEL: { 424 | final VelocityTracker velocityTracker = mVelocityTracker; 425 | velocityTracker.computeCurrentVelocity(mVelocityUnits); 426 | 427 | float yVelocity = velocityTracker.getYVelocity(); 428 | float xVelocity = velocityTracker.getXVelocity(); 429 | boolean negative; 430 | 431 | final boolean vertical = mVertical; 432 | if (vertical) { 433 | negative = yVelocity < 0; 434 | if (xVelocity < 0) { 435 | xVelocity = -xVelocity; 436 | } 437 | if (xVelocity > mMaximumMinorVelocity) { 438 | xVelocity = mMaximumMinorVelocity; 439 | } 440 | } else { 441 | negative = xVelocity < 0; 442 | if (yVelocity < 0) { 443 | yVelocity = -yVelocity; 444 | } 445 | if (yVelocity > mMaximumMinorVelocity) { 446 | yVelocity = mMaximumMinorVelocity; 447 | } 448 | } 449 | 450 | float velocity = (float) Math.hypot(xVelocity, yVelocity); 451 | if (negative) { 452 | velocity = -velocity; 453 | } 454 | 455 | final int top = mHandle.getTop(); 456 | final int left = mHandle.getLeft(); 457 | 458 | if (Math.abs(velocity) < mMaximumTapVelocity) { 459 | if (vertical ? (mExpanded && top < mTapThreshold + mTopOffset) || 460 | (!mExpanded && top > mBottomOffset + getBottom() - getTop() - 461 | mHandleHeight - mTapThreshold) : 462 | (mExpanded && left < mTapThreshold + mTopOffset) || 463 | (!mExpanded && left > mBottomOffset + getRight() - getLeft() - 464 | mHandleWidth - mTapThreshold)) { 465 | 466 | if (mAllowSingleTap) { 467 | playSoundEffect(SoundEffectConstants.CLICK); 468 | 469 | if (mExpanded) { 470 | animateClose(vertical ? top : left); 471 | } else { 472 | animateOpen(vertical ? top : left); 473 | } 474 | } else { 475 | performFling(vertical ? top : left, velocity, false); 476 | } 477 | 478 | } else { 479 | performFling(vertical ? top : left, velocity, false); 480 | } 481 | } else { 482 | performFling(vertical ? top : left, velocity, false); 483 | } 484 | } 485 | break; 486 | } 487 | } 488 | 489 | return mTracking || mAnimating || super.onTouchEvent(event); 490 | } 491 | 492 | private void animateClose(int position) { 493 | prepareTracking(position); 494 | performFling(position, mMaximumAcceleration, true); 495 | } 496 | 497 | private void animateOpen(int position) { 498 | prepareTracking(position); 499 | performFling(position, -mMaximumAcceleration, true); 500 | } 501 | 502 | @Override 503 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 504 | super.onInitializeAccessibilityEvent(event); 505 | event.setClassName(MySlidingDrawer.class.getName()); 506 | } 507 | 508 | @Override 509 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 510 | super.onInitializeAccessibilityNodeInfo(info); 511 | info.setClassName(MySlidingDrawer.class.getName()); 512 | } 513 | 514 | private void performFling(int position, float velocity, boolean always) { 515 | mAnimationPosition = position; 516 | mAnimatedVelocity = velocity; 517 | 518 | if (mExpanded) { 519 | if (always || (velocity > mMaximumMajorVelocity || 520 | (position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth) && 521 | velocity > -mMaximumMajorVelocity))) { 522 | // We are expanded, but they didn't move sufficiently to cause 523 | // us to retract. Animate back to the expanded position. 524 | mAnimatedAcceleration = mMaximumAcceleration; 525 | if (velocity < 0) { 526 | mAnimatedVelocity = 0; 527 | } 528 | } else { 529 | // We are expanded and are now going to animate away. 530 | mAnimatedAcceleration = -mMaximumAcceleration; 531 | if (velocity > 0) { 532 | mAnimatedVelocity = 0; 533 | } 534 | } 535 | } else { 536 | if (!always && (velocity > mMaximumMajorVelocity || 537 | (position > (mVertical ? getHeight() : getWidth()) / 2 && 538 | velocity > -mMaximumMajorVelocity))) { 539 | // We are collapsed, and they moved enough to allow us to expand. 540 | mAnimatedAcceleration = mMaximumAcceleration; 541 | if (velocity < 0) { 542 | mAnimatedVelocity = 0; 543 | } 544 | } else { 545 | // We are collapsed, but they didn't move sufficiently to cause 546 | // us to retract. Animate back to the collapsed position. 547 | mAnimatedAcceleration = -mMaximumAcceleration; 548 | if (velocity > 0) { 549 | mAnimatedVelocity = 0; 550 | } 551 | } 552 | } 553 | 554 | long now = SystemClock.uptimeMillis(); 555 | mAnimationLastTime = now; 556 | mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION; 557 | mAnimating = true; 558 | mHandler.removeMessages(MSG_ANIMATE); 559 | mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime); 560 | stopTracking(); 561 | } 562 | 563 | private void prepareTracking(int position) { 564 | mTracking = true; 565 | mVelocityTracker = VelocityTracker.obtain(); 566 | boolean opening = !mExpanded; 567 | if (opening) { 568 | mAnimatedAcceleration = mMaximumAcceleration; 569 | mAnimatedVelocity = mMaximumMajorVelocity; 570 | mAnimationPosition = mBottomOffset + 571 | (mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth); 572 | moveHandle((int) mAnimationPosition); 573 | mAnimating = true; 574 | mHandler.removeMessages(MSG_ANIMATE); 575 | long now = SystemClock.uptimeMillis(); 576 | mAnimationLastTime = now; 577 | mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION; 578 | mAnimating = true; 579 | } else { 580 | if (mAnimating) { 581 | mAnimating = false; 582 | mHandler.removeMessages(MSG_ANIMATE); 583 | } 584 | moveHandle(position); 585 | } 586 | } 587 | 588 | private void moveHandle(int position) { 589 | final View handle = mHandle; 590 | 591 | if (mVertical) { 592 | if (position == EXPANDED_FULL_OPEN) { 593 | handle.offsetTopAndBottom(mTopOffset - handle.getTop()); 594 | invalidate(); 595 | } else if (position == COLLAPSED_FULL_CLOSED) { 596 | handle.offsetTopAndBottom(mBottomOffset + getBottom() - getTop() - 597 | mHandleHeight - handle.getTop()); 598 | invalidate(); 599 | } else { 600 | final int top = handle.getTop(); 601 | int deltaY = position - top; 602 | if (position < mTopOffset) { 603 | deltaY = mTopOffset - top; 604 | } else if (deltaY > mBottomOffset + getBottom() - getTop() - mHandleHeight - top) { 605 | deltaY = mBottomOffset + getBottom() - getTop() - mHandleHeight - top; 606 | } 607 | handle.offsetTopAndBottom(deltaY); 608 | 609 | final Rect frame = mFrame; 610 | final Rect region = mInvalidate; 611 | 612 | handle.getHitRect(frame); 613 | region.set(frame); 614 | 615 | region.union(frame.left, frame.top - deltaY, frame.right, frame.bottom - deltaY); 616 | region.union(0, frame.bottom - deltaY, getWidth(), 617 | frame.bottom - deltaY + mContent.getHeight()); 618 | 619 | invalidate(region); 620 | } 621 | } else { 622 | if (position == EXPANDED_FULL_OPEN) { 623 | handle.offsetLeftAndRight(mTopOffset - handle.getLeft()); 624 | invalidate(); 625 | } else if (position == COLLAPSED_FULL_CLOSED) { 626 | handle.offsetLeftAndRight(mBottomOffset + getRight() - getLeft() - 627 | mHandleWidth - handle.getLeft()); 628 | invalidate(); 629 | } else { 630 | final int left = handle.getLeft(); 631 | int deltaX = position - left; 632 | if (position < mTopOffset) { 633 | deltaX = mTopOffset - left; 634 | } else if (deltaX > mBottomOffset + getRight() - getLeft() - mHandleWidth - left) { 635 | deltaX = mBottomOffset + getRight() - getLeft() - mHandleWidth - left; 636 | } 637 | handle.offsetLeftAndRight(deltaX); 638 | 639 | final Rect frame = mFrame; 640 | final Rect region = mInvalidate; 641 | 642 | handle.getHitRect(frame); 643 | region.set(frame); 644 | 645 | region.union(frame.left - deltaX, frame.top, frame.right - deltaX, frame.bottom); 646 | region.union(frame.right - deltaX, 0, 647 | frame.right - deltaX + mContent.getWidth(), getHeight()); 648 | 649 | invalidate(region); 650 | } 651 | } 652 | } 653 | 654 | private void prepareContent() { 655 | if (mAnimating) { 656 | return; 657 | } 658 | 659 | // Something changed in the content, we need to honor the layout request 660 | // before creating the cached bitmap 661 | final View content = mContent; 662 | if (content.isLayoutRequested()) { 663 | if (mVertical) { 664 | final int childHeight = mHandleHeight; 665 | int height = getBottom() - getTop() - childHeight - mTopOffset; 666 | content.measure(MeasureSpec.makeMeasureSpec(getRight() - getLeft(), MeasureSpec.EXACTLY), 667 | MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 668 | content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(), 669 | mTopOffset + childHeight + content.getMeasuredHeight()); 670 | } else { 671 | final int childWidth = mHandle.getWidth(); 672 | int width = getRight() - getLeft() - childWidth - mTopOffset; 673 | content.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 674 | MeasureSpec.makeMeasureSpec(getBottom() - getTop() , MeasureSpec.EXACTLY)); 675 | content.layout(childWidth + mTopOffset, 0, 676 | mTopOffset + childWidth + content.getMeasuredWidth(), 677 | content.getMeasuredHeight()); 678 | } 679 | } 680 | // Try only once... we should really loop but it's not a big deal 681 | // if the draw was cancelled, it will only be temporary anyway 682 | content.getViewTreeObserver().dispatchOnPreDraw(); 683 | if (!content.isHardwareAccelerated()) content.buildDrawingCache(); 684 | 685 | content.setVisibility(View.GONE); 686 | } 687 | 688 | private void stopTracking() { 689 | mHandle.setPressed(false); 690 | mTracking = false; 691 | 692 | if (mOnDrawerScrollListener != null) { 693 | mOnDrawerScrollListener.onScrollEnded(); 694 | } 695 | 696 | if (mVelocityTracker != null) { 697 | mVelocityTracker.recycle(); 698 | mVelocityTracker = null; 699 | } 700 | } 701 | 702 | private void doAnimation() { 703 | if (mAnimating) { 704 | incrementAnimation(); 705 | if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) { 706 | mAnimating = false; 707 | closeDrawer(); 708 | } else if (mAnimationPosition < mTopOffset) { 709 | mAnimating = false; 710 | openDrawer(); 711 | } else { 712 | moveHandle((int) mAnimationPosition); 713 | mCurrentAnimationTime += ANIMATION_FRAME_DURATION; 714 | mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), 715 | mCurrentAnimationTime); 716 | } 717 | } 718 | } 719 | 720 | private void incrementAnimation() { 721 | long now = SystemClock.uptimeMillis(); 722 | float t = (now - mAnimationLastTime) / 1000.0f; // ms -> s 723 | final float position = mAnimationPosition; 724 | final float v = mAnimatedVelocity; // px/s 725 | final float a = mAnimatedAcceleration; // px/s/s 726 | mAnimationPosition = position + (v * t) + (0.5f * a * t * t); // px 727 | mAnimatedVelocity = v + (a * t); // px/s 728 | mAnimationLastTime = now; // ms 729 | } 730 | 731 | /** 732 | * Toggles the drawer open and close. Takes effect immediately. 733 | * 734 | * @see #open() 735 | * @see #close() 736 | * @see #animateClose() 737 | * @see #animateOpen() 738 | * @see #animateToggle() 739 | */ 740 | public void toggle() { 741 | if (!mExpanded) { 742 | openDrawer(); 743 | } else { 744 | closeDrawer(); 745 | } 746 | invalidate(); 747 | requestLayout(); 748 | } 749 | 750 | /** 751 | * Toggles the drawer open and close with an animation. 752 | * 753 | * @see #open() 754 | * @see #close() 755 | * @see #animateClose() 756 | * @see #animateOpen() 757 | * @see #toggle() 758 | */ 759 | public void animateToggle() { 760 | if (!mExpanded) { 761 | animateOpen(); 762 | } else { 763 | animateClose(); 764 | } 765 | } 766 | 767 | /** 768 | * Opens the drawer immediately. 769 | * 770 | * @see #toggle() 771 | * @see #close() 772 | * @see #animateOpen() 773 | */ 774 | public void open() { 775 | openDrawer(); 776 | invalidate(); 777 | requestLayout(); 778 | 779 | sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 780 | } 781 | 782 | /** 783 | * Closes the drawer immediately. 784 | * 785 | * @see #toggle() 786 | * @see #open() 787 | * @see #animateClose() 788 | */ 789 | public void close() { 790 | closeDrawer(); 791 | invalidate(); 792 | requestLayout(); 793 | } 794 | 795 | /** 796 | * Closes the drawer with an animation. 797 | * 798 | * @see #close() 799 | * @see #open() 800 | * @see #animateOpen() 801 | * @see #animateToggle() 802 | * @see #toggle() 803 | */ 804 | public void animateClose() { 805 | prepareContent(); 806 | final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener; 807 | if (scrollListener != null) { 808 | scrollListener.onScrollStarted(); 809 | } 810 | animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft()); 811 | 812 | if (scrollListener != null) { 813 | scrollListener.onScrollEnded(); 814 | } 815 | } 816 | 817 | /** 818 | * Opens the drawer with an animation. 819 | * 820 | * @see #close() 821 | * @see #open() 822 | * @see #animateClose() 823 | * @see #animateToggle() 824 | * @see #toggle() 825 | */ 826 | public void animateOpen() { 827 | prepareContent(); 828 | final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener; 829 | if (scrollListener != null) { 830 | scrollListener.onScrollStarted(); 831 | } 832 | animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft()); 833 | 834 | sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 835 | 836 | if (scrollListener != null) { 837 | scrollListener.onScrollEnded(); 838 | } 839 | } 840 | 841 | private void closeDrawer() { 842 | moveHandle(COLLAPSED_FULL_CLOSED); 843 | mContent.setVisibility(View.GONE); 844 | mContent.destroyDrawingCache(); 845 | 846 | if (!mExpanded) { 847 | return; 848 | } 849 | 850 | mExpanded = false; 851 | if (mOnDrawerCloseListener != null) { 852 | mOnDrawerCloseListener.onDrawerClosed(); 853 | } 854 | } 855 | 856 | private void openDrawer() { 857 | moveHandle(EXPANDED_FULL_OPEN); 858 | mContent.setVisibility(View.VISIBLE); 859 | 860 | if (mExpanded) { 861 | return; 862 | } 863 | 864 | mExpanded = true; 865 | 866 | if (mOnDrawerOpenListener != null) { 867 | mOnDrawerOpenListener.onDrawerOpened(); 868 | } 869 | } 870 | 871 | /** 872 | * Sets the listener that receives a notification when the drawer becomes open. 873 | * 874 | * @param onDrawerOpenListener The listener to be notified when the drawer is opened. 875 | */ 876 | public void setOnDrawerOpenListener(OnDrawerOpenListener onDrawerOpenListener) { 877 | mOnDrawerOpenListener = onDrawerOpenListener; 878 | } 879 | 880 | /** 881 | * Sets the listener that receives a notification when the drawer becomes close. 882 | * 883 | * @param onDrawerCloseListener The listener to be notified when the drawer is closed. 884 | */ 885 | public void setOnDrawerCloseListener(OnDrawerCloseListener onDrawerCloseListener) { 886 | mOnDrawerCloseListener = onDrawerCloseListener; 887 | } 888 | 889 | /** 890 | * Sets the listener that receives a notification when the drawer starts or ends 891 | * a scroll. A fling is considered as a scroll. A fling will also trigger a 892 | * drawer opened or drawer closed event. 893 | * 894 | * @param onDrawerScrollListener The listener to be notified when scrolling 895 | * starts or stops. 896 | */ 897 | public void setOnDrawerScrollListener(OnDrawerScrollListener onDrawerScrollListener) { 898 | mOnDrawerScrollListener = onDrawerScrollListener; 899 | } 900 | 901 | /** 902 | * Returns the handle of the drawer. 903 | * 904 | * @return The View reprenseting the handle of the drawer, identified by 905 | * the "handle" id in XML. 906 | */ 907 | public View getHandle() { 908 | return mHandle; 909 | } 910 | 911 | /** 912 | * Returns the content of the drawer. 913 | * 914 | * @return The View reprenseting the content of the drawer, identified by 915 | * the "content" id in XML. 916 | */ 917 | public View getContent() { 918 | return mContent; 919 | } 920 | 921 | /** 922 | * Unlocks the SlidingDrawer so that touch events are processed. 923 | * 924 | * @see #lock() 925 | */ 926 | public void unlock() { 927 | mLocked = false; 928 | } 929 | 930 | /** 931 | * Locks the SlidingDrawer so that touch events are ignores. 932 | * 933 | * @see #unlock() 934 | */ 935 | public void lock() { 936 | mLocked = true; 937 | } 938 | 939 | /** 940 | * Indicates whether the drawer is currently fully opened. 941 | * 942 | * @return True if the drawer is opened, false otherwise. 943 | */ 944 | public boolean isOpened() { 945 | return mExpanded; 946 | } 947 | 948 | /** 949 | * Indicates whether the drawer is scrolling or flinging. 950 | * 951 | * @return True if the drawer is scroller or flinging, false otherwise. 952 | */ 953 | public boolean isMoving() { 954 | return mTracking || mAnimating; 955 | } 956 | 957 | private class DrawerToggler implements OnClickListener { 958 | public void onClick(View v) { 959 | if (mLocked) { 960 | return; 961 | } 962 | // mAllowSingleTap isn't relevant here; you're *always* 963 | // allowed to open/close the drawer by clicking with the 964 | // trackball. 965 | 966 | if (mAnimateOnClick) { 967 | animateToggle(); 968 | } else { 969 | toggle(); 970 | } 971 | } 972 | } 973 | 974 | private static class SlidingHandler extends Handler { 975 | private final WeakReference mService; 976 | 977 | SlidingHandler(MySlidingDrawer service) { 978 | mService = new WeakReference(service); 979 | 980 | } 981 | 982 | public void handleMessage(Message m) { 983 | MySlidingDrawer service = mService.get(); 984 | if (service != null) { 985 | switch (m.what) { 986 | case MSG_ANIMATE: 987 | service.doAnimation(); 988 | break; 989 | } 990 | } 991 | } 992 | } 993 | } -------------------------------------------------------------------------------- /screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miklen326/slidingtabs/b4f9b9fc8a2e596602b8dd60efc1dc6f89ad95eb/screen.png --------------------------------------------------------------------------------