Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreiMisiukevich committed Jul 21, 2018
1 parent 314faa7 commit 07419bb
Show file tree
Hide file tree
Showing 54 changed files with 8,669 additions and 0 deletions.
230 changes: 230 additions & 0 deletions ExpandableView/ExpandableView.cs
@@ -0,0 +1,230 @@
using System;
using Xamarin.Forms;

namespace Expandable
{
public class ExpandableView : StackLayout
{
public const string ExpandAnimationName = "expandAnimation";

public static readonly BindableProperty PrimaryViewProperty = BindableProperty.Create(nameof(PrimaryView), typeof(View), typeof(ExpandableView), null, propertyChanged: (bindable, oldValue, newValue) =>
{
(bindable as ExpandableView).SetPrimaryView(oldValue as View);
(bindable as ExpandableView).OnShouldHandleTapToExpandChanged();
});

public static readonly BindableProperty SecondaryViewTemplateProperty = BindableProperty.Create(nameof(SecondaryViewTemplate), typeof(DataTemplate), typeof(ExpandableView), null, propertyChanged: (bindable, oldValue, newValue) =>
{
(bindable as ExpandableView).SetSecondaryView(true);
(bindable as ExpandableView).OnIsExpandedChanged();
});

public static readonly BindableProperty IsExpandedProperty = BindableProperty.Create(nameof(IsExpanded), typeof(bool), typeof(ExpandableView), default(bool), BindingMode.TwoWay, propertyChanged: (bindable, oldValue, newValue) =>
{
(bindable as ExpandableView).SetSecondaryView();
(bindable as ExpandableView).OnIsExpandedChanged();
});

public static readonly BindableProperty ShouldHandleTapToExpandProperty = BindableProperty.Create(nameof(ShouldHandleTapToExpand), typeof(bool), typeof(ExpandableView), true, propertyChanged: (bindable, oldValue, newValue) =>
{
(bindable as ExpandableView).OnShouldHandleTapToExpandChanged();
});

public static readonly BindableProperty SecondaryViewHeightRequestProperty = BindableProperty.Create(nameof(SecondaryViewHeightRequest), typeof(double), typeof(ExpandableView), 0.0);

private readonly TapGestureRecognizer _defaultTapGesture;
private bool _shouldIgnoreAnimation;
private double _lastVisibleHeight = -1;
private double _startHeight;
private double _endHeight;
private View _secondaryView;

public ExpandableView()
{
_defaultTapGesture = new TapGestureRecognizer
{
Command = new Command(() => IsExpanded = !IsExpanded)
};
}

public View PrimaryView
{
get => GetValue(PrimaryViewProperty) as View;
set => SetValue(PrimaryViewProperty, value);
}

public DataTemplate SecondaryViewTemplate
{
get => GetValue(SecondaryViewTemplateProperty) as DataTemplate;
set => SetValue(SecondaryViewTemplateProperty, value);
}

public bool IsExpanded
{
get => (bool)GetValue(IsExpandedProperty);
set => SetValue(IsExpandedProperty, value);
}

public bool ShouldHandleTapToExpand
{
get => (bool)GetValue(ShouldHandleTapToExpandProperty);
set => SetValue(ShouldHandleTapToExpandProperty, value);
}

public double SecondaryViewHeightRequest
{
get => (double)GetValue(SecondaryViewHeightRequestProperty);
set => SetValue(SecondaryViewHeightRequestProperty, value);
}

public View SecondaryView
{
get => _secondaryView;
private set
{
if (_secondaryView != null)
{
_secondaryView.SizeChanged -= OnSubViewSizeChanged;
Children.Remove(_secondaryView);
}
if (value != null)
{
if (value is Layout layout)
{
layout.IsClippedToBounds = true;
}
value.HeightRequest = 0;
value.IsVisible = false;
Children.Add(_secondaryView = value);
}
}
}

protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
_lastVisibleHeight = -1;
}

private void OnIsExpandedChanged()
{
if (SecondaryView == null)
{
return;
}

SecondaryView.SizeChanged -= OnSubViewSizeChanged;

var isExpanding = SecondaryView.AnimationIsRunning(ExpandAnimationName);
SecondaryView.AbortAnimation(ExpandAnimationName);

if (IsExpanded)
{
SecondaryView.IsVisible = true;
}

_startHeight = 0;
_endHeight = SecondaryViewHeightRequest > 0
? SecondaryViewHeightRequest
: _lastVisibleHeight;

var shouldInvokeAnimation = true;

if(IsExpanded)
{
if(_endHeight <= 0)
{
shouldInvokeAnimation = false;
SecondaryView.HeightRequest = -1;
SecondaryView.SizeChanged += OnSubViewSizeChanged;
}
}
else
{
_lastVisibleHeight = _startHeight = SecondaryViewHeightRequest > 0
? SecondaryViewHeightRequest
: !isExpanding
? SecondaryView.Height
: _lastVisibleHeight;
_endHeight = 0;
}

_shouldIgnoreAnimation = Height < 0;

if(shouldInvokeAnimation)
{
InvokeAnimation();
}
}

private void OnShouldHandleTapToExpandChanged()
{
if(PrimaryView == null)
{
return;
}
PrimaryView.GestureRecognizers.Remove(_defaultTapGesture);
if(ShouldHandleTapToExpand)
{
PrimaryView.GestureRecognizers.Add(_defaultTapGesture);
}
}

private void SetPrimaryView(View oldView)
{
if(oldView != null)
{
Children.Remove(oldView);
}
Children.Insert(0, PrimaryView);
}

private void SetSecondaryView(bool forceUpdate = false)
{
if(IsExpanded && (SecondaryView == null || forceUpdate))
{
SecondaryView = CreateSecondaryView();
}
}

private View CreateSecondaryView()
{
var template = SecondaryViewTemplate;
if(template is DataTemplateSelector selector)
{
template = selector.SelectTemplate(BindingContext, this);
}
return template?.CreateContent() as View;
}

private void OnSubViewSizeChanged(object sender, EventArgs e)
{
if (SecondaryView.Height <= 0) return;
SecondaryView.SizeChanged -= OnSubViewSizeChanged;
SecondaryView.HeightRequest = 0;
_endHeight = SecondaryView.Height;
InvokeAnimation();
}

private void InvokeAnimation()
{
if (_shouldIgnoreAnimation)
{
SecondaryView.HeightRequest = _endHeight;
SecondaryView.IsVisible = IsExpanded;
return;
}

new Animation(v => SecondaryView.HeightRequest = v, _startHeight, _endHeight)
.Commit(SecondaryView,
ExpandAnimationName,
finished: (v, r) =>
{
if (!IsExpanded)
{
SecondaryView.IsVisible = false;
};
});
}
}
}
14 changes: 14 additions & 0 deletions ExpandableView/ExpandableView.csproj
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Expandable</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Xamarin.Forms" Version="3.0.0.446417" />
</ItemGroup>
<ItemGroup>
<Compile Remove="MainPage.xaml.cs" />
</ItemGroup>
</Project>
19 changes: 19 additions & 0 deletions ExpandableViewSample.Android/Assets/AboutAssets.txt
@@ -0,0 +1,19 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories) and given a Build Action of "AndroidAsset".

These files will be deployed with you package and will be accessible using Android's
AssetManager, like this:

public class ReadAsset : Activity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);

InputStream input = Assets.Open ("my_asset.txt");
}
}

Additionally, some Android functions will automatically load asset files:

Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
100 changes: 100 additions & 0 deletions ExpandableViewSample.Android/ExpandableViewSample.Android.csproj
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{3885ACF0-209F-4173-BFBF-7A1C0CD59415}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TemplateGuid>{c9e5eea5-ca05-42a1-839b-61506e0a37df}</TemplateGuid>
<OutputType>Library</OutputType>
<RootNamespace>ExpandableViewSample.Droid</RootNamespace>
<AssemblyName>ExpandableViewSample.Android</AssemblyName>
<AndroidApplication>True</AndroidApplication>
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v8.1</TargetFrameworkVersion>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.Forms" Version="3.0.0.446417" />
<PackageReference Include="Xamarin.Android.Support.Design" Version="27.0.2" />
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="27.0.2" />
<PackageReference Include="Xamarin.Android.Support.v4" Version="27.0.2" />
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="27.0.2" />
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="27.0.2" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
<None Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Tabbar.axml" />
<AndroidResource Include="Resources\layout\Toolbar.axml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\icon_round.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-hdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-mdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-mdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\Icon.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\drawable\" />
<Folder Include="Resources\drawable-hdpi\" />
<Folder Include="Resources\drawable-xhdpi\" />
<Folder Include="Resources\drawable-xxhdpi\" />
<Folder Include="Resources\drawable-xxxhdpi\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ExpandableViewSample\ExpandableViewSample.csproj">
<Project>{F9D508F8-CF53-4DC6-8C9A-D0C72B3D6284}</Project>
<Name>ExpandableViewSample</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project>
27 changes: 27 additions & 0 deletions ExpandableViewSample.Android/MainActivity.cs
@@ -0,0 +1,27 @@
using System;

using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace ExpandableViewSample.Droid
{
[Activity(Label = "ExpandableViewSample", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;

base.OnCreate(bundle);

global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
}
}
}

0 comments on commit 07419bb

Please sign in to comment.