├── LICENSE.md
├── README.md
└── src
├── .gitignore
├── Droid
├── Assets
│ └── AboutAssets.txt
├── MainActivity.cs
├── Properties
│ ├── AndroidManifest.xml
│ └── AssemblyInfo.cs
├── Renderers
│ ├── CarouselLayoutRenderer.cs
│ └── CustomNavigationRenderer.cs
├── Resources
│ ├── AboutResources.txt
│ ├── Resource.designer.cs
│ ├── drawable-hdpi
│ │ └── icon.png
│ ├── drawable-xhdpi
│ │ └── icon.png
│ ├── drawable-xxhdpi
│ │ └── icon.png
│ └── drawable
│ │ ├── icon.png
│ │ ├── one.jpeg
│ │ ├── pin.png
│ │ ├── pip.png
│ │ └── pip_selected.png
├── SwippableBottomTabView.Droid.csproj
└── packages.config
├── SwippableBottomTabView.sln
├── SwippableBottomTabView
├── App.cs
├── Controls
│ └── CarouselLayout.cs
├── DynamicTemplateLayout.cs
├── HomePage.cs
├── HomeView.cs
├── PagerIndicatorDots.cs
├── PagerIndicatorTabs.cs
├── SwippableBottomTabView.projitems
├── SwippableBottomTabView.shproj
├── SwitcherPage.cs
├── TabPageOne.cs
├── TabPageTwo.cs
└── ViewModels
│ ├── BaseViewModel.cs
│ ├── ICarouselViewModel.cs
│ ├── SwitcherPageViewModel.cs
│ ├── TabOneViewModel.cs
│ ├── TabTwoViewModel.cs
│ └── TabbedPageViewModel.cs
├── iOS
├── AppDelegate.cs
├── Entitlements.plist
├── ITunesArtwork
├── ITunesArtwork@2x
├── Info.plist
├── Main.cs
├── Renderers
│ └── CarouselLayoutRenderer.cs
├── Resources
│ ├── Default-568h@2x.png
│ ├── Default-Portrait.png
│ ├── Default-Portrait@2x.png
│ ├── Default.png
│ ├── Default@2x.png
│ ├── Icon-60@2x.png
│ ├── Icon-60@3x.png
│ ├── Icon-76.png
│ ├── Icon-76@2x.png
│ ├── Icon-Small-40.png
│ ├── Icon-Small-40@2x.png
│ ├── Icon-Small-40@3x.png
│ ├── Icon-Small.png
│ ├── Icon-Small@2x.png
│ ├── Icon-Small@3x.png
│ ├── LaunchScreen.storyboard
│ ├── pin.png
│ └── pin@2x.png
├── SwippableBottomTabView.iOS.csproj
└── packages.config
└── repositories.config
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Hailin Shu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # XamarinForms.SwippableBottomTabView
2 | This is a Tab control based off Chris Riesgo's excellent Carousel View
3 |
4 | https://github.com/chrisriesgo/xamarin-forms-carouselview
5 | https://gist.github.com/chrisriesgo/2e9c88fa346e57745e73
6 |
7 | with his point of direction of how to implement a Tab View
8 |
9 | One caveat: this code is not very reusable, meaning it's not something you can drop into your project and it will work right away. You still need to do some work to integrate it into your project.
10 | Also, there is no transiton of the tabs when swiping like View Indicator in Android. The tab will be changed after the swipe is in place.
11 |
--------------------------------------------------------------------------------
/src/.gitignore:
--------------------------------------------------------------------------------
1 | #Autosave files
2 | *~
3 |
4 | #build
5 | [Oo]bj/
6 | [Bb]in/
7 | packages/
8 | TestResults/
9 |
10 | # globs
11 | Makefile.in
12 | *.DS_Store
13 | *.sln.cache
14 | *.suo
15 | *.cache
16 | *.pidb
17 | *.userprefs
18 | *.usertasks
19 | config.log
20 | config.make
21 | config.status
22 | aclocal.m4
23 | install-sh
24 | autom4te.cache/
25 | *.user
26 | *.tar.gz
27 | tarballs/
28 | test-results/
29 | Thumbs.db
30 |
31 | #Mac bundle stuff
32 | *.dmg
33 | *.app
34 |
35 | #resharper
36 | *_Resharper.*
37 | *.Resharper
38 |
39 | #dotCover
40 | *.dotCover
41 |
--------------------------------------------------------------------------------
/src/Droid/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 your 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");
20 |
--------------------------------------------------------------------------------
/src/Droid/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using Android.OS;
4 |
5 | namespace SwippableBottomTabView.Droid
6 | {
7 | [Activity(Label = "TabView Demo", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
8 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
9 | {
10 | protected override void OnCreate(Bundle bundle)
11 | {
12 | base.OnCreate(bundle);
13 |
14 | global::Xamarin.Forms.Forms.Init(this, bundle);
15 |
16 | LoadApplication(new App());
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/Droid/Properties/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/Droid/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | // Information about this assembly is defined by the following attributes.
4 | // Change them to the values specific to your project.
5 |
6 | [assembly: AssemblyTitle("SwippableBottomTabView.Droid")]
7 | [assembly: AssemblyDescription("")]
8 | [assembly: AssemblyConfiguration("")]
9 | [assembly: AssemblyCompany("")]
10 | [assembly: AssemblyProduct("")]
11 | [assembly: AssemblyCopyright("chrisriesgo")]
12 | [assembly: AssemblyTrademark("")]
13 | [assembly: AssemblyCulture("")]
14 |
15 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
16 | // The form "{Major}.{Minor}.*" will automatically update the build and revision,
17 | // and "{Major}.{Minor}.{Build}.*" will update just the revision.
18 |
19 | [assembly: AssemblyVersion("1.0.0")]
20 |
21 | // The following attributes are used to specify the signing key for the assembly,
22 | // if desired. See the Mono documentation for more information about signing.
23 |
24 | //[assembly: AssemblyDelaySign(false)]
25 | //[assembly: AssemblyKeyFile("")]
--------------------------------------------------------------------------------
/src/Droid/Renderers/CarouselLayoutRenderer.cs:
--------------------------------------------------------------------------------
1 | using Android.Graphics;
2 | using Android.Support.V4.View;
3 | using Android.Views;
4 | using Android.Widget;
5 | using Java.Lang;
6 | using SwippableBottomTabView.Controls;
7 | using SwippableBottomTabView.Droid.Renderers;
8 | using System.ComponentModel;
9 | using System.Reflection;
10 | using System.Timers;
11 | using Xamarin.Forms;
12 | using Xamarin.Forms.Platform.Android;
13 |
14 | [assembly: ExportRenderer(typeof(CarouselLayout), typeof(CarouselLayoutRenderer))]
15 |
16 | namespace SwippableBottomTabView.Droid.Renderers
17 | {
18 | public class CarouselLayoutRenderer : ScrollViewRenderer
19 | {
20 | private int _prevScrollX;
21 | private int _deltaX;
22 | private bool _motionDown;
23 | private Timer _deltaXResetTimer;
24 | private Timer _scrollStopTimer;
25 | private HorizontalScrollView _scrollView;
26 |
27 | protected override void OnElementChanged(VisualElementChangedEventArgs e)
28 | {
29 | var a = new ViewPager(this.Context);
30 | var b = a.ClipBounds;
31 | base.OnElementChanged(e);
32 | if (e.NewElement == null) return;
33 |
34 | _deltaXResetTimer = new Timer(100) { AutoReset = false };
35 | _deltaXResetTimer.Elapsed += (object sender, ElapsedEventArgs args) => _deltaX = 0;
36 |
37 | _scrollStopTimer = new Timer(200) { AutoReset = false };
38 | _scrollStopTimer.Elapsed += (object sender, ElapsedEventArgs args2) => UpdateSelectedIndex();
39 |
40 | e.NewElement.PropertyChanged += ElementPropertyChanged;
41 | }
42 |
43 | private void ElementPropertyChanged(object sender, PropertyChangedEventArgs e)
44 | {
45 | if (e.PropertyName == "Renderer")
46 | {
47 | _scrollView = (HorizontalScrollView)typeof(ScrollViewRenderer)
48 | .GetField("hScrollView", BindingFlags.NonPublic | BindingFlags.Instance)
49 | .GetValue(this);
50 |
51 | _scrollView.HorizontalScrollBarEnabled = false;
52 | _scrollView.Touch += HScrollViewTouch;
53 | }
54 | if (e.PropertyName == CarouselLayout.SelectedIndexProperty.PropertyName && !_motionDown)
55 | {
56 | ScrollToIndex(((CarouselLayout)this.Element).SelectedIndex);
57 | }
58 | }
59 |
60 | private void HScrollViewTouch(object sender, TouchEventArgs e)
61 | {
62 | e.Handled = false;
63 |
64 | switch (e.Event.Action)
65 | {
66 | case MotionEventActions.Move:
67 | _deltaXResetTimer.Stop();
68 | _deltaX = _scrollView.ScrollX - _prevScrollX;
69 | _prevScrollX = _scrollView.ScrollX;
70 |
71 | UpdateSelectedIndex();
72 |
73 | _deltaXResetTimer.Start();
74 | break;
75 |
76 | case MotionEventActions.Down:
77 | _motionDown = true;
78 | _scrollStopTimer.Stop();
79 | break;
80 |
81 | case MotionEventActions.Up:
82 | _motionDown = false;
83 | SnapScroll();
84 | _scrollStopTimer.Start();
85 | break;
86 | }
87 | }
88 |
89 | private void UpdateSelectedIndex()
90 | {
91 | var center = _scrollView.ScrollX + (_scrollView.Width / 2);
92 | var carouselLayout = (CarouselLayout)this.Element;
93 | carouselLayout.SelectedIndex = (center / _scrollView.Width);
94 | }
95 |
96 | private void SnapScroll()
97 | {
98 | var roughIndex = (float)_scrollView.ScrollX / _scrollView.Width;
99 |
100 | var targetIndex =
101 | _deltaX < 0 ? Math.Floor(roughIndex)
102 | : _deltaX > 0 ? Math.Ceil(roughIndex)
103 | : Math.Round(roughIndex);
104 |
105 | ScrollToIndex((int)targetIndex);
106 | }
107 |
108 | private void ScrollToIndex(int targetIndex)
109 | {
110 | var targetX = targetIndex * _scrollView.Width;
111 | _scrollView.Post(new Runnable(() =>
112 | {
113 | _scrollView.SmoothScrollTo(targetX, 0);
114 | }));
115 | }
116 |
117 | private bool _initialized = false;
118 |
119 | public override void Draw(Canvas canvas)
120 | {
121 | base.Draw(canvas);
122 | if (_initialized) return;
123 | _initialized = true;
124 | var carouselLayout = (CarouselLayout)this.Element;
125 | _scrollView.ScrollTo(carouselLayout.SelectedIndex * Width, 0);
126 | }
127 |
128 | protected override void OnSizeChanged(int width, int height, int oldWidth, int oldHeight)
129 | {
130 | // if the screen width changed
131 | if (_initialized && (width != oldWidth))
132 | {
133 | // signal that on next Draw we will need ScrollView adjustment
134 | _initialized = false;
135 | }
136 | base.OnSizeChanged(width, height, oldWidth, oldHeight);
137 | }
138 | }
139 | }
--------------------------------------------------------------------------------
/src/Droid/Renderers/CustomNavigationRenderer.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Graphics.Drawables;
3 | using Xamarin.Forms;
4 | using Xamarin.Forms.Platform.Android;
5 | using CustomLayouts.Droid.Renderers;
6 |
7 | [assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomNavigationRenderer))]
8 |
9 | namespace CustomLayouts.Droid.Renderers
10 | {
11 | public class CustomNavigationRenderer : NavigationRenderer
12 | {
13 | protected override void OnElementChanged(ElementChangedEventArgs e)
14 | {
15 | base.OnElementChanged (e);
16 |
17 | RemoveAppIconFromActionBar ();
18 | }
19 |
20 | void RemoveAppIconFromActionBar()
21 | {
22 | // http://stackoverflow.com/questions/14606294/remove-icon-logo-from-action-bar-on-android
23 | var actionBar = ((Activity)Context).ActionBar;
24 | actionBar.SetIcon (new ColorDrawable(Color.Transparent.ToAndroid()));
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Droid/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.axml),
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/
12 | icon.png
13 |
14 | layout/
15 | main.axml
16 |
17 | values/
18 | strings.xml
19 |
20 | In order to get the build system to recognize Android resources, set the build action to
21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but
22 | instead operate on resource IDs. When you compile an Android application that uses resources,
23 | the build system will package the resources for distribution and generate a class called "R"
24 | (this is an Android convention) that contains the tokens for each one of the resources
25 | included. For example, for the above Resources layout, this is what the R class would expose:
26 |
27 | public class R {
28 | public class drawable {
29 | public const int icon = 0x123;
30 | }
31 |
32 | public class layout {
33 | public const int main = 0x456;
34 | }
35 |
36 | public class strings {
37 | public const int first_string = 0xabc;
38 | public const int second_string = 0xbcd;
39 | }
40 | }
41 |
42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first
44 | string in the dictionary file values/strings.xml.
45 |
--------------------------------------------------------------------------------
/src/Droid/Resources/Resource.designer.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable 1591
2 | //------------------------------------------------------------------------------
3 | //
4 | // This code was generated by a tool.
5 | // Runtime Version:4.0.30319.34209
6 | //
7 | // Changes to this file may cause incorrect behavior and will be lost if
8 | // the code is regenerated.
9 | //
10 | //------------------------------------------------------------------------------
11 |
12 | [assembly: global::Android.Runtime.ResourceDesignerAttribute("SwippableBottomTabView.Droid.Resource", IsApplication=true)]
13 |
14 | namespace SwippableBottomTabView.Droid
15 | {
16 |
17 |
18 | [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
19 | public partial class Resource
20 | {
21 |
22 | static Resource()
23 | {
24 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
25 | }
26 |
27 | public static void UpdateIdValues()
28 | {
29 | global::Xamarin.Forms.Platform.Resource.String.ApplicationName = global::SwippableBottomTabView.Droid.Resource.String.ApplicationName;
30 | global::Xamarin.Forms.Platform.Resource.String.Hello = global::SwippableBottomTabView.Droid.Resource.String.Hello;
31 | }
32 |
33 | public partial class Attribute
34 | {
35 |
36 | static Attribute()
37 | {
38 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
39 | }
40 |
41 | private Attribute()
42 | {
43 | }
44 | }
45 |
46 | public partial class Drawable
47 | {
48 |
49 | // aapt resource value: 0x7f020000
50 | public const int icon = 2130837504;
51 |
52 | // aapt resource value: 0x7f020001
53 | public const int pin = 2130837505;
54 |
55 | static Drawable()
56 | {
57 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
58 | }
59 |
60 | private Drawable()
61 | {
62 | }
63 | }
64 |
65 | public partial class String
66 | {
67 |
68 | // aapt resource value: 0x7f030001
69 | public const int ApplicationName = 2130903041;
70 |
71 | // aapt resource value: 0x7f030000
72 | public const int Hello = 2130903040;
73 |
74 | static String()
75 | {
76 | global::Android.Runtime.ResourceIdManager.UpdateIdValues();
77 | }
78 |
79 | private String()
80 | {
81 | }
82 | }
83 | }
84 | }
85 | #pragma warning restore 1591
86 |
--------------------------------------------------------------------------------
/src/Droid/Resources/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imgen/XamarinForms.SwippableBottomTabView/de205a80f4af5662b22d50486e33f435f73d3eb1/src/Droid/Resources/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/src/Droid/Resources/drawable-xhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imgen/XamarinForms.SwippableBottomTabView/de205a80f4af5662b22d50486e33f435f73d3eb1/src/Droid/Resources/drawable-xhdpi/icon.png
--------------------------------------------------------------------------------
/src/Droid/Resources/drawable-xxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imgen/XamarinForms.SwippableBottomTabView/de205a80f4af5662b22d50486e33f435f73d3eb1/src/Droid/Resources/drawable-xxhdpi/icon.png
--------------------------------------------------------------------------------
/src/Droid/Resources/drawable/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imgen/XamarinForms.SwippableBottomTabView/de205a80f4af5662b22d50486e33f435f73d3eb1/src/Droid/Resources/drawable/icon.png
--------------------------------------------------------------------------------
/src/Droid/Resources/drawable/one.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imgen/XamarinForms.SwippableBottomTabView/de205a80f4af5662b22d50486e33f435f73d3eb1/src/Droid/Resources/drawable/one.jpeg
--------------------------------------------------------------------------------
/src/Droid/Resources/drawable/pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imgen/XamarinForms.SwippableBottomTabView/de205a80f4af5662b22d50486e33f435f73d3eb1/src/Droid/Resources/drawable/pin.png
--------------------------------------------------------------------------------
/src/Droid/Resources/drawable/pip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imgen/XamarinForms.SwippableBottomTabView/de205a80f4af5662b22d50486e33f435f73d3eb1/src/Droid/Resources/drawable/pip.png
--------------------------------------------------------------------------------
/src/Droid/Resources/drawable/pip_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imgen/XamarinForms.SwippableBottomTabView/de205a80f4af5662b22d50486e33f435f73d3eb1/src/Droid/Resources/drawable/pip_selected.png
--------------------------------------------------------------------------------
/src/Droid/SwippableBottomTabView.Droid.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
7 | {26422919-3EAD-440B-B516-E41B10D2AC56}
8 | Library
9 | SwippableBottomTabView.Droid
10 | Assets
11 | Resources
12 | Properties\AndroidManifest.xml
13 | Resource
14 | Resources\Resource.designer.cs
15 | True
16 | True
17 | SwippableBottomTabView.Droid
18 | v5.0
19 |
20 |
21 | true
22 | full
23 | false
24 | bin\Debug
25 | DEBUG;
26 | prompt
27 | 4
28 | None
29 | false
30 | True
31 |
32 | False
33 | False
34 | False
35 | armeabi,armeabi-v7a,x86
36 |
37 |
38 | Xamarin
39 | False
40 | True
41 |
42 |
43 | full
44 | true
45 | bin\Release
46 | prompt
47 | 4
48 | false
49 | false
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | ..\packages\Xamarin.Forms.1.4.3.6374\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll
58 |
59 |
60 | ..\packages\Xamarin.Forms.1.4.3.6374\lib\MonoAndroid10\FormsViewGroup.dll
61 |
62 |
63 | ..\packages\Xamarin.Forms.1.4.3.6374\lib\MonoAndroid10\Xamarin.Forms.Core.dll
64 |
65 |
66 | ..\packages\Xamarin.Forms.1.4.3.6374\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll
67 |
68 |
69 | ..\packages\Xamarin.Forms.1.4.3.6374\lib\MonoAndroid10\Xamarin.Forms.Platform.dll
70 |
71 |
72 | ..\packages\Xamarin.Android.Support.v4.22.2.0.0\lib\MonoAndroid403\Xamarin.Android.Support.v4.dll
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 |
--------------------------------------------------------------------------------
/src/Droid/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/SwippableBottomTabView.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.31101.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SwippableBottomTabView", "SwippableBottomTabView\SwippableBottomTabView.shproj", "{77990895-4247-4E5A-9474-E3456B62A28D}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwippableBottomTabView.iOS", "iOS\SwippableBottomTabView.iOS.csproj", "{E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwippableBottomTabView.Droid", "Droid\SwippableBottomTabView.Droid.csproj", "{26422919-3EAD-440B-B516-E41B10D2AC56}"
11 | EndProject
12 | Global
13 | GlobalSection(SharedMSBuildProjectFiles) = preSolution
14 | SwippableBottomTabView\SwippableBottomTabView.projitems*{77990895-4247-4e5a-9474-e3456b62a28d}*SharedItemsImports = 13
15 | SwippableBottomTabView\SwippableBottomTabView.projitems*{e97c5e1f-83a0-4cd5-b1e1-91897b8c9f86}*SharedItemsImports = 4
16 | SwippableBottomTabView\SwippableBottomTabView.projitems*{26422919-3ead-440b-b516-e41b10d2ac56}*SharedItemsImports = 4
17 | EndGlobalSection
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Debug|iPhone = Debug|iPhone
21 | Debug|iPhoneSimulator = Debug|iPhoneSimulator
22 | Release|Any CPU = Release|Any CPU
23 | Release|iPhone = Release|iPhone
24 | Release|iPhoneSimulator = Release|iPhoneSimulator
25 | EndGlobalSection
26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
27 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
28 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator
29 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Debug|iPhone.ActiveCfg = Debug|iPhone
30 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Debug|iPhone.Build.0 = Debug|iPhone
31 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
32 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
33 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Release|Any CPU.ActiveCfg = Release|iPhone
34 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Release|Any CPU.Build.0 = Release|iPhone
35 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Release|iPhone.ActiveCfg = Release|iPhone
36 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Release|iPhone.Build.0 = Release|iPhone
37 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
38 | {E97C5E1F-83A0-4CD5-B1E1-91897B8C9F86}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
39 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
42 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Debug|iPhone.ActiveCfg = Debug|Any CPU
43 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Debug|iPhone.Build.0 = Debug|Any CPU
44 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
45 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
46 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Release|iPhone.ActiveCfg = Release|Any CPU
49 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Release|iPhone.Build.0 = Release|Any CPU
50 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
51 | {26422919-3EAD-440B-B516-E41B10D2AC56}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
52 | EndGlobalSection
53 | GlobalSection(SolutionProperties) = preSolution
54 | HideSolutionNode = FALSE
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/src/SwippableBottomTabView/App.cs:
--------------------------------------------------------------------------------
1 | using Xamarin.Forms;
2 |
3 | namespace SwippableBottomTabView
4 | {
5 | public class App : Application
6 | {
7 | public App()
8 | {
9 | // The root page of your application
10 | var navPage = new NavigationPage(new SwitcherPage());
11 | navPage.Icon = null;
12 | MainPage = navPage;
13 | }
14 |
15 | protected override void OnStart()
16 | {
17 | // Handle when your app starts
18 | }
19 |
20 | protected override void OnSleep()
21 | {
22 | // Handle when your app sleeps
23 | }
24 |
25 | protected override void OnResume()
26 | {
27 | // Handle when your app resumes
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/SwippableBottomTabView/Controls/CarouselLayout.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Timers;
5 | using Xamarin.Forms;
6 |
7 | namespace SwippableBottomTabView.Controls
8 | {
9 | public class CarouselLayout : ScrollView
10 | {
11 | public enum IndicatorStyleEnum
12 | {
13 | None,
14 | Dots,
15 | Tabs
16 | }
17 |
18 | private readonly StackLayout _stack;
19 | private Timer _selectedItemTimer;
20 |
21 | private int _selectedIndex;
22 |
23 | public CarouselLayout()
24 | {
25 | Orientation = ScrollOrientation.Horizontal;
26 |
27 | _stack = new StackLayout
28 | {
29 | Orientation = StackOrientation.Horizontal,
30 | Spacing = 0
31 | };
32 |
33 | Content = _stack;
34 |
35 | _selectedItemTimer = new Timer
36 | {
37 | AutoReset = false,
38 | Interval = 300
39 | };
40 |
41 | _selectedItemTimer.Elapsed += SelectedItemTimerElapsed;
42 | }
43 |
44 | public IndicatorStyleEnum IndicatorStyle { get; set; }
45 |
46 | public IList Children
47 | {
48 | get
49 | {
50 | return _stack.Children;
51 | }
52 | }
53 |
54 | private bool _layingOutChildren;
55 |
56 | protected override void LayoutChildren(double x, double y, double width, double height)
57 | {
58 | base.LayoutChildren(x, y, width, height);
59 | if (_layingOutChildren) return;
60 |
61 | _layingOutChildren = true;
62 | foreach (var child in Children) child.WidthRequest = width;
63 | _layingOutChildren = false;
64 | }
65 |
66 | public static readonly BindableProperty SelectedIndexProperty =
67 | BindableProperty.Create(
68 | carousel => carousel.SelectedIndex,
69 | 0,
70 | BindingMode.TwoWay,
71 | propertyChanged: (bindable, oldValue, newValue) =>
72 | {
73 | ((CarouselLayout)bindable).UpdateSelectedItem();
74 | }
75 | );
76 |
77 | public int SelectedIndex
78 | {
79 | get
80 | {
81 | return (int)GetValue(SelectedIndexProperty);
82 | }
83 | set
84 | {
85 | SetValue(SelectedIndexProperty, value);
86 | }
87 | }
88 |
89 | private void UpdateSelectedItem()
90 | {
91 | _selectedItemTimer.Stop();
92 | _selectedItemTimer.Start();
93 | }
94 |
95 | private void SelectedItemTimerElapsed(object sender, ElapsedEventArgs e)
96 | {
97 | SelectedItem = SelectedIndex > -1 ? Children[SelectedIndex].BindingContext : null;
98 | }
99 |
100 | public static readonly BindableProperty ItemsSourceProperty =
101 | BindableProperty.Create(
102 | view => view.ItemsSource,
103 | null,
104 | propertyChanging: (bindableObject, oldValue, newValue) =>
105 | {
106 | ((CarouselLayout)bindableObject).ItemsSourceChanging();
107 | },
108 | propertyChanged: (bindableObject, oldValue, newValue) =>
109 | {
110 | ((CarouselLayout)bindableObject).ItemsSourceChanged();
111 | }
112 | );
113 |
114 | public IList ItemsSource
115 | {
116 | get
117 | {
118 | return (IList)GetValue(ItemsSourceProperty);
119 | }
120 | set
121 | {
122 | SetValue(ItemsSourceProperty, value);
123 | }
124 | }
125 |
126 | private void ItemsSourceChanging()
127 | {
128 | if (ItemsSource == null) return;
129 | _selectedIndex = ItemsSource.IndexOf(SelectedItem);
130 | }
131 |
132 | private void ItemsSourceChanged()
133 | {
134 | _stack.Children.Clear();
135 | foreach (var item in ItemsSource)
136 | {
137 | var content = (DynamicTemplateLayout)ItemTemplate.CreateContent();
138 | content.BindingContext = item;
139 | _stack.Children.Add(content.View);
140 | }
141 |
142 | if (_selectedIndex >= 0) SelectedIndex = _selectedIndex;
143 | }
144 |
145 | public DataTemplate ItemTemplate
146 | {
147 | get;
148 | set;
149 | }
150 |
151 | public static readonly BindableProperty SelectedItemProperty =
152 | BindableProperty.Create(
153 | view => view.SelectedItem,
154 | null,
155 | BindingMode.TwoWay,
156 | propertyChanged: (bindable, oldValue, newValue) =>
157 | {
158 | ((CarouselLayout)bindable).UpdateSelectedIndex();
159 | }
160 | );
161 |
162 | public object SelectedItem
163 | {
164 | get
165 | {
166 | return GetValue(SelectedItemProperty);
167 | }
168 | set
169 | {
170 | SetValue(SelectedItemProperty, value);
171 | }
172 | }
173 |
174 | private void UpdateSelectedIndex()
175 | {
176 | if (SelectedItem == BindingContext) return;
177 |
178 | SelectedIndex = Children
179 | .Select(c => c.BindingContext)
180 | .ToList()
181 | .IndexOf(SelectedItem);
182 | }
183 | }
184 | }
--------------------------------------------------------------------------------
/src/SwippableBottomTabView/DynamicTemplateLayout.cs:
--------------------------------------------------------------------------------
1 | using SwippableBottomTabView.ViewModels;
2 | using Xamarin.Forms;
3 |
4 | namespace SwippableBottomTabView
5 | {
6 | public class DynamicTemplateLayout : ViewCell
7 | {
8 | protected override void OnBindingContextChanged()
9 | {
10 | base.OnBindingContextChanged();
11 |
12 | var vm = BindingContext as ICarouselViewModel;
13 | var page = vm.View;
14 | page.BindingContext = vm;
15 | View = page;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/SwippableBottomTabView/HomePage.cs:
--------------------------------------------------------------------------------
1 | using SwippableBottomTabView.Controls;
2 | using SwippableBottomTabView.ViewModels;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Globalization;
6 | using Xamarin.Forms;
7 |
8 | namespace SwippableBottomTabView
9 | {
10 | public class HomePage : ContentPage
11 | {
12 | private View _tabs;
13 |
14 | private RelativeLayout _relativeLayout;
15 |
16 | private CarouselLayout.IndicatorStyleEnum _indicatorStyle;
17 |
18 | private TabbedPageViewModel _viewModel;
19 |
20 | public HomePage(CarouselLayout.IndicatorStyleEnum indicatorStyle)
21 | {
22 | _indicatorStyle = indicatorStyle;
23 |
24 | _viewModel = new TabbedPageViewModel();
25 | BindingContext = _viewModel;
26 |
27 | BackgroundColor = Color.Black;
28 |
29 | Title = _indicatorStyle.ToString();
30 |
31 | _relativeLayout = new RelativeLayout
32 | {
33 | HorizontalOptions = LayoutOptions.FillAndExpand,
34 | VerticalOptions = LayoutOptions.FillAndExpand
35 | };
36 |
37 | var pagesCarousel = CreatePagesCarousel();
38 | var dots = CreatePagerIndicatorContainer();
39 | _tabs = CreateTabs();
40 |
41 | switch (pagesCarousel.IndicatorStyle)
42 | {
43 | case CarouselLayout.IndicatorStyleEnum.Dots:
44 | _relativeLayout.Children.Add(pagesCarousel,
45 | Constraint.RelativeToParent((parent) => { return parent.X; }),
46 | Constraint.RelativeToParent((parent) => { return parent.Y; }),
47 | Constraint.RelativeToParent((parent) => { return parent.Width; }),
48 | Constraint.RelativeToParent((parent) => { return parent.Height / 2; })
49 | );
50 |
51 | _relativeLayout.Children.Add(dots,
52 | Constraint.Constant(0),
53 | Constraint.RelativeToView(pagesCarousel,
54 | (parent, sibling) => { return sibling.Height - 18; }),
55 | Constraint.RelativeToParent(parent => parent.Width),
56 | Constraint.Constant(18)
57 | );
58 | break;
59 |
60 | case CarouselLayout.IndicatorStyleEnum.Tabs:
61 | var tabsHeight = 50;
62 | _relativeLayout.Children.Add(_tabs,
63 | Constraint.Constant(0),
64 | Constraint.RelativeToParent((parent) => { return parent.Height - tabsHeight; }),
65 | Constraint.RelativeToParent(parent => parent.Width),
66 | Constraint.Constant(tabsHeight)
67 | );
68 |
69 | _relativeLayout.Children.Add(pagesCarousel,
70 | Constraint.RelativeToParent((parent) => { return parent.X; }),
71 | Constraint.RelativeToParent((parent) => { return parent.Y; }),
72 | Constraint.RelativeToParent((parent) => { return parent.Width; }),
73 | Constraint.RelativeToView(_tabs, (parent, sibling) => { return parent.Height - (sibling.Height); })
74 | );
75 | break;
76 |
77 | default:
78 | _relativeLayout.Children.Add(pagesCarousel,
79 | Constraint.RelativeToParent((parent) => { return parent.X; }),
80 | Constraint.RelativeToParent((parent) => { return parent.Y; }),
81 | Constraint.RelativeToParent((parent) => { return parent.Width; }),
82 | Constraint.RelativeToParent((parent) => { return parent.Height; })
83 | );
84 | break;
85 | }
86 |
87 | Content = _relativeLayout;
88 | }
89 |
90 | private CarouselLayout CreatePagesCarousel()
91 | {
92 | var carousel = new CarouselLayout
93 | {
94 | HorizontalOptions = LayoutOptions.FillAndExpand,
95 | VerticalOptions = LayoutOptions.FillAndExpand,
96 | IndicatorStyle = _indicatorStyle,
97 | ItemTemplate = new DataTemplate(typeof(DynamicTemplateLayout))
98 | };
99 | carousel.SetBinding(CarouselLayout.ItemsSourceProperty, "Pages");
100 | carousel.SetBinding(CarouselLayout.SelectedItemProperty, "CurrentPage", BindingMode.TwoWay);
101 |
102 | return carousel;
103 | }
104 |
105 | private View CreatePagerIndicatorContainer()
106 | {
107 | return new StackLayout
108 | {
109 | Children = { CreatePagerIndicators() }
110 | };
111 | }
112 |
113 | private View CreatePagerIndicators()
114 | {
115 | var pagerIndicator = new PagerIndicatorDots() { DotSize = 5, DotColor = Color.Black };
116 | pagerIndicator.SetBinding(PagerIndicatorDots.ItemsSourceProperty, "Pages");
117 | pagerIndicator.SetBinding(PagerIndicatorDots.SelectedItemProperty, "CurrentPage");
118 | return pagerIndicator;
119 | }
120 |
121 | private View CreateTabsContainer()
122 | {
123 | return new StackLayout
124 | {
125 | Children = { CreateTabs() }
126 | };
127 | }
128 |
129 | private View CreateTabs()
130 | {
131 | var pagerIndicator = new PagerIndicatorTabs() { HorizontalOptions = LayoutOptions.CenterAndExpand };
132 | pagerIndicator.RowDefinitions.Add(new RowDefinition() { Height = 50 });
133 | pagerIndicator.SetBinding(PagerIndicatorTabs.ColumnDefinitionsProperty, "Pages", BindingMode.Default, new SpacingConverter());
134 | pagerIndicator.SetBinding(PagerIndicatorTabs.ItemsSourceProperty, "Pages");
135 | pagerIndicator.SetBinding(PagerIndicatorTabs.SelectedItemProperty, "CurrentPage");
136 |
137 | return pagerIndicator;
138 | }
139 | }
140 |
141 | public class SpacingConverter : IValueConverter
142 | {
143 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
144 | {
145 | var items = value as IEnumerable;
146 |
147 | var collection = new ColumnDefinitionCollection();
148 | foreach (var item in items)
149 | {
150 | collection.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
151 | }
152 | return collection;
153 | }
154 |
155 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
156 | {
157 | throw new NotImplementedException();
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/src/SwippableBottomTabView/HomeView.cs:
--------------------------------------------------------------------------------
1 | using Xamarin.Forms;
2 |
3 | namespace SwippableBottomTabView
4 | {
5 | public class HomeView : ContentView
6 | {
7 | public HomeView()
8 | {
9 | BackgroundColor = Color.White;
10 |
11 | var label = new Label
12 | {
13 | XAlign = TextAlignment.Center,
14 | TextColor = Color.Black
15 | };
16 |
17 | label.SetBinding(Label.TextProperty, "Title");
18 | this.SetBinding(ContentView.BackgroundColorProperty, "Background");
19 |
20 | Content = new StackLayout
21 | {
22 | VerticalOptions = LayoutOptions.CenterAndExpand,
23 | Children = {
24 | label
25 | }
26 | };
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/SwippableBottomTabView/PagerIndicatorDots.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Linq;
4 | using Xamarin.Forms;
5 |
6 | namespace SwippableBottomTabView
7 | {
8 | public interface ITabProvider
9 | {
10 | ImageSource ImageSource { get; set; }
11 | }
12 |
13 | public class PagerIndicatorDots : StackLayout
14 | {
15 | private int _dotCount = 1;
16 | private int _selectedIndex;
17 |
18 | public Color DotColor { get; set; }
19 |
20 | public double DotSize { get; set; }
21 |
22 | public PagerIndicatorDots()
23 | {
24 | HorizontalOptions = LayoutOptions.CenterAndExpand;
25 | VerticalOptions = LayoutOptions.Center;
26 | Orientation = StackOrientation.Horizontal;
27 | DotColor = Color.Black;
28 | }
29 |
30 | private void CreateDot()
31 | {
32 | //Make one button and add it to the dotLayout
33 | var dot = new Button
34 | {
35 | BorderRadius = Convert.ToInt32(DotSize / 2),
36 | HeightRequest = DotSize,
37 | WidthRequest = DotSize,
38 | BackgroundColor = DotColor
39 | };
40 | Children.Add(dot);
41 | }
42 |
43 | private void CreateTabs()
44 | {
45 | foreach (var item in ItemsSource)
46 | {
47 | var tab = item as ITabProvider;
48 | var image = new Image
49 | {
50 | HeightRequest = 42,
51 | WidthRequest = 42,
52 | BackgroundColor = DotColor,
53 | Source = tab.ImageSource,
54 | };
55 | Children.Add(image);
56 | }
57 | }
58 |
59 | public static BindableProperty ItemsSourceProperty =
60 | BindableProperty.Create(
61 | pi => pi.ItemsSource,
62 | null,
63 | BindingMode.OneWay,
64 | propertyChanging: (bindable, oldValue, newValue) =>
65 | {
66 | ((PagerIndicatorDots)bindable).ItemsSourceChanging();
67 | },
68 | propertyChanged: (bindable, oldValue, newValue) =>
69 | {
70 | ((PagerIndicatorDots)bindable).ItemsSourceChanged();
71 | }
72 | );
73 |
74 | public IList ItemsSource
75 | {
76 | get
77 | {
78 | return (IList)GetValue(ItemsSourceProperty);
79 | }
80 | set
81 | {
82 | SetValue(ItemsSourceProperty, value);
83 | }
84 | }
85 |
86 | public static BindableProperty SelectedItemProperty =
87 | BindableProperty.Create(
88 | pi => pi.SelectedItem,
89 | null,
90 | BindingMode.TwoWay,
91 | propertyChanged: (bindable, oldValue, newValue) =>
92 | {
93 | ((PagerIndicatorDots)bindable).SelectedItemChanged();
94 | });
95 |
96 | public object SelectedItem
97 | {
98 | get
99 | {
100 | return GetValue(SelectedItemProperty);
101 | }
102 | set
103 | {
104 | SetValue(SelectedItemProperty, value);
105 | }
106 | }
107 |
108 | private void ItemsSourceChanging()
109 | {
110 | if (ItemsSource != null)
111 | _selectedIndex = ItemsSource.IndexOf(SelectedItem);
112 | }
113 |
114 | private void ItemsSourceChanged()
115 | {
116 | if (ItemsSource == null) return;
117 |
118 | // Dots *************************************
119 | var countDelta = ItemsSource.Count - Children.Count;
120 |
121 | if (countDelta > 0)
122 | {
123 | for (var i = 0; i < countDelta; i++)
124 | {
125 | CreateDot();
126 | }
127 | }
128 | else if (countDelta < 0)
129 | {
130 | for (var i = 0; i < -countDelta; i++)
131 | {
132 | Children.RemoveAt(0);
133 | }
134 | }
135 | //*******************************************
136 | }
137 |
138 | private void SelectedItemChanged()
139 | {
140 | var selectedIndex = ItemsSource.IndexOf(SelectedItem);
141 | var pagerIndicators = Children.Cast