├── .config └── dotnet-tools.json ├── .editorconfig ├── .github └── workflows │ ├── build.yml │ └── release-nuget.yml ├── .gitignore ├── .mailmap ├── Directory.build.props ├── Directory.build.targets ├── GitVersion.yml ├── LICENSE ├── Library ├── CirclePageIndicator.cs ├── Extensions │ └── JavaObjectExtensions.cs ├── IconPageIndicator.cs ├── IcsLinearLayout.cs ├── Interfaces │ ├── IIconPageAdapter.cs │ └── IPageIndicator.cs ├── Library.csproj ├── LinePageIndicator.cs ├── Resources │ ├── color │ │ ├── vpi__dark_theme.xml │ │ └── vpi__light_theme.xml │ ├── drawable-hdpi │ │ ├── vpi__tab_selected_focused_holo.9.png │ │ ├── vpi__tab_selected_holo.9.png │ │ ├── vpi__tab_selected_pressed_holo.9.png │ │ ├── vpi__tab_unselected_focused_holo.9.png │ │ ├── vpi__tab_unselected_holo.9.png │ │ └── vpi__tab_unselected_pressed_holo.9.png │ ├── drawable-mdpi │ │ ├── vpi__tab_selected_focused_holo.9.png │ │ ├── vpi__tab_selected_holo.9.png │ │ ├── vpi__tab_selected_pressed_holo.9.png │ │ ├── vpi__tab_unselected_focused_holo.9.png │ │ ├── vpi__tab_unselected_holo.9.png │ │ └── vpi__tab_unselected_pressed_holo.9.png │ ├── drawable-xhdpi │ │ ├── vpi__tab_selected_focused_holo.9.png │ │ ├── vpi__tab_selected_holo.9.png │ │ ├── vpi__tab_selected_pressed_holo.9.png │ │ ├── vpi__tab_unselected_focused_holo.9.png │ │ ├── vpi__tab_unselected_holo.9.png │ │ └── vpi__tab_unselected_pressed_holo.9.png │ ├── drawable │ │ └── vpi__tab_indicator.xml │ ├── values-v11 │ │ └── vpi__styles.xml │ └── values │ │ ├── vpi__attrs.xml │ │ ├── vpi__colors.xml │ │ ├── vpi__defaults.xml │ │ └── vpi__styles.xml ├── TabPageIndicator.cs ├── TitlePageIndicator.cs ├── UnderlinePageIndicator.cs └── ViewPagerIndicatorEventHandlers.cs ├── README.md ├── Sample ├── ActivityListItem.cs ├── AndroidManifest.xml ├── Assets │ └── AboutAssets.txt ├── BaseSampleActivity.cs ├── Circles │ ├── SampleCirclesDefault.cs │ ├── SampleCirclesInitialPage.cs │ ├── SampleCirclesSnap.cs │ ├── SampleCirclesStyledLayout.cs │ ├── SampleCirclesStyledMethods.cs │ ├── SampleCirclesStyledTheme.cs │ └── SampleCirclesWithListener.cs ├── Icons │ └── SampleIconsDefault.cs ├── Lines │ ├── SampleLinesDefault.cs │ ├── SampleLinesStyledLayout.cs │ ├── SampleLinesStyledMethods.cs │ └── SampleLinesStyledTheme.cs ├── ListSamples.cs ├── Resources │ ├── AboutResources.txt │ ├── Drawable │ │ └── Icon.png │ ├── Values │ │ └── Strings.xml │ ├── drawable-hdpi │ │ ├── custom_tab_indicator_divider.9.png │ │ ├── custom_tab_indicator_focused.9.png │ │ ├── custom_tab_indicator_selected.9.png │ │ ├── custom_tab_indicator_selected_pressed.9.png │ │ ├── custom_tab_indicator_unselected.9.png │ │ ├── custom_tab_indicator_unselected_focused.9.png │ │ ├── custom_tab_indicator_unselected_pressed.9.png │ │ ├── icon.png │ │ ├── perm_group_calendar_normal.png │ │ ├── perm_group_calendar_selected.png │ │ ├── perm_group_camera_normal.png │ │ ├── perm_group_camera_selected.png │ │ ├── perm_group_device_alarms_normal.png │ │ ├── perm_group_device_alarms_selected.png │ │ ├── perm_group_location_normal.png │ │ └── perm_group_location_selected.png │ ├── drawable-mdpi │ │ ├── custom_tab_indicator_divider.9.png │ │ ├── custom_tab_indicator_selected.9.png │ │ ├── custom_tab_indicator_selected_focused.9.png │ │ ├── custom_tab_indicator_selected_pressed.9.png │ │ ├── custom_tab_indicator_unselected.9.png │ │ ├── custom_tab_indicator_unselected_focused.9.png │ │ ├── custom_tab_indicator_unselected_pressed.9.png │ │ ├── icon.png │ │ ├── perm_group_calendar_normal.png │ │ ├── perm_group_calendar_selected.png │ │ ├── perm_group_camera_normal.png │ │ ├── perm_group_camera_selected.png │ │ ├── perm_group_device_alarms_normal.png │ │ ├── perm_group_device_alarms_selected.png │ │ ├── perm_group_location_normal.png │ │ └── perm_group_location_selected.png │ ├── drawable-xhdpi │ │ ├── custom_tab_indicator_divider.9.png │ │ ├── custom_tab_indicator_selected.9.png │ │ ├── custom_tab_indicator_selected_focused.9.png │ │ ├── custom_tab_indicator_selected_pressed.9.png │ │ ├── custom_tab_indicator_unselected.9.png │ │ ├── custom_tab_indicator_unselected_focused.9.png │ │ ├── custom_tab_indicator_unselected_pressed.9.png │ │ ├── icon.png │ │ ├── perm_group_calendar_normal.png │ │ ├── perm_group_calendar_selected.png │ │ ├── perm_group_camera_normal.png │ │ ├── perm_group_camera_selected.png │ │ ├── perm_group_device_alarms_normal.png │ │ ├── perm_group_device_alarms_selected.png │ │ ├── perm_group_location_normal.png │ │ └── perm_group_location_selected.png │ ├── drawable │ │ ├── custom_tab_indicator.xml │ │ ├── perm_group_calendar.xml │ │ ├── perm_group_camera.xml │ │ ├── perm_group_device_alarms.xml │ │ └── perm_group_location.xml │ ├── layout │ │ ├── simple_circles.xml │ │ ├── simple_circles_vertical.xml │ │ ├── simple_icons.xml │ │ ├── simple_lines.xml │ │ ├── simple_tabs.xml │ │ ├── simple_titles.xml │ │ ├── simple_titles_bottom.xml │ │ ├── simple_underlines.xml │ │ ├── themed_circles.xml │ │ ├── themed_lines.xml │ │ ├── themed_titles.xml │ │ └── themed_underlines.xml │ ├── menu │ │ └── menu.xml │ ├── values-v11 │ │ └── styles.xml │ └── values │ │ └── styles.xml ├── Sample.csproj ├── Tabs │ ├── SampleTabsDefault.cs │ ├── SampleTabsStyled.cs │ └── SampleTabsWithIcons.cs ├── TestFragment.cs ├── TestFragmentAdapter.cs ├── Titles │ ├── SampleTitlesBottom.cs │ ├── SampleTitlesCenterClickListener.cs │ ├── SampleTitlesDefault.cs │ ├── SampleTitlesInitialPage.cs │ ├── SampleTitlesStyledLayout.cs │ ├── SampleTitlesStyledMethods.cs │ ├── SampleTitlesStyledTheme.cs │ ├── SampleTitlesTriangle.cs │ └── SampleTitlesWithListener.cs └── Underlines │ ├── SampleUnderlinesDefault.cs │ ├── SampleUnderlinesNoFade.cs │ ├── SampleUnderlinesStyledLayout.cs │ ├── SampleUnderlinesStyledMethods.cs │ └── SampleUnderlinesStyledTheme.cs ├── ViewPagerIndicator.sln ├── build.cake ├── build.ps1 ├── build.sh ├── global.json ├── icon.png └── releasenotes.md /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "cake.tool": { 6 | "version": "2.2.0", 7 | "commands": [ 8 | "dotnet-cake" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome:http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Don't use tabs for indentation. 7 | [*] 8 | indent_style = space 9 | # (Please don't specify an indent_size here; that has too many unintended consequences.) 10 | 11 | # Code files 12 | [*.{cs,csx,vb,vbx}] 13 | indent_size = 4 14 | insert_final_newline = true 15 | 16 | # Xml project files 17 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 18 | indent_size = 2 19 | 20 | # Xml config files 21 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 22 | indent_size = 2 23 | 24 | # JSON files 25 | [*.json] 26 | indent_size = 2 27 | 28 | # Dotnet code style settings: 29 | [*.{cs,vb}] 30 | # Sort using and Import directives with System.* appearing first 31 | dotnet_sort_system_directives_first = true 32 | # Avoid "this." and "Me." if not necessary 33 | dotnet_style_qualification_for_field = false:suggestion 34 | dotnet_style_qualification_for_property = false:suggestion 35 | dotnet_style_qualification_for_method = false:suggestion 36 | dotnet_style_qualification_for_event = false:suggestion 37 | 38 | # Use language keywords instead of framework type names for type references 39 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 40 | dotnet_style_predefined_type_for_member_access = true:suggestion 41 | 42 | # Suggest more modern language features when available 43 | dotnet_style_object_initializer = true:suggestion 44 | dotnet_style_collection_initializer = true:suggestion 45 | dotnet_style_coalesce_expression = true:suggestion 46 | dotnet_style_null_propagation = true:suggestion 47 | dotnet_style_explicit_tuple_names = true:suggestion 48 | 49 | # CSharp code style settings: 50 | [*.cs] 51 | # Prefer "var" everywhere 52 | csharp_style_var_for_built_in_types = true:suggestion 53 | csharp_style_var_when_type_is_apparent = true:suggestion 54 | csharp_style_var_elsewhere = true:suggestion 55 | 56 | # Prefer method-like constructs to have a block body 57 | csharp_style_expression_bodied_methods = false:none 58 | csharp_style_expression_bodied_constructors = false:none 59 | csharp_style_expression_bodied_operators = false:none 60 | 61 | # Prefer property-like constructs to have an expression-body 62 | csharp_style_expression_bodied_properties = true:none 63 | csharp_style_expression_bodied_indexers = true:none 64 | csharp_style_expression_bodied_accessors = true:none 65 | 66 | # Suggest more modern language features when available 67 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 68 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 69 | csharp_style_inlined_variable_declaration = true:suggestion 70 | csharp_style_throw_expression = true:suggestion 71 | csharp_style_conditional_delegate_call = true:suggestion 72 | 73 | # Newline settings 74 | csharp_new_line_before_open_brace = all 75 | csharp_new_line_before_else = true 76 | csharp_new_line_before_catch = true 77 | csharp_new_line_before_finally = true 78 | csharp_new_line_before_members_in_object_initializers = true 79 | csharp_new_line_before_members_in_anonymous_types = true 80 | 81 | # Braces after if 82 | csharp_prefer_braces = false 83 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | 13 | - name: Setup .NET 6 14 | uses: actions/setup-dotnet@v1 15 | with: 16 | dotnet-version: 6.0.x 17 | 18 | - name: Install iOS workload 19 | run: dotnet workload install android 20 | 21 | - name: Restore dotnet tools 22 | run: dotnet tool restore 23 | 24 | - name: Run the Cake script 25 | run: dotnet cake 26 | 27 | - uses: actions/upload-artifact@master 28 | with: 29 | name: NugetPackage 30 | path: artifacts -------------------------------------------------------------------------------- /.github/workflows/release-nuget.yml: -------------------------------------------------------------------------------- 1 | name: Release NuGet 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | inputs: 8 | runId: 9 | description: "The run id of the GitHub Action Run to publish artifacts from" 10 | required: false 11 | default: '-1' 12 | 13 | jobs: 14 | nuget-publish: 15 | runs-on: windows-latest 16 | steps: 17 | - name: Download specific artifact 18 | if: ${{ github.event.inputs.runId > 0 }} 19 | uses: dawidd6/action-download-artifact@v2 20 | with: 21 | workflow: build.yml 22 | branch: master 23 | run_id: ${{ github.event.inputs.runId }} 24 | 25 | - name: Download latest artifact 26 | if: ${{ github.event.inputs.runId == -1 }} 27 | uses: dawidd6/action-download-artifact@v2 28 | with: 29 | workflow: build.yml 30 | branch: master 31 | 32 | - shell: pwsh 33 | name: Publish NuGet Package 34 | run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Rr]elease/ 20 | x64/ 21 | *_i.c 22 | *_p.c 23 | *.ilk 24 | *.meta 25 | *.obj 26 | *.pch 27 | *.pdb 28 | *.pgc 29 | *.pgd 30 | *.rsp 31 | *.sbr 32 | *.tlb 33 | *.tli 34 | *.tlh 35 | *.tmp 36 | *.log 37 | *.vspscc 38 | *.vssscc 39 | .builds 40 | 41 | # Visual C++ cache files 42 | ipch/ 43 | *.aps 44 | *.ncb 45 | *.opensdf 46 | *.sdf 47 | 48 | # Visual Studio profiler 49 | *.psess 50 | *.vsp 51 | *.vspx 52 | 53 | # Guidance Automation Toolkit 54 | *.gpState 55 | 56 | # ReSharper is a .NET coding add-in 57 | _ReSharper* 58 | 59 | # NCrunch 60 | *.ncrunch* 61 | .*crunch*.local.xml 62 | 63 | # Installshield output folder 64 | [Ee]xpress 65 | 66 | # DocProject is a documentation generator add-in 67 | DocProject/buildhelp/ 68 | DocProject/Help/*.HxT 69 | DocProject/Help/*.HxC 70 | DocProject/Help/*.hhc 71 | DocProject/Help/*.hhk 72 | DocProject/Help/*.hhp 73 | DocProject/Help/Html2 74 | DocProject/Help/html 75 | 76 | # Click-Once directory 77 | publish 78 | 79 | # Publish Web Output 80 | *.Publish.xml 81 | 82 | # NuGet Packages Directory 83 | packages 84 | 85 | # Windows Azure Build Output 86 | csx 87 | *.build.csdef 88 | 89 | # Windows Store app package directory 90 | AppPackages/ 91 | 92 | # Others 93 | [Bb]in 94 | [Oo]bj 95 | sql 96 | TestResults 97 | [Tt]est[Rr]esult* 98 | *.Cache 99 | ClientBin 100 | [Ss]tyle[Cc]op.* 101 | ~$* 102 | *.dbmdl 103 | Generated_Code #added for RIA/Silverlight projects 104 | 105 | # Backup & report files from converting an old project file to a newer 106 | # Visual Studio version. Backup files are not needed, because we have git ;-) 107 | _UpgradeReport_Files/ 108 | Backup*/ 109 | UpgradeLog*.XML 110 | 111 | # Ignore Xamarin components 112 | [Cc]omponents/ 113 | 114 | # Xamarin.Android 115 | Resource.Designer.cs 116 | Resource.designer.cs 117 | 118 | # visual studio 119 | .vs/ 120 | 121 | # mfractor 122 | .mfractor/ 123 | 124 | # cake 125 | tools/ 126 | 127 | # build output 128 | artifacts/ 129 | 130 | # Rider 131 | .idea/ 132 | 133 | # macOS 134 | .DS_Store 135 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | 2 | Tomasz Cielecki 3 | Tomasz Cielecki 4 | -------------------------------------------------------------------------------- /Directory.build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Copyright (c) Tomasz Cielecki 4 | Apache-2.0 5 | https://github.com/Cheesebaron/ViewPagerIndicator 6 | icon.png 7 | Tomasz Cielecki 8 | cheesebaron 9 | viewpagerindicator, viewpager, android, xamarin, indicator, pager 10 | https://github.com/Cheesebaron/ViewPagerIndicator/blob/master/releasenotes.md 11 | README.md 12 | true 13 | 14 | https://github.com/Cheesebaron/ViewPagerIndicator 15 | git 16 | $(AssemblyName) ($(TargetFramework)) 17 | en 18 | 0.5.0 19 | AnyCPU 20 | 21 | $(NoWarn);1591;1701;1702;1705;VSX1000;NU1603 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | true 32 | true 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | snupkg 42 | true 43 | 44 | true 45 | true 46 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 47 | 48 | true 49 | true 50 | 51 | 52 | -------------------------------------------------------------------------------- /Directory.build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(DefineConstants);MONO;ANDROID 4 | Resources 5 | Resource 6 | Resources\Resource.designer.cs 7 | 8 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: Mainline -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright 2015 Jonathan Dick 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /Library/Extensions/JavaObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DK.Ostebaronen.Droid.ViewPagerIndicator.Extensions 4 | { 5 | internal static class JavaObjectExtensions 6 | { 7 | internal static bool IsNull(this Java.Lang.Object @object) 8 | { 9 | if (@object == null) 10 | return true; 11 | 12 | if (@object.Handle == IntPtr.Zero) 13 | return true; 14 | 15 | return false; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Library/IconPageIndicator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Android.Content; 3 | using Android.Runtime; 4 | using Android.Util; 5 | using Android.Views; 6 | using Android.Widget; 7 | using AndroidX.ViewPager.Widget; 8 | using DK.Ostebaronen.Droid.ViewPagerIndicator.Extensions; 9 | using DK.Ostebaronen.Droid.ViewPagerIndicator.Interfaces; 10 | using Java.Lang; 11 | 12 | namespace DK.Ostebaronen.Droid.ViewPagerIndicator 13 | { 14 | [Register("dk.ostebaronen.droid.viewpagerindicator.IconPageIndicator")] 15 | public class IconPageIndicator 16 | : HorizontalScrollView 17 | , IPageIndicator 18 | { 19 | private readonly IcsLinearLayout _iconsLayout; 20 | 21 | private ViewPager _viewPager; 22 | private ViewPager.IOnPageChangeListener _listener; 23 | private Runnable _iconSelector; 24 | private int _selectedIndex; 25 | 26 | public event PageScrollStateChangedEventHandler PageScrollStateChanged; 27 | public event PageSelectedEventHandler PageSelected; 28 | public event PageScrolledEventHandler PageScrolled; 29 | 30 | public IconPageIndicator(Context context) 31 | : this(context, null) 32 | { 33 | } 34 | 35 | public IconPageIndicator(Context context, IAttributeSet attrs) 36 | : base(context, attrs) 37 | { 38 | HorizontalScrollBarEnabled = false; 39 | 40 | _iconsLayout = new IcsLinearLayout(context, Resource.Attribute.vpiIconPageIndicatorStyle); 41 | AddView(_iconsLayout, new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.MatchParent, GravityFlags.Center)); 42 | } 43 | 44 | private ViewPager ViewPager 45 | { 46 | get 47 | { 48 | if (_viewPager.IsNull()) 49 | return null; 50 | 51 | return _viewPager; 52 | } 53 | } 54 | 55 | private void AnimateToIcon(int position) 56 | { 57 | var iconView = _iconsLayout.GetChildAt(position); 58 | if (iconView == null) 59 | return; 60 | 61 | if (_iconSelector != null) 62 | RemoveCallbacks(_iconSelector); 63 | 64 | _iconSelector = new Runnable(() => 65 | { 66 | var scrollPos = iconView.Left - (Width - iconView.Width) / 2; 67 | SmoothScrollTo(scrollPos, 0); 68 | _iconSelector = null; 69 | }); 70 | Post(_iconSelector); 71 | } 72 | 73 | protected override void OnAttachedToWindow() 74 | { 75 | base.OnAttachedToWindow(); 76 | if (_iconSelector != null) 77 | Post(_iconSelector); 78 | } 79 | 80 | protected override void OnDetachedFromWindow() 81 | { 82 | base.OnDetachedFromWindow(); 83 | if (_iconSelector != null) 84 | RemoveCallbacks(_iconSelector); 85 | } 86 | 87 | public void SetViewPager(ViewPager view) 88 | { 89 | if (_viewPager == view) return; 90 | 91 | if (null != ViewPager) 92 | _viewPager.ClearOnPageChangeListeners(); 93 | 94 | if (null == view.Adapter) 95 | throw new InvalidOperationException("ViewPager does not have an Adapter instance."); 96 | 97 | _viewPager = view; 98 | _viewPager.AddOnPageChangeListener(this); 99 | NotifyDataSetChanged(); 100 | } 101 | 102 | public void SetViewPager(ViewPager view, int initialPosition) 103 | { 104 | SetViewPager(view); 105 | CurrentItem = initialPosition; 106 | } 107 | 108 | public void NotifyDataSetChanged() 109 | { 110 | _iconsLayout.RemoveAllViews(); 111 | if (_viewPager.Adapter is not IIconPageAdapter iconAdapter) 112 | return; 113 | 114 | var count = iconAdapter.Count; 115 | for (var i = 0; i < count; i++) 116 | { 117 | var view = new ImageView(Context, null, Resource.Attribute.vpiIconPageIndicatorStyle); 118 | view.SetImageResource(iconAdapter.GetIconResId(i)); 119 | _iconsLayout.AddView(view); 120 | } 121 | if (_selectedIndex > count) 122 | _selectedIndex = count - 1; 123 | CurrentItem = _selectedIndex; 124 | RequestLayout(); 125 | } 126 | 127 | public void SetOnPageChangeListener(ViewPager.IOnPageChangeListener listener) { _listener = listener; } 128 | 129 | public void OnPageScrollStateChanged(int state) 130 | { 131 | _listener?.OnPageScrollStateChanged(state); 132 | 133 | PageScrollStateChanged?.Invoke(this, new PageScrollStateChangedEventArgs { State = state }); 134 | } 135 | 136 | public void OnPageScrolled(int position, float positionOffset, int positionOffsetPixels) 137 | { 138 | _listener?.OnPageScrolled(position, positionOffset, positionOffsetPixels); 139 | 140 | PageScrolled?.Invoke(this, 141 | new PageScrolledEventArgs 142 | { 143 | Position = position, 144 | PositionOffset = positionOffset, 145 | PositionOffsetPixels = positionOffsetPixels 146 | }); 147 | } 148 | 149 | public void OnPageSelected(int position) 150 | { 151 | CurrentItem = position; 152 | _listener?.OnPageSelected(position); 153 | 154 | PageSelected?.Invoke(this, new PageSelectedEventArgs { Position = position }); 155 | } 156 | 157 | public int CurrentItem 158 | { 159 | get { return _selectedIndex; } 160 | set 161 | { 162 | if (null == ViewPager) 163 | throw new InvalidOperationException("ViewPager has not been bound."); 164 | 165 | _viewPager.CurrentItem = value; 166 | _selectedIndex = value; 167 | 168 | var tabCount = _iconsLayout.ChildCount; 169 | for (var i = 0; i < tabCount; i++) 170 | { 171 | var child = _iconsLayout.GetChildAt(i); 172 | var selected = i == value; 173 | 174 | if (child != null) 175 | child.Selected = selected; 176 | 177 | if (selected) 178 | AnimateToIcon(value); 179 | } 180 | } 181 | } 182 | 183 | private bool _isDisposed; 184 | protected override void Dispose(bool disposing) 185 | { 186 | if (_isDisposed) 187 | return; 188 | 189 | if (disposing) 190 | { 191 | _iconSelector?.Dispose(); 192 | 193 | if (_viewPager != null) 194 | { 195 | _viewPager.RemoveOnPageChangeListener(this); 196 | _viewPager = null; 197 | } 198 | } 199 | 200 | _isDisposed = true; 201 | 202 | base.Dispose(disposing); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /Library/IcsLinearLayout.cs: -------------------------------------------------------------------------------- 1 | using Android.Content; 2 | using Android.Graphics; 3 | using Android.Graphics.Drawables; 4 | using Android.Views; 5 | using Android.Widget; 6 | 7 | namespace DK.Ostebaronen.Droid.ViewPagerIndicator 8 | { 9 | public class IcsLinearLayout 10 | : LinearLayout 11 | { 12 | private static readonly int[] Ll = 13 | { 14 | Android.Resource.Attribute.Divider, 15 | Android.Resource.Attribute.ShowDividers, 16 | Android.Resource.Attribute.DividerPadding 17 | }; 18 | 19 | private const int LlDivider = 0; 20 | private const int LlShowDivider = 1; 21 | private const int LlDividerPadding = 2; 22 | 23 | private Drawable _divider; 24 | private int _dividerWidth; 25 | private int _dividerHeight; 26 | private readonly int _showDividers; 27 | private readonly int _dividerPadding; 28 | 29 | public IcsLinearLayout(Context context, int themeAttr) 30 | : base(context) 31 | { 32 | using (var a = context.ObtainStyledAttributes(null, Ll, themeAttr, 0)) 33 | { 34 | DividerDrawable = a.GetDrawable(LlDivider); 35 | _dividerPadding = a.GetDimensionPixelSize(LlDividerPadding, 0); 36 | _showDividers = a.GetInteger(LlShowDivider, (int)ShowDividers.None); 37 | 38 | a.Recycle(); 39 | } 40 | } 41 | 42 | public new Drawable DividerDrawable 43 | { 44 | get { return _divider; } 45 | set 46 | { 47 | if (value == _divider) return; 48 | 49 | _divider = value; 50 | if (_divider != null) 51 | { 52 | _dividerWidth = _divider.IntrinsicWidth; 53 | _dividerHeight = _divider.IntrinsicHeight; 54 | } 55 | else 56 | { 57 | _dividerWidth = 0; 58 | _dividerHeight = 0; 59 | } 60 | SetWillNotDraw(_divider == null); 61 | RequestLayout(); 62 | } 63 | } 64 | 65 | protected override void MeasureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, 66 | int parentHeightMeasureSpec, int heightUsed) 67 | { 68 | var index = IndexOfChild(child); 69 | var lparams = (LayoutParams)child.LayoutParameters!; 70 | 71 | if (HasDividerBeforeChildAt(index)) 72 | { 73 | if (Orientation == Orientation.Vertical) 74 | lparams.TopMargin = _dividerHeight; 75 | else 76 | lparams.LeftMargin = _dividerWidth; 77 | } 78 | 79 | if (index == ChildCount - 1) 80 | { 81 | if (HasDividerBeforeChildAt(ChildCount)) 82 | { 83 | if (Orientation == Orientation.Vertical) 84 | lparams.BottomMargin = _dividerHeight; 85 | else 86 | lparams.RightMargin = _dividerWidth; 87 | } 88 | } 89 | 90 | base.MeasureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); 91 | } 92 | 93 | protected override void OnDraw(Canvas canvas) 94 | { 95 | if (_divider != null) 96 | { 97 | if (Orientation == Orientation.Vertical) 98 | DrawDividersVertical(canvas); 99 | else 100 | DrawDividersHorizontal(canvas); 101 | } 102 | 103 | base.OnDraw(canvas); 104 | } 105 | 106 | private void DrawDividersVertical(Canvas canvas) 107 | { 108 | for (var i = 0; i < ChildCount; i++) 109 | { 110 | var child = GetChildAt(i); 111 | 112 | if (child != null && child.Visibility != ViewStates.Gone) 113 | { 114 | if (HasDividerBeforeChildAt(i)) 115 | { 116 | var lp = (LayoutParams)child.LayoutParameters; 117 | var top = child.Top - lp.TopMargin; 118 | DrawHorizontalDivider(canvas, top); 119 | } 120 | } 121 | } 122 | 123 | if (HasDividerBeforeChildAt(ChildCount)) 124 | { 125 | var child = GetChildAt(ChildCount - 1); 126 | int bottom; 127 | if (child == null) 128 | bottom = Height - PaddingBottom - _dividerHeight; 129 | else 130 | bottom = child.Bottom; 131 | DrawHorizontalDivider(canvas, bottom); 132 | } 133 | } 134 | 135 | private void DrawDividersHorizontal(Canvas canvas) 136 | { 137 | for (var i = 0; i < ChildCount; i++) 138 | { 139 | var child = GetChildAt(i); 140 | 141 | if (child != null && child.Visibility != ViewStates.Gone) 142 | { 143 | if (HasDividerBeforeChildAt(i)) 144 | { 145 | var lp = (LayoutParams)child.LayoutParameters; 146 | var left = child.Left - lp.LeftMargin; 147 | DrawVerticalDivider(canvas, left); 148 | } 149 | } 150 | } 151 | 152 | if (HasDividerBeforeChildAt(ChildCount)) 153 | { 154 | var child = GetChildAt(ChildCount - 1); 155 | int right; 156 | if (child == null) 157 | right = Width - PaddingRight - _dividerWidth; 158 | else 159 | right = child.Right; 160 | DrawVerticalDivider(canvas, right); 161 | } 162 | } 163 | 164 | private void DrawHorizontalDivider(Canvas canvas, int top) 165 | { 166 | _divider.SetBounds(PaddingLeft + _dividerPadding, top, Width - PaddingRight - _dividerPadding, 167 | top + _dividerHeight); 168 | _divider.Draw(canvas); 169 | } 170 | 171 | private void DrawVerticalDivider(Canvas canvas, int left) 172 | { 173 | _divider.SetBounds(left, PaddingTop + _dividerPadding, 174 | left + _dividerWidth, Height - PaddingBottom - _dividerPadding); 175 | _divider.Draw(canvas); 176 | } 177 | 178 | private bool HasDividerBeforeChildAt(int childIndex) 179 | { 180 | if (childIndex == 0 || childIndex == ChildCount) 181 | return false; 182 | 183 | if ((_showDividers & (int)ShowDividers.Middle) != 0) 184 | { 185 | var hasVisibleViewBefore = false; 186 | for (var i = childIndex; i >= 0; i--) 187 | if (GetChildAt(i)?.Visibility != ViewStates.Gone) 188 | { 189 | hasVisibleViewBefore = true; 190 | break; 191 | } 192 | return hasVisibleViewBefore; 193 | } 194 | return false; 195 | } 196 | 197 | private bool _isDisposed; 198 | protected override void Dispose(bool disposing) 199 | { 200 | if (_isDisposed) 201 | return; 202 | 203 | if (disposing) 204 | { 205 | _divider?.Dispose(); 206 | } 207 | 208 | _isDisposed = true; 209 | 210 | base.Dispose(disposing); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Library/Interfaces/IIconPageAdapter.cs: -------------------------------------------------------------------------------- 1 | namespace DK.Ostebaronen.Droid.ViewPagerIndicator.Interfaces 2 | { 3 | public interface IIconPageAdapter 4 | { 5 | int GetIconResId(int index); 6 | int Count { get; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Library/Interfaces/IPageIndicator.cs: -------------------------------------------------------------------------------- 1 | using AndroidX.ViewPager.Widget; 2 | 3 | namespace DK.Ostebaronen.Droid.ViewPagerIndicator 4 | { 5 | public interface IPageIndicator : ViewPager.IOnPageChangeListener 6 | { 7 | void SetViewPager(ViewPager view); 8 | void SetViewPager(ViewPager view, int initialPosition); 9 | int CurrentItem { get; set; } 10 | void SetOnPageChangeListener(ViewPager.IOnPageChangeListener listener); 11 | void NotifyDataSetChanged(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Library/Library.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | monoandroid12.0;net6.0-android 4 | DK.Ostebaronen.Droid.ViewPagerIndicator 5 | DK.Ostebaronen.Droid.ViewPagerIndicator 6 | ViewPagerIndicator 7 | A port of ViewPagerIndicator for Xamarin.Android. A highly customizable indicator for ViewPager. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Library/Resources/color/vpi__dark_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Library/Resources/color/vpi__light_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Library/Resources/drawable-hdpi/vpi__tab_selected_focused_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-hdpi/vpi__tab_selected_focused_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-hdpi/vpi__tab_selected_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-hdpi/vpi__tab_selected_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-hdpi/vpi__tab_selected_pressed_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-hdpi/vpi__tab_selected_pressed_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-hdpi/vpi__tab_unselected_focused_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-hdpi/vpi__tab_unselected_focused_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-hdpi/vpi__tab_unselected_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-hdpi/vpi__tab_unselected_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-hdpi/vpi__tab_unselected_pressed_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-hdpi/vpi__tab_unselected_pressed_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-mdpi/vpi__tab_selected_focused_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-mdpi/vpi__tab_selected_focused_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-mdpi/vpi__tab_selected_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-mdpi/vpi__tab_selected_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-mdpi/vpi__tab_selected_pressed_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-mdpi/vpi__tab_selected_pressed_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-mdpi/vpi__tab_unselected_focused_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-mdpi/vpi__tab_unselected_focused_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-mdpi/vpi__tab_unselected_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-mdpi/vpi__tab_unselected_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-mdpi/vpi__tab_unselected_pressed_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-mdpi/vpi__tab_unselected_pressed_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-xhdpi/vpi__tab_selected_focused_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-xhdpi/vpi__tab_selected_focused_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-xhdpi/vpi__tab_selected_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-xhdpi/vpi__tab_selected_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-xhdpi/vpi__tab_selected_pressed_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-xhdpi/vpi__tab_selected_pressed_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-xhdpi/vpi__tab_unselected_focused_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-xhdpi/vpi__tab_unselected_focused_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-xhdpi/vpi__tab_unselected_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-xhdpi/vpi__tab_unselected_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable-xhdpi/vpi__tab_unselected_pressed_holo.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Library/Resources/drawable-xhdpi/vpi__tab_unselected_pressed_holo.9.png -------------------------------------------------------------------------------- /Library/Resources/drawable/vpi__tab_indicator.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Library/Resources/values-v11/vpi__styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /Library/Resources/values/vpi__attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /Library/Resources/values/vpi__colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | #ff000000 19 | #fff3f3f3 20 | @color/vpi__background_holo_light 21 | @color/vpi__background_holo_dark 22 | #ff4c4c4c 23 | #ffb2b2b2 24 | @color/vpi__bright_foreground_holo_light 25 | @color/vpi__bright_foreground_holo_dark 26 | 27 | -------------------------------------------------------------------------------- /Library/Resources/values/vpi__defaults.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | true 19 | #FFFFFFFF 20 | #00000000 21 | 0 22 | 3dp 23 | false 24 | #FFDDDDDD 25 | 1dp 26 | 5dp 27 | 28 | 12dp 29 | 4dp 30 | 1dp 31 | #FF33B5E5 32 | #FFBBBBBB 33 | true 34 | 35 | 4dp 36 | #FF33B5E5 37 | 2dp 38 | 2 39 | 4dp 40 | 20dp 41 | 7dp 42 | 0 43 | #FFFFFFFF 44 | true 45 | #BBFFFFFF 46 | 15dp 47 | 5dp 48 | 7dp 49 | 50 | true 51 | 300 52 | 400 53 | #FF33B5E5 54 | -------------------------------------------------------------------------------- /Library/Resources/values/vpi__styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 22 | 23 | 25 | 26 | 37 | 38 | 42 | 43 | 47 | 48 | -------------------------------------------------------------------------------- /Library/TabPageIndicator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Android.Content; 3 | using Android.Runtime; 4 | using Android.Util; 5 | using Android.Views; 6 | using Android.Widget; 7 | using AndroidX.ViewPager.Widget; 8 | using DK.Ostebaronen.Droid.ViewPagerIndicator.Extensions; 9 | using DK.Ostebaronen.Droid.ViewPagerIndicator.Interfaces; 10 | using Java.Lang; 11 | 12 | namespace DK.Ostebaronen.Droid.ViewPagerIndicator 13 | { 14 | [Register("dk.ostebaronen.droid.viewpagerindicator.TabPageIndicator")] 15 | public class TabPageIndicator 16 | : HorizontalScrollView 17 | , IPageIndicator 18 | { 19 | public static readonly ICharSequence EmptyTitle = new Java.Lang.String(""); 20 | 21 | private Runnable _tabSelector; 22 | private readonly IcsLinearLayout _tabLayout; 23 | 24 | private ViewPager _viewPager; 25 | private ViewPager.IOnPageChangeListener _listener; 26 | 27 | private int _maxTabWidth; 28 | private int _selectedTabIndex; 29 | 30 | public event TabReselectedEventHandler TabReselected; 31 | public event PageScrollStateChangedEventHandler PageScrollStateChanged; 32 | public event PageSelectedEventHandler PageSelected; 33 | public event PageScrolledEventHandler PageScrolled; 34 | 35 | public TabPageIndicator(Context context) 36 | : this(context, null) 37 | { } 38 | 39 | public TabPageIndicator(Context context, IAttributeSet attrs) 40 | : base(context, attrs) 41 | { 42 | HorizontalScrollBarEnabled = false; 43 | 44 | _tabLayout = new IcsLinearLayout(context, Resource.Attribute.vpiTabPageIndicatorStyle); 45 | AddView(_tabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.MatchParent)); 46 | } 47 | 48 | private ViewPager ViewPager 49 | { 50 | get 51 | { 52 | if (_viewPager.IsNull()) 53 | return null; 54 | 55 | return _viewPager; 56 | } 57 | } 58 | 59 | protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) 60 | { 61 | var widthMode = MeasureSpec.GetMode(widthMeasureSpec); 62 | var lockedExpanded = widthMode == MeasureSpecMode.Exactly; 63 | FillViewport = lockedExpanded; 64 | 65 | var childCount = _tabLayout.ChildCount; 66 | if (childCount > 1 && (widthMode == MeasureSpecMode.Exactly || widthMode == MeasureSpecMode.AtMost)) 67 | { 68 | if (childCount > 2) 69 | _maxTabWidth = (int)(MeasureSpec.GetSize(widthMeasureSpec) * 0.4f); 70 | else 71 | _maxTabWidth = MeasureSpec.GetSize(widthMeasureSpec) / 2; 72 | } 73 | else 74 | _maxTabWidth = -1; 75 | 76 | var oldWidth = MeasuredWidth; 77 | base.OnMeasure(widthMeasureSpec, heightMeasureSpec); 78 | var newWidth = MeasuredWidth; 79 | 80 | if (lockedExpanded && oldWidth != newWidth) 81 | CurrentItem = _selectedTabIndex; 82 | } 83 | 84 | private void AnimateToTab(int position) 85 | { 86 | var iconView = _tabLayout.GetChildAt(position); 87 | if (iconView == null) 88 | return; 89 | 90 | if (_tabSelector != null) 91 | RemoveCallbacks(_tabSelector); 92 | 93 | _tabSelector = new Runnable(() => 94 | { 95 | var scrollPos = iconView.Left - (Width - iconView.Width) / 2; 96 | SmoothScrollTo(scrollPos, 0); 97 | _tabSelector = null; 98 | }); 99 | Post(_tabSelector); 100 | } 101 | 102 | protected override void OnAttachedToWindow() 103 | { 104 | base.OnAttachedToWindow(); 105 | if (_tabSelector != null) 106 | Post(_tabSelector); 107 | } 108 | 109 | protected override void OnDetachedFromWindow() 110 | { 111 | base.OnDetachedFromWindow(); 112 | if (_tabSelector != null) 113 | RemoveCallbacks(_tabSelector); 114 | } 115 | 116 | private void AddTab(int index, ICharSequence text, int iconResId) 117 | { 118 | var tabView = new TabView(Context, this) { Focusable = true, Index = index, TextFormatted = text }; 119 | tabView.Click += OnTabClick; 120 | 121 | if (iconResId != 0) 122 | tabView.SetCompoundDrawablesWithIntrinsicBounds(iconResId, 0, 0, 0); 123 | 124 | _tabLayout.AddView(tabView, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MatchParent, 1)); 125 | } 126 | 127 | private void OnTabClick(object sender, EventArgs args) 128 | { 129 | var tabview = (TabView)sender; 130 | var oldSelected = _viewPager.CurrentItem; 131 | var newSelected = tabview.Index; 132 | 133 | _viewPager.CurrentItem = newSelected; 134 | if (oldSelected == newSelected && TabReselected != null) 135 | TabReselected(this, new TabReselectedEventArgs { Position = newSelected }); 136 | } 137 | 138 | public void OnPageScrollStateChanged(int state) 139 | { 140 | if (_listener != null) 141 | _listener.OnPageScrollStateChanged(state); 142 | 143 | PageScrollStateChanged?.Invoke(this, new PageScrollStateChangedEventArgs { State = state }); 144 | } 145 | 146 | public void OnPageScrolled(int position, float positionOffset, int positionOffsetPixels) 147 | { 148 | if (_listener != null) 149 | _listener.OnPageScrolled(position, positionOffset, positionOffsetPixels); 150 | 151 | PageScrolled?.Invoke(this, 152 | new PageScrolledEventArgs 153 | { 154 | Position = position, 155 | PositionOffset = positionOffset, 156 | PositionOffsetPixels = positionOffsetPixels 157 | }); 158 | } 159 | 160 | public void OnPageSelected(int position) 161 | { 162 | CurrentItem = position; 163 | if (_listener != null) 164 | _listener.OnPageSelected(position); 165 | 166 | PageSelected?.Invoke(this, new PageSelectedEventArgs { Position = position }); 167 | } 168 | 169 | public void SetOnPageChangeListener(ViewPager.IOnPageChangeListener listener) { _listener = listener; } 170 | 171 | public void SetViewPager(ViewPager view) 172 | { 173 | if (_viewPager == view) return; 174 | 175 | if (null != ViewPager) 176 | _viewPager.ClearOnPageChangeListeners(); 177 | 178 | if (null == view.Adapter) 179 | throw new InvalidOperationException("ViewPager does not have an Adapter instance."); 180 | 181 | _viewPager = view; 182 | _viewPager.AddOnPageChangeListener(this); 183 | NotifyDataSetChanged(); 184 | } 185 | 186 | public void SetViewPager(ViewPager view, int initialPosition) 187 | { 188 | SetViewPager(view); 189 | CurrentItem = initialPosition; 190 | } 191 | 192 | public void NotifyDataSetChanged() 193 | { 194 | RemoveAllTabItems(); 195 | var adapter = _viewPager.Adapter; 196 | var iconAdapter = adapter as IIconPageAdapter; 197 | 198 | var count = adapter?.Count ?? 0; 199 | for (var i = 0; i < count; i++) 200 | { 201 | var title = adapter!.GetPageTitleFormatted(i) ?? EmptyTitle; 202 | 203 | var iconResId = 0; 204 | if (iconAdapter != null) 205 | iconResId = iconAdapter.GetIconResId(i); 206 | AddTab(i, title, iconResId); 207 | } 208 | if (_selectedTabIndex > count) 209 | _selectedTabIndex = count - 1; 210 | CurrentItem = _selectedTabIndex; 211 | RequestLayout(); 212 | } 213 | 214 | public int CurrentItem 215 | { 216 | get { return _selectedTabIndex; } 217 | set 218 | { 219 | if (null == _viewPager) 220 | throw new InvalidOperationException("ViewPager has not been bound."); 221 | 222 | _viewPager.CurrentItem = value; 223 | _selectedTabIndex = value; 224 | 225 | var tabCount = _tabLayout.ChildCount; 226 | for (var i = 0; i < tabCount; i++) 227 | { 228 | var child = _tabLayout.GetChildAt(i); 229 | var selected = i == value; 230 | 231 | if (child != null) 232 | child.Selected = selected; 233 | 234 | if (selected) 235 | AnimateToTab(value); 236 | } 237 | } 238 | } 239 | 240 | private class TabView : TextView 241 | { 242 | private readonly WeakReference _weakIndicator; 243 | 244 | public int Index { get; set; } 245 | 246 | public TabView(Context context, TabPageIndicator indicator) 247 | : base(context, null, Resource.Attribute.vpiTabPageIndicatorStyle) 248 | { 249 | _weakIndicator = new WeakReference(indicator); 250 | } 251 | 252 | protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) 253 | { 254 | base.OnMeasure(widthMeasureSpec, heightMeasureSpec); 255 | 256 | //Re-measure if we went beyond our maximum size. 257 | if (Indicator._maxTabWidth > 0 && MeasuredWidth > Indicator._maxTabWidth) 258 | base.OnMeasure(MeasureSpec.MakeMeasureSpec(Indicator._maxTabWidth, MeasureSpecMode.Exactly), 259 | heightMeasureSpec); 260 | } 261 | 262 | private TabPageIndicator Indicator => _weakIndicator.TryGetTarget(out var indicator) ? indicator : null; 263 | } 264 | 265 | private bool _isDisposed; 266 | protected override void Dispose(bool disposing) 267 | { 268 | if (_isDisposed) 269 | return; 270 | 271 | if (disposing) 272 | { 273 | if (_tabLayout != null) 274 | { 275 | RemoveAllTabItems(); 276 | _tabLayout.Dispose(); 277 | } 278 | 279 | _tabSelector?.Dispose(); 280 | 281 | if (_viewPager != null) 282 | { 283 | _viewPager.RemoveOnPageChangeListener(this); 284 | _viewPager = null; 285 | } 286 | } 287 | 288 | _isDisposed = true; 289 | 290 | base.Dispose(disposing); 291 | } 292 | 293 | private void RemoveAllTabItems() 294 | { 295 | if (_tabLayout == null) 296 | return; 297 | 298 | var childCount = _tabLayout.ChildCount; 299 | for (var i = 0; i < childCount; i++) 300 | { 301 | var child = _tabLayout.GetChildAt(i); 302 | if (child is TabView tabView) 303 | { 304 | tabView.Click -= OnTabClick; 305 | } 306 | } 307 | 308 | _tabLayout.RemoveAllViews(); 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /Library/UnderlinePageIndicator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Android.Content; 3 | using Android.Graphics; 4 | using Android.OS; 5 | using Android.Runtime; 6 | using Android.Util; 7 | using Android.Views; 8 | using AndroidX.Core.Content; 9 | using AndroidX.ViewPager.Widget; 10 | using DK.Ostebaronen.Droid.ViewPagerIndicator.Extensions; 11 | using Java.Interop; 12 | using Java.Lang; 13 | using Math = System.Math; 14 | 15 | namespace DK.Ostebaronen.Droid.ViewPagerIndicator 16 | { 17 | [Register("dk.ostebaronen.droid.viewpagerindicator.UnderlinePageIndicator")] 18 | public class UnderlinePageIndicator 19 | : View 20 | , IPageIndicator 21 | { 22 | private const int InvalidPointer = -1; 23 | private const int FadeFrameMs = 30; 24 | 25 | private readonly Paint _paint = new(PaintFlags.AntiAlias); 26 | private bool _fades; 27 | private int _fadeBy; 28 | private int _fadeLength; 29 | 30 | private ViewPager _viewPager; 31 | private ViewPager.IOnPageChangeListener _listener; 32 | private int _scrollState; 33 | private float _positionOffset; 34 | 35 | private readonly int _touchSlop; 36 | private float _lastMotionX = -1; 37 | private int _activePointerId = InvalidPointer; 38 | private bool _isDragging; 39 | private int _currentPage; 40 | private Color _selectedColor; 41 | private readonly Runnable _fadeRunnable; 42 | 43 | public event PageScrollStateChangedEventHandler PageScrollStateChanged; 44 | public event PageSelectedEventHandler PageSelected; 45 | public event PageScrolledEventHandler PageScrolled; 46 | 47 | public UnderlinePageIndicator(Context context) 48 | : this(context, null) 49 | { 50 | } 51 | 52 | public UnderlinePageIndicator(Context context, IAttributeSet attrs) 53 | : this(context, attrs, Resource.Attribute.vpiUnderlinePageIndicatorStyle) 54 | { 55 | } 56 | 57 | public UnderlinePageIndicator(Context context, IAttributeSet attrs, int defStyle) 58 | : base(context, attrs, defStyle) 59 | { 60 | if (IsInEditMode) return; 61 | 62 | var res = Resources; 63 | 64 | var defaultFades = res.GetBoolean(Resource.Boolean.default_underline_indicator_fades); 65 | var defaultFadeDelay = res.GetInteger(Resource.Integer.default_underline_indicator_fade_delay); 66 | var defaultFadeLength = res.GetInteger(Resource.Integer.default_underline_indicator_fade_length); 67 | var defaultSelectedColor = ContextCompat.GetColor(context, Resource.Color.default_underline_indicator_selected_color); 68 | 69 | using (var a = context.ObtainStyledAttributes(attrs, Resource.Styleable.UnderlinePageIndicator, defStyle, 0)) 70 | { 71 | Fades = a.GetBoolean(Resource.Styleable.UnderlinePageIndicator_fades, defaultFades); 72 | SelectedColor = a.GetColor(Resource.Styleable.UnderlinePageIndicator_selectedColor, defaultSelectedColor); 73 | FadeDelay = a.GetInteger(Resource.Styleable.UnderlinePageIndicator_fadeDelay, defaultFadeDelay); 74 | FadeLength = a.GetInteger(Resource.Styleable.UnderlinePageIndicator_fadeLength, defaultFadeLength); 75 | 76 | var background = a.GetDrawable(Resource.Styleable.UnderlinePageIndicator_android_background); 77 | if (null != background) 78 | Background = background; 79 | 80 | a.Recycle(); 81 | } 82 | 83 | using (var configuration = ViewConfiguration.Get(context)) 84 | _touchSlop = configuration?.ScaledPagingTouchSlop ?? 0; 85 | 86 | _fadeRunnable = new Runnable(() => 87 | { 88 | if (!_fades) return; 89 | 90 | var alpha = Math.Max(_paint.Alpha - _fadeBy, 0); 91 | _paint.Alpha = alpha; 92 | Invalidate(); 93 | if (alpha > 0) 94 | PostDelayed(_fadeRunnable, FadeFrameMs); 95 | }); 96 | } 97 | 98 | private ViewPager ViewPager 99 | { 100 | get 101 | { 102 | if (_viewPager.IsNull()) 103 | return null; 104 | 105 | return _viewPager; 106 | } 107 | } 108 | 109 | public bool Fades 110 | { 111 | get => _fades; 112 | set 113 | { 114 | if (value != _fades) 115 | { 116 | _fades = value; 117 | if (_fades) 118 | { 119 | Post(_fadeRunnable); 120 | } 121 | else 122 | { 123 | RemoveCallbacks(_fadeRunnable); 124 | _paint.Alpha = Color.GetAlphaComponent(_selectedColor); 125 | Invalidate(); 126 | } 127 | } 128 | } 129 | } 130 | 131 | public int FadeDelay { get; set; } 132 | 133 | public int FadeLength 134 | { 135 | get => _fadeLength; 136 | set 137 | { 138 | _fadeLength = value; 139 | _fadeBy = Color.GetAlphaComponent(_selectedColor) / (_fadeLength / FadeFrameMs); 140 | } 141 | } 142 | 143 | public Color SelectedColor 144 | { 145 | get => _paint.Color; 146 | set 147 | { 148 | _selectedColor = value; 149 | _paint.Color = value; 150 | Invalidate(); 151 | } 152 | } 153 | 154 | public int CurrentItem 155 | { 156 | get => _currentPage; 157 | set 158 | { 159 | if (null == ViewPager) 160 | throw new InvalidOperationException("ViewPager has not been bound."); 161 | 162 | _viewPager.CurrentItem = value; 163 | _currentPage = value; 164 | Invalidate(); 165 | } 166 | } 167 | 168 | protected override void OnDraw(Canvas canvas) 169 | { 170 | base.OnDraw(canvas); 171 | 172 | if (null == ViewPager) return; 173 | 174 | var count = _viewPager.Adapter?.Count ?? 0; 175 | if (count == 0) return; 176 | 177 | if (_currentPage >= count) 178 | { 179 | CurrentItem = count - 1; 180 | return; 181 | } 182 | 183 | var pageWidth = (Width - PaddingLeft - PaddingRight) / (1f * count); 184 | var left = PaddingLeft + pageWidth * (_currentPage + _positionOffset); 185 | var right = left + pageWidth; 186 | var top = PaddingTop; 187 | var bottom = Height - PaddingBottom; 188 | canvas.DrawRect(left, top, right, bottom, _paint); 189 | } 190 | 191 | public override bool OnTouchEvent(MotionEvent e) 192 | { 193 | if (base.OnTouchEvent(e)) 194 | return true; 195 | 196 | if (ViewPager?.Adapter?.Count == 0) 197 | return false; 198 | 199 | var action = e.ActionMasked; 200 | switch (action) 201 | { 202 | case MotionEventActions.Down: 203 | _activePointerId = e.GetPointerId(0); 204 | _lastMotionX = e.GetX(); 205 | break; 206 | 207 | case MotionEventActions.Move: 208 | var activePointerIndex = e.FindPointerIndex(_activePointerId); 209 | var x = e.GetX(activePointerIndex); 210 | var deltaX = x - _lastMotionX; 211 | 212 | if (!_isDragging) 213 | if (Math.Abs(deltaX) > _touchSlop) 214 | _isDragging = true; 215 | 216 | if (_isDragging) 217 | { 218 | _lastMotionX = x; 219 | if (ViewPager != null && (ViewPager.IsFakeDragging || ViewPager.BeginFakeDrag())) 220 | ViewPager.FakeDragBy(deltaX); 221 | } 222 | 223 | break; 224 | 225 | case MotionEventActions.Cancel: 226 | case MotionEventActions.Up: 227 | if (!_isDragging) 228 | { 229 | var count = _viewPager.Adapter?.Count ?? 0; 230 | var halfWidth = Width / 2f; 231 | var sixthWidth = Width / 6f; 232 | 233 | if (_currentPage > 0 && e.GetX() > halfWidth - sixthWidth) 234 | { 235 | if (action != MotionEventActions.Cancel) 236 | _viewPager.CurrentItem = _currentPage - 1; 237 | return true; 238 | } 239 | if (_currentPage < count - 1 && e.GetX() > halfWidth + sixthWidth) 240 | { 241 | if (action != MotionEventActions.Cancel) 242 | _viewPager.CurrentItem = _currentPage + 1; 243 | return true; 244 | } 245 | } 246 | 247 | _isDragging = false; 248 | _activePointerId = InvalidPointer; 249 | if (ViewPager?.IsFakeDragging ?? false) ViewPager.EndFakeDrag(); 250 | break; 251 | 252 | case MotionEventActions.PointerDown: 253 | { 254 | var pointerIndex = e.ActionIndex; 255 | _lastMotionX = e.GetX(pointerIndex); 256 | _activePointerId = e.GetPointerId(pointerIndex); 257 | break; 258 | } 259 | 260 | case MotionEventActions.PointerUp: 261 | { 262 | var pointerIndex = e.ActionIndex; 263 | var pointerId = e.GetPointerId(pointerIndex); 264 | if (pointerId == _activePointerId) 265 | { 266 | var newPointerIndex = pointerIndex == 0 ? 1 : 0; 267 | _activePointerId = e.GetPointerId(newPointerIndex); 268 | } 269 | _lastMotionX = e.GetX(e.FindPointerIndex(_activePointerId)); 270 | break; 271 | } 272 | } 273 | 274 | return true; 275 | } 276 | 277 | public void SetViewPager(ViewPager view) 278 | { 279 | if (_viewPager == view) return; 280 | 281 | if (null != ViewPager) 282 | _viewPager.ClearOnPageChangeListeners(); 283 | 284 | if (null == view.Adapter) 285 | throw new InvalidOperationException("ViewPager does not have an Adapter instance."); 286 | 287 | _viewPager = view; 288 | _viewPager.AddOnPageChangeListener(this); 289 | Invalidate(); 290 | Post(_fadeRunnable); 291 | } 292 | 293 | public void SetViewPager(ViewPager view, int initialPosition) 294 | { 295 | SetViewPager(view); 296 | CurrentItem = initialPosition; 297 | } 298 | 299 | public void NotifyDataSetChanged() 300 | { 301 | Invalidate(); 302 | } 303 | 304 | public void SetOnPageChangeListener(ViewPager.IOnPageChangeListener listener) { _listener = listener; } 305 | 306 | public void OnPageScrollStateChanged(int state) 307 | { 308 | _scrollState = state; 309 | 310 | if (null != _listener) 311 | _listener.OnPageScrollStateChanged(state); 312 | 313 | if (null != PageScrollStateChanged) 314 | PageScrollStateChanged(this, new PageScrollStateChangedEventArgs { State = state }); 315 | } 316 | 317 | public void OnPageScrolled(int position, float positionOffset, int positionOffsetPixels) 318 | { 319 | _currentPage = position; 320 | _positionOffset = positionOffset; 321 | if (_fades) 322 | { 323 | if (positionOffsetPixels > 0) 324 | { 325 | RemoveCallbacks(_fadeRunnable); 326 | _paint.Alpha = Color.GetAlphaComponent(_selectedColor); 327 | } 328 | else if (_scrollState != ViewPager.ScrollStateDragging) 329 | PostDelayed(_fadeRunnable, FadeFrameMs); 330 | } 331 | Invalidate(); 332 | 333 | if (null != _listener) 334 | _listener.OnPageScrolled(position, positionOffset, positionOffsetPixels); 335 | 336 | PageScrolled?.Invoke(this, 337 | new PageScrolledEventArgs 338 | { 339 | Position = position, 340 | PositionOffset = positionOffset, 341 | PositionOffsetPixels = positionOffsetPixels 342 | }); 343 | } 344 | 345 | public void OnPageSelected(int position) 346 | { 347 | if (_scrollState == ViewPager.ScrollStateIdle) 348 | { 349 | _currentPage = position; 350 | _positionOffset = 0; 351 | Invalidate(); 352 | _fadeRunnable.Run(); 353 | } 354 | 355 | if (null != _listener) 356 | _listener.OnPageSelected(position); 357 | 358 | PageSelected?.Invoke(this, new PageSelectedEventArgs { Position = position }); 359 | } 360 | 361 | protected override void OnRestoreInstanceState(IParcelable state) 362 | { 363 | if (state is UnderlineSavedState savedState) 364 | { 365 | base.OnRestoreInstanceState(savedState.SuperState); 366 | _currentPage = savedState.CurrentPage; 367 | } 368 | else 369 | base.OnRestoreInstanceState(state); 370 | RequestLayout(); 371 | } 372 | 373 | protected override IParcelable OnSaveInstanceState() 374 | { 375 | var superState = base.OnSaveInstanceState(); 376 | var savedState = new UnderlineSavedState(superState) 377 | { 378 | CurrentPage = _currentPage 379 | }; 380 | return savedState; 381 | } 382 | 383 | public class UnderlineSavedState 384 | : BaseSavedState 385 | { 386 | public int CurrentPage { get; set; } 387 | 388 | public UnderlineSavedState(IParcelable superState) 389 | : base(superState) { } 390 | 391 | private UnderlineSavedState(Parcel parcel) 392 | : base(parcel) 393 | { 394 | CurrentPage = parcel.ReadInt(); 395 | } 396 | 397 | public override void WriteToParcel(Parcel dest, ParcelableWriteFlags flags) 398 | { 399 | base.WriteToParcel(dest, flags); 400 | dest.WriteInt(CurrentPage); 401 | } 402 | 403 | [ExportField("CREATOR")] 404 | public static UnderlineSavedStateCreator InitializeCreator() 405 | { 406 | return new UnderlineSavedStateCreator(); 407 | } 408 | 409 | public class UnderlineSavedStateCreator : Java.Lang.Object, IParcelableCreator 410 | { 411 | public Java.Lang.Object CreateFromParcel(Parcel source) 412 | { 413 | return new UnderlineSavedState(source); 414 | } 415 | 416 | public Java.Lang.Object[] NewArray(int size) 417 | { 418 | return new UnderlineSavedState[size]; 419 | } 420 | } 421 | } 422 | 423 | private bool _isDisposed; 424 | protected override void Dispose(bool disposing) 425 | { 426 | if (_isDisposed) 427 | return; 428 | 429 | if (disposing) 430 | { 431 | _paint?.Dispose(); 432 | _fadeRunnable?.Dispose(); 433 | 434 | if (_viewPager != null) 435 | { 436 | _viewPager.RemoveOnPageChangeListener(this); 437 | _viewPager = null; 438 | } 439 | } 440 | 441 | _isDisposed = true; 442 | 443 | base.Dispose(disposing); 444 | } 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /Library/ViewPagerIndicatorEventHandlers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DK.Ostebaronen.Droid.ViewPagerIndicator 4 | { 5 | public class PageScrollStateChangedEventArgs 6 | : EventArgs 7 | { 8 | public int State { get; set; } 9 | } 10 | 11 | public class PageScrolledEventArgs 12 | : EventArgs 13 | { 14 | public int Position { get; set; } 15 | public float PositionOffset { get; set; } 16 | public int PositionOffsetPixels { get; set; } 17 | } 18 | 19 | public class PageSelectedEventArgs 20 | : EventArgs 21 | { 22 | public int Position { get; set; } 23 | } 24 | 25 | public class TabReselectedEventArgs 26 | : EventArgs 27 | { 28 | public int Position { get; set; } 29 | } 30 | 31 | public delegate void PageScrollStateChangedEventHandler(object sender, PageScrollStateChangedEventArgs args); 32 | 33 | public delegate void PageScrolledEventHandler(object sender, PageScrolledEventArgs args); 34 | 35 | public delegate void PageSelectedEventHandler(object sender, PageSelectedEventArgs args); 36 | 37 | public delegate void TabReselectedEventHandler(object sender, TabReselectedEventArgs args); 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ViewPagerIndicator 2 | ========================== 3 | 4 | [![CI](https://github.com/Cheesebaron/ViewPagerIndicator/actions/workflows/build.yml/badge.svg)](https://github.com/Cheesebaron/ViewPagerIndicator/actions/workflows/build.yml) 5 | 6 | Paging indicator widgets that are compatible with the `ViewPager` from the 7 | [AndroidX Library][2] to improve discoverability of content. 8 | 9 | For Material Design look at: 10 | - [PagerSlidingTabStrip](https://github.com/jamesmontemagno/PagerSlidingTabStrip-for-Xamarin.Android) 11 | - [PagerTabStrip from AndroidX](https://developer.android.com/reference/androidx/viewpager/widget/PagerTabStrip) 12 | - [PagerTitleStrip from AndroidX](https://developer.android.com/reference/androidx/viewpager/widget/PagerTitleStrip) 13 | 14 | Usage 15 | ===== 16 | 17 | *For a working implementation of this project see the `sample` solution.* 18 | 19 | 1. Include one of the widgets in your view. This should usually be placed 20 | adjacent to the `ViewPager` it represents. 21 | 22 | ```xml 23 | 27 | ``` 28 | 29 | 2. In your `OnCreate` method (or `OnCreateView` for a fragment), bind the 30 | indicator to the `ViewPager`. 31 | 32 | ```csharp 33 | //Set the pager with an adapter 34 | var pager = FindViewById(Resource.Id.pager); 35 | pager.Adapter = new TestAdapter(SupportFragmentManager); 36 | 37 | //Bind the title indicator to the adapter 38 | var titleIndicator = FindViewById(Resource.Id.titles); 39 | titleIndicator.SetViewPager(pager); 40 | ``` 41 | 42 | 3. *(Optional)* If you want to listen for a `PageChange` event, you should use it 43 | on the `ViewPagerIndicator`, rather than setting an `OnPageChangeListener` on the 44 | `ViewPager`, otherwise the `ViewPagerIndicator` will not update. 45 | 46 | ```csharp 47 | //continued from above 48 | titleIndicator.PageChange += MyPageChangeEventHandler; 49 | ``` 50 | 51 | 52 | Theming 53 | ------- 54 | 55 | There are three ways to style the look of the indicators. 56 | 57 | 1. **Theme XML**. An attribute for each type of indicator is provided in which 58 | you can specify a custom style. 59 | 2. **Layout XML**. Through the use of a custom namespace you can include any 60 | desired styles. 61 | 3. **Object methods**. Both styles have Properties for each style 62 | attribute which can be changed at any point. 63 | 64 | Each indicator has a demo which creates the same look using each of these 65 | methods. 66 | 67 | 68 | Including In Your Project 69 | ------------------------- 70 | 71 | ViewPagerIndicator is an Android Library project, which you can reference in 72 | your Android Project. 73 | 74 | This project depends on the `ViewPager` class which is available in the 75 | [Android Support Library][2]. 76 | 77 | Ported to Xamarin.Android By 78 | ============ 79 | 80 | * Tomasz Cielecki 81 | 82 | 83 | Originally Developed By 84 | ============ 85 | 86 | * Jake Wharton - 87 | 88 | 89 | License 90 | ======= 91 | 92 | Copyright 2013 Tomasz Cielecki 93 | Copyright 2012 Jake Wharton 94 | Copyright 2011 Patrik Åkerfeldt 95 | Copyright 2011 Francisco Figueiredo Jr. 96 | 97 | Licensed under the Apache License, Version 2.0 (the "License"); 98 | you may not use this file except in compliance with the License. 99 | You may obtain a copy of the License at 100 | 101 | http://www.apache.org/licenses/LICENSE-2.0 102 | 103 | Unless required by applicable law or agreed to in writing, software 104 | distributed under the License is distributed on an "AS IS" BASIS, 105 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 106 | See the License for the specific language governing permissions and 107 | limitations under the License. 108 | 109 | 110 | [1]: https://github.com/pakerfeldt 111 | [2]: http://developer.android.com/sdk/compatibility-library.html 112 | [3]: http://actionbarsherlock.com 113 | [4]: https://github.com/pakerfeldt/android-viewflow 114 | [5]: https://github.com/franciscojunior 115 | [6]: https://gist.github.com/1122947 116 | [7]: http://developer.android.com/guide/developing/projects/projects-eclipse.html 117 | [8]: http://developer.android.com/guide/developing/projects/projects-eclipse.html#ReferencingLibraryProject 118 | [9]: https://raw.github.com/JakeWharton/Android-ViewPagerIndicator/master/sample/screens.png 119 | [10]: https://play.google.com/store/apps/details?id=com.viewpagerindicator.sample 120 | -------------------------------------------------------------------------------- /Sample/ActivityListItem.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) 2007 The Android Open Source Project 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | 20 | namespace Sample 21 | { 22 | class ActivityListItem : Java.Lang.Object 23 | { 24 | public string LaunchData { get; set; } 25 | 26 | public string Path { get; set; } 27 | 28 | public string Prefix { get; set; } 29 | 30 | public ActivityListItem(string prefix, string path, string launchData) 31 | { 32 | Prefix = prefix; 33 | Path = path; 34 | LaunchData = launchData; 35 | } 36 | 37 | public string Component 38 | { 39 | get { return LaunchData.Split(':')[1]; } 40 | } 41 | 42 | public bool IsMenuItem { get { return !IsSubMenu; } } 43 | 44 | public bool IsSubMenu { get { return NoPrefixPath.Contains("/"); } } 45 | 46 | public string Name 47 | { 48 | get 49 | { 50 | if (IsMenuItem) 51 | return NoPrefixPath; 52 | 53 | return NoPrefixPath.Split('/')[0]; 54 | } 55 | } 56 | 57 | public string NoPrefixPath 58 | { 59 | get { return Path.Substring(Prefix.Length).TrimStart('/'); } 60 | } 61 | 62 | public string Package 63 | { 64 | get { return LaunchData.Split(':')[0]; } 65 | } 66 | 67 | public bool IsInPrefix(string prefix) 68 | { 69 | return Path.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase); 70 | } 71 | 72 | public override string ToString() 73 | { 74 | return Name; 75 | } 76 | 77 | public class NameComparer : IEqualityComparer 78 | { 79 | #region IEqualityComparer Members 80 | public bool Equals(ActivityListItem x, ActivityListItem y) 81 | { 82 | return string.Compare(x.Name, y.Name) == 0; 83 | } 84 | 85 | public int GetHashCode(ActivityListItem obj) 86 | { 87 | return obj.Name.GetHashCode(); 88 | } 89 | #endregion 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sample/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Sample/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with you package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); -------------------------------------------------------------------------------- /Sample/BaseSampleActivity.cs: -------------------------------------------------------------------------------- 1 | using Android.Views; 2 | using AndroidX.AppCompat.App; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample 7 | { 8 | public abstract class BaseSampleActivity : AppCompatActivity 9 | { 10 | private static readonly Random _random = new Random(); 11 | 12 | protected TestFragmentAdapter _adapter; 13 | protected ViewPager _pager; 14 | protected IPageIndicator _indicator; 15 | 16 | public override bool OnCreateOptionsMenu(IMenu menu) 17 | { 18 | MenuInflater.Inflate(Resource.Menu.menu, menu); 19 | return true; 20 | } 21 | 22 | public override bool OnOptionsItemSelected(IMenuItem item) 23 | { 24 | switch (item.ItemId) 25 | { 26 | case Resource.Id.random: 27 | var page = _random.Next(_adapter.Count); 28 | Toast.MakeText(this, "Changing to page " + page, ToastLength.Short).Show(); 29 | _pager.CurrentItem = page; 30 | return true; 31 | 32 | case Resource.Id.add_page: 33 | if (_adapter.Count < 10) 34 | { 35 | _adapter.SetCount(_adapter.Count + 1); 36 | _indicator.NotifyDataSetChanged(); 37 | } 38 | return true; 39 | 40 | case Resource.Id.remove_page: 41 | if (_adapter.Count > 1) 42 | { 43 | _adapter.SetCount(_adapter.Count - 1); 44 | _indicator.NotifyDataSetChanged(); 45 | } 46 | return true; 47 | } 48 | 49 | 50 | return base.OnOptionsItemSelected(item); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sample/Circles/SampleCirclesDefault.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Circles 7 | { 8 | [Activity(Label = "Circles/Default")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleCirclesDefault : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle bundle) 13 | { 14 | base.OnCreate(bundle); 15 | 16 | SetContentView(Resource.Layout.simple_circles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Circles/SampleCirclesInitialPage.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Circles 7 | { 8 | [Activity(Label = "Circles/Initial")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleCirclesInitialPage : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_circles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | _indicator.CurrentItem = _adapter.Count - 1; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sample/Circles/SampleCirclesSnap.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Circles 7 | { 8 | [Activity(Label = "Circles/Snap")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleCirclesSnap : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_circles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | ((CirclePageIndicator)_indicator).Snap = true; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sample/Circles/SampleCirclesStyledLayout.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Circles 7 | { 8 | [Activity(Label = "Circles/Styled", Theme = "@style/LightTheme")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleCirclesStyledLayout : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.themed_circles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Circles/SampleCirclesStyledMethods.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Graphics; 3 | using Android.OS; 4 | using AndroidX.ViewPager.Widget; 5 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 6 | 7 | namespace Sample.Circles 8 | { 9 | [Activity(Label = "Circles/Styled Methods", Theme = "@style/LightTheme")] 10 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 11 | public class SampleCirclesStyledMethods : BaseSampleActivity 12 | { 13 | protected override void OnCreate(Bundle savedInstanceState) 14 | { 15 | base.OnCreate(savedInstanceState); 16 | 17 | SetContentView(Resource.Layout.simple_circles); 18 | 19 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 20 | 21 | _pager = FindViewById(Resource.Id.pager); 22 | _pager.Adapter = _adapter; 23 | 24 | _indicator = FindViewById(Resource.Id.indicator); 25 | _indicator.SetViewPager(_pager); 26 | 27 | var density = Resources.DisplayMetrics.Density; 28 | var indicator = (CirclePageIndicator)_indicator; 29 | indicator.SetBackgroundColor(Color.Argb(255, 204, 204, 204)); 30 | indicator.Radius = 10 * density; 31 | indicator.PageColor = Color.Argb(136, 0, 0, 255); 32 | indicator.FillColor = Color.Argb(255, 136, 136, 136); 33 | indicator.StrokeColor = Color.Argb(255, 0, 0, 0); 34 | indicator.StrokeWidth = 2 * density; 35 | indicator.ExtraSpacing = 30 * density; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sample/Circles/SampleCirclesStyledTheme.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Circles 7 | { 8 | [Activity(Label = "Circles/Styled Theme", Theme = "@style/StyledIndicators")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleCirclesStyledTheme : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.themed_circles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Circles/SampleCirclesWithListener.cs: -------------------------------------------------------------------------------- 1 | using AndroidX.ViewPager.Widget; 2 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 3 | 4 | namespace Sample.Circles 5 | { 6 | [Activity(Label = "Circles/With Listener")] 7 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 8 | public class SampleCirclesWithListener : BaseSampleActivity 9 | { 10 | protected override void OnCreate(Bundle savedInstanceState) 11 | { 12 | base.OnCreate(savedInstanceState); 13 | 14 | SetContentView(Resource.Layout.simple_circles); 15 | 16 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 17 | 18 | _pager = FindViewById(Resource.Id.pager); 19 | _pager.Adapter = _adapter; 20 | 21 | _indicator = FindViewById(Resource.Id.indicator); 22 | _indicator.SetViewPager(_pager); 23 | 24 | ((CirclePageIndicator)_indicator).PageSelected += (s, e) => 25 | Toast.MakeText(this, "Changed to page " + e.Position, 26 | ToastLength.Short).Show(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sample/Icons/SampleIconsDefault.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Icons 7 | { 8 | [Activity(Label = "Icons/Default")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleIconsDefault : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_icons); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Lines/SampleLinesDefault.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Lines 7 | { 8 | [Activity(Label = "Lines/Default")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleLinesDefault : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_lines); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Lines/SampleLinesStyledLayout.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Lines 7 | { 8 | [Activity(Label = "Lines/Styled Layout", Theme = "@style/LightTheme")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleLinesStyledLayout : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_lines); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Lines/SampleLinesStyledMethods.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Graphics; 3 | using Android.OS; 4 | using AndroidX.ViewPager.Widget; 5 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 6 | 7 | namespace Sample.Lines 8 | { 9 | [Activity(Label = "Lines/Styled Methods", Theme = "@style/LightTheme")] 10 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 11 | public class SampleLinesStyledMethods : BaseSampleActivity 12 | { 13 | protected override void OnCreate(Bundle savedInstanceState) 14 | { 15 | base.OnCreate(savedInstanceState); 16 | 17 | SetContentView(Resource.Layout.simple_lines); 18 | 19 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 20 | 21 | _pager = FindViewById(Resource.Id.pager); 22 | _pager.Adapter = _adapter; 23 | 24 | var indicator = FindViewById(Resource.Id.indicator); 25 | indicator.SetViewPager(_pager); 26 | 27 | var density = Resources.DisplayMetrics.Density; 28 | indicator.SelectedColor = Color.Argb(136, 255, 0, 0); 29 | indicator.UnselectedColor = Color.Argb(255, 136, 136, 136); 30 | indicator.StrokeWidth = 4 * density; 31 | indicator.LineWidth = 30 * density; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sample/Lines/SampleLinesStyledTheme.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Lines 7 | { 8 | [Activity(Label = "Lines/Styled Theme", Theme = "@style/StyledIndicators")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleLinesStyledTheme : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_lines); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/ListSamples.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Android.App; 5 | using Android.Content; 6 | using Android.OS; 7 | using Android.Widget; 8 | 9 | namespace Sample 10 | { 11 | [Activity(Label = "ViewPagerIndicator Sample", MainLauncher = true, Exported = true)] 12 | public class ListSamples : ListActivity 13 | { 14 | public const string SampleCategory = "dk.ostebaronen.viewpagerindicator.droid.sample"; 15 | 16 | protected override void OnCreate(Bundle savedInstanceState) 17 | { 18 | base.OnCreate(savedInstanceState); 19 | 20 | var prefix = Intent.GetStringExtra("dk.ostebaronen.viewpagerindicator.droid.Path") ?? string.Empty; 21 | 22 | var activities = GetDemoActivities(prefix); 23 | 24 | var items = GetMenuItems(activities, prefix); 25 | 26 | ListAdapter = new ArrayAdapter(this, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, items); 27 | 28 | ListView.ItemClick += (s, e) => 29 | { 30 | var listView = s as ListView; 31 | if (listView == null) return; 32 | 33 | var item = (ActivityListItem)listView.GetItemAtPosition(e.Position); 34 | LaunchActivityItem(item); 35 | }; 36 | } 37 | 38 | private List GetDemoActivities(string prefix) 39 | { 40 | var results = new List(); 41 | 42 | // Create an intent to query the package manager with, 43 | // we are looking for ActionMain with our custom category 44 | var query = new Intent(Intent.ActionMain, null); 45 | query.AddCategory(SampleCategory); 46 | 47 | var list = PackageManager.QueryIntentActivities(query, 0); 48 | 49 | // If there were no results, bail 50 | if (list == null) 51 | return results; 52 | 53 | results.AddRange(from resolve in list 54 | let category = resolve.LoadLabel(PackageManager) 55 | let type = 56 | string.Format("{0}:{1}", resolve.ActivityInfo.ApplicationInfo.PackageName, 57 | resolve.ActivityInfo.Name) 58 | where 59 | string.IsNullOrWhiteSpace(prefix) || 60 | category.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase) 61 | select new ActivityListItem(prefix, category, type)); 62 | 63 | return results; 64 | } 65 | 66 | private static List GetMenuItems(IReadOnlyCollection activities, string prefix) 67 | { 68 | // Get menu items at this level 69 | var items = activities.Where(a => a.IsMenuItem); 70 | 71 | // Get Submenus at this level, but we only need 1 of each 72 | var submenus = activities.Where(a => a.IsSubMenu).Distinct(new ActivityListItem.NameComparer()); 73 | 74 | // Combine, sort, return 75 | return items.Union(submenus).OrderBy(a => a.Name).ToList(); 76 | } 77 | 78 | private void LaunchActivityItem(ActivityListItem item) 79 | { 80 | if (item.IsSubMenu) 81 | { 82 | // Launch this menu activity again with an updated prefix 83 | var result = new Intent(); 84 | 85 | result.SetClass(this, typeof(ListSamples)); 86 | result.PutExtra("dk.ostebaronen.viewpagerindicator.droid.Path", string.Format("{0}/{1}", item.Prefix, item.Name).Trim('/')); 87 | 88 | StartActivity(result); 89 | } 90 | else 91 | { 92 | // Launch the item activity 93 | var result = new Intent(); 94 | result.SetClassName(item.Package, item.Component); 95 | 96 | StartActivity(result); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sample/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.xml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable-hdpi/ 12 | icon.png 13 | 14 | drawable-ldpi/ 15 | icon.png 16 | 17 | drawable-mdpi/ 18 | icon.png 19 | 20 | layout/ 21 | main.xml 22 | 23 | values/ 24 | strings.xml 25 | 26 | In order to get the build system to recognize Android resources, set the build action to 27 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 28 | instead operate on resource IDs. When you compile an Android application that uses resources, 29 | the build system will package the resources for distribution and generate a class called 30 | "Resource" that contains the tokens for each one of the resources included. For example, 31 | for the above Resources layout, this is what the Resource class would expose: 32 | 33 | public class Resource { 34 | public class drawable { 35 | public const int icon = 0x123; 36 | } 37 | 38 | public class layout { 39 | public const int main = 0x456; 40 | } 41 | 42 | public class strings { 43 | public const int first_string = 0xabc; 44 | public const int second_string = 0xbcd; 45 | } 46 | } 47 | 48 | You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main 49 | to reference the layout/main.xml file, or Resource.strings.first_string to reference the first 50 | string in the dictionary file values/strings.xml. -------------------------------------------------------------------------------- /Sample/Resources/Drawable/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/Drawable/Icon.png -------------------------------------------------------------------------------- /Sample/Resources/Values/Strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World, Click Me! 4 | Sample 5 | 6 | -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/custom_tab_indicator_divider.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/custom_tab_indicator_divider.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/custom_tab_indicator_focused.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/custom_tab_indicator_focused.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/custom_tab_indicator_selected.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/custom_tab_indicator_selected.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/custom_tab_indicator_selected_pressed.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/custom_tab_indicator_selected_pressed.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/custom_tab_indicator_unselected.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/custom_tab_indicator_unselected.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/custom_tab_indicator_unselected_focused.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/custom_tab_indicator_unselected_focused.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/custom_tab_indicator_unselected_pressed.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/custom_tab_indicator_unselected_pressed.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/perm_group_calendar_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/perm_group_calendar_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/perm_group_calendar_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/perm_group_calendar_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/perm_group_camera_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/perm_group_camera_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/perm_group_camera_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/perm_group_camera_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/perm_group_device_alarms_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/perm_group_device_alarms_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/perm_group_device_alarms_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/perm_group_device_alarms_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/perm_group_location_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/perm_group_location_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-hdpi/perm_group_location_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-hdpi/perm_group_location_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/custom_tab_indicator_divider.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/custom_tab_indicator_divider.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/custom_tab_indicator_selected.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/custom_tab_indicator_selected.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/custom_tab_indicator_selected_focused.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/custom_tab_indicator_selected_focused.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/custom_tab_indicator_selected_pressed.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/custom_tab_indicator_selected_pressed.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/custom_tab_indicator_unselected.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/custom_tab_indicator_unselected.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/custom_tab_indicator_unselected_focused.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/custom_tab_indicator_unselected_focused.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/custom_tab_indicator_unselected_pressed.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/custom_tab_indicator_unselected_pressed.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/perm_group_calendar_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/perm_group_calendar_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/perm_group_calendar_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/perm_group_calendar_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/perm_group_camera_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/perm_group_camera_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/perm_group_camera_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/perm_group_camera_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/perm_group_device_alarms_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/perm_group_device_alarms_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/perm_group_device_alarms_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/perm_group_device_alarms_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/perm_group_location_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/perm_group_location_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-mdpi/perm_group_location_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-mdpi/perm_group_location_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/custom_tab_indicator_divider.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/custom_tab_indicator_divider.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/custom_tab_indicator_selected.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/custom_tab_indicator_selected.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/custom_tab_indicator_selected_focused.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/custom_tab_indicator_selected_focused.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/custom_tab_indicator_selected_pressed.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/custom_tab_indicator_selected_pressed.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/custom_tab_indicator_unselected.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/custom_tab_indicator_unselected.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/custom_tab_indicator_unselected_focused.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/custom_tab_indicator_unselected_focused.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/custom_tab_indicator_unselected_pressed.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/custom_tab_indicator_unselected_pressed.9.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/perm_group_calendar_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/perm_group_calendar_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/perm_group_calendar_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/perm_group_calendar_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/perm_group_camera_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/perm_group_camera_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/perm_group_camera_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/perm_group_camera_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/perm_group_device_alarms_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/perm_group_device_alarms_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/perm_group_device_alarms_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/perm_group_device_alarms_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/perm_group_location_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/perm_group_location_normal.png -------------------------------------------------------------------------------- /Sample/Resources/drawable-xhdpi/perm_group_location_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/Sample/Resources/drawable-xhdpi/perm_group_location_selected.png -------------------------------------------------------------------------------- /Sample/Resources/drawable/custom_tab_indicator.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Sample/Resources/drawable/perm_group_calendar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Sample/Resources/drawable/perm_group_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Sample/Resources/drawable/perm_group_device_alarms.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Sample/Resources/drawable/perm_group_location.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Sample/Resources/layout/simple_circles.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 22 | 23 | 29 | 35 | -------------------------------------------------------------------------------- /Sample/Resources/layout/simple_circles_vertical.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 22 | 23 | 29 | 35 | -------------------------------------------------------------------------------- /Sample/Resources/layout/simple_icons.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 22 | 23 | 29 | 34 | 35 | -------------------------------------------------------------------------------- /Sample/Resources/layout/simple_lines.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 22 | 23 | 29 | 35 | -------------------------------------------------------------------------------- /Sample/Resources/layout/simple_tabs.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 22 | 23 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /Sample/Resources/layout/simple_titles.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 22 | 23 | 29 | 35 | 36 | -------------------------------------------------------------------------------- /Sample/Resources/layout/simple_titles_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 23 | 24 | 30 | 37 | 38 | -------------------------------------------------------------------------------- /Sample/Resources/layout/simple_underlines.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 22 | 23 | 29 | 34 | -------------------------------------------------------------------------------- /Sample/Resources/layout/themed_circles.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 23 | 24 | 30 | 42 | 43 | -------------------------------------------------------------------------------- /Sample/Resources/layout/themed_lines.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 23 | 24 | 30 | 40 | 41 | -------------------------------------------------------------------------------- /Sample/Resources/layout/themed_titles.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 23 | 24 | 38 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Sample/Resources/layout/themed_underlines.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 23 | 24 | 30 | 39 | 40 | -------------------------------------------------------------------------------- /Sample/Resources/menu/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/Resources/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | 17 | 18 | 19 | 21 | 22 | 29 | -------------------------------------------------------------------------------- /Sample/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | 17 | 18 | 19 | 20 | 21 | 28 | 29 | 39 | 40 | 46 | 47 | 54 | 55 | 68 | 69 | 72 | 73 | 79 | -------------------------------------------------------------------------------- /Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0-android 4 | 21 5 | Exe 6 | enable 7 | enable 8 | dk.ostebaronen.droid.viewpagerindicator.Sample 9 | 1 10 | 1.0 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Sample/Tabs/SampleTabsDefault.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | using DK.Ostebaronen.Droid.ViewPagerIndicator.Interfaces; 6 | using Fragment = AndroidX.Fragment.App.Fragment; 7 | using FragmentManager = AndroidX.Fragment.App.FragmentManager; 8 | 9 | namespace Sample.Tabs 10 | { 11 | [Activity(Label = "Tabs/Default", Theme = "@style/Theme.PageIndicatorDefaults")] 12 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 13 | public class SampleTabsDefault : BaseSampleActivity 14 | { 15 | protected override void OnCreate(Bundle savedInstanceState) 16 | { 17 | base.OnCreate(savedInstanceState); 18 | 19 | SetContentView(Resource.Layout.simple_tabs); 20 | 21 | _adapter = new GoogleMusicAdapter(SupportFragmentManager); 22 | 23 | _pager = FindViewById(Resource.Id.pager); 24 | _pager.Adapter = _adapter; 25 | 26 | _indicator = FindViewById(Resource.Id.indicator); 27 | _indicator.SetViewPager(_pager); 28 | } 29 | 30 | private class GoogleMusicAdapter : TestFragmentAdapter, IIconPageAdapter 31 | { 32 | private static readonly string[] Content = new[] 33 | { 34 | "Recent", "Artists", "Album", "Songs", "Playlists", "Genres" 35 | }; 36 | 37 | public GoogleMusicAdapter(FragmentManager p0) 38 | : base(p0) 39 | { } 40 | 41 | public override int Count 42 | { 43 | get { return Content.Length; } 44 | } 45 | 46 | public new int GetIconResId(int index) { return 0; } 47 | 48 | public override Fragment GetItem(int p0) { return TestFragment.NewInstance(Content[p0 % Content.Length]); } 49 | 50 | public override Java.Lang.ICharSequence GetPageTitleFormatted(int p0) { return new Java.Lang.String(Content[p0 % Content.Length].ToUpper()); } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sample/Tabs/SampleTabsStyled.cs: -------------------------------------------------------------------------------- 1 | using AndroidX.ViewPager.Widget; 2 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 3 | using DK.Ostebaronen.Droid.ViewPagerIndicator.Interfaces; 4 | using Fragment = AndroidX.Fragment.App.Fragment; 5 | using FragmentManager = AndroidX.Fragment.App.FragmentManager; 6 | 7 | namespace Sample.Tabs 8 | { 9 | [Activity(Label = "Tabs/Styled", Theme = "@style/StyledIndicators")] 10 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 11 | public class SampleTabsStyled : BaseSampleActivity 12 | { 13 | protected override void OnCreate(Bundle savedInstanceState) 14 | { 15 | base.OnCreate(savedInstanceState); 16 | 17 | SetContentView(Resource.Layout.simple_tabs); 18 | 19 | var adapter = new GoogleMusicAdapter(SupportFragmentManager); 20 | 21 | _pager = FindViewById(Resource.Id.pager); 22 | _pager.Adapter = adapter; 23 | 24 | _indicator = FindViewById(Resource.Id.indicator); 25 | _indicator.SetViewPager(_pager); 26 | } 27 | 28 | private class GoogleMusicAdapter : TestFragmentAdapter, IIconPageAdapter 29 | { 30 | private static readonly string[] Content = 31 | { 32 | "Recent", "Artists", "Album", "Songs", "Playlists", "Genres" 33 | }; 34 | 35 | public GoogleMusicAdapter(FragmentManager p0) 36 | : base(p0) 37 | { } 38 | 39 | public override int Count 40 | { 41 | get { return Content.Length; } 42 | } 43 | 44 | public new int GetIconResId(int index) { return 0; } 45 | 46 | public override Fragment GetItem(int p0) { return TestFragment.NewInstance(Content[p0 % Content.Length]); } 47 | 48 | public override Java.Lang.ICharSequence GetPageTitleFormatted(int p0) { return new Java.Lang.String(Content[p0 % Content.Length].ToUpper()); } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sample/Tabs/SampleTabsWithIcons.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | using DK.Ostebaronen.Droid.ViewPagerIndicator.Interfaces; 6 | using Fragment = AndroidX.Fragment.App.Fragment; 7 | using FragmentManager = AndroidX.Fragment.App.FragmentManager; 8 | 9 | namespace Sample.Tabs 10 | { 11 | [Activity(Label = "Tabs/With Icons", Theme = "@style/StyledIndicators")] 12 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 13 | public class SampleTabsWithIcons : BaseSampleActivity 14 | { 15 | protected override void OnCreate(Bundle savedInstanceState) 16 | { 17 | base.OnCreate(savedInstanceState); 18 | 19 | SetContentView(Resource.Layout.simple_tabs); 20 | 21 | var adapter = new GoogleMusicAdapter(SupportFragmentManager); 22 | 23 | _pager = FindViewById(Resource.Id.pager); 24 | _pager.Adapter = adapter; 25 | 26 | _indicator = FindViewById(Resource.Id.indicator); 27 | _indicator.SetViewPager(_pager); 28 | } 29 | 30 | private class GoogleMusicAdapter : TestFragmentAdapter, IIconPageAdapter 31 | { 32 | private static readonly string[] Content = 33 | { 34 | "Calendar", "Camera", "Alarms", "Location" 35 | }; 36 | 37 | private static readonly int[] Icons = 38 | { 39 | Resource.Drawable.perm_group_calendar, 40 | Resource.Drawable.perm_group_camera, 41 | Resource.Drawable.perm_group_device_alarms, 42 | Resource.Drawable.perm_group_location 43 | }; 44 | 45 | public GoogleMusicAdapter(FragmentManager p0) 46 | : base(p0) 47 | { } 48 | 49 | public new int GetIconResId(int index) { return Icons[index]; } 50 | 51 | public override int Count 52 | { 53 | get { return Content.Length; } 54 | } 55 | 56 | public override Fragment GetItem(int p0) { return TestFragment.NewInstance(Content[p0 % Content.Length]); } 57 | 58 | public override Java.Lang.ICharSequence GetPageTitleFormatted(int p0) { return new Java.Lang.String(Content[p0 % Content.Length].ToUpper()); } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sample/TestFragment.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Android.OS; 3 | using Android.Views; 4 | using Android.Widget; 5 | using Fragment = AndroidX.Fragment.App.Fragment; 6 | 7 | namespace Sample 8 | { 9 | public class TestFragment : Fragment 10 | { 11 | private const string KeyContent = "TestFragment:Content"; 12 | private string _content = "???"; 13 | 14 | public static TestFragment NewInstance(string content) 15 | { 16 | var fragment = new TestFragment(); 17 | 18 | var builder = new StringBuilder(); 19 | for (var i = 0; i < 20; i++) 20 | builder.Append(content).Append(" "); 21 | builder.Remove(builder.Length - 1, 1); 22 | fragment._content = builder.ToString(); 23 | 24 | return fragment; 25 | } 26 | 27 | public override void OnCreate(Bundle savedInstanceState) 28 | { 29 | base.OnCreate(savedInstanceState); 30 | 31 | if ((savedInstanceState != null) && savedInstanceState.ContainsKey(KeyContent)) 32 | _content = savedInstanceState.GetString(KeyContent); 33 | } 34 | 35 | public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 36 | { 37 | var text = new TextView(Activity) 38 | { 39 | Gravity = GravityFlags.Center, 40 | Text = _content, 41 | TextSize = 20 * Resources.DisplayMetrics.Density 42 | }; 43 | text.SetPadding(20, 20, 20, 20); 44 | 45 | var layout = new LinearLayout(Activity) 46 | { 47 | LayoutParameters = 48 | new ViewGroup.LayoutParams( 49 | ViewGroup.LayoutParams.MatchParent, 50 | ViewGroup.LayoutParams.MatchParent) 51 | }; 52 | layout.SetGravity(GravityFlags.Center); 53 | layout.AddView(text); 54 | 55 | return layout; 56 | } 57 | 58 | public override void OnSaveInstanceState(Bundle outState) 59 | { 60 | base.OnSaveInstanceState(outState); 61 | outState.PutString(KeyContent, _content); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sample/TestFragmentAdapter.cs: -------------------------------------------------------------------------------- 1 | using AndroidX.Fragment.App; 2 | using DK.Ostebaronen.Droid.ViewPagerIndicator.Interfaces; 3 | using Fragment = AndroidX.Fragment.App.Fragment; 4 | using FragmentManager = AndroidX.Fragment.App.FragmentManager; 5 | 6 | namespace Sample 7 | { 8 | public class TestFragmentAdapter : FragmentPagerAdapter, IIconPageAdapter 9 | { 10 | private static readonly string[] Content = { "This", "Is", "A", "Test" }; 11 | private static readonly int[] Icons = 12 | { 13 | Resource.Drawable.perm_group_calendar, 14 | Resource.Drawable.perm_group_camera, 15 | Resource.Drawable.perm_group_device_alarms, 16 | Resource.Drawable.perm_group_location 17 | }; 18 | 19 | private int _count = Content.Length; 20 | 21 | public TestFragmentAdapter(FragmentManager p0) 22 | : base(p0) 23 | { 24 | } 25 | 26 | public override int Count 27 | { 28 | get { return _count; } 29 | } 30 | 31 | public override Fragment GetItem(int position) 32 | { 33 | return TestFragment.NewInstance(Content[position % Content.Length]); 34 | } 35 | 36 | public override Java.Lang.ICharSequence GetPageTitleFormatted(int p0) 37 | { 38 | return new Java.Lang.String(Content[p0 % Content.Length]); 39 | } 40 | 41 | public void SetCount(int count) 42 | { 43 | if (count <= 0 || count > 10) return; 44 | 45 | _count = count; 46 | NotifyDataSetChanged(); 47 | } 48 | 49 | public int GetIconResId(int index) 50 | { 51 | return Icons[index % Icons.Length]; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sample/Titles/SampleTitlesBottom.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Titles 7 | { 8 | [Activity(Label = "Titles/Bottom")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleTitlesBottom : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_titles_bottom); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Titles/SampleTitlesCenterClickListener.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using Android.Widget; 4 | using AndroidX.ViewPager.Widget; 5 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 6 | 7 | namespace Sample.Titles 8 | { 9 | [Activity(Label = "Titles/Center Click Listener")] 10 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 11 | public class SampleTitlesCenterClickListener : BaseSampleActivity 12 | { 13 | protected override void OnCreate(Bundle savedInstanceState) 14 | { 15 | base.OnCreate(savedInstanceState); 16 | 17 | SetContentView(Resource.Layout.simple_titles); 18 | 19 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 20 | 21 | _pager = FindViewById(Resource.Id.pager); 22 | _pager.Adapter = _adapter; 23 | 24 | var indicator = FindViewById(Resource.Id.indicator); 25 | indicator.SetViewPager(_pager); 26 | indicator.FooterIndicatorStyle = TitlePageIndicator.IndicatorStyle.Underline; 27 | indicator.CenterItemClick += 28 | (sender, args) => Toast.MakeText(this, "You clicked the center title!", ToastLength.Short).Show(); 29 | _indicator = indicator; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sample/Titles/SampleTitlesDefault.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Titles 7 | { 8 | [Activity(Label = "Titles/Default")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleTitlesDefault : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_titles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Titles/SampleTitlesInitialPage.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Titles 7 | { 8 | [Activity(Label = "Titles/Initial Page")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleTitlesInitialPAge : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_titles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | _indicator.CurrentItem = _adapter.Count - 1; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sample/Titles/SampleTitlesStyledLayout.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Titles 7 | { 8 | [Activity(Label = "Titles/Styled Layout", Theme = "@style/LightTheme")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleTitlesStyledLayout : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.themed_titles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Titles/SampleTitlesStyledMethods.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Graphics; 3 | using Android.OS; 4 | using AndroidX.ViewPager.Widget; 5 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 6 | 7 | namespace Sample.Titles 8 | { 9 | [Activity(Label = "Titles/Styled Methods", Theme = "@style/LightTheme")] 10 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 11 | public class SampleTitlesStyledMethods : BaseSampleActivity 12 | { 13 | protected override void OnCreate(Bundle savedInstanceState) 14 | { 15 | base.OnCreate(savedInstanceState); 16 | 17 | SetContentView(Resource.Layout.simple_titles); 18 | 19 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 20 | 21 | _pager = FindViewById(Resource.Id.pager); 22 | _pager.Adapter = _adapter; 23 | 24 | var density = Resources.DisplayMetrics.Density; 25 | var indicator = FindViewById(Resource.Id.indicator); 26 | indicator.SetViewPager(_pager); 27 | indicator.SetBackgroundColor(Color.Argb(0x18, 0xFF, 0x00, 0x00)); 28 | indicator.FooterColor = Color.Argb(0xFF, 0xAA, 0x22, 0x22); 29 | indicator.FooterLineHeight = 1 * density; 30 | indicator.FooterIndicatorHeight = 3 * density; 31 | indicator.FooterIndicatorStyle = TitlePageIndicator.IndicatorStyle.Underline; 32 | indicator.TextColor = Color.Argb(0xAA, 0xFF, 0xFF, 0xFF); 33 | indicator.SelectedColor = Color.Argb(0xFF, 0xFF, 0xFF, 0xFF); 34 | indicator.IsSelectedBold = true; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sample/Titles/SampleTitlesStyledTheme.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Titles 7 | { 8 | [Activity(Label = "Titles/Styled Theme", Theme = "@style/StyledIndicators")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleTitlesStyledTheme : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_titles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Titles/SampleTitlesTriangle.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Titles 7 | { 8 | [Activity(Label = "Titles/Triangle")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleTitlesTriangle : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_titles); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | var indicator = FindViewById(Resource.Id.indicator); 24 | indicator.SetViewPager(_pager); 25 | indicator.FooterIndicatorStyle = TitlePageIndicator.IndicatorStyle.Triangle; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sample/Titles/SampleTitlesWithListener.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using Android.Widget; 4 | using AndroidX.ViewPager.Widget; 5 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 6 | 7 | namespace Sample.Titles 8 | { 9 | [Activity(Label = "Titles/With Listener")] 10 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 11 | public class SampleTitlesWithListener : BaseSampleActivity 12 | { 13 | protected override void OnCreate(Bundle savedInstanceState) 14 | { 15 | base.OnCreate(savedInstanceState); 16 | 17 | SetContentView(Resource.Layout.simple_titles); 18 | 19 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 20 | 21 | _pager = FindViewById(Resource.Id.pager); 22 | _pager.Adapter = _adapter; 23 | 24 | var indicator = FindViewById(Resource.Id.indicator); 25 | indicator.SetViewPager(_pager); 26 | indicator.PageSelected += 27 | (sender, args) => Toast.MakeText(this, "Changed to page " + args.Position, ToastLength.Short).Show(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sample/Underlines/SampleUnderlinesDefault.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Underlines 7 | { 8 | [Activity(Label = "Underlines/Default")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleUnderlinesDefault : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_underlines); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Underlines/SampleUnderlinesNoFade.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Underlines 7 | { 8 | [Activity(Label = "Underlines/No Fade")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleUnderlinesNoFade : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_underlines); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | var indicator = FindViewById(Resource.Id.indicator); 24 | indicator.SetViewPager(_pager); 25 | indicator.Fades = false; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sample/Underlines/SampleUnderlinesStyledLayout.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Underlines 7 | { 8 | [Activity(Label = "Underlines/Styled", Theme = "@style/LightTheme")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleUnderlinesStyledLayout : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.themed_underlines); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sample/Underlines/SampleUnderlinesStyledMethods.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Graphics; 3 | using Android.OS; 4 | using AndroidX.ViewPager.Widget; 5 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 6 | 7 | namespace Sample.Underlines 8 | { 9 | [Activity(Label = "Underlines/Styled Methods", Theme = "@style/LightTheme")] 10 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 11 | public class SampleUnderlinesStyledMethods : BaseSampleActivity 12 | { 13 | protected override void OnCreate(Bundle savedInstanceState) 14 | { 15 | base.OnCreate(savedInstanceState); 16 | 17 | SetContentView(Resource.Layout.simple_underlines); 18 | 19 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 20 | 21 | _pager = FindViewById(Resource.Id.pager); 22 | _pager.Adapter = _adapter; 23 | 24 | var indicator = FindViewById(Resource.Id.indicator); 25 | indicator.SetViewPager(_pager); 26 | indicator.SelectedColor = Color.Argb(0x33, 0xCC, 0x00, 0x00); 27 | indicator.SetBackgroundColor(Color.Argb(0xFF, 0xCC, 0xCC, 0xCC)); 28 | indicator.FadeLength = 1000; 29 | indicator.FadeDelay = 1000; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sample/Underlines/SampleUnderlinesStyledTheme.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.OS; 3 | using AndroidX.ViewPager.Widget; 4 | using DK.Ostebaronen.Droid.ViewPagerIndicator; 5 | 6 | namespace Sample.Underlines 7 | { 8 | [Activity(Label = "Underlines/Styled Theme", Theme = "@style/StyledIndicators")] 9 | [IntentFilter(new[] { Android.Content.Intent.ActionMain }, Categories = new[] { "dk.ostebaronen.viewpagerindicator.droid.sample" })] 10 | public class SampleUnderlinesStyledTheme : BaseSampleActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | base.OnCreate(savedInstanceState); 15 | 16 | SetContentView(Resource.Layout.simple_underlines); 17 | 18 | _adapter = new TestFragmentAdapter(SupportFragmentManager); 19 | 20 | _pager = FindViewById(Resource.Id.pager); 21 | _pager.Adapter = _adapter; 22 | 23 | _indicator = FindViewById(Resource.Id.indicator); 24 | _indicator.SetViewPager(_pager); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ViewPagerIndicator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29411.138 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Library", "Library\Library.csproj", "{FA322A14-2B8E-4496-B950-4D8263CA0A2D}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{551C29FF-3EC6-450A-A9A7-5654813289D1}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{74800907-7195-4E35-A09D-A1B5239B2666}" 11 | ProjectSection(SolutionItems) = preProject 12 | build.cake = build.cake 13 | releasenotes.md = releasenotes.md 14 | global.json = global.json 15 | Directory.build.props = Directory.build.props 16 | Directory.build.targets = Directory.build.targets 17 | icon.png = icon.png 18 | GitVersion.yml = GitVersion.yml 19 | .editorconfig = .editorconfig 20 | README.md = README.md 21 | EndProjectSection 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{FD7BE8A8-0E56-4DA7-8BDC-87232F0C89C0}" 24 | ProjectSection(SolutionItems) = preProject 25 | .github\workflows\build.yml = .github\workflows\build.yml 26 | .github\workflows\release-nuget.yml = .github\workflows\release-nuget.yml 27 | EndProjectSection 28 | EndProject 29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{995828A1-8453-4F6E-8523-8043ABF151A9}" 30 | ProjectSection(SolutionItems) = preProject 31 | .config\dotnet-tools.json = .config\dotnet-tools.json 32 | EndProjectSection 33 | EndProject 34 | Global 35 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 36 | Debug|Any CPU = Debug|Any CPU 37 | Release|Any CPU = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 40 | {FA322A14-2B8E-4496-B950-4D8263CA0A2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {FA322A14-2B8E-4496-B950-4D8263CA0A2D}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {FA322A14-2B8E-4496-B950-4D8263CA0A2D}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {FA322A14-2B8E-4496-B950-4D8263CA0A2D}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {551C29FF-3EC6-450A-A9A7-5654813289D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {551C29FF-3EC6-450A-A9A7-5654813289D1}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {551C29FF-3EC6-450A-A9A7-5654813289D1}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 47 | {551C29FF-3EC6-450A-A9A7-5654813289D1}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {551C29FF-3EC6-450A-A9A7-5654813289D1}.Release|Any CPU.Deploy.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {D37AFB0C-50DD-4C68-936F-66E6821D3593} 55 | EndGlobalSection 56 | GlobalSection(NestedProjects) = preSolution 57 | {FD7BE8A8-0E56-4DA7-8BDC-87232F0C89C0} = {74800907-7195-4E35-A09D-A1B5239B2666} 58 | {995828A1-8453-4F6E-8523-8043ABF151A9} = {74800907-7195-4E35-A09D-A1B5239B2666} 59 | EndGlobalSection 60 | EndGlobal 61 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | #tool dotnet:https://api.nuget.org/v3/index.json?package=GitVersion.Tool&version=5.10.3 2 | #addin nuget:https://api.nuget.org/v3/index.json?package=Cake.Figlet&version=2.0.1 3 | 4 | var target = Argument("target", "Default"); 5 | var configuration = Argument("configuration", "Release"); 6 | var verbosityArg = Argument("verbosity", "Minimal"); 7 | var outputDirArg = Argument("outputDir", "./artifacts"); 8 | var verbosity = DotNetVerbosity.Minimal; 9 | 10 | var gitVersionLog = new FilePath("./gitversion.log"); 11 | 12 | var sln = new FilePath("./ViewPagerIndicator.sln"); 13 | var outputDir = new DirectoryPath(outputDirArg); 14 | 15 | var isGitHubActionsBuild = GitHubActions.IsRunningOnGitHubActions; 16 | 17 | GitVersion versionInfo = null; 18 | 19 | Setup(context => 20 | { 21 | versionInfo = context.GitVersion(new GitVersionSettings 22 | { 23 | UpdateAssemblyInfo = true, 24 | OutputType = GitVersionOutput.Json, 25 | LogFilePath = gitVersionLog.MakeAbsolute(context.Environment) 26 | }); 27 | 28 | var cakeVersion = typeof(ICakeContext).Assembly.GetName().Version.ToString(); 29 | 30 | Information(Figlet("ViewPagerIndicator")); 31 | Information("Building version {0}, ({1}, {2}) using version {3} of Cake.", 32 | versionInfo.SemVer, 33 | configuration, 34 | target, 35 | cakeVersion); 36 | 37 | verbosity = (DotNetVerbosity) Enum.Parse(typeof(DotNetVerbosity), verbosityArg, true); 38 | }); 39 | 40 | Task("Clean").Does(() => 41 | { 42 | CleanDirectories("./**/bin"); 43 | CleanDirectories("./**/obj"); 44 | CleanDirectories(outputDir.FullPath); 45 | 46 | EnsureDirectoryExists(outputDir); 47 | }); 48 | 49 | Task("Restore") 50 | .Does(() => 51 | { 52 | DotNetRestore(sln.ToString()); 53 | }); 54 | 55 | Task("Build") 56 | .IsDependentOn("Clean") 57 | .IsDependentOn("Restore") 58 | .Does(() => 59 | { 60 | var msBuildSettings = new DotNetMSBuildSettings 61 | { 62 | Version = versionInfo.SemVer, 63 | PackageVersion = versionInfo.SemVer, 64 | InformationalVersion = versionInfo.InformationalVersion 65 | }; 66 | 67 | var settings = new DotNetBuildSettings 68 | { 69 | Configuration = configuration, 70 | Verbosity = verbosity, 71 | MSBuildSettings = msBuildSettings 72 | }; 73 | 74 | DotNetBuild(sln.ToString(), settings); 75 | }); 76 | 77 | Task("CopyArtifacts") 78 | .IsDependentOn("Build") 79 | .Does(() => 80 | { 81 | var nugetFiles = GetFiles("Library/bin/" + configuration + "/**/*.nupkg"); 82 | CopyFiles(nugetFiles, outputDir); 83 | CopyFileToDirectory(gitVersionLog, outputDir); 84 | }); 85 | 86 | Task("Default") 87 | .IsDependentOn("Clean") 88 | .IsDependentOn("Restore") 89 | .IsDependentOn("Build") 90 | .IsDependentOn("CopyArtifacts"); 91 | 92 | RunTarget(target); 93 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # This is the Cake bootstrapper script for PowerShell. 3 | # This file was downloaded from https://github.com/cake-build/resources 4 | # Feel free to change this file to fit your needs. 5 | ########################################################################## 6 | 7 | <# 8 | 9 | .SYNOPSIS 10 | This is a Powershell script to bootstrap a Cake build. 11 | 12 | .DESCRIPTION 13 | This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) 14 | and execute your Cake build script with the parameters you provide. 15 | 16 | .PARAMETER Script 17 | The build script to execute. 18 | .PARAMETER Target 19 | The build script target to run. 20 | .PARAMETER Configuration 21 | The build configuration to use. 22 | .PARAMETER Verbosity 23 | Specifies the amount of information to be displayed. 24 | .PARAMETER ShowDescription 25 | Shows description about tasks. 26 | .PARAMETER DryRun 27 | Performs a dry run. 28 | .PARAMETER Experimental 29 | Uses the nightly builds of the Roslyn script engine. 30 | .PARAMETER Mono 31 | Uses the Mono Compiler rather than the Roslyn script engine. 32 | .PARAMETER SkipToolPackageRestore 33 | Skips restoring of packages. 34 | .PARAMETER ScriptArgs 35 | Remaining arguments are added here. 36 | 37 | .LINK 38 | https://cakebuild.net 39 | 40 | #> 41 | 42 | [CmdletBinding()] 43 | Param( 44 | [string]$Script = "build.cake", 45 | [string]$Target, 46 | [string]$Configuration, 47 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] 48 | [string]$Verbosity, 49 | [switch]$ShowDescription, 50 | [Alias("WhatIf", "Noop")] 51 | [switch]$DryRun, 52 | [switch]$Experimental, 53 | [switch]$Mono, 54 | [switch]$SkipToolPackageRestore, 55 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 56 | [string[]]$ScriptArgs 57 | ) 58 | 59 | [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null 60 | function MD5HashFile([string] $filePath) 61 | { 62 | if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) 63 | { 64 | return $null 65 | } 66 | 67 | [System.IO.Stream] $file = $null; 68 | [System.Security.Cryptography.MD5] $md5 = $null; 69 | try 70 | { 71 | $md5 = [System.Security.Cryptography.MD5]::Create() 72 | $file = [System.IO.File]::OpenRead($filePath) 73 | return [System.BitConverter]::ToString($md5.ComputeHash($file)) 74 | } 75 | finally 76 | { 77 | if ($file -ne $null) 78 | { 79 | $file.Dispose() 80 | } 81 | } 82 | } 83 | 84 | function GetProxyEnabledWebClient 85 | { 86 | $wc = New-Object System.Net.WebClient 87 | $proxy = [System.Net.WebRequest]::GetSystemWebProxy() 88 | $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials 89 | $wc.Proxy = $proxy 90 | return $wc 91 | } 92 | 93 | Write-Host "Preparing to run build script..." 94 | 95 | if(!$PSScriptRoot){ 96 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 97 | } 98 | 99 | $TOOLS_DIR = Join-Path $PSScriptRoot "tools" 100 | $ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" 101 | $MODULES_DIR = Join-Path $TOOLS_DIR "Modules" 102 | $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" 103 | $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" 104 | $NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" 105 | $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" 106 | $PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" 107 | $ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" 108 | $MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" 109 | 110 | # Make sure tools folder exists 111 | if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { 112 | Write-Verbose -Message "Creating tools directory..." 113 | New-Item -Path $TOOLS_DIR -Type directory | out-null 114 | } 115 | 116 | # Make sure that packages.config exist. 117 | if (!(Test-Path $PACKAGES_CONFIG)) { 118 | Write-Verbose -Message "Downloading packages.config..." 119 | try { 120 | $wc = GetProxyEnabledWebClient 121 | $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { 122 | Throw "Could not download packages.config." 123 | } 124 | } 125 | 126 | # Try find NuGet.exe in path if not exists 127 | if (!(Test-Path $NUGET_EXE)) { 128 | Write-Verbose -Message "Trying to find nuget.exe in PATH..." 129 | $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } 130 | $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 131 | if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { 132 | Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." 133 | $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName 134 | } 135 | } 136 | 137 | # Try download NuGet.exe if not exists 138 | if (!(Test-Path $NUGET_EXE)) { 139 | Write-Verbose -Message "Downloading NuGet.exe..." 140 | try { 141 | $wc = GetProxyEnabledWebClient 142 | $wc.DownloadFile($NUGET_URL, $NUGET_EXE) 143 | } catch { 144 | Throw "Could not download NuGet.exe." 145 | } 146 | } 147 | 148 | # Save nuget.exe path to environment to be available to child processed 149 | $ENV:NUGET_EXE = $NUGET_EXE 150 | 151 | # Restore tools from NuGet? 152 | if(-Not $SkipToolPackageRestore.IsPresent) { 153 | Push-Location 154 | Set-Location $TOOLS_DIR 155 | 156 | # Check for changes in packages.config and remove installed tools if true. 157 | [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) 158 | if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or 159 | ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { 160 | Write-Verbose -Message "Missing or changed package.config hash..." 161 | Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery | 162 | Remove-Item -Recurse 163 | } 164 | 165 | Write-Verbose -Message "Restoring tools from NuGet..." 166 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" 167 | 168 | if ($LASTEXITCODE -ne 0) { 169 | Throw "An error occurred while restoring NuGet tools." 170 | } 171 | else 172 | { 173 | $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" 174 | } 175 | Write-Verbose -Message ($NuGetOutput | out-string) 176 | 177 | Pop-Location 178 | } 179 | 180 | # Restore addins from NuGet 181 | if (Test-Path $ADDINS_PACKAGES_CONFIG) { 182 | Push-Location 183 | Set-Location $ADDINS_DIR 184 | 185 | Write-Verbose -Message "Restoring addins from NuGet..." 186 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" 187 | 188 | if ($LASTEXITCODE -ne 0) { 189 | Throw "An error occurred while restoring NuGet addins." 190 | } 191 | 192 | Write-Verbose -Message ($NuGetOutput | out-string) 193 | 194 | Pop-Location 195 | } 196 | 197 | # Restore modules from NuGet 198 | if (Test-Path $MODULES_PACKAGES_CONFIG) { 199 | Push-Location 200 | Set-Location $MODULES_DIR 201 | 202 | Write-Verbose -Message "Restoring modules from NuGet..." 203 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" 204 | 205 | if ($LASTEXITCODE -ne 0) { 206 | Throw "An error occurred while restoring NuGet modules." 207 | } 208 | 209 | Write-Verbose -Message ($NuGetOutput | out-string) 210 | 211 | Pop-Location 212 | } 213 | 214 | # Make sure that Cake has been installed. 215 | if (!(Test-Path $CAKE_EXE)) { 216 | Throw "Could not find Cake.exe at $CAKE_EXE" 217 | } 218 | 219 | 220 | 221 | # Build Cake arguments 222 | $cakeArguments = @("$Script"); 223 | if ($Target) { $cakeArguments += "-target=$Target" } 224 | if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } 225 | if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } 226 | if ($ShowDescription) { $cakeArguments += "-showdescription" } 227 | if ($DryRun) { $cakeArguments += "-dryrun" } 228 | if ($Experimental) { $cakeArguments += "-experimental" } 229 | if ($Mono) { $cakeArguments += "-mono" } 230 | $cakeArguments += $ScriptArgs 231 | 232 | # Start Cake 233 | Write-Host "Running build script..." 234 | &$CAKE_EXE $cakeArguments 235 | exit $LASTEXITCODE 236 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ########################################################################## 4 | # This is the Cake bootstrapper script for Linux and OS X. 5 | # This file was downloaded from https://github.com/cake-build/resources 6 | # Feel free to change this file to fit your needs. 7 | ########################################################################## 8 | 9 | # Define directories. 10 | SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 11 | TOOLS_DIR=$SCRIPT_DIR/tools 12 | ADDINS_DIR=$TOOLS_DIR/Addins 13 | MODULES_DIR=$TOOLS_DIR/Modules 14 | NUGET_EXE=$TOOLS_DIR/nuget.exe 15 | CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe 16 | PACKAGES_CONFIG=$TOOLS_DIR/packages.config 17 | PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum 18 | ADDINS_PACKAGES_CONFIG=$ADDINS_DIR/packages.config 19 | MODULES_PACKAGES_CONFIG=$MODULES_DIR/packages.config 20 | 21 | # Define md5sum or md5 depending on Linux/OSX 22 | MD5_EXE= 23 | if [[ "$(uname -s)" == "Darwin" ]]; then 24 | MD5_EXE="md5 -r" 25 | else 26 | MD5_EXE="md5sum" 27 | fi 28 | 29 | # Define default arguments. 30 | SCRIPT="build.cake" 31 | CAKE_ARGUMENTS=() 32 | 33 | # Parse arguments. 34 | for i in "$@"; do 35 | case $1 in 36 | -s|--script) SCRIPT="$2"; shift ;; 37 | --) shift; CAKE_ARGUMENTS+=("$@"); break ;; 38 | *) CAKE_ARGUMENTS+=("$1") ;; 39 | esac 40 | shift 41 | done 42 | 43 | # Make sure the tools folder exist. 44 | if [ ! -d "$TOOLS_DIR" ]; then 45 | mkdir "$TOOLS_DIR" 46 | fi 47 | 48 | # Make sure that packages.config exist. 49 | if [ ! -f "$TOOLS_DIR/packages.config" ]; then 50 | echo "Downloading packages.config..." 51 | curl -Lsfo "$TOOLS_DIR/packages.config" https://cakebuild.net/download/bootstrapper/packages 52 | if [ $? -ne 0 ]; then 53 | echo "An error occurred while downloading packages.config." 54 | exit 1 55 | fi 56 | fi 57 | 58 | # Download NuGet if it does not exist. 59 | if [ ! -f "$NUGET_EXE" ]; then 60 | echo "Downloading NuGet..." 61 | curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe 62 | if [ $? -ne 0 ]; then 63 | echo "An error occurred while downloading nuget.exe." 64 | exit 1 65 | fi 66 | fi 67 | 68 | # Restore tools from NuGet. 69 | pushd "$TOOLS_DIR" >/dev/null 70 | if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then 71 | find . -type d ! -name . ! -name 'Cake.Bakery' | xargs rm -rf 72 | fi 73 | 74 | mono "$NUGET_EXE" install -ExcludeVersion 75 | if [ $? -ne 0 ]; then 76 | echo "Could not restore NuGet tools." 77 | exit 1 78 | fi 79 | 80 | $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5" 81 | 82 | popd >/dev/null 83 | 84 | # Restore addins from NuGet. 85 | if [ -f "$ADDINS_PACKAGES_CONFIG" ]; then 86 | pushd "$ADDINS_DIR" >/dev/null 87 | 88 | mono "$NUGET_EXE" install -ExcludeVersion 89 | if [ $? -ne 0 ]; then 90 | echo "Could not restore NuGet addins." 91 | exit 1 92 | fi 93 | 94 | popd >/dev/null 95 | fi 96 | 97 | # Restore modules from NuGet. 98 | if [ -f "$MODULES_PACKAGES_CONFIG" ]; then 99 | pushd "$MODULES_DIR" >/dev/null 100 | 101 | mono "$NUGET_EXE" install -ExcludeVersion 102 | if [ $? -ne 0 ]; then 103 | echo "Could not restore NuGet modules." 104 | exit 1 105 | fi 106 | 107 | popd >/dev/null 108 | fi 109 | 110 | # Make sure that Cake has been installed. 111 | if [ ! -f "$CAKE_EXE" ]; then 112 | echo "Could not find Cake.exe at '$CAKE_EXE'." 113 | exit 1 114 | fi 115 | 116 | # Start Cake 117 | exec mono "$CAKE_EXE" $SCRIPT "${CAKE_ARGUMENTS[@]}" 118 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild-sdks": { 3 | "Xamarin.Legacy.Sdk": "0.2.0-alpha1" 4 | } 5 | } -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheesebaron/ViewPagerIndicator/155d7d16d4c841aceb21e04b2d5ad17e298a31a6/icon.png -------------------------------------------------------------------------------- /releasenotes.md: -------------------------------------------------------------------------------- 1 | # New in 5.0.0 2 | - Migrate to AndroidX 3 | - Fix bug in TitlePageIndicator where selected item would be transparent 4 | 5 | # New in 0.4.0 6 | - Being a better citizen by helping GC disposing of managed resources 7 | - Updated to Xamarin.Android.Support.ViewPager 28.0.0.3 8 | - Updated library project to SDK Style project 9 | - Added TargetFramework 29 (Android 10) 10 | 11 | # New in 0.3.0 12 | - Added the possibility to add extra spacing between circles in CirclePageIndicator, the default spacing is now 5dp, adjust it by using the `ExtraSpacing` property or the `extraSpacing` attribute on the view 13 | - Fixed usage of deprecated methods to calculate touches and more 14 | - Fixed underline fade not taking selected color alpha into consideration always jumping to alpha 0xFF before animating 15 | - Fixed usage of `fill_parent` and now using `match_parent` in layouts and README 16 | 17 | # New in 0.2.2 18 | - Switched to Xamarin.Android.Support.ViewPager 28.0.0.1 instead of Xamarin.Android.Support.v4 25.4.0.2 19 | - Updated to TargetFramework 28 (Android 9.0) 20 | --------------------------------------------------------------------------------