Repository: vikramkakkar/SublimeNavigationView Branch: master Commit: 99d56fd11662 Files: 114 Total size: 581.4 KB Directory structure: gitextract_a88i62nk/ ├── .gitignore ├── .idea/ │ ├── .name │ ├── compiler.xml │ ├── copyright/ │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── libraries/ │ │ ├── appcompat_v7_23_1_1.xml │ │ ├── cardview_v7_23_1_1.xml │ │ ├── design_23_1_1.xml │ │ ├── recyclerview_v7_23_1_1.xml │ │ ├── support_annotations_23_1_1.xml │ │ └── support_v4_23_1_1.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ ├── vcs.xml │ └── workspace.xml ├── LICENSE ├── README.md ├── SublimeNavigationView.iml ├── app/ │ ├── .gitignore │ ├── app.iml │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── appeaser/ │ │ └── sublimenavigationview/ │ │ └── ApplicationTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── appeaser/ │ │ │ └── sublimenavigationview/ │ │ │ └── Sampler.java │ │ └── res/ │ │ ├── color/ │ │ │ └── text_explore_some_more.xml │ │ ├── drawable/ │ │ │ ├── bg_explore_some_more.xml │ │ │ ├── bitmap_header_bg.xml │ │ │ └── user_bg.xml │ │ ├── drawable-v21/ │ │ │ └── bg_explore_some_more.xml │ │ ├── layout/ │ │ │ ├── act_sampler.xml │ │ │ ├── nav_header.xml │ │ │ └── sampler_content.xml │ │ ├── menu/ │ │ │ ├── menu_sampler.xml │ │ │ ├── test_nav_menu_1.xml │ │ │ └── test_nav_menu_2.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-v21/ │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── com/ │ └── appeaser/ │ └── sublimenavigationview/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── sublimenavigationviewlibrary/ ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── appeaser/ │ │ └── sublimenavigationviewlibrary/ │ │ └── ApplicationTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── appeaser/ │ │ │ └── sublimenavigationviewlibrary/ │ │ │ ├── Config.java │ │ │ ├── OnNavigationMenuEventListener.java │ │ │ ├── ScrimInsetsFrameLayout.java │ │ │ ├── StateAwareImageView.java │ │ │ ├── StateAwareTextView.java │ │ │ ├── SublimeBaseItemView.java │ │ │ ├── SublimeBaseMenuItem.java │ │ │ ├── SublimeCheckboxItemView.java │ │ │ ├── SublimeCheckboxMenuItem.java │ │ │ ├── SublimeGroup.java │ │ │ ├── SublimeGroupHeaderMenuItem.java │ │ │ ├── SublimeMenu.java │ │ │ ├── SublimeMenuInflater.java │ │ │ ├── SublimeMenuPresenter.java │ │ │ ├── SublimeNavMenuView.java │ │ │ ├── SublimeNavigationView.java │ │ │ ├── SublimeSeparatorItemView.java │ │ │ ├── SublimeSeparatorMenuItem.java │ │ │ ├── SublimeSubheaderItemView.java │ │ │ ├── SublimeSwitchItemView.java │ │ │ ├── SublimeSwitchMenuItem.java │ │ │ ├── SublimeTextItemView.java │ │ │ ├── SublimeTextMenuItem.java │ │ │ ├── SublimeTextWithBadgeItemView.java │ │ │ ├── SublimeTextWithBadgeMenuItem.java │ │ │ ├── SublimeThemer.java │ │ │ └── TextViewStyleProfile.java │ │ └── res/ │ │ ├── color-v23/ │ │ │ ├── snv_c_switch_track_material.xml │ │ │ └── white_disabled_material.xml │ │ ├── drawable/ │ │ │ ├── checkbox_pre_lollipop.xml │ │ │ └── switch_thumb_pre_lollipop.xml │ │ ├── drawable-v21/ │ │ │ ├── snv_switch_thumb_material_anim.xml │ │ │ └── snv_switch_track_material.xml │ │ ├── drawable-v23/ │ │ │ └── snv_switch_track_material.xml │ │ ├── layout/ │ │ │ ├── include_icon_holder.xml │ │ │ ├── include_text_hint_content.xml │ │ │ ├── sublime_checkbox_item_view.xml │ │ │ ├── sublime_menu_checkbox_item_content.xml │ │ │ ├── sublime_menu_header_item.xml │ │ │ ├── sublime_menu_separator_item_content.xml │ │ │ ├── sublime_menu_subheader_item_content.xml │ │ │ ├── sublime_menu_switch_item_content.xml │ │ │ ├── sublime_menu_text_item_content.xml │ │ │ ├── sublime_menu_text_with_badge_item_content.xml │ │ │ ├── sublime_navigation_menu_view.xml │ │ │ ├── sublime_separator_item_view.xml │ │ │ ├── sublime_subheader_item_view.xml │ │ │ ├── sublime_switch_item_view.xml │ │ │ ├── sublime_text_item_view.xml │ │ │ └── sublime_text_with_badge_item_view.xml │ │ └── values/ │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── com/ │ └── appeaser/ │ └── sublimenavigationviewlibrary/ │ └── ExampleUnitTest.java └── sublimenavigationviewlibrary.iml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Built application files *.apk *.ap_ # Files for the Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ # Gradle files .gradle/ build/ /*/build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log ================================================ FILE: .idea/.name ================================================ SublimeNavigationView ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/libraries/appcompat_v7_23_1_1.xml ================================================ ================================================ FILE: .idea/libraries/cardview_v7_23_1_1.xml ================================================ ================================================ FILE: .idea/libraries/design_23_1_1.xml ================================================ ================================================ FILE: .idea/libraries/recyclerview_v7_23_1_1.xml ================================================ ================================================ FILE: .idea/libraries/support_annotations_23_1_1.xml ================================================ ================================================ FILE: .idea/libraries/support_v4_23_1_1.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: .idea/workspace.xml ================================================ 1451450335571 ================================================ FILE: LICENSE ================================================ 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. ================================================ FILE: README.md ================================================ [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-SublimeNavigationView-orange.svg?style=flat-square)](http://android-arsenal.com/details/1/3043) # SublimeNavigationView ... is a complete rewrite of NavigationView (from Design Support library) that enables usage of `Checkboxes`, `Switches` & `Badges` in/as menu items. Menus are defined in good-old `XML`, and parsed using a custom `MenuInflater`. `SublimeNavigationView` works with `Parcelable` menus which means that state retention is built-in. Moreover, it can support multiple menus while preserving their respective states. Groups added to the menu have the added feature of being collapsible/expandable. Along with this, SublimeNavigationView allows a few options for custom styling. Gradle dependency ----------------- compile 'com.appeaser.sublimenavigationviewlibrary:sublimenavigationviewlibrary:0.0.1' Walkthrough ----------- Screenshots have been taken from the sample application available here: [Get it on Google Play][1]

How the menu looks:

`SublimeNavigationView` can work with any number of menus. As an example of this, the sample application shows how to handle two of them:

Switching between menus: First Menu | Second Menu :-------------------------:|:-------------------------: ![](https://github.com/vikramkakkar/SublimeNavigationView/blob/master/img/switch_menus_first_menu.png?raw=true) | ![](https://github.com/vikramkakkar/SublimeNavigationView/blob/master/img/switch_menus_second_menu.png?raw=true) `SublimeMenu` supports grouping of menu items. In addition to the standard features such as defining a `checkable` policy, ordering etc., a `SublimeGroup` can be expanded/collapsed - on user input, through XML definition, or programmatically: Collapsed | Expanded :-------------------------:|:-------------------------: ![](https://github.com/vikramkakkar/SublimeNavigationView/blob/master/img/group_collapsed.png?raw=true) | ![](https://github.com/vikramkakkar/SublimeNavigationView/blob/master/img/group_expanded.png?raw=true) XML definitions are quite straight-forward. An example: This would translate to:

A `Text` item with an icon - and another, that shows icon space, but doesn't display an icon: Output:

`TextWithBadge` menu items can be presented in two forms - initialized & uninitialized. In uninitialized form, the item will display an indeterminate `ProgressBar` in place of `badgeText`:

In initialized form, the item will have its `badgeText` set in XML:

This feature can be used if the `badgeText` is being retrieved through a network call, or if some computation needs to be performed before it can be displayed. Once the text is available, you can display it using: ((SublimeTextWithBadgeMenuItem)snv.getMenu().getMenuItem(R.id.text_with_badge_item_1)) .setBadgeText("25").setValueProvidedAsync(false); SublimeNavigationView also supports a few styling options. As an example, the sample application uses a custom typeface spicified in the view's xml definition: Styling info can also be set programmatically by providing a initialized `SublimeThemer`. There were some features that were left out on purpose. One of them was the option to define sub-menus. This feature is essential when used within the `ActionBar` design pattern, but its importance in a navigation view is lost on me. The `NavigationView` from design library adds separators automatically. SublimeNavigationView takes a different approach, and lets you decide where the separators should go. So, to create a divider, add a `Separator` menu item: Since there are no space concerns, `orderInCategory` & `menuCategory` have also been left out. License ------- Copyright (c) 2015 Vikram Kakkar 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]: https://play.google.com/store/apps/details?id=com.appeaser.sublimenavigationview ================================================ FILE: SublimeNavigationView.iml ================================================ ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/app.iml ================================================ ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "com.appeaser.sublimenavigationview" minSdkVersion 14 targetSdkVersion 23 versionCode 2 versionName "1.1" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile project(':sublimenavigationviewlibrary') compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:cardview-v7:23.1.1' compile 'com.android.support:design:23.1.1' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in C:\Users\Vikram\Documents\Android\adt-bundle-windows-x86-20130219\sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: app/src/androidTest/java/com/appeaser/sublimenavigationview/ApplicationTest.java ================================================ package com.appeaser.sublimenavigationview; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/appeaser/sublimenavigationview/Sampler.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationview; import android.content.ActivityNotFoundException; import android.content.Intent; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.Html; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.appeaser.sublimenavigationviewlibrary.OnNavigationMenuEventListener; import com.appeaser.sublimenavigationviewlibrary.SublimeBaseMenuItem; import com.appeaser.sublimenavigationviewlibrary.SublimeMenu; import com.appeaser.sublimenavigationviewlibrary.SublimeNavigationView; /** * Sample usage */ public class Sampler extends AppCompatActivity { public static final String TAG = Sampler.class.getSimpleName(); // Keys used when saving menu state to Bundle final String SS_KEY_MENU_1 = "ss.key.menu.1"; final String SS_KEY_MENU_2 = "ss.key.menu.2"; // For maintaining menu state in case of multiple menus SublimeMenu firstMenu, secondMenu; // Navigation menu SublimeNavigationView snv; TextView tvFirstMenuLabel, tvSecondMenuLabel; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.act_sampler); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayShowTitleEnabled(false); } if (savedInstanceState != null) { // retrieve saved instances of the two menus if (savedInstanceState.containsKey(SS_KEY_MENU_1)) { firstMenu = savedInstanceState.getParcelable(SS_KEY_MENU_1); } if (savedInstanceState.containsKey(SS_KEY_MENU_2)) { secondMenu = savedInstanceState.getParcelable(SS_KEY_MENU_2); } } snv = (SublimeNavigationView) findViewById(R.id.navigation_view); tvFirstMenuLabel = (TextView) snv.getHeaderView().findViewById(R.id.tvFirstMenu); tvSecondMenuLabel = (TextView) snv.getHeaderView().findViewById(R.id.tvSecondMenu); // set listener to get notified of menu events snv.setNavigationMenuEventListener(new OnNavigationMenuEventListener() { @Override public boolean onNavigationMenuEvent(OnNavigationMenuEventListener.Event event, SublimeBaseMenuItem menuItem) { switch (event) { case CHECKED: Log.i(TAG, "Item checked"); break; case UNCHECKED: Log.i(TAG, "Item unchecked"); break; case GROUP_EXPANDED: Log.i(TAG, "Group expanded"); break; case GROUP_COLLAPSED: Log.i(TAG, "Group collapsed"); break; default: //CLICK // Something like handleClick(menuItem); // Here, we toggle the 'checked' state Log.i(TAG, "Item clicked"); menuItem.setChecked(!menuItem.isChecked()); break; } return true; } }); // set up mechanism to switch between the 2 menus snv.getHeaderView().findViewById(R.id.tvFirstMenu) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SublimeMenu menu = snv.getMenu(); if (menu.getMenuResourceID() != R.menu.test_nav_menu_1) { secondMenu = menu; if (firstMenu == null) { snv.switchMenuTo(R.menu.test_nav_menu_1); } else { snv.switchMenuTo(firstMenu); } updateMenuLabel(); } } }); // set up mechanism to switch between the 2 menus snv.getHeaderView().findViewById(R.id.tvSecondMenu) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SublimeMenu menu = snv.getMenu(); if (menu.getMenuResourceID() != R.menu.test_nav_menu_2) { firstMenu = menu; if (secondMenu == null) { snv.switchMenuTo(R.menu.test_nav_menu_2); } else { snv.switchMenuTo(secondMenu); } updateMenuLabel(); } } }); initSamplerContent(); } private void updateMenuLabel() { if (snv.getMenu().getMenuResourceID() == R.menu.test_nav_menu_1) { tvFirstMenuLabel.setTypeface(null, Typeface.BOLD); tvSecondMenuLabel.setTypeface(null, Typeface.NORMAL); } else if (snv.getMenu().getMenuResourceID() == R.menu.test_nav_menu_2) { tvSecondMenuLabel.setTypeface(null, Typeface.BOLD); tvFirstMenuLabel.setTypeface(null, Typeface.NORMAL); } } /** * Information about the library */ private void initSamplerContent() { TextView tvSummary = (TextView) findViewById(R.id.tv_summary_heading); tvSummary.setText(Html.fromHtml("... is a complete rewrite of NavigationView from Design Support library. What it allows:")); TextView tvSummaryBullet1 = (TextView) findViewById(R.id.tv_summary_bullet_1); tvSummaryBullet1.setText(Html.fromHtml("Menu definition in XML")); TextView tvSummaryBullet2 = (TextView) findViewById(R.id.tv_summary_bullet_2); tvSummaryBullet2.setText(Html.fromHtml("Menu implements Parcelable for retention of menu state")); TextView tvSummaryBullet3 = (TextView) findViewById(R.id.tv_summary_bullet_3); tvSummaryBullet3.setText(Html.fromHtml("Choose from Text, Checkbox, Switch, Group, TextWithBadge menu item types")); TextView tvSummaryBullet4 = (TextView) findViewById(R.id.tv_summary_bullet_4); tvSummaryBullet4.setText(Html.fromHtml("Custom MenuInflater allows defining menu items such as <Checkbox ... /> directly in XML")); TextView tvSummaryBullet5 = (TextView) findViewById(R.id.tv_summary_bullet_5); tvSummaryBullet5.setText(Html.fromHtml("Switch between any number of Menus without losing state")); TextView tvSummaryBullet6 = (TextView) findViewById(R.id.tv_summary_bullet_6); tvSummaryBullet6.setText(Html.fromHtml("Collapsible/expandable Groups")); TextView tvSummaryBullet7 = (TextView) findViewById(R.id.tv_summary_bullet_7); tvSummaryBullet7.setText(Html.fromHtml("On-demand indentation (show icon space without assigning an icon)")); TextView tvSummaryBullet8 = (TextView) findViewById(R.id.tv_summary_bullet_8); tvSummaryBullet8.setText(Html.fromHtml("<TextWithBadge ... /> item displays a (flushed-right) badge that can be set asynchronously - a ProgressBar is displayed while the value is being retrieved")); TextView tvSummaryBullet9 = (TextView) findViewById(R.id.tv_summary_bullet_9); tvSummaryBullet9.setText(Html.fromHtml("Provide custom Typefaces and text-styles (normal, bold, italic & bold_italic) for item, hint, group-header etc.")); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_sampler, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_rate_this_app) { try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.appeaser.sublimenavigationview"))); } catch (ActivityNotFoundException anfe1) { try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://market.android.com/details?id=com.appeaser.sublimenavigationview"))); } catch (ActivityNotFoundException anfe2) { Toast.makeText(this, "You need a browser app to view this link", Toast.LENGTH_LONG).show(); } } return true; } else if (id == R.id.action_github_link) { String data = "https://github.com/vikramkakkar/SublimeNavigationView"; try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(data))); } catch (ActivityNotFoundException anfe) { Toast.makeText(this, "You need a browser app to view this link", Toast.LENGTH_LONG).show(); } return true; } return super.onOptionsItemSelected(item); } @Override protected void onResume() { super.onResume(); updateMenuLabel(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // save state for the two menus outState.putParcelable(SS_KEY_MENU_1, firstMenu); outState.putParcelable(SS_KEY_MENU_2, secondMenu); } } ================================================ FILE: app/src/main/res/color/text_explore_some_more.xml ================================================ ================================================ FILE: app/src/main/res/drawable/bg_explore_some_more.xml ================================================ ================================================ FILE: app/src/main/res/drawable/bitmap_header_bg.xml ================================================ ================================================ FILE: app/src/main/res/drawable/user_bg.xml ================================================ ================================================ FILE: app/src/main/res/drawable-v21/bg_explore_some_more.xml ================================================ ================================================ FILE: app/src/main/res/layout/act_sampler.xml ================================================ ================================================ FILE: app/src/main/res/layout/nav_header.xml ================================================ ================================================ FILE: app/src/main/res/layout/sampler_content.xml ================================================ ================================================ FILE: app/src/main/res/menu/menu_sampler.xml ================================================ ================================================ FILE: app/src/main/res/menu/test_nav_menu_1.xml ================================================ ================================================ FILE: app/src/main/res/menu/test_nav_menu_2.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ #FF5722 #E64A19 #CC7007 #F28509 #F9AD56 #FBCF9C #FABE7A #D24317 #F7992E #E07B08 #212121 ================================================ FILE: app/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: app/src/main/res/values/strings.xml ================================================ SublimeNavigationView Rate this app Github link Settings ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: app/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: app/src/test/java/com/appeaser/sublimenavigationview/ExampleUnitTest.java ================================================ package com.appeaser.sublimenavigationview; import org.junit.Test; import static org.junit.Assert.*; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.3.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Sun Nov 15 15:57:51 EST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # For Cygwin, ensure paths are in UNIX format before anything is touched. if $cygwin ; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` fi # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >&- APP_HOME="`pwd -P`" cd "$SAVED" >&- CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: settings.gradle ================================================ include ':app', ':sublimenavigationviewlibrary' ================================================ FILE: sublimenavigationviewlibrary/.gitignore ================================================ /build ================================================ FILE: sublimenavigationviewlibrary/build.gradle ================================================ buildscript { repositories { mavenCentral() } dependencies { classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' } } apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' ext { bintrayRepo = 'maven' bintrayName = 'sublime-nav-view' publishedGroupId = 'com.appeaser.sublimenavigationviewlibrary' libraryName = 'SublimeNavigationView' artifact = 'sublimenavigationviewlibrary' libraryDescription = 'SublimeNavigationView is a complete rewrite of NavigationView (from Design Support library) that enables usage of Checkboxes, Switches & Badges as menu items. Menus are defined in XML, and parsed using a custom MenuInflater. SublimeNavigationView works with Parcelable Menus which means that state retention is built-in. Moreover, it can support multiple menus while preserving their respective states. Groups added to the Menu have the added feature of being collapsible/expandable. Along with this, SublimeNavigationView allows a few options for custom styling.' siteUrl = 'https://github.com/vikramkakkar/SublimeNavigationView' gitUrl = 'https://github.com/vikramkakkar/SublimeNavigationView.git' libraryVersion = '0.0.1' developerId = 'vikramkakkar' developerName = 'Vikram Kakkar' developerEmail = 'vikram.kakkar.01@gmail.com' licenseName = 'The Apache Software License, Version 2.0' licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' allLicenses = ["Apache-2.0"] } android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "0.0.1" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:recyclerview-v7:23.1.1' compile 'com.android.support:support-annotations:23.1.1' } apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' ================================================ FILE: sublimenavigationviewlibrary/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in C:\Users\Vikram\Documents\Android\adt-bundle-windows-x86-20130219\sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: sublimenavigationviewlibrary/src/androidTest/java/com/appeaser/sublimenavigationviewlibrary/ApplicationTest.java ================================================ package com.appeaser.sublimenavigationviewlibrary; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: sublimenavigationviewlibrary/src/main/AndroidManifest.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/Config.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; public class Config { public static final boolean DEBUG = true; } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/OnNavigationMenuEventListener.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; /** * Listener for Menu events */ public interface OnNavigationMenuEventListener { // actions enum Event { CLICKED, CHECKED, UNCHECKED, GROUP_EXPANDED, GROUP_COLLAPSED } boolean onNavigationMenuEvent(Event event, SublimeBaseMenuItem menuItem); } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/ScrimInsetsFrameLayout.java ================================================ /* * Copyright (C) 2015 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v4.view.WindowInsetsCompat; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; /** * Borrowed from Support Design library */ public class ScrimInsetsFrameLayout extends FrameLayout { private Drawable mInsetForeground; private Rect mInsets; private Rect mTempRect; public ScrimInsetsFrameLayout(Context context) { this(context, null); } public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mTempRect = new Rect(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnvScrimInsetsFrameLayout, defStyleAttr, R.style.SnvScrimInsetsFrameLayout); this.mInsetForeground = a.getDrawable(R.styleable.SnvScrimInsetsFrameLayout_snvInsetForeground); a.recycle(); this.setWillNotDraw(true); ViewCompat.setOnApplyWindowInsetsListener(this, new android.support.v4.view.OnApplyWindowInsetsListener() { public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) { if (null == ScrimInsetsFrameLayout.this.mInsets) { ScrimInsetsFrameLayout.this.mInsets = new Rect(); } ScrimInsetsFrameLayout.this.mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); ScrimInsetsFrameLayout.this.setWillNotDraw(ScrimInsetsFrameLayout.this.mInsets.isEmpty() || ScrimInsetsFrameLayout.this.mInsetForeground == null); ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this); return insets.consumeSystemWindowInsets(); } }); } public void draw(@NonNull Canvas canvas) { super.draw(canvas); int width = this.getWidth(); int height = this.getHeight(); if (mInsets != null && mInsetForeground != null) { int sc = canvas.save(); canvas.translate(getScrollX(), getScrollY()); this.mTempRect.set(0, 0, width, this.mInsets.top); this.mInsetForeground.setBounds(this.mTempRect); this.mInsetForeground.draw(canvas); this.mTempRect.set(0, height - this.mInsets.bottom, width, height); this.mInsetForeground.setBounds(this.mTempRect); this.mInsetForeground.draw(canvas); this.mTempRect.set(0, this.mInsets.top, this.mInsets.left, height - this.mInsets.bottom); this.mInsetForeground.setBounds(this.mTempRect); this.mInsetForeground.draw(canvas); this.mTempRect.set(width - this.mInsets.right, this.mInsets.top, width, height - this.mInsets.bottom); this.mInsetForeground.setBounds(this.mTempRect); this.mInsetForeground.draw(canvas); canvas.restoreToCount(sc); } } protected void onAttachedToWindow() { super.onAttachedToWindow(); if (this.mInsetForeground != null) { this.mInsetForeground.setCallback(this); } } protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (this.mInsetForeground != null) { this.mInsetForeground.setCallback(null); } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/StateAwareImageView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.util.AttributeSet; import android.widget.ImageView; /** * Sub-classed ImageView that responds to custom states. */ public class StateAwareImageView extends ImageView { // Drawable state set - checked private static final int[] CHECKED_STATE_SET = { R.attr.state_item_checked }; // Keeps track of checked state private boolean mChecked; public StateAwareImageView(Context context) { this(context, null); } public StateAwareImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public StateAwareImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * Sets the checked state for this view. * * @param checked current state */ void setItemChecked(boolean checked) { if (checked != mChecked) { mChecked = checked; refreshDrawableState(); } } @Override public int[] onCreateDrawableState(int extraSpace) { int[] drawableState = super.onCreateDrawableState(extraSpace + 1); if (mChecked) { mergeDrawableStates(drawableState, CHECKED_STATE_SET); } return drawableState; } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/StateAwareTextView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.widget.TextView; /** * Sub-classed TextView that responds to custom states & style attrs. */ public class StateAwareTextView extends TextView { public static final String TAG = StateAwareTextView.class.getSimpleName(); // Drawable state set - checked private static final int[] CHECKED_STATE_SET = { R.attr.state_item_checked }; // Keeps track of checked state private boolean mChecked; public StateAwareTextView(Context context) { this(context, null); } public StateAwareTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public StateAwareTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StateAwareTextView, defStyleAttr, 0); try { // If user has provided a valid value // for @android:attr/textAppearance, it has already been // applied in our parent - TextView. Else, resolve & use the value // of @attr/saTextAppearance. if (a.hasValue(R.styleable.StateAwareTextView_android_textAppearance)) { int textAppearance = a.getResourceId( R.styleable.StateAwareTextView_android_textAppearance, -1); if (textAppearance == -1) { if (Config.DEBUG) { Log.i(TAG, "textAppearance is -1"); } int saTextAppearance = a.getResourceId( R.styleable.StateAwareTextView_saTextAppearance, R.style.SnvDefaultTextAppearance); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { setTextAppearance(saTextAppearance); } else { setTextAppearance(getContext(), saTextAppearance); } } else { if (Config.DEBUG) { Log.i(TAG, "textAppearance is AVAILABLE"); } } } } finally { a.recycle(); } } /** * Sets the checked state for this view. * * @param checked current state */ void setItemChecked(boolean checked) { if (checked != mChecked) { mChecked = checked; refreshDrawableState(); } } @Override protected int[] onCreateDrawableState(int extraSpace) { int[] drawableState = super.onCreateDrawableState(extraSpace + 1); if (mChecked) { mergeDrawableStates(drawableState, CHECKED_STATE_SET); } return drawableState; } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeBaseItemView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.v4.graphics.drawable.DrawableCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; /** * Base ViewGroup for all menu item types. */ public class SublimeBaseItemView extends LinearLayout { private static final String TAG = SublimeBaseItemView.class.getSimpleName(); // Drawable state set - checked private static final int[] CHECKED_STATE_SET = { R.attr.state_item_checked }; private static final boolean isJBorHigher = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; protected int mIconSize; protected StateAwareTextView mText, mHint; protected SublimeBaseMenuItem mItemData; protected StateAwareImageView mIconHolder; protected ColorStateList mIconTintList; public SublimeBaseItemView(Context context) { this(context, null); } public SublimeBaseItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SublimeBaseItemView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mIconSize = context.getResources().getDimensionPixelSize(R.dimen.snv_navigation_icon_size); } protected void initializeViews() { mText = (StateAwareTextView) findViewById(R.id.text); mHint = (StateAwareTextView) findViewById(R.id.hint); mIconHolder = (StateAwareImageView) findViewById(R.id.iconHolder); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public void initialize(SublimeBaseMenuItem itemData, SublimeThemer themer) { mItemData = itemData; setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); setEnabled(itemData.isEnabled()); // Item Title styling TextViewStyleProfile itemStyleProfile = themer.getItemStyleProfile(); setItemTextColor(itemStyleProfile.getTextColor()); if (itemStyleProfile.getTypeface() != null) { setItemTypeface(itemStyleProfile.getTypeface(), itemStyleProfile.getTypefaceStyle()); } else { setItemTypefaceStyle(itemStyleProfile.getTypefaceStyle()); } setTitle(itemData.getTitle()); setIconTintList(themer.getIconTintList()); setIcon(itemData.getIcon()); boolean showHint = !TextUtils.isEmpty(itemData.getHint()); mHint.setVisibility(showHint ? View.VISIBLE : View.GONE); if (showHint) { // Hint styling TextViewStyleProfile hintStyleProfile = themer.getItemHintStyleProfile(); setHintTextColor(hintStyleProfile.getTextColor()); if (hintStyleProfile.getTypeface() != null) { setHintTypeface(hintStyleProfile.getTypeface(), hintStyleProfile.getTypefaceStyle()); } else { setHintTypefaceStyle(hintStyleProfile.getTypefaceStyle()); } mHint.setText(itemData.getHint()); } setItemChecked(itemData.isChecked()); setItemBackground(themer.getItemBackground()); } public SublimeBaseMenuItem getItemData() { return mItemData; } public void setTitle(CharSequence title) { mText.setText(title); } private Drawable prepareIcon(Drawable icon) { icon = DrawableCompat.wrap(icon.getConstantState().newDrawable()).mutate(); icon.setBounds(0, 0, mIconSize, mIconSize); DrawableCompat.setTintList(icon, mIconTintList); return icon; } public void setIcon(Drawable icon) { if (icon != null) { mIconHolder.setVisibility(View.VISIBLE); mIconHolder.setImageDrawable(prepareIcon(icon)); } else { mIconHolder.setVisibility(View.GONE); } } public void setIconTintList(ColorStateList tintList) { mIconTintList = tintList; } public void setItemTextColor(ColorStateList textColor) { mText.setTextColor(textColor); } public void setHintTextColor(ColorStateList hintTextColor) { mHint.setTextColor(hintTextColor); } public void setItemTypeface(Typeface itemTypeface, int itemTypefaceStyle) { mText.setTypeface(itemTypeface, itemTypefaceStyle); } public void setItemTypefaceStyle(int itemTypefaceStyle) { mText.setTypeface(mText.getTypeface(), itemTypefaceStyle); } public void setHintTypeface(Typeface hintTypeface, int hintTypefaceStyle) { mHint.setTypeface(hintTypeface, hintTypefaceStyle); } public void setHintTypefaceStyle(int hintTypefaceStyle) { mHint.setTypeface(mHint.getTypeface(), hintTypefaceStyle); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); mText.setEnabled(enabled); mHint.setEnabled(enabled); mIconHolder.setEnabled(enabled); } public void setItemChecked(boolean checked) { mText.setItemChecked(mItemData.isChecked()); mHint.setItemChecked(mItemData.isChecked()); mIconHolder.setItemChecked(mItemData.isChecked()); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public void setItemBackground(Drawable itemBackground) { if (isJBorHigher) { setBackground(itemBackground); } else { setBackgroundDrawable(itemBackground); } refreshDrawableState(); } @Override protected int[] onCreateDrawableState(int extraSpace) { int[] drawableState = super.onCreateDrawableState(extraSpace + 1); if (mItemData != null && mItemData.isCheckable() && mItemData.isChecked()) { mergeDrawableStates(drawableState, CHECKED_STATE_SET); } return drawableState; } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeBaseMenuItem.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.ActivityNotFoundException; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; /** * Base menu item implementation for all menu item types. */ public abstract class SublimeBaseMenuItem implements Parcelable { private static final String TAG = SublimeBaseMenuItem.class.getSimpleName(); private static final Drawable TRANSPARENT_ICON = new ColorDrawable(Color.TRANSPARENT); public enum ItemType {SEPARATOR, TEXT, CHECKBOX, SWITCH, BADGE, GROUP_HEADER, HEADER} private final int mId; private final int mGroup; private CharSequence mTitle, mHint; private Intent mIntent; private boolean mShowsIconSpace; /** * The icon's drawable which is only created as needed */ private Drawable mIconDrawable; /** * The icon's resource ID which is used to get the Drawable when it is * needed (if the Drawable isn't already obtained--only one of the two is * needed). */ private int mIconResId = NO_ICON; /** * The menu to which this item belongs */ private SublimeMenu mMenu; private int mFlags = ENABLED | CHECKABLE; private static final int CHECKABLE = 0x00000001; private static final int CHECKED = 0x00000002; private static final int HIDDEN = 0x00000004; static final int ENABLED = 0x00000008; /** * Used for the icon resource ID if this item does not have an icon */ static final int NO_ICON = 0; private ItemType mItemType; private boolean mValueProvidedAsync, mInvalidateEntireMenu; private boolean mBlockUpdates; /** * Instantiates this menu item. * * @param menu parent menu. * @param group ID of the Group that this item belongs to. * @param id Unique item ID. * @param title The text to display for the item. * @param hint The text to display as hint for this item. * @param itemType One of {@link SublimeBaseMenuItem.ItemType} enums. * @param valueProvidedAsync Indicates that the badge text will be provided later on. * Used with TextWithBadge ItemType. * @param showsIconSpace Whether to indent the item or not. */ SublimeBaseMenuItem(SublimeMenu menu, int group, int id, CharSequence title, CharSequence hint, ItemType itemType, boolean valueProvidedAsync, boolean showsIconSpace) { mMenu = menu; mId = id; mGroup = group; mTitle = title; mHint = hint; if (itemType == null) itemType = ItemType.TEXT; mItemType = itemType; mValueProvidedAsync = valueProvidedAsync; mShowsIconSpace = showsIconSpace; } /** * Restores this menu item. {@link SublimeBaseMenuItem#setParentMenu(SublimeMenu)} must * be called after this. * * @param group ID of the Group that this item belongs to. * @param id Unique item ID. * @param title The text to display for the item. * @param hint The text to display as hint for this item. * @param iconResId Restored iconResId. * @param itemType One of {@link SublimeBaseMenuItem.ItemType} enums. * @param valueProvidedAsync Indicates that the badge text will be provided later on. * Used with TextWithBadge ItemType. * @param showsIconSpace Whether to indent the item or not. * @param flags Restored flags. */ SublimeBaseMenuItem(int group, int id, CharSequence title, CharSequence hint, int iconResId, ItemType itemType, boolean valueProvidedAsync, boolean showsIconSpace, int flags) { mId = id; mGroup = group; mTitle = title; mHint = hint; mIconResId = iconResId; if (itemType == null) { itemType = ItemType.TEXT; } mItemType = itemType; mValueProvidedAsync = valueProvidedAsync; mShowsIconSpace = showsIconSpace; mFlags = flags; } protected void setParentMenu(SublimeMenu menu) { mMenu = menu; } public abstract boolean invoke(); /** * Invokes the item by calling various listeners or callbacks. * * @return true if the invocation was handled, false otherwise */ protected boolean invoke(OnNavigationMenuEventListener.Event event, SublimeBaseMenuItem item) { if (mMenu.dispatchMenuItemSelected(item, event)) { return true; } if (mIntent != null) { try { if (Config.DEBUG) { Log.d(TAG, "Context requested from parent menu"); } mMenu.getContext().startActivity(mIntent); return true; } catch (ActivityNotFoundException e) { Log.e(TAG, "Can't find activity to handle intent; ignoring", e); } } return false; } protected void attemptItemUpdate() { if (mBlockUpdates) { return; } if (mInvalidateEntireMenu) { // invalidate entire menu mInvalidateEntireMenu = false; mMenu.onItemsChanged(); } else { mMenu.onItemChanged(getItemId()); } } /** * Indicate that the badge text will be provided later on. Works only with * {@link SublimeBaseMenuItem.ItemType#BADGE}. * * @param valueProvidedAsync 'true' if badge text will be set later on. * @return This {@link SublimeBaseMenuItem} for chaining. */ public SublimeBaseMenuItem setValueProvidedAsync(boolean valueProvidedAsync) { mValueProvidedAsync = valueProvidedAsync; attemptItemUpdate(); return this; } /** * Whether the badge text is ready to be displayed or not. * * @return 'true' if the badge text will be set later on, 'false' otherwise. */ public boolean providesValueAsync() { return mValueProvidedAsync; } public boolean isEnabled() { return (mFlags & ENABLED) != 0; } public SublimeBaseMenuItem setEnabled(boolean enabled) { if (enabled) { mFlags |= ENABLED; } else { mFlags &= ~ENABLED; } attemptItemUpdate(); return this; } public int getGroupId() { return mGroup; } public int getItemId() { return mId; } public ItemType getItemType() { return mItemType; } public Intent getIntent() { return mIntent; } public SublimeBaseMenuItem setIntent(Intent intent) { mIntent = intent; return this; } public CharSequence getTitle() { return mTitle; } public SublimeBaseMenuItem setTitle(CharSequence title) { mTitle = title; attemptItemUpdate(); return this; } public SublimeBaseMenuItem setTitle(int title) { if (Config.DEBUG) { Log.d(TAG, "Context requested from parent menu"); } return setTitle(mMenu.getContext().getString(title)); } public CharSequence getHint() { return mHint; } public SublimeBaseMenuItem setHint(CharSequence hint) { mHint = hint; attemptItemUpdate(); return this; } public Drawable getIcon() { if (mIconDrawable != null) { return mIconDrawable; } if (mIconResId != NO_ICON) { if (Config.DEBUG) { Log.d(TAG, "Context requested from parent menu"); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mIconDrawable = mMenu.getContext().getResources().getDrawable(mIconResId, mMenu.getContext().getTheme()); } else { mIconDrawable = mMenu.getContext().getResources().getDrawable(mIconResId); } return mIconDrawable; } if (mShowsIconSpace) { return TRANSPARENT_ICON; } return null; } public boolean showsIconSpace() { return mShowsIconSpace; } public SublimeBaseMenuItem setShowsIconSpace(boolean showsIconSpace) { mShowsIconSpace = showsIconSpace; attemptItemUpdate(); return this; } public SublimeBaseMenuItem blockUpdates() { mBlockUpdates = true; return this; } public SublimeBaseMenuItem allowUpdates() { mBlockUpdates = false; return this; } public SublimeBaseMenuItem setIcon(int iconResId) { mIconDrawable = null; mIconResId = iconResId; // If we have a view, we need to push the Drawable to them mInvalidateEntireMenu = true; attemptItemUpdate(); return this; } public boolean isCheckable() { return (mFlags & CHECKABLE) == CHECKABLE; } public SublimeBaseMenuItem setCheckable(boolean checkable) { final int oldFlags = mFlags; mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0); if (oldFlags != mFlags) { attemptItemUpdate(); } return this; } public boolean isChecked() { return (mFlags & CHECKED) == CHECKED; } public SublimeBaseMenuItem setChecked(boolean checked) { if (!isCheckable()) { return this; } if (!checked) { setCheckedInt(false); } else { mMenu.setItemChecked(this); setCheckedInt(true); } return this; } public SublimeBaseMenuItem setCheckedInt(boolean checkedInt) { final int oldFlags = mFlags; mFlags = (mFlags & ~CHECKED) | (checkedInt ? CHECKED : 0); if (oldFlags != mFlags) { attemptItemUpdate(); } return this; } public boolean isVisible() { return (mFlags & HIDDEN) == 0; } /** * Changes the visibility of the item. This method DOES NOT notify the parent menu of a change * in this item, so this should only be called from methods that will eventually trigger this * change. If unsure, use {@link #setVisible(boolean)} instead. * * @param shown Whether to show (true) or hide (false). * @return Whether the item's shown state was changed */ boolean setVisibleInt(boolean shown) { final int oldFlags = mFlags; mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN); return oldFlags != mFlags; } public SublimeBaseMenuItem setVisible(boolean shown) { // Try to set the shown state to the given state. If the shown state was changed // (i.e. the previous state isn't the same as given state), notify the parent menu that // the shown state has changed for this item if (setVisibleInt(shown)) { //mInvalidateEntireMenu = true; attemptItemUpdate(); } return this; } public String toString() { return mTitle.toString(); } public SublimeMenu getMenu() { return mMenu; } protected int getFlags() { return mFlags; } @Override public boolean equals(Object o) { if (!(o instanceof SublimeBaseMenuItem)) { return false; } SublimeBaseMenuItem other = (SublimeBaseMenuItem) o; if (getItemId() != other.getItemId() || getItemType() != other.getItemType() || getFlags() != other.getFlags() || providesValueAsync() != other.providesValueAsync()) { return false; } if (getTitle() == null) { if (other.getTitle() != null) { return false; } } else if (!getTitle().equals(other.getTitle())) { return false; } if (getHint() == null) { if (other.getHint() != null) { return false; } } else if (!getHint().equals(other.getHint())) { return false; } if (getIntent() == null) { if (other.getIntent() != null) { return false; } } else if (!getIntent().equals(other.getIntent())) { return false; } if (getIcon() == null) { if (other.getIcon() != null) { return false; } } else if (!getIcon().equals(other.getIcon())) { return false; } return super.equals(o); } //----------------------------------------------------------------// //---------------------------Parcelable---------------------------// //----------------------------------------------------------------// @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeBundle(saveState()); } private static final String SS_ID = "ss.id"; private static final String SS_GROUP_ID = "ss.group.id"; private static final String SS_TITLE = "ss.title"; private static final String SS_HINT = "ss.hint"; private static final String SS_INTENT = "ss.intent"; private static final String SS_SHOWS_ICON_SPACE = "ss.shows.icon.space"; private static final String SS_ITEM_TYPE = "ss.item.type"; private static final String SS_VALUE_PROVIDED_ASYNC = "ss.value.provided.async"; private static final String SS_ICON_RES_ID = "ss.icon.res.id"; private static final String SS_FLAGS = "ss.flags"; private Bundle saveState() { Bundle bundle = new Bundle(); bundle.putInt(SS_ID, mId); bundle.putInt(SS_GROUP_ID, mGroup); bundle.putCharSequence(SS_TITLE, mTitle); bundle.putCharSequence(SS_HINT, mHint); bundle.putParcelable(SS_INTENT, mIntent); bundle.putBoolean(SS_SHOWS_ICON_SPACE, mShowsIconSpace); bundle.putString(SS_ITEM_TYPE, mItemType.name()); bundle.putBoolean(SS_VALUE_PROVIDED_ASYNC, mValueProvidedAsync); bundle.putInt(SS_ICON_RES_ID, mIconResId); bundle.putInt(SS_FLAGS, mFlags); return bundle; } public static final Creator CREATOR = new Creator() { public SublimeBaseMenuItem createFromParcel(Parcel in) { return createItemFromParcel(in); } public SublimeBaseMenuItem[] newArray(int size) { return new SublimeBaseMenuItem[size]; } }; private static SublimeBaseMenuItem createItemFromParcel(Parcel in) { Bundle bundle = in.readBundle(); ItemType itemType = ItemType.valueOf(bundle.getString(SS_ITEM_TYPE)); int groupId = bundle.getInt(SS_GROUP_ID); int id = bundle.getInt(SS_ID); String title = bundle.getString(SS_TITLE); String hint = bundle.getString(SS_HINT); int iconResId = bundle.getInt(SS_ICON_RES_ID); boolean valueProvidedAsync = bundle.getBoolean(SS_VALUE_PROVIDED_ASYNC); boolean showsIconSpace = bundle.getBoolean(SS_SHOWS_ICON_SPACE); int flags = bundle.getInt(SS_FLAGS); switch (itemType) { case SWITCH: return new SublimeSwitchMenuItem(groupId, id, title, hint, iconResId, valueProvidedAsync, showsIconSpace, flags); case CHECKBOX: return new SublimeCheckboxMenuItem(groupId, id, title, hint, iconResId, valueProvidedAsync, showsIconSpace, flags); case BADGE: return SublimeTextWithBadgeMenuItem.createFromBundle(bundle, groupId, id, title, hint, iconResId, valueProvidedAsync, showsIconSpace, flags); case SEPARATOR: return new SublimeSeparatorMenuItem(groupId, id); case HEADER: return SublimeMenu.HEADER_STUB; case GROUP_HEADER: return new SublimeGroupHeaderMenuItem(groupId, id, title, hint, iconResId, valueProvidedAsync, showsIconSpace, flags); default: /* TEXT */ // TODO: This may be a problem. Will the default always be TEXT? return new SublimeTextMenuItem(groupId, id, title, hint, iconResId, valueProvidedAsync, showsIconSpace, flags); } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeCheckboxItemView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.animation.StateListAnimator; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.support.v4.graphics.drawable.DrawableCompat; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.widget.CheckBox; /** * View implementation for Checkbox menu item. */ public class SublimeCheckboxItemView extends SublimeBaseItemView { private CheckBox mCheckbox; public SublimeCheckboxItemView(Context context) { this(context, null); } public SublimeCheckboxItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SublimeCheckboxItemView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LayoutInflater.from(context).inflate(R.layout.sublime_menu_checkbox_item_content, this, true); initializeViews(); } @Override protected void initializeViews() { super.initializeViews(); mCheckbox = (CheckBox) findViewById(R.id.checkbox_ctrl); } @Override public void initialize(SublimeBaseMenuItem itemData, SublimeThemer themer) { setCheckableItemTintList(themer.getCheckableItemTintList()); super.initialize(itemData, themer); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); mCheckbox.setEnabled(enabled); } @Override public void setItemChecked(boolean checked) { super.setItemChecked(checked); mCheckbox.setChecked(checked); } public void setCheckableItemTintList(ColorStateList checkableItemTintList) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mCheckbox.setButtonTintList(checkableItemTintList); } else { Drawable dCheckbox = getResources().getDrawable(R.drawable.checkbox_pre_lollipop); if (dCheckbox != null) { dCheckbox = DrawableCompat.wrap(dCheckbox); DrawableCompat.setTintList(dCheckbox, checkableItemTintList); mCheckbox.setButtonDrawable(dCheckbox); } } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeCheckboxMenuItem.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; /** * Checkbox menu item implementation. * * Created by Vikram. */ public class SublimeCheckboxMenuItem extends SublimeBaseMenuItem { public SublimeCheckboxMenuItem(SublimeMenu menu, int group, int id, CharSequence title, CharSequence hint, boolean valueProvidedAsync, boolean showsIconSpace) { super(menu, group, id, title, hint, ItemType.CHECKBOX, valueProvidedAsync, showsIconSpace); } // Restores state public SublimeCheckboxMenuItem(int group, int id, CharSequence title, CharSequence hint, int iconResId, boolean valueProvidedAsync, boolean showsIconSpace, int flags) { super(group, id, title, hint, iconResId, ItemType.CHECKBOX, valueProvidedAsync, showsIconSpace, flags); } @Override public boolean invoke() { if (isCheckable()) { setChecked(!isChecked()); return invoke(isChecked() ? OnNavigationMenuEventListener.Event.CHECKED : OnNavigationMenuEventListener.Event.UNCHECKED, this); } else { return invoke(OnNavigationMenuEventListener.Event.CLICKED, this); } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeGroup.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.os.Parcel; import android.os.Parcelable; /** * Used for grouping menu items together. * * Created by Vikram. */ public class SublimeGroup implements Parcelable { public enum CheckableBehavior {NONE, SINGLE, ALL} private SublimeMenu mMenu; private int mGroupId; private boolean mIsCollapsible, mStateCollapsed, mEnabled, mVisible; private CheckableBehavior mCheckableBehavior; public SublimeGroup(SublimeMenu menu, int groupId, boolean isCollapsible, boolean stateCollapsed, boolean enabled, boolean visible, CheckableBehavior checkableBehavior) { mMenu = menu; mGroupId = groupId; mIsCollapsible = isCollapsible; mStateCollapsed = stateCollapsed; mEnabled = enabled; mVisible = visible; mCheckableBehavior = checkableBehavior; } /** * Set this {@link SublimeGroup}'s Parent. * * @param menu parent menu */ protected void setParentMenu(SublimeMenu menu) { mMenu = menu; } /** * Indicates whether this {@link SublimeGroup} is currently enabled/disabled. * * @return 'true' if this {@link SublimeGroup} is enabled, 'false' otherwise. */ public boolean isEnabled() { return mEnabled; } /** * Sets the enabled/disabled state for this {@link SublimeGroup}. * * @param enabled 'true' if this {@link SublimeGroup} is now 'enabled', * 'false' if it isn't. * @return this {@link SublimeGroup} for chaining */ public SublimeGroup setEnabled(boolean enabled) { mEnabled = enabled; mMenu.onGroupEnabledOrDisabled(getGroupId(), enabled); return this; } /** * Returns the id assigned to this {@link SublimeGroup}. * * @return group's id */ public int getGroupId() { return mGroupId; } /** * Sets/changes the 'collapsible' behavior of this Group. * If 'collapsible' is true, the group remains in its current * state, whether 'collapsed' or 'expanded' - a chevron appears * on the 'SublimeSubheaderItemView'. * If 'collapsible' is set to false: * - if group is 'collapsed', the group is expanded * and the chevron disappears * - if group is 'expanded', the chevron disappears * * @param collapsible 'true' to indicate that the group is * now 'collapsible', 'false' otherwise * @return this {@link SublimeGroup} for chaining */ public SublimeGroup setIsCollapsible(boolean collapsible) { mIsCollapsible = collapsible; mMenu.onGroupCollapsibleStatusChanged(this); return this; } /** * Indicates if this {@link SublimeGroup} is collapsible. * Note that a {@link SublimeGroup} is _not_ collapsible if it does not * conatin any visible items. * * @return 'true' is this {@link SublimeGroup} is collapsible, * 'false' otherwise */ public boolean isCollapsible() { return mIsCollapsible && mMenu.groupHasVisibleItems(getGroupId()); } /** * Sets the collapsed/expanded status of this {@link SublimeGroup}. * * @param collapsed 'true' to collapse this {@link SublimeGroup}, 'false' * to expand it. * @return this {@link SublimeGroup} for chaining */ public SublimeGroup setStateCollapsed(boolean collapsed) { mStateCollapsed = collapsed; mMenu.onGroupExpandedOrCollapsed(getGroupId(), collapsed); return this; } /** * Indicates whether this {@link SublimeGroup} is currently collapsed. * * @return 'true' if this {@link SublimeGroup} is collapsed, * 'false' if it is expanded */ public boolean isCollapsed() { return mStateCollapsed; } /** * Set the checkable behavior for this Group. Following changes * will happen: * - NONE: 'setCheckable(false)' will be called on all group members. * Group members that are currently checked will NOT lose their * 'checked' state. * - ALL: 'setCheckable(true)' will be called on all group members. * Group members that are NOT currently checked will remain in * their 'unchecked' state. * - SINGLE: 'setChecked(false)' will be called on all group members * except the first 'checked' item (if one is found). * * @param checkableBehavior The {@link SublimeGroup.CheckableBehavior} to set. * @return this {@link SublimeGroup} for chaining */ public SublimeGroup setCheckableBehavior(CheckableBehavior checkableBehavior) { mCheckableBehavior = checkableBehavior; mMenu.onGroupCheckableBehaviorChanged(getGroupId(), checkableBehavior); return this; } /** * Returns the {@link SublimeGroup.CheckableBehavior} set for this group. * * @return currently set {@link SublimeGroup.CheckableBehavior} */ public CheckableBehavior getCheckableBehavior() { return mCheckableBehavior; } /** * Returns the visibility status of this group. * * @return 'true' if the group is visible, 'false' if invisible. */ public boolean isVisible() { return mVisible; } /** * Set the visibility status of this group. * * @param visible 'true' if the group should now be visible, false otherwise * @return this {@link SublimeGroup} for chaining */ public SublimeGroup setVisible(boolean visible) { mVisible = visible; mMenu.onGroupVisibilityChanged(getGroupId(), visible); return this; } //----------------------------------------------------------------// //---------------------------Parcelable---------------------------// //----------------------------------------------------------------// @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mGroupId); dest.writeByte((byte) (mIsCollapsible ? 1 : 0)); dest.writeByte((byte) (mStateCollapsed ? 1 : 0)); dest.writeByte((byte) (mEnabled ? 1 : 0)); dest.writeByte((byte) (mVisible ? 1 : 0)); dest.writeString(mCheckableBehavior.name()); } protected SublimeGroup(Parcel in) { mGroupId = in.readInt(); mIsCollapsible = in.readByte() != 0; mStateCollapsed = in.readByte() != 0; mEnabled = in.readByte() != 0; mVisible = in.readByte() != 0; mCheckableBehavior = CheckableBehavior.valueOf(in.readString()); } public static final Creator CREATOR = new Creator() { public SublimeGroup createFromParcel(Parcel in) { return new SublimeGroup(in); } public SublimeGroup[] newArray(int size) { return new SublimeGroup[size]; } }; } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeGroupHeaderMenuItem.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; /** * Group header menu item implementation. * * Created by Vikram. */ public class SublimeGroupHeaderMenuItem extends SublimeBaseMenuItem { public SublimeGroupHeaderMenuItem(SublimeMenu menu, int group, int id, CharSequence title, CharSequence hint, boolean valueProvidedAsync, boolean showsIconSpace) { super(menu, group, id, title, hint, ItemType.GROUP_HEADER, valueProvidedAsync, showsIconSpace); } public SublimeGroupHeaderMenuItem(int group, int id, CharSequence title, CharSequence hint, int iconResId, boolean valueProvidedAsync, boolean showsIconSpace, int flags) { super(group, id, title, hint, iconResId, ItemType.GROUP_HEADER, valueProvidedAsync, showsIconSpace, flags); } @Override public boolean invoke() { SublimeGroup group = getMenu().getGroup(getGroupId()); if (group == null) return false; if (group.isCollapsible()) { group.setStateCollapsed(!group.isCollapsed()); } return invoke(group.isCollapsed() ? OnNavigationMenuEventListener.Event.GROUP_COLLAPSED : OnNavigationMenuEventListener.Event.GROUP_EXPANDED, this); } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeMenu.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.Nullable; import android.util.Log; import android.util.SparseArray; import java.util.ArrayList; import java.util.List; /** * Menu implementation. * * Created by Vikram. */ public class SublimeMenu implements Parcelable { private static final String TAG = SublimeMenu.class.getSimpleName(); public static final int NO_GROUP_ID = -1; public static final int NO_ITEM_ID = -1; protected static final SublimeBaseMenuItem HEADER_STUB = new SublimeBaseMenuItem(null, NO_GROUP_ID, NO_ITEM_ID, "", "", SublimeBaseMenuItem.ItemType.HEADER, false, false) { @Override public boolean invoke() { return false; } }; private int mMenuResourceID = -1; private Context mContext; /** * Callback that will receive the various menu-related events generated by this class. Use * getCallback to get a reference to the callback. */ private Callback mCallback; /** * Contains all of the items for this menu */ private ArrayList mItems = new ArrayList<>(); /** * Contains all of the groups for this menu */ private ArrayList mGroups = new ArrayList<>(); /** * Contains only the items that are currently visible. This will be created/refreshed from * {@link #getVisibleItems()} */ private ArrayList mVisibleItems = new ArrayList<>(); // We only require _one_ presenter private SublimeMenuPresenter mPresenter; private boolean mBlockUpdates; private ArrayList mAdapterData = new ArrayList<>(); /** * Called by menu to notify of close and selection changes. */ public interface Callback { /** * Called when a menu item is selected. * * @param menu The menu that is the parent of the item * @param item The menu item that is selected * @param event EventType associated with this selection * @return whether the menu item selection was handled */ boolean onMenuItemSelected(SublimeMenu menu, SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event); } public SublimeMenu(int menuResourceID) { mMenuResourceID = menuResourceID; } public int getMenuResourceID() { return mMenuResourceID; } /** * Set a presenter to this menu. * * @param presenter The presenter to set */ public void setMenuPresenter(Context context, @Nullable SublimeMenuPresenter presenter) { mContext = context; mPresenter = presenter; if (presenter != null) { if (Config.DEBUG) { Log.d(TAG, "Presenter set on menu"); } presenter.initForMenu(mContext, this); presenter.invalidateEntireMenu(); } } public void setCallback(Callback cb) { mCallback = cb; } public SublimeGroup addGroup(boolean isCollapsible, boolean collapsed, boolean enabled, boolean visible, SublimeGroup.CheckableBehavior checkableBehavior) { return addGroup(generateUniqueGroupID(), isCollapsible, collapsed, enabled, visible, checkableBehavior); } protected SublimeGroup addGroup(int groupId, boolean isCollapsible, boolean collapsed, boolean enabled, boolean visible, SublimeGroup.CheckableBehavior checkableBehavior) { SublimeGroup group = new SublimeGroup(this, groupId, isCollapsible, collapsed, enabled, visible, checkableBehavior); mGroups.add(group); return group; } private int generateUniqueGroupID() { int groupId = 1; if (mGroups.size() > 0) { while (getGroup(groupId) != null) { groupId++; } } return groupId; } private int generateUniqueItemID() { int menuItemId = 1; if (mItems.size() > 0) { while (getMenuItem(menuItemId) != null) { menuItemId++; } } return menuItemId; } /** * Adds an item to the menu. The other add methods funnel to this. */ private SublimeBaseMenuItem addInternal(int group, int id, CharSequence title, CharSequence hint, SublimeBaseMenuItem.ItemType itemType, boolean valueProvidedAsync, CharSequence badgeText, boolean showsIconSpace, boolean addedByUser) { SublimeBaseMenuItem item; boolean isGroupHeader = false; switch (itemType) { case SEPARATOR: item = new SublimeSeparatorMenuItem(this, group, id); break; case BADGE: item = new SublimeTextWithBadgeMenuItem(this, group, id, title, hint, valueProvidedAsync, badgeText, showsIconSpace); break; case SWITCH: item = new SublimeSwitchMenuItem(this, group, id, title, hint, valueProvidedAsync, showsIconSpace); break; case CHECKBOX: item = new SublimeCheckboxMenuItem(this, group, id, title, hint, valueProvidedAsync, showsIconSpace); break; case GROUP_HEADER: isGroupHeader = true; item = new SublimeGroupHeaderMenuItem(this, group, id, title, hint, valueProvidedAsync, showsIconSpace); break; default: // TEXT item = new SublimeTextMenuItem(this, group, id, title, hint, valueProvidedAsync, showsIconSpace); break; } // if itemType is 'GroupHeader', 'group' != NO_GROUP_ID // since we check for this in 'addGroupHeaderItem(...)' checkExistenceOfGroup(group); if (isGroupHeader) { checkIfGroupHeaderAlreadyExistsForGroup(group); int index = findGroupIndex(group); if (index >= 0) { mItems.add(index, item); } else { mItems.add(item); } } else if (addedByUser) { if (group != NO_GROUP_ID) { int lastGroupIndex = findLastGroupIndex(group); mItems.add(lastGroupIndex == mItems.size() ? lastGroupIndex : lastGroupIndex + 1, item); } else { mItems.add(item); } } else { mItems.add(item); } onItemsChanged(); return item; } public enum Positioned {BEFORE, AFTER} /** * Adds an item to the menu and positions it using the given `pivot`. * * @param pivotID Item ID that will be used for positioning the added item * @param positioned before or after * @param newItem added item * @return item that was just added */ private SublimeBaseMenuItem addInternal(int pivotID, Positioned positioned, SublimeBaseMenuItem newItem) { int newItemGroupId = newItem.getGroupId(); // if itemType is 'GroupHeader', 'group' != NO_GROUP_ID // since we check for this in 'addGroupHeaderItem(...)' checkExistenceOfGroup(newItemGroupId); int pivotIndex = findItemIndex(pivotID); SublimeBaseMenuItem pivot = mItems.get(pivotIndex); // GROUP_HEADER is a special item. In addition to the positional // requirements given above, it should be placed at the beginning // of the Group. if (newItem.getItemType() == SublimeBaseMenuItem.ItemType.GROUP_HEADER) { checkIfGroupHeaderAlreadyExistsForGroup(newItemGroupId); int index = findGroupIndex(newItemGroupId); if (index >= 0) { // we found a valid index for the group mItems.add(index, newItem); } else { // group exists, but does not contain any items at the moment // try to use the `pivot` to place the first group item if (positioned == Positioned.BEFORE) { if (pivot.getGroupId() == NO_GROUP_ID || pivot.getItemType() == SublimeBaseMenuItem.ItemType.GROUP_HEADER) { // `pivot` is not part of a Group // - or - // `pivot` is a GROUP_HEADER mItems.add(pivotIndex, newItem); } else { // we could not position the item as per the // requirements - add the item at the very end mItems.add(newItem); } } else if (positioned == Positioned.AFTER) { if (pivot.getGroupId() == NO_GROUP_ID || pivotIndex == findLastGroupIndex(newItemGroupId)) { // `pivot` is not part of a Group // - or - // `pivot` is the very last item in a Group mItems.add(pivotIndex + 1, newItem); } else { // we could not position the item as per the // requirements - add the item at the very end mItems.add(newItem); } } } } else { // item is not a GROUP_HEADER if (newItemGroupId != NO_GROUP_ID) { // item is part of a Group int lastGroupIndex = findLastGroupIndex(newItemGroupId); if (lastGroupIndex == mItems.size()) { // no Group items found // try to use the `pivot` to position the new item if (positioned == Positioned.BEFORE) { if (pivot.getGroupId() == NO_GROUP_ID || pivotIndex == findGroupIndex(pivot.getGroupId())) { // `pivot` is not part of a Group // - or - // `pivot` is the very first item in a Group mItems.add(pivotIndex, newItem); } else { // we could not position the item as per the // requirements - add the item at the very end //mItems.add(lastGroupIndex + 1, item); mItems.add(newItem); } } else if (positioned == Positioned.AFTER) { if (pivot.getGroupId() == NO_GROUP_ID || pivotIndex == findLastGroupIndex(pivot.getGroupId())) { // `pivot` is not part of a Group // - or - // `pivot` is the very last item in a Group mItems.add(pivotIndex + 1, newItem); } else { // we could not position the item as per the // requirements - add the item at the very end //mItems.add(lastGroupIndex + 1, item); mItems.add(newItem); } } } else { if (newItemGroupId == pivot.getGroupId()) { if (positioned == Positioned.BEFORE) { if (pivot.getItemType() != SublimeBaseMenuItem.ItemType.GROUP_HEADER) { // `pivot` is not GroupHeader - add before mItems.add(pivotIndex, newItem); } else { // cannot add an item before the GroupHeader. // add item at the end of the Group mItems.add(lastGroupIndex + 1, newItem); } } else if (positioned == Positioned.AFTER) { // all positions after the `pivot` are valid mItems.add(pivotIndex + 1, newItem); } } else { // `newItem` & `pivot` belong to different Groups. // The only valid position for `newItem` in this case // is at the end of `newItem's` Group. mItems.add(lastGroupIndex + 1, newItem); } } } else { // `newItem` is independent - no Group membership if (positioned == Positioned.BEFORE) { if (pivot.getGroupId() == NO_GROUP_ID || findGroupIndex(pivot.getGroupId()) == pivotIndex) { // `pivot` is not part of a Group // - or - // `pivot` is the very first item in a Group mItems.add(pivotIndex, newItem); } else { // we could not position the item as per the // requirements - add the item at the very end mItems.add(newItem); } } else if (positioned == Positioned.AFTER) { if (pivot.getGroupId() == NO_GROUP_ID || pivotIndex == findLastGroupIndex(pivot.getGroupId())) { // `pivot` is not part of a Group // - or - // `pivot` is the very last item in a Group mItems.add(pivotIndex + 1, newItem); } else { // we could not position the item as per the // requirements - add the item at the very end mItems.add(newItem); } } } } onItemsChanged(); return newItem; } /** * Creates an item. The other 'create' methods funnel to this. */ private SublimeBaseMenuItem createInternal(int group, int id, CharSequence title, CharSequence hint, SublimeBaseMenuItem.ItemType itemType, boolean valueProvidedAsync, CharSequence badgeText, boolean showsIconSpace) { SublimeBaseMenuItem item; switch (itemType) { case SEPARATOR: item = new SublimeSeparatorMenuItem(this, group, id); break; case BADGE: item = new SublimeTextWithBadgeMenuItem(this, group, id, title, hint, valueProvidedAsync, badgeText, showsIconSpace); break; case SWITCH: item = new SublimeSwitchMenuItem(this, group, id, title, hint, valueProvidedAsync, showsIconSpace); break; case CHECKBOX: item = new SublimeCheckboxMenuItem(this, group, id, title, hint, valueProvidedAsync, showsIconSpace); break; case GROUP_HEADER: item = new SublimeGroupHeaderMenuItem(this, group, id, title, hint, valueProvidedAsync, showsIconSpace); break; default: // TEXT item = new SublimeTextMenuItem(this, group, id, title, hint, valueProvidedAsync, showsIconSpace); break; } return item; } /** * This check is performed before adding an item to this menu * that has positional instructions: addAfter(...), addBefore(...) etc.. * Throws {@link RuntimeException} if the item * used for positioning does not exist. * * @param itemId ID of the item to check for */ private void checkExistenceOfItem(int itemId) { if (itemId == NO_ITEM_ID || getMenuItem(itemId) == null) { throw new RuntimeException("'itemId' passed was invalid: '" + itemId + "'."); } } /** * This check is performed before adding an item to this menu. * If the item indicates its membership to a {@link SublimeGroup} * (by supplying a 'groupID'), this check confirms that such a * group does exist. Throws {@link RuntimeException} if a group * with ID == 'groupId' is not found. * * @param groupId ID of the group to check for */ private void checkExistenceOfGroup(int groupId) { if (groupId != NO_GROUP_ID && getGroup(groupId) == null) { throw new RuntimeException("'groupId' passed was invalid: '" + groupId + "'. Items can only " + "be added to existing Group(s)"); } } /** * This check is performed before adding an item of type * {@link SublimeBaseMenuItem.ItemType#GROUP_HEADER}. * A {@link RuntimeException} is thrown if the {@link SublimeGroup} * already contains an item of this type. A {@link SublimeGroup} * can only have one header. * * @param groupId ID of the group on which to perform this check. */ private void checkIfGroupHeaderAlreadyExistsForGroup(int groupId) { for (SublimeBaseMenuItem item : mItems) { if (item.getGroupId() == groupId && item.getItemType() == SublimeBaseMenuItem.ItemType.GROUP_HEADER) { throw new RuntimeException("Attempt to add 'GroupHeader' to " + "a 'Group' that already contains one."); } } } public SublimeBaseMenuItem createTextItem(int groupId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return createInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.TEXT, false /*valueProvidedAsync*/, null, showsIconSpace); } public SublimeBaseMenuItem addTextItem(int groupId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return addInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.TEXT, false /*valueProvidedAsync*/, null, showsIconSpace, true); } protected SublimeBaseMenuItem addTextItem(int groupId, int itemId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return addInternal(groupId, itemId, title, hint, SublimeBaseMenuItem.ItemType.TEXT, false /*valueProvidedAsync*/, null, showsIconSpace, false); } public SublimeBaseMenuItem createTextWithBadgeItem(int groupId, CharSequence title, CharSequence hint, CharSequence badgeText, boolean showsIconSpace) { return createInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.BADGE, false /*valueProvidedAsync*/, badgeText, showsIconSpace); } public SublimeBaseMenuItem addTextWithBadgeItem(int groupId, CharSequence title, CharSequence hint, CharSequence badgeText, boolean showsIconSpace) { return addInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.BADGE, false /*valueProvidedAsync*/, badgeText, showsIconSpace, true); } protected SublimeBaseMenuItem addTextWithBadgeItem(int groupId, int itemId, CharSequence title, CharSequence hint, CharSequence badgeText, boolean showsIconSpace) { return addInternal(groupId, itemId, title, hint, SublimeBaseMenuItem.ItemType.BADGE, false /*valueProvidedAsync*/, badgeText, showsIconSpace, false); } public SublimeBaseMenuItem createCheckboxItem(int groupId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return createInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.CHECKBOX, false /*valueProvidedAsync*/, null, showsIconSpace); } public SublimeBaseMenuItem addCheckboxItem(int groupId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return addInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.CHECKBOX, false /*valueProvidedAsync*/, null, showsIconSpace, true); } protected SublimeBaseMenuItem addCheckboxItem(int groupId, int itemId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return addInternal(groupId, itemId, title, hint, SublimeBaseMenuItem.ItemType.CHECKBOX, false /*valueProvidedAsync*/, null, showsIconSpace, false); } public SublimeBaseMenuItem createSwitchItem(int groupId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return createInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.SWITCH, false /*valueProvidedAsync*/, null, showsIconSpace); } public SublimeBaseMenuItem addSwitchItem(int groupId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return addInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.SWITCH, false /*valueProvidedAsync*/, null, showsIconSpace, true); } protected SublimeBaseMenuItem addSwitchItem(int groupId, int itemId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return addInternal(groupId, itemId, title, hint, SublimeBaseMenuItem.ItemType.SWITCH, false /*valueProvidedAsync*/, null, showsIconSpace, false); } public SublimeBaseMenuItem createGroupHeaderItem(int groupId, CharSequence title, CharSequence hint, boolean showsIconSpace) { if (groupId == NO_GROUP_ID) { throw new RuntimeException("Attempt to create 'GroupHeader' without " + "providing a valid groupID"); } return createInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.GROUP_HEADER, false /*valueProvidedAsync*/, null, showsIconSpace); } public SublimeBaseMenuItem addGroupHeaderItem(int groupId, CharSequence title, CharSequence hint, boolean showsIconSpace) { if (groupId == NO_GROUP_ID) { throw new RuntimeException("Attempt to add 'GroupHeader' without " + "providing a valid groupID"); } return addInternal(groupId, generateUniqueItemID()/* itemId */, title, hint, SublimeBaseMenuItem.ItemType.GROUP_HEADER, false /*valueProvidedAsync*/, null, showsIconSpace, true); } protected SublimeBaseMenuItem addGroupHeaderItem(int groupId, int itemId, CharSequence title, CharSequence hint, boolean showsIconSpace) { return addInternal(groupId, itemId, title, hint, SublimeBaseMenuItem.ItemType.GROUP_HEADER, false /*valueProvidedAsync*/, null, showsIconSpace, false); } public SublimeBaseMenuItem createSeparatorItem(int groupId) { return createInternal(groupId, generateUniqueItemID()/* itemId */, null, null, SublimeBaseMenuItem.ItemType.SEPARATOR, false /*valueProvidedAsync*/, null, false); } public SublimeBaseMenuItem addSeparatorItem(int groupId) { return addInternal(groupId, generateUniqueItemID()/* itemId */, null, null, SublimeBaseMenuItem.ItemType.SEPARATOR, false /*valueProvidedAsync*/, null, false, true); } protected SublimeBaseMenuItem addSeparatorItem(int groupId, int itemId) { return addInternal(groupId, itemId, null, null, SublimeBaseMenuItem.ItemType.SEPARATOR, false /*valueProvidedAsync*/, null, false, false); } public void addBefore(int pivotId, SublimeBaseMenuItem item) { checkExistenceOfItem(pivotId); addInternal(pivotId, Positioned.BEFORE, item); } public void addAfter(int pivotId, SublimeBaseMenuItem item) { checkExistenceOfItem(pivotId); addInternal(pivotId, Positioned.AFTER, item); } public void removeItem(int id) { removeItemAtInt(findItemIndex(id), true); } public void removeGroup(int groupId) { final int i = findGroupIndex(groupId); if (i >= 0) { final int maxRemovable = mItems.size() - i; int numRemoved = 0; while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == groupId)) { // Don't force update for each one, this method will do it at the end removeItemAtInt(i, false); } // Remove Group int groups = mGroups.size(); for (int j = 0; j < groups; j++) { SublimeGroup curGroup = mGroups.get(j); if (curGroup.getGroupId() == groupId) { mGroups.remove(j); break; } } // Notify menu views onItemsChanged(); } } /** * Remove the item at the given index and optionally forces menu views to * update. * * @param index The index of the item to be removed. If this index is * invalid an exception is thrown. * @param updateChildrenOnMenuViews Whether to force update on menu views. * Please make sure you eventually call this after your batch of * removals. */ private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) { if ((index < 0) || (index >= mItems.size())) return; mItems.remove(index); if (updateChildrenOnMenuViews) { onItemsChanged(); } } public void clear() { mItems.clear(); mGroups.clear(); onItemsChanged(); } /** * Performs required changes to other group items * before the passed item's checked state is set to true. * * @param item Item on which setChecked(true) has been called */ void setItemChecked(SublimeBaseMenuItem item) { SublimeGroup group = getGroup(item.getGroupId()); if (group == null) return; if (group.getCheckableBehavior() == SublimeGroup.CheckableBehavior.SINGLE) { final int N = mItems.size(); for (int i = 0; i < N; i++) { SublimeBaseMenuItem curItem = mItems.get(i); if (curItem.getGroupId() == group.getGroupId()) { if (!curItem.isCheckable()) { continue; } // Check the item meant to be checked, // un-check the others (that are in the group) if (curItem != item) { curItem.setCheckedInt(false); } } } } } protected List getItemsForGroup(int groupId) { ArrayList groupItems = new ArrayList<>(); final int N = mItems.size(); for (int i = 0; i < N; i++) { SublimeBaseMenuItem item = mItems.get(i); if (item.getGroupId() == groupId) { groupItems.add(item); } } return groupItems; } protected int getVisibleItemCountForGroup(List groupItems) { int visibleItems = 0; for (SublimeBaseMenuItem menuItem : groupItems) { if (menuItem.isVisible()) { visibleItems++; } } return visibleItems; } public boolean hasVisibleItems() { final int size = size(); for (int i = 0; i < size; i++) { SublimeBaseMenuItem item = mItems.get(i); if (item.isVisible()) { return true; } } return false; } public boolean groupHasVisibleItems(int groupId) { final int size = size(); for (int i = 0; i < size; i++) { SublimeBaseMenuItem item = mItems.get(i); if (item.getGroupId() == groupId && item.isVisible()) { return true; } } return false; } public SublimeBaseMenuItem getMenuItem(int itemId) { final int size = size(); for (int i = 0; i < size; i++) { SublimeBaseMenuItem item = mItems.get(i); if (item.getItemId() == itemId) { return item; } } return null; } private int findItemIndex(int itemId) { final int size = size(); for (int i = 0; i < size; i++) { SublimeBaseMenuItem item = mItems.get(i); if (item.getItemId() == itemId) { return i; } } return -1; } private int findGroupIndex(int groupId) { final int size = size(); for (int i = 0; i < size; i++) { final SublimeBaseMenuItem item = mItems.get(i); if (item.getGroupId() == groupId) { return i; } } return -1; } /** * Returns the last index at which a member of the indicated group * exists. If the group currently does not have any members, * size of the MenuItem-list is returned. * * @param groupId ID of the group whose last member's index needs to be * found * @return index of the last member of the indicated group, or size of * the MenuItem-list if no members are found. */ private int findLastGroupIndex(int groupId) { int i = 0, size = mItems.size(); boolean traversingGroup = false; while (true) { if (i < size) { final SublimeBaseMenuItem item = mItems.get(i); if (item.getGroupId() == groupId) { traversingGroup = true; } else if (traversingGroup) { // Take one step back return i - 1; } i++; } else { return traversingGroup ? size - 1 : size; } } } public int size() { return mItems.size(); } public Context getContext() { return mContext; } boolean dispatchMenuItemSelected(SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event) { return mCallback != null && mCallback.onMenuItemSelected(this, item, event); } public boolean performItemAction(SublimeBaseMenuItem item) { return !(item == null || !item.isEnabled()) && item.invoke(); } public ArrayList getVisibleItems() { // Refresh the visible items mVisibleItems.clear(); final int itemsSize = mItems.size(); SublimeBaseMenuItem item; for (int i = 0; i < itemsSize; i++) { item = mItems.get(i); if (item.isVisible()) mVisibleItems.add(item); } return mVisibleItems; } //----------------------------------------------------------------// //---------------------------Item changes-------------------------// //----------------------------------------------------------------// protected SublimeMenu blockUpdates() { mBlockUpdates = true; return this; } protected SublimeMenu allowUpdates() { mBlockUpdates = false; return this; } public void finalizeUpdates() { if (mBlockUpdates) { Log.e(TAG, "Cannot finalize updates until 'allowUpdates()' is called."); return; } if (mPresenter == null) { Log.e(TAG, "Cannot finalize updates until a presenter is set."); return; } mPresenter.invalidateEntireMenu(); } private void attemptUpdate() { if (!mBlockUpdates) { finalizeUpdates(); } } //----------------------------------------------------------------// //---------------------------Adapter data-------------------------// //----------------------------------------------------------------// public static class Change { protected enum ChangeType { ITEM_INSERTED, ITEM_REMOVED, ITEM_CHANGED, ITEM_MOVED, RANGE_INSERTED, RANGE_REMOVED, RANGE_CHANGED, INVALIDATE_ENTIRE_MENU } // Used with ChangeType: ITEM_INSERTED, ITEM_REMOVED, ITEM_CHANGED, // RANGE_INSERTED, RANGE_REMOVED, RANGE_CHANGED private int mAffectedPosition; // Used with ChangeType: ITEM_MOVED private int mMovedFromPosition; private int mMovedToPosition; // Used with ChangeType: RANGE_INSERTED, RANGE_REMOVED, RANGE_CHANGED private int mNumberOfAffectedItems; private ChangeType mChangeType; public Change(ChangeType changeType, int affectedPosition, int movedFromPosition, int movedToPosition, int numberOfAffectedItems) { mChangeType = changeType; mAffectedPosition = affectedPosition; mMovedFromPosition = movedFromPosition; mMovedToPosition = movedToPosition; mNumberOfAffectedItems = numberOfAffectedItems; } public ChangeType getChangeType() { return mChangeType; } public int getAffectedPosition() { return mAffectedPosition; } public int getNumberOfAffectedItems() { return mNumberOfAffectedItems; } public int getMovedFromPosition() { return mMovedFromPosition; } public int getMovedToPosition() { return mMovedToPosition; } } protected ArrayList getAdapterData() { // Possibly redundant check unless this method is // called from outside of SublimeMenuPresenterNew. // We shouldn't return 'null' here. if (mPresenter == null) return mAdapterData; prepareMenuItems(); return mAdapterData; } /** * Creates/refreshes the data that is presented by SublimeMenuPresenter. */ private void prepareMenuItems() { if (Config.DEBUG) { Log.i(TAG, "prepareMenuItems()"); } mAdapterData.clear(); boolean hasHeader = mPresenter != null && mPresenter.hasHeader(); if (hasHeader) { mAdapterData.add(SublimeMenu.HEADER_STUB); } int i = 0; SublimeGroup currentGroup = null; for (int totalSize = getVisibleItems().size(); i < totalSize; ++i) { SublimeBaseMenuItem item = getVisibleItems().get(i); if (currentGroup == null || currentGroup.getGroupId() != item.getGroupId()) { currentGroup = getGroup(item.getGroupId()); } if (currentGroup != null && (!currentGroup.isVisible() || (currentGroup.isCollapsed() && item.getItemType() != SublimeBaseMenuItem.ItemType.GROUP_HEADER))) { continue; } mAdapterData.add(item); } } //----------------------------------------------------------------// //-----------------------Adapter data changes---------------------// //----------------------------------------------------------------// /** * Called when an item is changed. Type of change is evaluated * by comparing the 'old' position & the 'new' position of the item. * * @param itemId id of item that's been changed */ public void onItemChanged(int itemId) { if (mBlockUpdates || mPresenter == null) return; int oldPos = getAdapterPosForId(itemId); prepareMenuItems(); int newPos = getAdapterPosForId(itemId); if (oldPos == -1 && newPos == -1) { // No change to report return; } if (oldPos == -1) { mPresenter.reportChange( new Change(Change.ChangeType.ITEM_INSERTED, newPos, -1, -1, -1), mAdapterData); } else if (newPos == -1) { mPresenter.reportChange( new Change(Change.ChangeType.ITEM_REMOVED, oldPos, -1, -1, -1), mAdapterData); } else if (oldPos == newPos) { mPresenter.reportChange( new Change(Change.ChangeType.ITEM_CHANGED, newPos, -1, -1, -1), mAdapterData); } else { mPresenter.reportChange( new Change(Change.ChangeType.ITEM_MOVED, -1, oldPos, newPos, -1), mAdapterData); } } /** * Called by {@link SublimeBaseMenuItem} after a batch update. */ void onItemsChanged() { if (mBlockUpdates || mPresenter == null) return; mPresenter.invalidateEntireMenu(); } private int getAdapterPosForId(int itemId) { for (int i = 0; i < mAdapterData.size(); i++) { if (mAdapterData.get(i).getItemId() == itemId) { return i; } } return -1; } //----------------------------------------------------------------// //--------------------------SublimeGroup--------------------------// //----------------------------------------------------------------// /** * Finds and returns the SublimeGroup with id == groupId. * * @param groupId id of the group to find * @return SublimeGroup with id == groupId if found, 'null' otherwise */ public SublimeGroup getGroup(int groupId) { for (int i = 0; i < mGroups.size(); i++) { if (mGroups.get(i).getGroupId() == groupId) { return mGroups.get(i); } } return null; } /** * Called when a Group is collapsed/expanded. * * @param groupId id of group that's been collapsed/expanded * @param collapsed 'true' if group has been collapsed, 'false' if expanded */ protected void onGroupExpandedOrCollapsed(int groupId, boolean collapsed) { if (mBlockUpdates || mPresenter == null) return; List groupItems = getItemsForGroup(groupId); if (groupItems.size() > 0) { int headerPos = getAdapterPosForId(groupItems.get(0).getItemId()); int visibleItemCountForGroupBeforeUpdate = getVisibleItemCountForGroup(groupItems); prepareMenuItems(); int visibleItemCountForGroupAfterUpdate = getVisibleItemCountForGroup(groupItems); if (headerPos != -1) { // GroupHeader qualifies for ITEM_CHANGED. mPresenter.reportChange( new Change(Change.ChangeType.ITEM_CHANGED, headerPos, -1, -1, -1), mAdapterData); if (collapsed) { // The '> 1' check determines if there are any // GroupItems that are *visible*. // '- 1' because the GroupHeader does not // qualify for RANGE_REMOVED. if (visibleItemCountForGroupBeforeUpdate > 1) { mPresenter.reportChange( new Change(Change.ChangeType.RANGE_REMOVED, headerPos + 1, //-1, -1, groupItems.size() - 1), -1, -1, visibleItemCountForGroupBeforeUpdate - 1), mAdapterData); } } else { if (visibleItemCountForGroupAfterUpdate > 1) { mPresenter.reportChange( new Change(Change.ChangeType.RANGE_INSERTED, headerPos + 1, //-1, -1, groupItems.size() - 1), -1, -1, visibleItemCountForGroupAfterUpdate - 1), mAdapterData); } } } else { // be safe mPresenter.invalidateEntireMenu(); } } } /** * Called when a Group's visibility is changed. * * @param groupId id of group that is now visible/invisible * @param visible 'true' if the group is now visible, 'false' otherwise */ protected void onGroupVisibilityChanged(int groupId, boolean visible) { if (mBlockUpdates || mPresenter == null) return; List groupItems = getItemsForGroup(groupId); if (groupItems.size() > 0) { boolean invalidateEntireMenu = false; if (visible) { prepareMenuItems(); int headerPos = getAdapterPosForId(groupItems.get(0).getItemId()); if (headerPos != -1) { mPresenter.reportChange( new Change(Change.ChangeType.RANGE_INSERTED, headerPos, //-1, -1, groupItems.size()), -1, -1, getVisibleItemCountForGroup(groupItems)), mAdapterData); } else { // be safe invalidateEntireMenu = true; } } else { // Get position & count before preparing updated menu int headerPos = getAdapterPosForId(groupItems.get(0).getItemId()); int visibleItemCountForGroup = getVisibleItemCountForGroup(groupItems); prepareMenuItems(); if (headerPos != -1) { mPresenter.reportChange( new Change(Change.ChangeType.RANGE_REMOVED, headerPos, //-1, -1, groupItems.size()), -1, -1, visibleItemCountForGroup), mAdapterData); } else { invalidateEntireMenu = true; } } if (invalidateEntireMenu) { // be safe mPresenter.invalidateEntireMenu(); } } } /** * Called in response to change in 'collapsible' status of a group * * @param sublimeGroup group for which 'collapsible' status has changed */ protected void onGroupCollapsibleStatusChanged(SublimeGroup sublimeGroup) { if (mBlockUpdates || mPresenter == null) return; List groupItems = getItemsForGroup(sublimeGroup.getGroupId()); if (groupItems.size() > 0) { int headerPos = getAdapterPosForId(groupItems.get(0).getItemId()); if (headerPos != -1) { if (sublimeGroup.isCollapsible() || !sublimeGroup.isCollapsed()) { // Todo: check if call to 'prepareMenuItems()' is required mPresenter.reportChange( new Change(Change.ChangeType.ITEM_CHANGED, headerPos, -1, -1, -1), mAdapterData); } else { sublimeGroup.setStateCollapsed(false); } } else { // be safe mPresenter.invalidateEntireMenu(); } } } /** * Called when a Group is enabled/disabled. * * @param groupId 'id' of group that's been enabled/disabled * @param enabled 'true' if the group has been 'enabled', false * otherwise */ protected void onGroupEnabledOrDisabled(int groupId, boolean enabled) { if (mBlockUpdates || mPresenter == null) return; List groupItems = getItemsForGroup(groupId); for (SublimeBaseMenuItem item : groupItems) { item.blockUpdates().setEnabled(enabled).allowUpdates(); } if (groupItems.size() > 0) { int firstPos = getAdapterPosForId(groupItems.get(0).getItemId()); if (firstPos != -1) { mPresenter.reportChange( new Change(Change.ChangeType.RANGE_CHANGED, firstPos, //-1, -1, groupItems.size()), -1, -1, getVisibleItemCountForGroup(groupItems)), mAdapterData); } else { // be safe mPresenter.invalidateEntireMenu(); } } } /** * Sets the 'checkable' behavior for this group. For more information, * see {@link SublimeGroup#setCheckableBehavior(SublimeGroup.CheckableBehavior)}. * * @param groupId 'id' for which to set the new 'checkableBehavior' * @param newCheckableBehavior the 'checkableBehavior' to set */ protected void onGroupCheckableBehaviorChanged( int groupId, SublimeGroup.CheckableBehavior newCheckableBehavior) { List groupItems = getItemsForGroup(groupId); // flag set when 'newCheckableBehavior' is 'SINGLE' & the first // checked item is found. boolean foundCheckedIfExclusive = false; for (SublimeBaseMenuItem item : groupItems) { switch (newCheckableBehavior) { case NONE: item.setCheckable(false); break; case ALL: item.setCheckable(true); break; case SINGLE: if (foundCheckedIfExclusive && item.isCheckable()) { item.setChecked(false); } else { // Can an item be !checkable & checked? if (item.isCheckable() && item.isChecked()) { foundCheckedIfExclusive = true; } } break; } } } //----------------------------------------------------------------// //---------------------------Parcelable---------------------------// //----------------------------------------------------------------// public SublimeMenu(Parcel in) { readParcel(in); } private void readParcel(Parcel in) { mMenuResourceID = in.readInt(); in.readTypedList(mItems, SublimeBaseMenuItem.CREATOR); for (SublimeBaseMenuItem item : mItems) { item.setParentMenu(this); } in.readTypedList(mGroups, SublimeGroup.CREATOR); for (SublimeGroup group : mGroups) { group.setParentMenu(this); } } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mMenuResourceID); dest.writeTypedList(mItems); dest.writeTypedList(mGroups); } public static final Creator CREATOR = new Creator() { public SublimeMenu createFromParcel(Parcel in) { return new SublimeMenu(in); } public SublimeMenu[] newArray(int size) { return new SublimeMenu[size]; } }; } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeMenuInflater.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.util.AttributeSet; import android.util.Xml; import android.view.InflateException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; /** * Custom menu inflater. */ public class SublimeMenuInflater { private static final String TAG = SublimeMenuInflater.class.getSimpleName(); /** * Menu tag name in XML. */ private static final String XML_MENU = "menu"; /** * Group tag name in XML. */ private static final String XML_GROUP = "Group"; /** * Text item tag name in XML. */ private static final String XML_TEXT = "Text"; /** * Text w/ badge item tag name in XML. */ private static final String XML_TEXT_WITH_BADGE = "TextWithBadge"; /** * Checkbox item tag name in XML. */ private static final String XML_CHECKBOX = "Checkbox"; /** * Switch item tag name in XML. */ private static final String XML_SWITCH = "Switch"; /** * GroupHeader item tag name in XML. */ private static final String XML_GROUP_HEADER = "GroupHeader"; /** * Separator item tag name in XML. */ private static final String XML_SEPARATOR = "Separator"; private static final int NO_ID = -1; private Context mContext; /** * Constructs a menu inflater. * * @param context Context of the calling entity. */ public SublimeMenuInflater(Context context) { mContext = context; } /** * Inflate a menu hierarchy from the specified XML resource. Throws * {@link InflateException} if there is an error. * * @param menuRes Resource ID for an XML layout resource to load (e.g., * R.menu.nav_main) * @param menu The Menu to inflate into. The items and groups will be * added to this Menu. */ public void inflate(int menuRes, SublimeMenu menu) { XmlResourceParser parser = null; try { parser = mContext.getResources().getLayout(menuRes); AttributeSet attrs = Xml.asAttributeSet(parser); parseMenu(parser, attrs, menu); } catch (XmlPullParserException e) { throw new InflateException("Error inflating menu XML", e); } catch (IOException e) { throw new InflateException("Error inflating menu XML", e); } finally { if (parser != null) parser.close(); } } /** * Called internally to fill the given menu. * * @param parser an XmlPullParser used to parse XML menu definition * @param attrs attributes * @param menu the menu to inflate into * @throws XmlPullParserException error occurred while parsing XML * @throws IOException in case the given xml file cannot be accessed * because of an I/O related issue. */ private void parseMenu(XmlPullParser parser, AttributeSet attrs, SublimeMenu menu) throws XmlPullParserException, IOException { MenuState menuState = new MenuState(menu); int eventType = parser.getEventType(); String tagName; boolean lookingForEndOfUnknownTag = false; String unknownTagName = null; // This loop will skip to the menu start tag do { if (eventType == XmlPullParser.START_TAG) { tagName = parser.getName(); if (tagName.equals(XML_MENU)) { // Go to next tag eventType = parser.next(); break; } throw new RuntimeException("Expecting menu, got " + tagName); } eventType = parser.next(); } while (eventType != XmlPullParser.END_DOCUMENT); boolean reachedEndOfMenu = false; while (!reachedEndOfMenu) { switch (eventType) { case XmlPullParser.START_TAG: if (lookingForEndOfUnknownTag) { break; } tagName = parser.getName(); if (tagName.equals(XML_GROUP)) { // A Group item cannot have other Group items as children if (menuState.groupId != MenuState.defaultGroupId) { throw new RuntimeException("A 'Group' item cannot have " + "other 'Group' items as children."); } menuState.readGroup(attrs); menuState.addGroup(); } else if (tagName.equals(XML_TEXT) || tagName.equals(XML_TEXT_WITH_BADGE) || tagName.equals(XML_CHECKBOX) || tagName.equals(XML_SWITCH)) { menuState.readMenuItem(attrs, tagName); } else if (tagName.equals(XML_SEPARATOR)) { menuState.readMenuItem(attrs, tagName); } else if (tagName.equals(XML_GROUP_HEADER)) { if (menuState.groupId == MenuState.defaultGroupId) { throw new RuntimeException("'GroupHeader' item should " + "be placed inside a Group element."); } menuState.readMenuItem(attrs, tagName); } else if (tagName.equals(XML_MENU)) { throw new RuntimeException("Sub-menus are not supported. " + "Similar functionality can be afforded " + "using the 'group' tag."); } else { lookingForEndOfUnknownTag = true; unknownTagName = tagName; } break; case XmlPullParser.END_TAG: tagName = parser.getName(); if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) { lookingForEndOfUnknownTag = false; unknownTagName = null; } else if (tagName.equals(XML_GROUP)) { if (menuState.isGroupCollapsible() && menuState.groupHeadersAdded != 1) { if (menuState.groupHeadersAdded < 1) { throw new RuntimeException("A 'GroupHeader' is required " + "to create a 'collapsible' Group."); } else { throw new RuntimeException("A 'collapsible' Group can only " + "have ONE 'GroupHeader'. You have provided: " + menuState.groupHeadersAdded + "."); } } menuState.resetGroup(); } else if (tagName.equals(XML_TEXT) || tagName.equals(XML_TEXT_WITH_BADGE) || tagName.equals(XML_CHECKBOX) || tagName.equals(XML_SWITCH) || tagName.equals(XML_GROUP_HEADER) || tagName.equals(XML_SEPARATOR)) { // Add the item if it hasn't been added (if the item was // a submenu, it would have been added already) if (!menuState.hasAddedItem()) { menuState.addItem(); } } else if (tagName.equals(XML_MENU)) { reachedEndOfMenu = true; } break; case XmlPullParser.END_DOCUMENT: throw new RuntimeException("Unexpected end of document"); } eventType = parser.next(); } } /** * State for the current menu. *

* Groups can not be nested unless there is another menu (which will have * its state class). */ private class MenuState { private SublimeMenu menu; /* * Group state is set on items as they are added, allowing an item to * override its group state. (As opposed to set on items at the group end tag.) */ private int groupId; private boolean groupVisible; private boolean groupEnabled; private boolean groupIsCollapsible; private boolean groupIsCollapsed; private SublimeGroup.CheckableBehavior groupCheckableBehavior; private int groupHeadersAdded; private boolean itemAdded; private int itemId; private CharSequence itemTitle; private CharSequence itemBadgeText; private int itemIconResId; private boolean itemCheckable; private boolean itemChecked; private boolean itemVisible; private boolean itemEnabled; private boolean itemShowIconSpace; private SublimeBaseMenuItem.ItemType itemType; private boolean valueProvidedAsync; private CharSequence itemHint; private static final int defaultGroupId = NO_ID; private static final int defaultItemId = NO_ID; private static final int defaultItemCheckable = 1; private static final boolean defaultItemChecked = false; private static final boolean defaultItemVisible = true; private static final boolean defaultItemEnabled = true; private static final boolean defaultItemShowIconSpace = false; private static final boolean defaultGroupCollapsible = false; private static final boolean defaultGroupCollapsed = false; private static final int defaultGroupHeadersAdded = 0; public MenuState(final SublimeMenu menu) { this.menu = menu; resetGroup(); } public void addGroup() { menu.addGroup(groupId, groupIsCollapsible, groupIsCollapsed, groupEnabled, groupVisible, groupCheckableBehavior); } public boolean isGroupCollapsible() { return groupIsCollapsible; } public void resetGroup() { groupId = defaultGroupId; groupVisible = defaultItemVisible; groupEnabled = defaultItemEnabled; groupIsCollapsed = defaultGroupCollapsed; groupIsCollapsible = defaultGroupCollapsible; groupCheckableBehavior = SublimeGroup.CheckableBehavior.ALL; groupHeadersAdded = defaultGroupHeadersAdded; } /** * Called when the parser is pointing to a group tag. */ public void readGroup(AttributeSet attrs) { TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.SublimeMenuGroup); groupId = a.getResourceId(R.styleable.SublimeMenuGroup_android_id, defaultGroupId); groupVisible = a.getBoolean(R.styleable.SublimeMenuGroup_android_visible, defaultItemVisible); groupEnabled = a.getBoolean(R.styleable.SublimeMenuGroup_android_enabled, defaultItemEnabled); groupIsCollapsible = a.getBoolean(R.styleable.SublimeMenuGroup_collapsible, defaultGroupCollapsible); groupIsCollapsed = a.getBoolean(R.styleable.SublimeMenuGroup_collapsed, defaultGroupCollapsed); groupCheckableBehavior = getGroupCheckableBehavior( a.getInt(R.styleable.SublimeMenuGroup_android_checkableBehavior, defaultItemCheckable)); a.recycle(); } /** * Returns {@link SublimeGroup.CheckableBehavior} corresponding to the * passed value. * * @param checkable value defined in XML * @return {@link SublimeGroup.CheckableBehavior} that corresponds to * {@param checkable}. */ private SublimeGroup.CheckableBehavior getGroupCheckableBehavior(final int checkable) { switch (checkable) { case 1: return SublimeGroup.CheckableBehavior.ALL; case 2: return SublimeGroup.CheckableBehavior.SINGLE; default: return SublimeGroup.CheckableBehavior.NONE; } } /** * Called when the parser is pointing to a generic item tag. */ public void readMenuItem(AttributeSet attrs, String tagName) { itemType = getItemType(tagName); TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.SublimeMenuGenericItem); // Inherit attributes from the group as default value itemId = a.getResourceId(R.styleable.SublimeMenuGenericItem_android_id, defaultItemId); itemTitle = a.getText(R.styleable.SublimeMenuGenericItem_android_title); itemHint = a.getText(R.styleable.SublimeMenuGenericItem_android_hint); itemIconResId = a.getResourceId(R.styleable.SublimeMenuGenericItem_android_icon, 0); itemCheckable = a.getBoolean(R.styleable.SublimeMenuGenericItem_android_checkable, (groupCheckableBehavior != SublimeGroup.CheckableBehavior.NONE)); itemChecked = a.getBoolean(R.styleable.SublimeMenuGenericItem_android_checked, defaultItemChecked); itemVisible = a.getBoolean(R.styleable.SublimeMenuGenericItem_android_visible, groupVisible); itemEnabled = a.getBoolean(R.styleable.SublimeMenuGenericItem_android_enabled, groupEnabled); itemShowIconSpace = a.getBoolean(R.styleable.SublimeMenuGenericItem_showIconSpace, itemIconResId != 0); valueProvidedAsync = a.getBoolean(R.styleable.SublimeMenuGenericItem_valueProvidedAsync, false); itemBadgeText = a.getText(R.styleable.SublimeMenuGenericItem_badgeText); a.recycle(); itemAdded = false; } private SublimeBaseMenuItem.ItemType getItemType(final String type) { switch (type) { case XML_CHECKBOX: return SublimeBaseMenuItem.ItemType.CHECKBOX; case XML_SWITCH: return SublimeBaseMenuItem.ItemType.SWITCH; case XML_TEXT_WITH_BADGE: return SublimeBaseMenuItem.ItemType.BADGE; case XML_GROUP_HEADER: return SublimeBaseMenuItem.ItemType.GROUP_HEADER; case XML_SEPARATOR: return SublimeBaseMenuItem.ItemType.SEPARATOR; default: /* XML_TEXT */ return SublimeBaseMenuItem.ItemType.TEXT; } } private void setItem(SublimeBaseMenuItem item) { // Ordering: 'setChecked(boolean)' checks if // the item 'isCheckable()' before making changes. item.setCheckable(itemCheckable) .setChecked(itemChecked) .setVisible(itemVisible) .setEnabled(itemEnabled) .setIcon(itemIconResId) .setHint(itemHint) .setShowsIconSpace(itemShowIconSpace) .setValueProvidedAsync(valueProvidedAsync); } public void addItem() { itemAdded = true; switch (itemType) { case CHECKBOX: setItem(menu.addCheckboxItem(groupId, itemId, itemTitle, itemHint, itemShowIconSpace)); break; case SWITCH: setItem(menu.addSwitchItem(groupId, itemId, itemTitle, itemHint, itemShowIconSpace)); break; case BADGE: setItem(menu.addTextWithBadgeItem(groupId, itemId, itemTitle, itemHint, itemBadgeText, itemShowIconSpace)); break; case SEPARATOR: setItem(menu.addSeparatorItem(groupId, itemId)); break; case GROUP_HEADER: setItem(menu.addGroupHeaderItem(groupId, itemId, itemTitle, itemHint, itemShowIconSpace)); groupHeadersAdded++; break; default: /* TEXT */ setItem(menu.addTextItem(groupId, itemId, itemTitle, itemHint, itemShowIconSpace)); break; } } public boolean hasAddedItem() { return itemAdded; } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeMenuPresenter.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import java.util.ArrayList; import java.util.List; /** * Presenter used to display a SublimeMenu. * * Created by Vikram. */ public class SublimeMenuPresenter { private static final String TAG = "Presenter"; private SublimeNavMenuView mMenuView; private LinearLayout mHeader; private SublimeMenu mMenu; private MenuRecyclerAdapter mAdapter; private LayoutInflater mLayoutInflater; private int mPaddingTopDefault; private SublimeThemer mThemer; private Context mContext; private boolean mInitializing; public SublimeMenuPresenter() { mInitializing = true; } /** * Upon creation, and until initializations are done, * {@link SublimeMenuPresenter} blocks all calls for invalidation. * We can now finalize the initialization phase to allow * invalidation of the menu when required. */ protected void setInitializationDone() { mInitializing = false; invalidateEntireMenu(); } public void initForMenu(Context context, SublimeMenu menu) { mContext = context; mLayoutInflater = LayoutInflater.from(context); mMenu = menu; mPaddingTopDefault = context.getResources() .getDimensionPixelOffset(R.dimen.snv_navigation_padding_top_default); if (mThemer == null) { mThemer = new SublimeThemer(context); } } void setThemer(SublimeThemer sublimeThemer) { mThemer = sublimeThemer; invalidateEntireMenu(); } public SublimeNavMenuView getMenuView(ViewGroup root) { if (mMenuView == null) { mMenuView = (SublimeNavMenuView) mLayoutInflater .inflate(R.layout.sublime_navigation_menu_view, root, false); mMenuView.setLayoutManager(new LinearLayoutManager(root.getContext(), LinearLayoutManager.VERTICAL, false)); if (mAdapter == null) { mAdapter = new MenuRecyclerAdapter(); mAdapter.setHasStableIds(false); } mHeader = (LinearLayout) mLayoutInflater .inflate(R.layout.sublime_menu_header_item, mMenuView, false); mMenuView.setAdapter(mAdapter); } return mMenuView; } protected void reportChange(SublimeMenu.Change change, List freshData) { if (mAdapter == null) return; mAdapter.refreshData(freshData); switch (change.getChangeType()) { case ITEM_INSERTED: mAdapter.notifyItemInserted(change.getAffectedPosition()); break; case ITEM_REMOVED: mAdapter.notifyItemRemoved(change.getAffectedPosition()); break; case ITEM_CHANGED: mAdapter.notifyItemChanged(change.getAffectedPosition()); break; case ITEM_MOVED: mAdapter.notifyItemMoved(change.getMovedFromPosition(), change.getMovedToPosition()); break; case RANGE_INSERTED: mAdapter.notifyItemRangeInserted(change.getAffectedPosition(), change.getNumberOfAffectedItems()); break; case RANGE_REMOVED: mAdapter.notifyItemRangeRemoved(change.getAffectedPosition(), change.getNumberOfAffectedItems()); break; case RANGE_CHANGED: mAdapter.notifyItemRangeChanged(change.getAffectedPosition(), change.getNumberOfAffectedItems()); break; default: /* INVALIDATE_ENTIRE_MENU */ mAdapter.notifyDataSetChanged(); break; } } public void invalidateEntireMenu() { if (mInitializing) return; if (Config.DEBUG) { Log.i(TAG, "invalidateEntireMenu()"); } reportChange( new SublimeMenu.Change( SublimeMenu.Change.ChangeType.INVALIDATE_ENTIRE_MENU, -1, -1, -1, -1), mMenu.getAdapterData()); } public View getHeaderView() { return mHeader; } public View inflateHeaderView(@LayoutRes int res) { View view = mLayoutInflater.inflate(res, mHeader, false); addHeaderView(view); return view; } public void addHeaderView(@NonNull View view) { mHeader.addView(view); mMenuView.setPadding(0, 0, 0, mMenuView.getPaddingBottom()); invalidateEntireMenu(); } public void removeHeaderView(@NonNull View view) { mHeader.removeView(view); if (mHeader.getChildCount() == 0) { mMenuView.setPadding(0, mPaddingTopDefault, 0, mMenuView.getPaddingBottom()); } invalidateEntireMenu(); } protected boolean hasHeader() { return mHeader != null && mHeader.getChildCount() > 0; } ////////////////////// RV public interface Holders { void initialize(SublimeBaseMenuItem sublimeMenuItemDef, int boundPosition); } public abstract class BaseHolder extends RecyclerView.ViewHolder implements Holders, View.OnClickListener { protected int mPosition; public BaseHolder(View itemView) { super(itemView); itemView.setOnClickListener(this); } @Override public void onClick(View v) { mMenu.performItemAction(mAdapter.getItem(getAdapterPosition())); } @Override public void initialize(SublimeBaseMenuItem sublimeMenuItemDef, int boundPosition) { mPosition = boundPosition; } public SublimeBaseMenuItem getBoundData() { return mAdapter != null ? mAdapter.getItem(mPosition) : null; } } private class MenuViewNavigationHeaderHolder extends BaseHolder { public MenuViewNavigationHeaderHolder(View itemView) { super(itemView); } @Override public void initialize(SublimeBaseMenuItem sublimeMenuItemDef, int boundPosition) { super.initialize(sublimeMenuItemDef, boundPosition); } @Override public void onClick(View v) { // No-op } } private class MenuViewSeparatorHolder extends BaseHolder { public MenuViewSeparatorHolder(View itemView) { super(itemView); } @Override public void initialize(SublimeBaseMenuItem sublimeMenuItemDef, int boundPosition) { super.initialize(sublimeMenuItemDef, boundPosition); } @Override public void onClick(View v) { // No-op } } private class MenuViewSubHeaderHolder extends BaseHolder { public MenuViewSubHeaderHolder(View itemView) { super(itemView); } @Override public void initialize(SublimeBaseMenuItem sublimeMenuItemDef, int boundPosition) { super.initialize(sublimeMenuItemDef, boundPosition); SublimeSubheaderItemView itemSubHeader = (SublimeSubheaderItemView) itemView; SublimeGroup group = SublimeMenuPresenter.this .mMenu.getGroup(sublimeMenuItemDef.getGroupId()); itemSubHeader.initialize(sublimeMenuItemDef, group, mThemer); } } private class MenuViewTextHolder extends BaseHolder { public MenuViewTextHolder(View itemView) { super(itemView); } @Override public void initialize(SublimeBaseMenuItem sublimeMenuItemDef, int boundPosition) { super.initialize(sublimeMenuItemDef, boundPosition); SublimeTextItemView itemTextView = (SublimeTextItemView) itemView; itemTextView.initialize(sublimeMenuItemDef, mThemer); } } private class MenuViewCheckboxHolder extends BaseHolder { public MenuViewCheckboxHolder(View itemView) { super(itemView); } @Override public void initialize(SublimeBaseMenuItem sublimeMenuItemDef, int boundPosition) { super.initialize(sublimeMenuItemDef, boundPosition); SublimeCheckboxItemView itemCheckbox = (SublimeCheckboxItemView) itemView; itemCheckbox.initialize(sublimeMenuItemDef, mThemer); } } private class MenuViewSwitchHolder extends BaseHolder { public MenuViewSwitchHolder(View itemView) { super(itemView); } @Override public void initialize(SublimeBaseMenuItem sublimeMenuItemDef, int boundPosition) { super.initialize(sublimeMenuItemDef, boundPosition); SublimeSwitchItemView itemSwitch = (SublimeSwitchItemView) itemView; itemSwitch.initialize(sublimeMenuItemDef, mThemer); } @Override public void onClick(View v) { super.onClick(v); } } private class MenuViewTextWithBadgeHolder extends BaseHolder { public MenuViewTextWithBadgeHolder(View itemView) { super(itemView); } @Override public void initialize(SublimeBaseMenuItem sublimeMenuItemDef, int boundPosition) { super.initialize(sublimeMenuItemDef, boundPosition); SublimeTextWithBadgeItemView itemBadge = (SublimeTextWithBadgeItemView) itemView; itemBadge.initialize(sublimeMenuItemDef, mThemer); } } private class MenuRecyclerAdapter extends RecyclerView.Adapter { private static final int VIEW_TYPE_NAVIGATION_HEADER = 0; private static final int VIEW_TYPE_SEPARATOR = VIEW_TYPE_NAVIGATION_HEADER + 1; private static final int VIEW_TYPE_SUBHEADER = VIEW_TYPE_SEPARATOR + 1; private static final int VIEW_TYPE_TEXT = VIEW_TYPE_SUBHEADER + 1; private static final int VIEW_TYPE_CHECKBOX = VIEW_TYPE_TEXT + 1; private static final int VIEW_TYPE_SWITCH = VIEW_TYPE_CHECKBOX + 1; private static final int VIEW_TYPE_BADGE = VIEW_TYPE_SWITCH + 1; private final ArrayList mItems = new ArrayList<>(); MenuRecyclerAdapter() { this.mItems.addAll(SublimeMenuPresenter.this.mMenu.getAdapterData()); } @Override public int getItemCount() { return mItems.size(); } @Override public BaseHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { switch (viewType) { case VIEW_TYPE_NAVIGATION_HEADER: return new MenuViewNavigationHeaderHolder(mHeader); case VIEW_TYPE_SEPARATOR: return new MenuViewSeparatorHolder(SublimeMenuPresenter.this .mLayoutInflater.inflate( R.layout.sublime_separator_item_view, viewGroup, false)); case VIEW_TYPE_SUBHEADER: return new MenuViewSubHeaderHolder(SublimeMenuPresenter.this .mLayoutInflater.inflate( R.layout.sublime_subheader_item_view, viewGroup, false)); case VIEW_TYPE_CHECKBOX: return new MenuViewCheckboxHolder(SublimeMenuPresenter.this .mLayoutInflater.inflate( R.layout.sublime_checkbox_item_view, viewGroup, false)); case VIEW_TYPE_SWITCH: return new MenuViewSwitchHolder(SublimeMenuPresenter.this .mLayoutInflater.inflate( R.layout.sublime_switch_item_view, viewGroup, false)); case VIEW_TYPE_BADGE: return new MenuViewTextWithBadgeHolder(SublimeMenuPresenter .this.mLayoutInflater.inflate( R.layout.sublime_text_with_badge_item_view, viewGroup, false)); default: /* VIEW_TYPE_TEXT */ return new MenuViewTextHolder(SublimeMenuPresenter.this .mLayoutInflater.inflate( R.layout.sublime_text_item_view, viewGroup, false)); } } @Override public void onBindViewHolder(BaseHolder menuViewHolder, int position) { menuViewHolder.initialize(getItem(position), position); } public SublimeBaseMenuItem getItem(int position) { return mItems.get(position); } @Override public int getItemViewType(int position) { return resolveItemViewType(getItem(position)); } @Override public long getItemId(int position) { return (long) position; } private int resolveItemViewType(SublimeBaseMenuItem item) { switch (item.getItemType()) { case HEADER: return VIEW_TYPE_NAVIGATION_HEADER; case SEPARATOR: return VIEW_TYPE_SEPARATOR; case GROUP_HEADER: return VIEW_TYPE_SUBHEADER; case CHECKBOX: return VIEW_TYPE_CHECKBOX; case SWITCH: return VIEW_TYPE_SWITCH; case BADGE: return VIEW_TYPE_BADGE; default: return VIEW_TYPE_TEXT; } } public void refreshData(List freshData) { mItems.clear(); mItems.addAll(freshData); } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeNavMenuView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; /** * Currently inert. * * Created by Vikram. */ public class SublimeNavMenuView extends RecyclerView { public SublimeNavMenuView(Context context) { this(context, null); } public SublimeNavMenuView(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.listViewStyle); } public SublimeNavMenuView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeNavigationView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.LayoutRes; import android.support.annotation.MenuRes; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; /** * Top level view that hosts a SublimeMenu */ public class SublimeNavigationView extends ScrimInsetsFrameLayout { private static final String TAG = SublimeNavigationView.class.getSimpleName(); // Key used for saving state private static final String SS_MENU = "ss.menu"; // Menu private SublimeMenu mMenu; // Presenter private final SublimeMenuPresenter mPresenter; // Listener private OnNavigationMenuEventListener mEventListener; // Max width set for this drawer private int mMaxWidth; // Custom MenuInflater private SublimeMenuInflater mMenuInflater; // Theme controller private SublimeThemer mThemer; public SublimeNavigationView(Context context) { this(context, null); } public SublimeNavigationView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SublimeNavigationView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SublimeNavigationView, defStyleAttr, R.style.SnvSublimeNavigationView); try { // Used for creating default resources SublimeThemer.DefaultTheme defaultTheme = SublimeThemer.DefaultTheme.LIGHT; if (a.hasValue(R.styleable.SublimeNavigationView_snvDefaultTheme)) { defaultTheme = a.getInt(R.styleable.SublimeNavigationView_snvDefaultTheme, 0) == 0 ? SublimeThemer.DefaultTheme.LIGHT : SublimeThemer.DefaultTheme.DARK; } mThemer = new SublimeThemer(getContext(), defaultTheme); mThemer.setDrawerBackground(a.getDrawable( R.styleable.SublimeNavigationView_android_background)); if (a.hasValue(R.styleable.SublimeNavigationView_elevation)) { mThemer.setElevation((float) a.getDimensionPixelSize( R.styleable.SublimeNavigationView_elevation, 0)); } ViewCompat.setFitsSystemWindows(this, a.getBoolean(R.styleable.SublimeNavigationView_android_fitsSystemWindows, false)); mMaxWidth = a.getDimensionPixelSize( R.styleable.SublimeNavigationView_android_maxWidth, 0); if (a.hasValue(R.styleable.SublimeNavigationView_snvItemIconTint)) { mThemer.setIconTintList(a.getColorStateList( R.styleable.SublimeNavigationView_snvItemIconTint)); } mThemer.setGroupExpandDrawable(a.getDrawable( R.styleable.SublimeNavigationView_snvGroupExpandDrawable)); mThemer.setGroupCollapseDrawable(a.getDrawable( R.styleable.SublimeNavigationView_snvGroupCollapseDrawable)); // Text style profiles for Item, Hint, SubheaderItem & SubheaderHint ColorStateList itemTextColor = null, hintTextColor = null, subheaderItemTextColor = null, subheaderHintTextColor = null, badgeTextColor = null; Typeface itemTypeface = null, hintTypeface = null, subheaderItemTypeface = null, subheaderHintTypeface = null, badgeTypeface = null; int itemTypefaceStyle = 0, hintTypefaceStyle = 0, subheaderItemTypefaceStyle = 0, subheaderHintTypefaceStyle = 0, badgeTypefaceStyle = 0; if (a.hasValue(R.styleable.SublimeNavigationView_snvItemTextColor)) { itemTextColor = a.getColorStateList( R.styleable.SublimeNavigationView_snvItemTextColor); } if (a.hasValue(R.styleable.SublimeNavigationView_snvHintTextColor)) { hintTextColor = a.getColorStateList( R.styleable.SublimeNavigationView_snvHintTextColor); } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderItemTextColor)) { subheaderItemTextColor = a.getColorStateList( R.styleable.SublimeNavigationView_snvSubheaderItemTextColor); } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderHintTextColor)) { subheaderHintTextColor = a.getColorStateList( R.styleable.SublimeNavigationView_snvSubheaderHintTextColor); } if (a.hasValue(R.styleable.SublimeNavigationView_snvBadgeTextColor)) { badgeTextColor = a.getColorStateList( R.styleable.SublimeNavigationView_snvBadgeTextColor); } try { // Catch the RuntimeException thrown if // the Typeface filename is incorrect if (a.hasValue(R.styleable.SublimeNavigationView_snvItemTypefaceFilename)) { String itemTypefaceFilename = a.getString( R.styleable.SublimeNavigationView_snvItemTypefaceFilename); if (!TextUtils.isEmpty(itemTypefaceFilename)) { itemTypeface = Typeface.createFromAsset(context.getAssets(), itemTypefaceFilename); } } if (a.hasValue(R.styleable.SublimeNavigationView_snvHintTypefaceFilename)) { String hintTypefaceFilename = a.getString( R.styleable.SublimeNavigationView_snvHintTypefaceFilename); if (!TextUtils.isEmpty(hintTypefaceFilename)) { hintTypeface = Typeface.createFromAsset(context.getAssets(), hintTypefaceFilename); } } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderItemTypefaceFilename)) { String subheaderItemTypefaceFilename = a.getString( R.styleable.SublimeNavigationView_snvSubheaderItemTypefaceFilename); if (!TextUtils.isEmpty(subheaderItemTypefaceFilename)) { subheaderItemTypeface = Typeface.createFromAsset(context.getAssets(), subheaderItemTypefaceFilename); } } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderHintTypefaceFilename)) { String subheaderHintTypefaceFilename = a.getString( R.styleable.SublimeNavigationView_snvSubheaderHintTypefaceFilename); if (!TextUtils.isEmpty(subheaderHintTypefaceFilename)) { subheaderHintTypeface = Typeface.createFromAsset(context.getAssets(), subheaderHintTypefaceFilename); } } if (a.hasValue(R.styleable.SublimeNavigationView_snvBadgeTypefaceFilename)) { String badgeTypefaceFilename = a.getString( R.styleable.SublimeNavigationView_snvBadgeTypefaceFilename); if (!TextUtils.isEmpty(badgeTypefaceFilename)) { badgeTypeface = Typeface.createFromAsset(context.getAssets(), badgeTypefaceFilename); } } } catch (RuntimeException re) { Log.e(TAG, "Error loading Typeface from Assets. " + "Confirm that the Typeface filename is correct:\n" + " - filename should include the extension\n" + " - filename is case-sensitive"); } if (a.hasValue(R.styleable.SublimeNavigationView_snvItemTypefaceStyle)) { itemTypefaceStyle = a.getInt( R.styleable.SublimeNavigationView_snvItemTypefaceStyle, Typeface.NORMAL); switch (itemTypefaceStyle) { case 1: itemTypefaceStyle = Typeface.BOLD; break; case 2: itemTypefaceStyle = Typeface.ITALIC; break; case 3: itemTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL itemTypefaceStyle = Typeface.NORMAL; break; } } if (a.hasValue(R.styleable.SublimeNavigationView_snvHintTypefaceStyle)) { hintTypefaceStyle = a.getInt( R.styleable.SublimeNavigationView_snvHintTypefaceStyle, Typeface.NORMAL); switch (hintTypefaceStyle) { case 1: hintTypefaceStyle = Typeface.BOLD; break; case 2: hintTypefaceStyle = Typeface.ITALIC; break; case 3: hintTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL hintTypefaceStyle = Typeface.NORMAL; break; } } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderItemTypefaceStyle)) { subheaderItemTypefaceStyle = a.getInt( R.styleable.SublimeNavigationView_snvSubheaderItemTypefaceStyle, Typeface.NORMAL); switch (subheaderItemTypefaceStyle) { case 1: subheaderItemTypefaceStyle = Typeface.BOLD; break; case 2: subheaderItemTypefaceStyle = Typeface.ITALIC; break; case 3: subheaderItemTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL subheaderItemTypefaceStyle = Typeface.NORMAL; break; } } if (a.hasValue(R.styleable.SublimeNavigationView_snvSubheaderHintTypefaceStyle)) { subheaderHintTypefaceStyle = a.getInt( R.styleable.SublimeNavigationView_snvSubheaderHintTypefaceStyle, Typeface.NORMAL); switch (subheaderHintTypefaceStyle) { case 1: subheaderHintTypefaceStyle = Typeface.BOLD; break; case 2: subheaderHintTypefaceStyle = Typeface.ITALIC; break; case 3: subheaderHintTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL subheaderHintTypefaceStyle = Typeface.NORMAL; break; } } if (a.hasValue(R.styleable.SublimeNavigationView_snvBadgeTypefaceStyle)) { badgeTypefaceStyle = a.getInt( R.styleable.SublimeNavigationView_snvBadgeTypefaceStyle, Typeface.NORMAL); switch (badgeTypefaceStyle) { case 1: badgeTypefaceStyle = Typeface.BOLD; break; case 2: badgeTypefaceStyle = Typeface.ITALIC; break; case 3: badgeTypefaceStyle = Typeface.BOLD_ITALIC; break; default: // case 0: NORMAL badgeTypefaceStyle = Typeface.NORMAL; break; } } // Item text styling TextViewStyleProfile itemStyleProfile = new TextViewStyleProfile(context, defaultTheme); itemStyleProfile.setTextColor(itemTextColor) .setTypeface(itemTypeface) .setTypefaceStyle(itemTypefaceStyle); mThemer.setItemStyleProfile(itemStyleProfile); // Hint text styling TextViewStyleProfile hintStyleProfile = new TextViewStyleProfile(context, defaultTheme); hintStyleProfile.setTextColor(hintTextColor) .setTypeface(hintTypeface) .setTypefaceStyle(hintTypefaceStyle); mThemer.setItemHintStyleProfile(hintStyleProfile); // Sub-header item text styling TextViewStyleProfile subheaderItemStyleProfile = new TextViewStyleProfile(context, defaultTheme); subheaderItemStyleProfile.setTextColor(subheaderItemTextColor) .setTypeface(subheaderItemTypeface) .setTypefaceStyle(subheaderItemTypefaceStyle); mThemer.setSubheaderStyleProfile(subheaderItemStyleProfile); // Sub-header hint text styling TextViewStyleProfile subheaderHintStyleProfile = new TextViewStyleProfile(context, defaultTheme); subheaderHintStyleProfile.setTextColor(subheaderHintTextColor) .setTypeface(subheaderHintTypeface) .setTypefaceStyle(subheaderHintTypefaceStyle); mThemer.setSubheaderHintStyleProfile(subheaderHintStyleProfile); // Badge text styling TextViewStyleProfile badgeStyleProfile = new TextViewStyleProfile(context, defaultTheme); badgeStyleProfile.setTextColor(badgeTextColor) .setTypeface(badgeTypeface) .setTypefaceStyle(badgeTypefaceStyle); mThemer.setBadgeStyleProfile(badgeStyleProfile); mThemer.setItemBackground(a.getDrawable( R.styleable.SublimeNavigationView_snvItemBackground)); if (a.hasValue(R.styleable.SublimeNavigationView_snvMenu)) { int menuResId = a.getResourceId(R.styleable.SublimeNavigationView_snvMenu, -1); if (menuResId == -1) { throw new RuntimeException("Passed menuResId was not valid"); } mMenu = new SublimeMenu(menuResId); inflateMenu(menuResId); } mMenu.setCallback(new SublimeMenu.Callback() { public boolean onMenuItemSelected(SublimeMenu menu, SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event) { return SublimeNavigationView.this.mEventListener != null && SublimeNavigationView.this .mEventListener.onNavigationMenuEvent(event, item); } }); mPresenter = new SublimeMenuPresenter(); applyThemer(); mMenu.setMenuPresenter(getContext(), mPresenter); addView(mPresenter.getMenuView(this)); if (a.hasValue(R.styleable.SublimeNavigationView_snvHeaderLayout)) { inflateHeaderView(a.getResourceId(R.styleable.SublimeNavigationView_snvHeaderLayout, 0)); } } finally { a.recycle(); } // Upon creation, and until initializations are done, // SublimeMenuPresenter blocks all calls for invalidation. // We can now finalize the initialization phase to allow // invalidation of the menu when required. mPresenter.setInitializationDone(); } /** * Provides a mechanism for switching between any number of Menus. * * @param newMenuResId id of the menu that you wish * to switch to. Eg: R.menu.new_menu_id */ public void switchMenuTo(@MenuRes int newMenuResId) { if (newMenuResId < 1) { Log.e(TAG, "Could not switch to new menu: passed menuResourceId was invalid."); return; } mMenu = new SublimeMenu(newMenuResId); inflateMenu(newMenuResId); mMenu.setCallback(new SublimeMenu.Callback() { public boolean onMenuItemSelected(SublimeMenu menu, SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event) { return SublimeNavigationView.this.mEventListener != null && SublimeNavigationView.this .mEventListener.onNavigationMenuEvent(event, item); } }); mMenu.setMenuPresenter(getContext(), mPresenter); } /** * Provides a mechanism for switching between any number of Menus. * * @param newMenu Typically, this would * have been returned from a former call to * {@link SublimeNavigationView#getMenu()}. */ public void switchMenuTo(@NonNull SublimeMenu newMenu) { // Todo: pending removal of this NULL check if (newMenu == null) { Log.e(TAG, "Could not switch to new menu: passed menu was 'null'."); return; } mMenu = newMenu; mMenu.setCallback(new SublimeMenu.Callback() { public boolean onMenuItemSelected(SublimeMenu menu, SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event) { return SublimeNavigationView.this.mEventListener != null && SublimeNavigationView.this .mEventListener.onNavigationMenuEvent(event, item); } }); mMenu.setMenuPresenter(getContext(), mPresenter); } /** * Returns the currently set header view. * * @return Currently set header {@link View}. Null, if the header * is not set. */ public View getHeaderView() { return mPresenter.getHeaderView(); } @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SublimeNavigationView.SavedState state = new SublimeNavigationView.SavedState(superState); state.getMenuState().putParcelable(SS_MENU, mMenu); return state; } @Override protected void onRestoreInstanceState(Parcelable savedState) { SublimeNavigationView.SavedState state = (SublimeNavigationView.SavedState) savedState; super.onRestoreInstanceState(state.getSuperState()); Bundle menuState = state.getMenuState(); if (menuState != null && menuState.containsKey(SS_MENU)) { mMenu = menuState.getParcelable(SS_MENU); } if (mMenu != null) { mMenu.setCallback(new SublimeMenu.Callback() { public boolean onMenuItemSelected(SublimeMenu menu, SublimeBaseMenuItem item, OnNavigationMenuEventListener.Event event) { return SublimeNavigationView.this.mEventListener != null && SublimeNavigationView.this .mEventListener.onNavigationMenuEvent(event, item); } }); mMenu.setMenuPresenter(getContext(), mPresenter); } } /** * Sets the listener. * * @param listener Listener */ public void setNavigationMenuEventListener(OnNavigationMenuEventListener listener) { mEventListener = listener; } @Override protected void onMeasure(int widthSpec, int heightSpec) { switch (View.MeasureSpec.getMode(widthSpec)) { case View.MeasureSpec.AT_MOST: widthSpec = View.MeasureSpec.makeMeasureSpec(Math.min(View.MeasureSpec.getSize(widthSpec), mMaxWidth), View.MeasureSpec.EXACTLY); break; case View.MeasureSpec.UNSPECIFIED: widthSpec = View.MeasureSpec.makeMeasureSpec(mMaxWidth, View.MeasureSpec.EXACTLY); case View.MeasureSpec.EXACTLY: } super.onMeasure(widthSpec, heightSpec); } /** * Inflates menu resource with the given ID into the * current {@link SublimeMenu} item. * * @param menuResId ID of the resource to inflate. */ private void inflateMenu(@MenuRes int menuResId) { getMenuInflater().inflate(menuResId, mMenu); } /** * Returns the current {@link SublimeMenu} item. * * @return current {@link SublimeMenu}. */ @NonNull public SublimeMenu getMenu() { return mMenu; } /** * Inflates and returns the view resource with the given ID. * * @param res view resource ID to inflate. * @return inflated {@link View}. */ private View inflateHeaderView(@LayoutRes int res) { return mPresenter.inflateHeaderView(res); } /** * Sets the given {@link View} as the header this Menu. * * @param view the {@link View} to add as header. */ public void addHeaderView(@NonNull View view) { mPresenter.addHeaderView(view); } /** * Removes the given {@link View} from the header. * * @param view the {@link View} to remove from header. */ public void removeHeaderView(@NonNull View view) { mPresenter.removeHeaderView(view); } /** * Used internally to create a custom inflater for xml * definition of a {@link SublimeMenu}. * * @return inflater that can work with xml definition * of a {@link SublimeMenu}. */ @NonNull private SublimeMenuInflater getMenuInflater() { if (mMenuInflater == null) { mMenuInflater = new SublimeMenuInflater(getContext()); } return mMenuInflater; } /** * Returns the currently used {@link SublimeThemer}. * Note that several method that provide access to * theme elements has been removed from {@link SublimeNavigationView}. * For example, to gain access to the currently used * Icon Tint List (accessible through 'NavigationView#getItemIconTintList()'), * call {@link SublimeThemer#getIconTintList()}. * * @return Currently used {@link SublimeThemer}. */ @NonNull public SublimeThemer getCurrentThemer() { return mThemer; } /** * Style navigation view components through java (instead of xml). * Note that {@link SublimeThemer} is currently not * preserved when saving state. * * @param sublimeThemer A valid {@link SublimeThemer}. Method does nothing * if this parameter is NULL. */ public void updateThemer(@NonNull SublimeThemer sublimeThemer) { // Todo: pending removal of this NULL check if (sublimeThemer == null) { Log.e(TAG, "'updateThemer(SublimeThemer)' was called with a 'null' value"); return; } mThemer = sublimeThemer; applyThemer(); } /** * Used internally to apply the currently set {@link SublimeThemer}. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void applyThemer() { Log.i(TAG, "applyThemer()"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { setBackground(mThemer.getDrawerBackground()); } else { setBackgroundDrawable(mThemer.getDrawerBackground()); } ViewCompat.setElevation(this, mThemer.getElevation()); // propagate themer to the presenter mPresenter.setThemer(mThemer); } //----------------------------------------------------------------// //------------------------Preserving State------------------------// //----------------------------------------------------------------// public static class SavedState extends View.BaseSavedState { public Bundle sMenuState; public static final Creator CREATOR = new Creator() { public SublimeNavigationView.SavedState createFromParcel(Parcel parcel) { return new SublimeNavigationView.SavedState(parcel); } public SublimeNavigationView.SavedState[] newArray(int size) { return new SublimeNavigationView.SavedState[size]; } }; public SavedState(Parcel in) { super(in); sMenuState = in.readBundle(); } public SavedState(Parcelable superState) { super(superState); sMenuState = new Bundle(); } public Bundle getMenuState() { return sMenuState; } public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeBundle(sMenuState); } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeSeparatorItemView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; /** * View implementation for Separator menu item. * * Created by Vikram. */ public class SublimeSeparatorItemView extends SublimeBaseItemView { public SublimeSeparatorItemView(Context context) { this(context, null); } public SublimeSeparatorItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SublimeSeparatorItemView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LayoutInflater.from(context).inflate(R.layout.sublime_menu_separator_item_content, this, true); } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeSeparatorMenuItem.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; /** * Separator menu item implementation. * * Created by Vikram. */ public class SublimeSeparatorMenuItem extends SublimeBaseMenuItem { private static final String EMPTY_STRING = ""; public SublimeSeparatorMenuItem(SublimeMenu menu, int group, int id) { super(menu, group, id, EMPTY_STRING, EMPTY_STRING, ItemType.SEPARATOR, false, false); } // Restores state // public SublimeSeparatorMenuItem(int group, int id) { super(group, id, EMPTY_STRING, EMPTY_STRING, NO_ICON, ItemType.SEPARATOR, false, false, ENABLED); } @Override public boolean invoke() { return false; } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeSubheaderItemView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.support.v4.graphics.drawable.DrawableCompat; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; /** * View implementation for Subheader menu item. * * Created by Vikram. */ public class SublimeSubheaderItemView extends SublimeBaseItemView { ImageView mExpandCollapse; Drawable mExpandDrawable, mCollapseDrawable; public SublimeSubheaderItemView(Context context) { this(context, null); } public SublimeSubheaderItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SublimeSubheaderItemView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LayoutInflater.from(context).inflate(R.layout.sublime_menu_subheader_item_content, this, true); initializeViews(); } @Override protected void initializeViews() { super.initializeViews(); mExpandCollapse = (ImageView) findViewById(R.id.expand_collapse); } private void initializeDrawables(SublimeThemer themer) { mExpandDrawable = themer.getGroupExpandDrawable(); mCollapseDrawable = themer.getGroupCollapseDrawable(); } public void initialize(SublimeBaseMenuItem itemData, SublimeGroup groupData, SublimeThemer themer) { initializeDrawables(themer); // Subheader Item styling TextViewStyleProfile subheaderItemStyleProfile = themer.getSubheaderStyleProfile(); setSubheaderItemTextColor(subheaderItemStyleProfile.getTextColor()); if (subheaderItemStyleProfile.getTypeface() != null) { setSubheaderItemTypeface(subheaderItemStyleProfile.getTypeface(), subheaderItemStyleProfile.getTypefaceStyle()); } else { setSubheaderItemTypefaceStyle(subheaderItemStyleProfile.getTypefaceStyle()); } // Subheader Hint styling TextViewStyleProfile subheaderHintStyleProfile = themer.getSubheaderHintStyleProfile(); setSubheaderHintTextColor(subheaderHintStyleProfile.getTextColor()); if (subheaderHintStyleProfile.getTypeface() != null) { setSubheaderHintTypeface(subheaderHintStyleProfile.getTypeface(), subheaderHintStyleProfile.getTypefaceStyle()); } else { setSubheaderHintTypefaceStyle(subheaderHintStyleProfile.getTypefaceStyle()); } super.initialize(itemData, themer); setExpandCollapseIconVisibility(groupData.isCollapsible()); setExpandCollapseIconState(groupData.isCollapsed()); } @Override public void setIconTintList(ColorStateList tintList) { mExpandDrawable = DrawableCompat.wrap(mExpandDrawable .getConstantState().newDrawable()).mutate(); DrawableCompat.setTintList(mExpandDrawable, tintList); mCollapseDrawable = DrawableCompat.wrap(mCollapseDrawable .getConstantState().newDrawable()).mutate(); DrawableCompat.setTintList(mCollapseDrawable, tintList); super.setIconTintList(tintList); } @Override public void setItemTextColor(ColorStateList textColor) { // Block this call } @Override public void setHintTextColor(ColorStateList hintTextColor) { // Block this call } @Override public void setItemTypeface(Typeface itemTypeface, int itemTypefaceStyle) { // Block this call } @Override public void setHintTypeface(Typeface hintTypeface, int hintTypefaceStyle) { // Block this call } @Override public void setItemTypefaceStyle(int itemTypefaceStyle) { // Block this call } @Override public void setHintTypefaceStyle(int hintTypefaceStyle) { // Block this call } public void setSubheaderItemTextColor(ColorStateList subheaderTextColor) { mText.setTextColor(subheaderTextColor); } public void setSubheaderHintTextColor(ColorStateList subheaderHintTextColor) { mHint.setTextColor(subheaderHintTextColor); } public void setSubheaderItemTypeface(Typeface itemTypeface, int itemTypefaceStyle) { mText.setTypeface(itemTypeface, itemTypefaceStyle); } public void setSubheaderHintTypeface(Typeface hintTypeface, int hintTypefaceStyle) { mHint.setTypeface(hintTypeface, hintTypefaceStyle); } public void setSubheaderItemTypefaceStyle(int itemTypefaceStyle) { mText.setTypeface(mText.getTypeface(), itemTypefaceStyle); } public void setSubheaderHintTypefaceStyle(int hintTypefaceStyle) { mHint.setTypeface(mHint.getTypeface(), hintTypefaceStyle); } private void setExpandCollapseIconVisibility(boolean visible) { mExpandCollapse.setVisibility(visible ? View.VISIBLE : View.GONE); } private void setExpandCollapseIconState(boolean collapsed) { mExpandCollapse.setImageDrawable( collapsed ? mExpandDrawable : mCollapseDrawable ); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); mExpandCollapse.setEnabled(enabled); } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeSwitchItemView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.v4.content.ContextCompat; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.widget.SwitchCompat; import android.util.AttributeSet; import android.view.LayoutInflater; /** * View implementation for Switch menu item. * * Created by Vikram. */ public class SublimeSwitchItemView extends SublimeBaseItemView { private SwitchCompat mSwitch; public SublimeSwitchItemView(Context context) { this(context, null); } public SublimeSwitchItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SublimeSwitchItemView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LayoutInflater.from(context).inflate(R.layout.sublime_menu_switch_item_content, this, true); initializeViews(); } @Override protected void initializeViews() { super.initializeViews(); mSwitch = (SwitchCompat) findViewById(R.id.switch_ctrl); } @Override public void initialize(SublimeBaseMenuItem itemData, SublimeThemer themer) { setCheckableItemTintList(themer.getCheckableItemTintList()); super.initialize(itemData, themer); } @Override public void setItemTextColor(ColorStateList textColor) { super.setItemTextColor(textColor); mSwitch.setTextColor(textColor); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); mSwitch.setEnabled(enabled); } @Override public void setItemChecked(boolean checked) { super.setItemChecked(checked); mSwitch.setChecked(checked); } public void setCheckableItemTintList(ColorStateList checkableItemTintList) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Covers android M (23) Drawable dTrack = getResources().getDrawable(R.drawable.snv_switch_track_material, getContext().getTheme()); Drawable dThumb = getResources().getDrawable(R.drawable.snv_switch_thumb_material_anim, getContext().getTheme()); if (dTrack != null && dThumb != null) { DrawableCompat.setTintList(dTrack, checkableItemTintList); DrawableCompat.setTintList(dThumb, checkableItemTintList); mSwitch.setTrackDrawable(dTrack); mSwitch.setThumbDrawable(dThumb); } } else { Drawable dTrack = ContextCompat.getDrawable(getContext(), R.drawable.snv_switch_track); Drawable dThumb = ContextCompat.getDrawable(getContext(), R.drawable.switch_thumb_pre_lollipop); if (dTrack != null && dThumb != null) { dTrack = DrawableCompat.wrap(dTrack); DrawableCompat.setTintList(dTrack, checkableItemTintList); dTrack.setAlpha(85 /* 0.3f */); dThumb = DrawableCompat.wrap(dThumb); DrawableCompat.setTintList(dThumb, checkableItemTintList); mSwitch.setTrackDrawable(dTrack); mSwitch.setThumbDrawable(dThumb); } } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeSwitchMenuItem.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; /** * Switch menu item implementation. */ public class SublimeSwitchMenuItem extends SublimeBaseMenuItem { public SublimeSwitchMenuItem(SublimeMenu menu, int group, int id, CharSequence title, CharSequence hint, boolean valueProvidedAsync, boolean showsIconSpace) { super(menu, group, id, title, hint, ItemType.SWITCH, valueProvidedAsync, showsIconSpace); } // Restores state public SublimeSwitchMenuItem(int group, int id, CharSequence title, CharSequence hint, int iconResId, boolean valueProvidedAsync, boolean showsIconSpace, int flags) { super(group, id, title, hint, iconResId, ItemType.SWITCH, valueProvidedAsync, showsIconSpace, flags); } @Override public boolean invoke() { if (isCheckable()) { setChecked(!isChecked()); return invoke(isChecked() ? OnNavigationMenuEventListener.Event.CHECKED : OnNavigationMenuEventListener.Event.UNCHECKED, this); } else { return invoke(OnNavigationMenuEventListener.Event.CLICKED, this); } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeTextItemView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; /** * View implementation for Text menu item. * * Created by Vikram. */ public class SublimeTextItemView extends SublimeBaseItemView { public SublimeTextItemView(Context context) { this(context, null); } public SublimeTextItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SublimeTextItemView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LayoutInflater.from(context).inflate(R.layout.sublime_menu_text_item_content, this, true); initializeViews(); } @Override protected void initializeViews() { super.initializeViews(); } @Override public void initialize(SublimeBaseMenuItem itemData, SublimeThemer themer) { super.initialize(itemData, themer); } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeTextMenuItem.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; /** * Text menu item implementation. * * Created by Vikram. */ public class SublimeTextMenuItem extends SublimeBaseMenuItem { private static final String TAG = SublimeTextMenuItem.class.getSimpleName(); public SublimeTextMenuItem(SublimeMenu menu, int group, int id, CharSequence title, CharSequence hint, boolean valueProvidedAsync, boolean showsIconSpace) { super(menu, group, id, title, hint, ItemType.TEXT, valueProvidedAsync, showsIconSpace); } public SublimeTextMenuItem(int group, int id, CharSequence title, CharSequence hint, int iconResId, boolean valueProvidedAsync, boolean showsIconSpace, int flags) { super(group, id, title, hint, iconResId, ItemType.TEXT, valueProvidedAsync, showsIconSpace, flags); } @Override public boolean invoke() { return invoke(OnNavigationMenuEventListener.Event.CLICKED, this); } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeTextWithBadgeItemView.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Typeface; import android.util.AttributeSet; import android.view.LayoutInflater; import android.widget.ProgressBar; /** * View implementation for Text with Badge menu item. * * Created by Vikram. */ public class SublimeTextWithBadgeItemView extends SublimeBaseItemView { private StateAwareTextView mBadgeView; private ProgressBar mProgress; public SublimeTextWithBadgeItemView(Context context) { this(context, null); } public SublimeTextWithBadgeItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SublimeTextWithBadgeItemView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LayoutInflater.from(context) .inflate(R.layout.sublime_menu_text_with_badge_item_content, this, true); initializeViews(); } @Override protected void initializeViews() { super.initializeViews(); mBadgeView = (StateAwareTextView) findViewById(R.id.badge); mProgress = (ProgressBar) findViewById(R.id.progress); } @Override public void initialize(SublimeBaseMenuItem itemData, SublimeThemer themer) { super.initialize(itemData, themer); TextViewStyleProfile badgeStyleProfile = themer.getBadgeStyleProfile(); setBadgeTextColor(badgeStyleProfile.getTextColor()); if (badgeStyleProfile.getTypeface() != null) { setBadgeTypeface(badgeStyleProfile.getTypeface(), badgeStyleProfile.getTypefaceStyle()); } else { setBadgeTypefaceStyle(badgeStyleProfile.getTypefaceStyle()); } if (itemData.providesValueAsync()) { mBadgeView.setVisibility(GONE); mProgress.setVisibility(VISIBLE); } else { mProgress.setVisibility(GONE); mBadgeView.setVisibility(VISIBLE); mBadgeView.setText(((SublimeTextWithBadgeMenuItem) itemData).getBadgeText()); } } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); mBadgeView.setEnabled(enabled); mProgress.setEnabled(enabled); } public void setBadgeTextColor(ColorStateList textColor) { mBadgeView.setTextColor(textColor); } public void setBadgeTypeface(Typeface typeface, int typefaceStyle) { mBadgeView.setTypeface(typeface, typefaceStyle); } public void setBadgeTypefaceStyle(int typefaceStyle) { mBadgeView.setTypeface(mBadgeView.getTypeface(), typefaceStyle); } @Override public void setItemChecked(boolean checked) { super.setItemChecked(checked); mBadgeView.setItemChecked(checked); } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeTextWithBadgeMenuItem.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.os.Bundle; /** * Text with Badge menu item implementation. * * Created by Vikram. */ public class SublimeTextWithBadgeMenuItem extends SublimeBaseMenuItem { private static final String SS_BADGE_TEXT = "ss.badge.text"; private CharSequence mBadgeText; public SublimeTextWithBadgeMenuItem(SublimeMenu menu, int group, int id, CharSequence title, CharSequence hint, boolean valueProvidedAsync, CharSequence badgeText, boolean showsIconSpace) { super(menu, group, id, title, hint, ItemType.BADGE, valueProvidedAsync, showsIconSpace); mBadgeText = badgeText; } // Restores state public SublimeTextWithBadgeMenuItem(int group, int id, CharSequence title, CharSequence hint, int iconResId, boolean valueProvidedAsync, CharSequence badgeText, boolean showsIconSpace, int flags) { super(group, id, title, hint, iconResId, ItemType.BADGE, valueProvidedAsync, showsIconSpace, flags); mBadgeText = badgeText; } static SublimeTextWithBadgeMenuItem createFromBundle(Bundle bundle, int group, int id, CharSequence title, CharSequence hint, int iconResId, boolean valueProvidedAsync, boolean showsIconSpace, int flags) { String badgeText = bundle.getString(SS_BADGE_TEXT); return new SublimeTextWithBadgeMenuItem(group, id, title, hint, iconResId, valueProvidedAsync, badgeText, showsIconSpace, flags); } @Override public boolean invoke() { return invoke(OnNavigationMenuEventListener.Event.CLICKED, this); } /** * Set/change badge text. * * @param badgeText The text that should be displayed as the badge. * @return This {@link SublimeTextWithBadgeMenuItem} for chaining. */ public SublimeTextWithBadgeMenuItem setBadgeText(CharSequence badgeText) { mBadgeText = badgeText; attemptItemUpdate(); return this; } /** * Returns the text that should be displayed as the badge. * * @return text to display as the badge. */ public CharSequence getBadgeText() { return mBadgeText; } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/SublimeThemer.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.StateListDrawable; import android.support.annotation.NonNull; import android.support.v4.content.res.ResourcesCompat; import android.util.Log; import android.util.TypedValue; /** * Handles details about styling {@link SublimeNavigationView}. * * Created by Vikram. */ public class SublimeThemer { private static final String TAG = SublimeThemer.class.getSimpleName(); private static final int[] EMPTY_STATE_SET = new int[]{}; private static final int[] CHECKED_STATE_SET = new int[]{R.attr.state_item_checked}; private static final int[] CHECKABLE_ITEM_CHECKED_STATE_SET = new int[]{android.R.attr.state_checked}; private static final int[] DISABLED_STATE_SET = new int[]{-android.R.attr.state_enabled}; public enum DefaultTheme {DARK, LIGHT} private Context mContext; private ColorStateList mIconTintList, mCheckableItemTintList; private Drawable mItemBackground, mDrawerBackground; private DefaultTheme mDefaultTheme; private float mElevation; private Drawable mGroupExpandDrawable, mGroupCollapseDrawable; private TextViewStyleProfile mItemStyleProfile, mItemHintStyleProfile, mSubheaderStyleProfile, mSubheaderHintStyleProfile, mBadgeStyleProfile; public SublimeThemer(@NonNull Context context) { this(context, DefaultTheme.LIGHT); } public SublimeThemer(@NonNull Context context, @NonNull DefaultTheme defaultTheme) { mContext = context; mDefaultTheme = defaultTheme; } public SublimeThemer setIconTintList(ColorStateList iconTintList) { if (iconTintList == null) { Log.e(TAG, "'setIconTintList(ColorStateList)' was called with a 'null' value"); } mIconTintList = iconTintList; return this; } public SublimeThemer setCheckableItemTintList(ColorStateList checkableItemTintList) { if (checkableItemTintList == null) { Log.e(TAG, "'setCheckableItemTintList(ColorStateList)' was called with a 'null' value"); } mCheckableItemTintList = checkableItemTintList; return this; } public SublimeThemer setItemStyleProfile(TextViewStyleProfile itemStyleProfile) { if (itemStyleProfile == null) { Log.e(TAG, "'setItemStyleProfile(TextViewStyleProfile)' was " + "called with a 'null' value"); } mItemStyleProfile = itemStyleProfile; return this; } public SublimeThemer setItemHintStyleProfile(TextViewStyleProfile itemHintStyleProfile) { if (itemHintStyleProfile == null) { Log.e(TAG, "'setItemHintStyleProfile(TextViewStyleProfile)' was " + "called with a 'null' value"); } mItemHintStyleProfile = itemHintStyleProfile; return this; } public SublimeThemer setSubheaderStyleProfile(TextViewStyleProfile subheaderStyleProfile) { if (subheaderStyleProfile == null) { Log.e(TAG, "'setSubheaderStyleProfile(TextViewStyleProfile)' was " + "called with a 'null' value"); } mSubheaderStyleProfile = subheaderStyleProfile; return this; } public SublimeThemer setSubheaderHintStyleProfile(TextViewStyleProfile subheaderHintStyleProfile) { if (subheaderHintStyleProfile == null) { Log.e(TAG, "'setSubheaderHintStyleProfile(TextViewStyleProfile)' was " + "called with a 'null' value"); } mSubheaderHintStyleProfile = subheaderHintStyleProfile; return this; } public SublimeThemer setBadgeStyleProfile(TextViewStyleProfile badgeStyleProfile) { if (badgeStyleProfile == null) { Log.e(TAG, "'setBadgeStyleProfile(TextViewStyleProfile)' was " + "called with a 'null' value"); } mBadgeStyleProfile = badgeStyleProfile; return this; } public SublimeThemer setGroupExpandDrawable(Drawable groupExpandDrawable) { if (groupExpandDrawable == null) { Log.e(TAG, "'setGroupExpandDrawable(Drawable)' was called with a 'null' value"); } mGroupExpandDrawable = groupExpandDrawable; return this; } public SublimeThemer setGroupCollapseDrawable(Drawable groupCollapseDrawable) { if (groupCollapseDrawable == null) { Log.e(TAG, "'setGroupCollapseDrawable(Drawable)' was called with a 'null' value"); } mGroupCollapseDrawable = groupCollapseDrawable; return this; } public SublimeThemer setItemBackground(Drawable itemBackground) { if (itemBackground == null) { Log.e(TAG, "'setItemBackground(Drawable)' was called with a 'null' value"); } mItemBackground = itemBackground; return this; } public SublimeThemer setDrawerBackground(Drawable drawerBackground) { if (drawerBackground == null) { Log.e(TAG, "'setDrawerBackground(Drawable)' was called with a 'null' value"); } mDrawerBackground = drawerBackground; return this; } public SublimeThemer setElevation(float elevation) { if (elevation < 0f) { Log.e(TAG, "'setElevation(float)' was called with an invalid value"); return this; } mElevation = elevation; return this; } public ColorStateList getIconTintList() { if (mIconTintList == null) { setDefaultIconTintList(); } return mIconTintList; } public ColorStateList getCheckableItemTintList() { if (mCheckableItemTintList == null) { setDefaultCheckableItemTintList(); } return mCheckableItemTintList; } public Drawable getGroupExpandDrawable() { if (mGroupExpandDrawable == null) { mGroupExpandDrawable = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.snv_expand, mContext.getTheme()); } // Return a new drawable since this method will be // called multiple times return mGroupExpandDrawable.getConstantState().newDrawable(); } public Drawable getGroupCollapseDrawable() { if (mGroupCollapseDrawable == null) { mGroupCollapseDrawable = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.snv_collapse, mContext.getTheme()); } // Return a new drawable since this method will be // called multiple times return mGroupCollapseDrawable.getConstantState().newDrawable(); } public Drawable getItemBackground() { if (mItemBackground == null) { setDefaultItemBackground(); } return mItemBackground.getConstantState().newDrawable(); } public Drawable getDrawerBackground() { if (mDrawerBackground == null) { setDefaultDrawerBackground(); } return mDrawerBackground; } public TextViewStyleProfile getItemStyleProfile() { if (mItemStyleProfile == null) { mItemStyleProfile = new TextViewStyleProfile(mContext, mDefaultTheme); } return mItemStyleProfile; } public TextViewStyleProfile getItemHintStyleProfile() { if (mItemHintStyleProfile == null) { mItemHintStyleProfile = new TextViewStyleProfile(mContext, mDefaultTheme); } return mItemHintStyleProfile; } public TextViewStyleProfile getSubheaderStyleProfile() { if (mSubheaderStyleProfile == null) { mSubheaderStyleProfile = new TextViewStyleProfile(mContext, mDefaultTheme); } return mSubheaderStyleProfile; } public TextViewStyleProfile getSubheaderHintStyleProfile() { if (mSubheaderHintStyleProfile == null) { mSubheaderHintStyleProfile = new TextViewStyleProfile(mContext, mDefaultTheme); } return mSubheaderHintStyleProfile; } public TextViewStyleProfile getBadgeStyleProfile() { if (mBadgeStyleProfile == null) { mBadgeStyleProfile = new TextViewStyleProfile(mContext, mDefaultTheme); } return mBadgeStyleProfile; } public DefaultTheme getDefaultTheme() { return mDefaultTheme; } public float getElevation() { return mElevation; } private void setDefaultIconTintList() { TypedValue value = new TypedValue(); if (mContext.getTheme().resolveAttribute(android.R.attr.textColorPrimary, value, true)) { ColorStateList baseColor = mContext.getResources().getColorStateList(value.resourceId); if (mContext.getTheme().resolveAttribute(R.attr.colorPrimary, value, true)) { int colorPrimary = value.data; int defaultColor = baseColor.getDefaultColor(); mIconTintList = new ColorStateList(new int[][]{DISABLED_STATE_SET, CHECKED_STATE_SET, EMPTY_STATE_SET}, new int[]{baseColor.getColorForState(DISABLED_STATE_SET, defaultColor), colorPrimary, defaultColor}); } } if (mIconTintList == null) { // Defaults boolean isLightTheme = mDefaultTheme == SublimeThemer.DefaultTheme.LIGHT; int defDisabled = isLightTheme ? mContext.getResources().getColor(R.color.snv_primary_text_disabled_material_light) : mContext.getResources().getColor(R.color.snv_primary_text_disabled_material_dark); int defChecked = isLightTheme ? mContext.getResources().getColor(R.color.snv_primary_material_light) : mContext.getResources().getColor(R.color.snv_primary_material_dark); int defEmptySet = isLightTheme ? mContext.getResources().getColor(R.color.snv_primary_text_default_material_light) : mContext.getResources().getColor(R.color.snv_primary_text_default_material_dark); mIconTintList = new ColorStateList(new int[][]{DISABLED_STATE_SET, CHECKED_STATE_SET, EMPTY_STATE_SET}, new int[]{defDisabled, defChecked, defEmptySet}); } } private void setDefaultCheckableItemTintList() { TypedValue value = new TypedValue(); if (mContext.getTheme().resolveAttribute(android.R.attr.textColorPrimary, value, true)) { ColorStateList baseColor = mContext.getResources().getColorStateList(value.resourceId); if (mContext.getTheme().resolveAttribute(R.attr.colorPrimary, value, true)) { int colorPrimary = value.data; int defaultColor = baseColor.getDefaultColor(); mCheckableItemTintList = new ColorStateList(new int[][]{DISABLED_STATE_SET, CHECKABLE_ITEM_CHECKED_STATE_SET, EMPTY_STATE_SET}, new int[]{baseColor.getColorForState(DISABLED_STATE_SET, defaultColor), colorPrimary, defaultColor}); } } if (mCheckableItemTintList == null) { // Defaults boolean isLightTheme = mDefaultTheme == SublimeThemer.DefaultTheme.LIGHT; int defDisabled = isLightTheme ? mContext.getResources().getColor(R.color.snv_primary_text_disabled_material_light) : mContext.getResources().getColor(R.color.snv_primary_text_disabled_material_dark); int defChecked = isLightTheme ? mContext.getResources().getColor(R.color.snv_primary_material_light) : mContext.getResources().getColor(R.color.snv_primary_material_dark); int defEmptySet = isLightTheme ? mContext.getResources().getColor(R.color.snv_primary_text_default_material_light) : mContext.getResources().getColor(R.color.snv_primary_text_default_material_dark); mCheckableItemTintList = new ColorStateList(new int[][]{DISABLED_STATE_SET, CHECKABLE_ITEM_CHECKED_STATE_SET, EMPTY_STATE_SET}, new int[]{defDisabled, defChecked, defEmptySet}); } } private void setDefaultItemBackground() { TypedValue value = new TypedValue(); int colorControlHighlight = 0; if (mContext.getTheme().resolveAttribute(R.attr.colorControlHighlight, value, true)) { colorControlHighlight = value.data; } else { colorControlHighlight = mDefaultTheme == DefaultTheme.LIGHT ? mContext.getResources().getColor(R.color.snv_ripple_material_light) : mContext.getResources().getColor(R.color.snv_ripple_material_dark); } StateListDrawable drawable = new StateListDrawable(); LayerDrawable checked = new LayerDrawable(new Drawable[]{ new ColorDrawable(colorControlHighlight), obtainSelectableItemBackground() }); drawable.addState(CHECKED_STATE_SET, checked); drawable.addState(EMPTY_STATE_SET, obtainSelectableItemBackground()); mItemBackground = drawable; } private Drawable obtainSelectableItemBackground() { // Create an array of the attributes we want to resolve int[] attrs = new int[]{R.attr.selectableItemBackground}; // index 0 // Obtain the styled attributes TypedArray ta = mContext.obtainStyledAttributes(attrs); Drawable drawableFromTheme = null; if (ta.hasValue(0)) { // Get the value of the 'selectableItemBackground' attribute that was // set in the theme. The parameter is the index // of the attribute in the 'attrs' array. drawableFromTheme = ta.getDrawable(0); // index } // Finally free resources used by TypedArray ta.recycle(); return drawableFromTheme; } private void setDefaultDrawerBackground() { TypedValue value = new TypedValue(); if (mContext.getTheme().resolveAttribute(android.R.attr.colorBackground, value, true)) { mDrawerBackground = new ColorDrawable(value.data); } else { mDrawerBackground = new ColorDrawable(mDefaultTheme == DefaultTheme.LIGHT ? mContext.getResources().getColor(R.color.snv_background_material_light) : mContext.getResources().getColor(R.color.snv_background_material_dark)); } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/java/com/appeaser/sublimenavigationviewlibrary/TextViewStyleProfile.java ================================================ /* * Copyright 2015 Vikram Kakkar * * 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 com.appeaser.sublimenavigationviewlibrary; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.util.Log; import android.util.TypedValue; /** * Keeps track of TextView styling based * on its role: Item, Hint, Group header etc. * * Created by Vikram */ public class TextViewStyleProfile { private static final String TAG = TextViewStyleProfile.class.getSimpleName(); private static final int[] EMPTY_STATE_SET = new int[]{}; private static final int[] CHECKED_STATE_SET = new int[]{R.attr.state_item_checked}; private static final int[] DISABLED_STATE_SET = new int[]{-android.R.attr.state_enabled}; private ColorStateList mTextColor; private Typeface mTypeface; private int mTypefaceStyle = Typeface.NORMAL; private Context mContext; // Used to create the default colors. private SublimeThemer.DefaultTheme mDefaultTheme; public TextViewStyleProfile(@NonNull Context context, @NonNull SublimeThemer.DefaultTheme defaultTheme) { mContext = context; mDefaultTheme = defaultTheme; } /** * Sets the text color to the given {@link ColorStateList}. * 'null' is a valid argument, in which case, a default * text color will be used. * See {@link TextViewStyleProfile#setDefaultItemTextColor()} * for details about the default text color. * * @param textColor The text color to use. * @return This {@link TextViewStyleProfile} for chaining. */ public TextViewStyleProfile setTextColor(ColorStateList textColor) { if (textColor == null) { Log.e(TAG, "'setTextColor(ColorStateList)' was called with a 'null' value"); } mTextColor = textColor; return this; } /** * Sets the typeface to the given value. 'null' is valid argument, * in which case, the system default typeface will be used. * * @param typeface The typeface to use. * @return This {@link TextViewStyleProfile} for chaining. */ public TextViewStyleProfile setTypeface(Typeface typeface) { if (typeface == null) { Log.e(TAG, "'setTypeface(Typeface)' was called with a 'null' value"); } mTypeface = typeface; return this; } /** * Sets the typeface style to the given value. The typeface style * defaults to Typeface.NORMAL if the value isn't one of * Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC. * * @param typefaceStyle The style to set. * @return This {@link TextViewStyleProfile} for chaining. */ public TextViewStyleProfile setTypefaceStyle(int typefaceStyle) { if (typefaceStyle < Typeface.NORMAL || typefaceStyle > Typeface.BOLD_ITALIC) { Log.e(TAG, "'setTypefaceStyle(int)' was called with a invalid value " + "- allowed values are Typeface.NORMAL, Typeface.BOLD, " + "Typeface.ITALIC and Typeface.BOLD_ITALIC."); mTypefaceStyle = Typeface.NORMAL; } else { mTypefaceStyle = typefaceStyle; } return this; } /** * Returns the currently set text color. * * @return {@link ColorStateList} that will be used * as the text color. */ public ColorStateList getTextColor() { if (mTextColor == null) { setDefaultItemTextColor(); } return mTextColor; } /** * Returns the (custom) Typeface. * * @return given Typeface. May be 'null'. */ public Typeface getTypeface() { return mTypeface; } /** * Returns the currently set text style: normal, bold, * italic, bold_italic. * * @return one of Typeface.NORMAL, Typeface.BOLD, * Typeface.ITALIC, Typeface.BOLD_ITALIC. */ public int getTypefaceStyle() { return mTypefaceStyle; } /** * Creates and sets the default item text color. */ private void setDefaultItemTextColor() { TypedValue value = new TypedValue(); if (mContext.getTheme().resolveAttribute(android.R.attr.textColorPrimary, value, true)) { ColorStateList baseColor = mContext.getResources().getColorStateList(value.resourceId); if (mContext.getTheme().resolveAttribute(R.attr.colorPrimary, value, true)) { int colorPrimary = value.data; int defaultColor = baseColor.getDefaultColor(); mTextColor = new ColorStateList(new int[][]{DISABLED_STATE_SET, CHECKED_STATE_SET, EMPTY_STATE_SET}, new int[]{baseColor.getColorForState(DISABLED_STATE_SET, defaultColor), colorPrimary, defaultColor}); } } if (mTextColor == null) { // Defaults boolean isLightTheme = mDefaultTheme == SublimeThemer.DefaultTheme.LIGHT; int defDisabled = isLightTheme ? mContext.getResources().getColor(R.color.snv_primary_text_disabled_material_light) : mContext.getResources().getColor(R.color.snv_primary_text_disabled_material_dark); int defChecked = isLightTheme ? mContext.getResources().getColor(R.color.snv_primary_material_light) : mContext.getResources().getColor(R.color.snv_primary_material_dark); int defEmptySet = isLightTheme ? mContext.getResources().getColor(R.color.snv_primary_text_default_material_light) : mContext.getResources().getColor(R.color.snv_primary_text_default_material_dark); mTextColor = new ColorStateList(new int[][]{DISABLED_STATE_SET, CHECKED_STATE_SET, EMPTY_STATE_SET}, new int[]{defDisabled, defChecked, defEmptySet}); } } } ================================================ FILE: sublimenavigationviewlibrary/src/main/res/color-v23/snv_c_switch_track_material.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/color-v23/white_disabled_material.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/drawable/checkbox_pre_lollipop.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/drawable/switch_thumb_pre_lollipop.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/drawable-v21/snv_switch_thumb_material_anim.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/drawable-v21/snv_switch_track_material.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/drawable-v23/snv_switch_track_material.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/include_icon_holder.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/include_text_hint_content.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_checkbox_item_view.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_menu_checkbox_item_content.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_menu_header_item.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_menu_separator_item_content.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_menu_subheader_item_content.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_menu_switch_item_content.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_menu_text_item_content.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_menu_text_with_badge_item_content.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_navigation_menu_view.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_separator_item_view.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_subheader_item_view.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_switch_item_view.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_text_item_view.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/layout/sublime_text_with_badge_item_view.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/values/attrs.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/main/res/values/colors.xml ================================================ #24000000 #36ffffff #ff212121 #fff5f5f5 #8a000000 #b3ffffff #1f000000 #42ffffff #fffafafa #ff303030 #de000000 #ffffffff #39000000 #4Dffffff ================================================ FILE: sublimenavigationviewlibrary/src/main/res/values/dimens.xml ================================================ 32dp 24dp 12dp 320dp 0dp 8dp 8dp ================================================ FILE: sublimenavigationviewlibrary/src/main/res/values/strings.xml ================================================ SublimeNavigationViewLibrary ================================================ FILE: sublimenavigationviewlibrary/src/main/res/values/styles.xml ================================================ ================================================ FILE: sublimenavigationviewlibrary/src/test/java/com/appeaser/sublimenavigationviewlibrary/ExampleUnitTest.java ================================================ package com.appeaser.sublimenavigationviewlibrary; import org.junit.Test; import static org.junit.Assert.*; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: sublimenavigationviewlibrary/sublimenavigationviewlibrary.iml ================================================