Xamarin.Forms набирает обороты и, к сожалению, из коробки доступно очень мало функций; все должно быть добавлено через службу зависимостей или средства визуализации.
В этой волне много разных библиотек, которые часто добавляют базовый функционал.
Мое решение не является исключением.
Передо мной стояла задача сделать небольшое расширение, которое позволило бы мне добавить эффект клика практически на любой элемент для iOS и Android. Изначально у меня была идея создать контейнер с эффектом клика и добавить в него необходимые элементы.
От этой идеи пришлось отказаться из-за дополнительного вложенности и неправильного отбора.
То есть, если бы я поместил в этот контейнер непрямоугольный элемент типа CircleImage или Frame, я бы получил выделение вне закругленной области.
Было бы глупо переписывать и расширять все элементы управления, поэтому я решил добавить статическое расширение.
Как это должно выглядеть
Для Android 5+, очевидно, нужно использовать эффект Ripple. Но для iOS и Android <5 this solution will look inappropriate. For these platforms, I decided to implement a colored animated highlight that triggered when touched.Выполнение
ПКЛ
Для начала в проекте PCL был создан статический класс TouchEffect с BindableProperty, отвечающим за цвет эффекта.
Андроид
Необходимо определить переменную, которая определяет, использовать эффект Ripple или нет, в зависимости от версии Android:Реализация стандартных волн для Android достаточно проста:public bool EnableRipple => Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop;
private void AddRipple()
{
if (Element is Layout)
{
_rippleOverlay = new FrameLayout(Container.Context)
{
LayoutParameters = new ViewGroup.LayoutParams(-1, -1)
};
_rippleListener = new ContainerOnLayoutChangeListener(_rippleOverlay);
_view.AddOnLayoutChangeListener(_rippleListener);
((ViewGroup)_view).
AddView(_rippleOverlay);
_rippleOverlay.BringToFront();
_rippleOverlay.Foreground = CreateRipple(Color.Accent.ToAndroid());
}
else
{
_orgDrawable = _view.Background;
_view.Background = CreateRipple(Color.Accent.ToAndroid());
}
_ripple.SetColor(GetPressedColorSelector(_color));
}
private void RemoveRipple()
{
if (Element is Layout)
{
var viewgrp = (ViewGroup)_view;
viewgrp?.
RemoveOnLayoutChangeListener(_rippleListener);
viewgrp?.
RemoveView(_rippleOverlay);
_rippleListener?.
Dispose();
_rippleListener = null;
_rippleOverlay?.
Dispose();
_rippleOverlay = null;
}
else
{
_view.Background = _orgDrawable;
_orgDrawable?.
Dispose();
_orgDrawable = null;
}
_ripple?.
Dispose();
_ripple = null;
}
private RippleDrawable CreateRipple(Android.Graphics.Color color)
{
if (Element is Layout)
{
var mask = new ColorDrawable(Android.Graphics.Color.White);
return _ripple = new RippleDrawable(GetPressedColorSelector(color), null, mask);
}
var back = _view.Background;
if (back == null)
{
var mask = new ColorDrawable(Android.Graphics.Color.White);
return _ripple = new RippleDrawable(GetPressedColorSelector(color), null, mask);
}
else if (back is RippleDrawable)
{
_ripple = (RippleDrawable) back.GetConstantState().
NewDrawable();
_ripple.SetColor(GetPressedColorSelector(color));
return _ripple;
}
else
{
return _ripple = new RippleDrawable(GetPressedColorSelector(color), back, null);
}
}
Фон берется из элемента управления и к нему добавляется эффект. Для старых версий Android я решил добавить РамкаLayout поверх фонового элемента анимации альфа-канала.
На событие Touch элемента подписывается этот метод: private void OnTouch(object sender, View.TouchEventArgs args)
{
switch (args.Event.Action)
{
case MotionEventActions.Down:
Container.RemoveView(_layer);
Container.AddView(_layer);
_layer.Top = 0;
_layer.Left = 0;
_layer.Right = _view.Width;
_layer.Bottom = _view.Height;
_layer.BringToFront();
TapAnimation(250, 0, 65, false);
break;
case MotionEventActions.Up:
case MotionEventActions.Cancel:
TapAnimation(250, 65, 0);
break;
}
}
Которая при нажатии добавляет в контейнер новый макет с анимацией А-канала от 0 до 65, а при отпускании анимирует обратно от 65 до 0 и удаляет его из контейнера.
Тогда в методе OnAttached Определяемся, что делать, создавать эффект Ripple или подписываться на Touch: if (EnableRipple)
AddRipple();
else
_view.Touch += OnTouch;
iOS
Для iOS подход аналогичен предыдущему шагу: добавление UIView поверх основного элемента при нажатии, а также анимация A-канала.
Для этого создаются и добавляются к элементу UITapGestureRecouncer и UILongPressGestureRecouncer: _tapGesture = new UITapGestureRecognizer(async (obj) => {
await TapAnimation(0.3, _alpha, 0);
});
_longTapGesture = new UILongPressGestureRecognizer(async (obj) => {
switch (obj.State)
{
case UIGestureRecognizerState.Began:
await TapAnimation(0.5, 0, _alpha, false);
break;
case UIGestureRecognizerState.Ended:
case UIGestureRecognizerState.Cancelled:
case UIGestureRecognizerState.Failed:
await TapAnimation(0.5, _alpha);
break;
}
});
_view.AddGestureRecognizer(_longTapGesture);
_view.AddGestureRecognizer(_tapGesture);
Длительное нажатие устанавливает разное время анимации и, в отличие от простого нажатия, маска снимается только после отпускания пальца.
Вот и все.
Применение
КСАМЛ: <ContentPage xmlns=" http://xamarin.com/schemas/2014/forms "
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml "
xmlns:local="clr-namespace:XamEffects.Sample "
xmlns:xe="clr-namespace:XamEffects;assembly=XamEffects "
x:Class="XamEffects.Sample.MainPage">
<Grid HorizontalOptions="Center"
VerticalOptions="Center"
HeightRequest="100"
WidthRequest="200"
BackgroundColor="LightGray"
xe:TouchEffect.Color="Red ">
<Label Text="Test touch effect"
HorizontalOptions="Center"
VerticalOptions="Center"/>
</Grid>
</ContentPage>
iOS | Android API > =21 | Android API < 21 |
---|---|---|
|
|
|
Полученные результаты
Я дал основную идею реализации сенсорного эффекта; весь код, а также пакеты Nuget доступны на GitHub .P.S.: Опыта нативной разработки у меня мало, буду рад советам, что можно улучшить/доработать.
П.
П.
С.
: Хабрастораж конвертировал гифки немного коряво.
Теги: #xamarin #xamarin.forms #effects #.
NET #Разработка мобильных приложений #C++ #xamarin
-
Теплый Ламповый Звук
19 Oct, 24 -
Интерфейс Командной Строки Aws — Все В Одном
19 Oct, 24