private TextLabel headerText = null;
private TextLabel buttonText = null;
private ImageView listBackgroundImage = null;
- private FlexibleView list = null;
+
+ // Component that scrolls the child added to it.
+ private LayoutScroller layoutScroller = null;
+
+ // The LinearLayout container to house the items in the drop down list.
+ private View dropDownMenuFullList = null;
+
private DropDownListBridge adapter = new DropDownListBridge();
- private DropDownItemView touchedView = null;
private int selectedItemIndex = -1;
private Extents listPadding = null;
+ DropDownItemView selectedItemView = null;
+
+ private bool itemPressed = false;
+
+ private TapGestureDetector tapGestureDetector = null;
/// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
public event ClickEventHandler<ItemClickEventArgs> ItemClickEvent;
/// <summary>
- /// List orientation.
+ /// List position in relation to the main button.
/// </summary>
/// <since_tizen> 6 </since_tizen>
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
set
{
SetValue(ListRelativeOrientationProperty, value);
+ RelayoutRequest();
}
}
private ListOrientation privateListRelativeOrientation
}
set
{
- SetValue(SelectedItemIndexProperty, value);
+ if (value == selectedItemIndex || adapter == null || value >= adapter.GetItemCount())
+ {
+ return;
+ }
+
+ SetValue(SelectedItemIndexProperty, value); // UpdatesSelectedItem
}
}
private int privateSelectedItemIndex
[EditorBrowsable(EditorBrowsableState.Never)]
public void AddItem(DropDownDataItem itemData)
{
- adapter.InsertData(-1, itemData);
+ // Add item to adaptor, will be added to list via AddItemAt during OnUpdate()
+ int insertionPosition = adapter.GetItemCount();
+ adapter.InsertData(insertionPosition, itemData);
}
/// <summary>
}
adapter.RemoveData(index);
+
+ if(index < dropDownMenuFullList.ChildCount)
+ {
+ View childToRemove = dropDownMenuFullList.GetChildAt((uint)index);
+ if (childToRemove)
+ {
+ childToRemove.TouchEvent -= ListItemTouchEvent;
+ dropDownMenuFullList.Remove(childToRemove);
+ dropDownMenuFullList?.Layout?.RequestLayout();
+ }
+ }
}
/// <summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public void AttachScrollBar(ScrollBar scrollBar)
{
- if (list == null)
+ if (layoutScroller == null)
{
return;
}
- list.AttachScrollBar(scrollBar);
+ Tizen.Log.Error("DropDown","Feature unsupported");
}
/// <summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public void DetachScrollBar()
{
- if (list == null)
+ if (layoutScroller == null)
{
return;
}
- list.DetachScrollBar();
+ Tizen.Log.Error("DropDown","Feature unsupported");
}
protected override void RegisterDetectionOfSubstyleChanges()
Style.HeaderText.PropertyChanged += HeaderTextAttributesPropertyChanged;
Style.Button.PropertyChanged += ButtonAttributesPropertyChanged;
- Style.Button.Icon.PropertyChanged += IconStylePropertyChanged;
+ Style.Button.Icon.PropertyChanged += IconStylePropertyChanged;;
}
private void IconStylePropertyChanged(object sender, PropertyChangedEventArgs e)
if (listBackgroundImage == null)
{
CreateListBackgroundImage();
- CreateList();
}
int temp = (int)Style.FocusedItemIndex;
- list.FocusedItemIndex = temp;
- list.Size = Style.ListSize;
- list.Padding = Style.ListPadding;
+
+ if (layoutScroller==null) // layoutScroller used to test of ListContainer Setup invoked already
+ {
+ SetUpListContainer();
+ }
+
+ listBackgroundImage.Size = Style.ListSize;
+
+ // Resize and position scrolling list within the drop down list container. Can be used to position list in relation to the background image.
+ layoutScroller.Size = Style.ListSize - new Size((Style.ListPadding.Start + Style.ListPadding.End), (Style.ListPadding.Top + Style.ListPadding.Bottom), 0 );
+ layoutScroller.Position2D = new Position2D(Style.ListPadding.Start,Style.ListPadding.Top);
int listBackgroundImageX = 0;
int listBackgroundImageY = 0;
if (Style.ListMargin != null)
{
int listWidth = 0;
- if (list.Size2D != null)
+ if (dropDownMenuFullList.Size2D != null)
{
- listWidth = list.Size2D.Width;
+ listWidth = dropDownMenuFullList.Size2D.Width;
}
listBackgroundImageX = Size2D.Width - listWidth - (int)Style.ListMargin.End;
listBackgroundImageY = (int)Style.ListMargin.Top;
}
}
listBackgroundImage.Position2D = new Position2D(listBackgroundImageX, listBackgroundImageY);
+ dropDownMenuFullList?.Layout?.RequestLayout();
+
+ int numberOfItemsToAdd = adapter.GetItemCount();
+
+ if (adapter.AdapterPurge == true)
+ {
+ adapter.AdapterPurge = false;
+ for (int i =0; i<numberOfItemsToAdd; i++)
+ {
+ AddItemAt(adapter.GetData(i), i);
+ }
+ }
+
+ // Set selection icon on View
+ UpdateSelectedItem(selectedItemIndex);
}
}
Utility.Dispose(button);
}
- if (list != null)
+ if (layoutScroller != null)
{
+ if (dropDownMenuFullList != null)
+ {
+ Utility.Dispose(dropDownMenuFullList);
+ }
+
if (listBackgroundImage != null)
{
Utility.Dispose(listBackgroundImage);
}
- Utility.Dispose(list);
+ Utility.Dispose(layoutScroller);
}
}
return new DropDownStyle();
}
+ private void AddItemAt(DropDownDataItem itemData,int index)
+ {
+ ViewHolder viewHolder = adapter.OnCreateViewHolder();
+ if (!viewHolder.IsBound)
+ {
+ adapter.BindViewHolder(viewHolder, index);
+ viewHolder.IsBound = true;
+ }
+
+ if (tapGestureDetector == null)
+ {
+ tapGestureDetector = new TapGestureDetector();
+ }
+ View view = viewHolder.ItemView;
+ view.TouchEvent += ListItemTouchEvent;
+ dropDownMenuFullList.Add(view);
+ }
+
private void OnClickEvent(object sender, ItemClickEventArgs e)
{
ItemClickEvent?.Invoke(sender, e);
}
+ private void CreateHeaderText()
+ {
+ headerText = new TextLabel();
+ headerText.Name = "DropDownHeaderText";
+ Add(headerText);
+ }
+
+ private void SetUpListContainer()
+ {
+ LinearLayout linear = new LinearLayout()
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ };
+
+ dropDownMenuFullList = new View()
+ {
+ Layout = linear,
+ Name = "DropDownMenuList",
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ Focusable = true,
+ };
+
+ layoutScroller = new LayoutScroller()
+ {
+ Name = "LayoutScroller",
+ };
+ layoutScroller.AddLayoutToScroll(dropDownMenuFullList);
- private void CreateList()
- {
- list = new FlexibleView();
- list.Name = "DropDownList";
- LinearLayoutManager layoutManager = new LinearLayoutManager(LinearLayoutManager.VERTICAL);
- list.SetLayoutManager(layoutManager);
- list.SetAdapter(adapter);
- list.Focusable = true;
- list.ItemTouchEvent += ListItemTouchEvent;
- list.ItemClickEvent += ListItemClickEvent;
- listBackgroundImage.Add(list);
+ listBackgroundImage.Add(layoutScroller);
listBackgroundImage.Hide();
}
- private void ListItemClickEvent(object sender, FlexibleView.ItemClickEventArgs e)
+ private View GetViewFromIndex(uint index)
{
- if (e.ClickedView != null)
+ if ((index < dropDownMenuFullList.ChildCount) && (index >=0) )
{
- UpdateSelectedItem(e.ClickedView.AdapterPosition);
-
- ItemClickEventArgs args = new ItemClickEventArgs();
- args.Index = e.ClickedView.AdapterPosition;
- args.Text = (e.ClickedView.ItemView as DropDownItemView)?.Text;
- OnClickEvent(this, args);
+ return dropDownMenuFullList.GetChildAt(index);
}
+ else
+ {
+ return null;
+ }
+ }
- listBackgroundImage.Hide();
+ private void SetListItemToSelected(DropDownItemView targetItemView)
+ {
+ // Set the DropDownItemView matching the targetItemView to selected.
+ if (selectedItemView!=targetItemView)
+ {
+ if (selectedItemView!=null)
+ {
+ // clear selection status of currently selected item view
+ selectedItemView.IsSelected = false;
+ }
+ // Set target item to selected
+ targetItemView.IsSelected = true;
+ selectedItemView = targetItemView;
+ }
}
- private void ListItemTouchEvent(object sender, FlexibleView.ItemTouchEventArgs e)
+ private bool ListItemTouchEvent(object sender, TouchEventArgs e)
{
PointStateType state = e.Touch.GetState(0);
+ DropDownItemView touchedView = sender as DropDownItemView;;
switch (state)
{
case PointStateType.Down:
- if (e.TouchedView != null)
+ if (touchedView != null && touchedView.BackgroundColorSelector != null)
{
- touchedView = e.TouchedView.ItemView as DropDownItemView;
- if (touchedView != null && touchedView.BackgroundColorSelector != null)
- {
- touchedView.BackgroundColor = touchedView.BackgroundColorSelector.GetValue(ControlStates.Pressed);
- }
+ touchedView.BackgroundColor = touchedView.BackgroundColorSelector.GetValue(ControlStates.Pressed);
}
+ itemPressed = true; // if matched with a Up then a click event.
break;
case PointStateType.Motion:
if (touchedView != null && touchedView.BackgroundColorSelector != null)
{
touchedView.BackgroundColor = touchedView.BackgroundColorSelector.GetValue(ControlStates.Normal);
}
+ itemPressed = false;
break;
case PointStateType.Up:
if (touchedView != null && touchedView.BackgroundColorSelector != null)
{
touchedView.BackgroundColor = touchedView.BackgroundColorSelector.GetValue(ControlStates.Selected);
+
+ if (itemPressed) // if Down was previously sent without motion (Scrolling) in-between then a clicked event occurred.
+ {
+ // List item clicked
+ Console.WriteLine("Tapped{0}", touchedView.Name);
+ SetListItemToSelected(touchedView);
+ button.Text = touchedView.Text;
+ button.Show();
+ listBackgroundImage.Hide();
+ }
}
break;
default:
break;
}
- }
+ return true;
+ }
private void UpdateSelectedItem(int index)
{
{
data.IsSelected = false;
}
- DropDownItemView view = list?.FindViewHolderForAdapterPosition(selectedItemIndex)?.ItemView as DropDownItemView;
- if (view != null)
- {
- view.IsSelected = false;
- }
+ DropDownItemView listItemView = dropDownMenuFullList.GetChildAt((uint)selectedItemIndex) as DropDownItemView;
+ data.IsSelected = false;
+ SetListItemToSelected(listItemView);
}
if (index != -1)
if (data != null)
{
data.IsSelected = true;
- }
- DropDownItemView view = list?.FindViewHolderForAdapterPosition(index)?.ItemView as DropDownItemView;
- if (view != null)
- {
- view.IsSelected = true;
- button.Style.Text.Text = view.Text;
+ DropDownItemView listItemView = dropDownMenuFullList?.GetChildAt((uint)index) as DropDownItemView;
+ if(listItemView)
+ {
+ SetListItemToSelected(listItemView);
+ }
}
}
selectedItemIndex = index;
+ dropDownMenuFullList?.Layout?.RequestLayout();
}
private void CreateListBackgroundImage()
private void ButtonClickEvent(object sender, Button.ClickEventArgs e)
{
+ button.Hide();
listBackgroundImage.Show();
+ dropDownMenuFullList?.Layout?.RequestLayout();
+ listBackgroundImage.RaiseToTop();
}
private void CreateHeaderTextAttributes()
[EditorBrowsable(EditorBrowsableState.Never)]
public DropDownDataItem()
{
- Initalize();
+ Initialize();
}
/// <summary>
}
itemDataStyle = attributes as DropDownItemStyle;
}
- Initalize();
+ Initialize();
}
/// <summary>
public DropDownDataItem(DropDownItemStyle style)
{
itemDataStyle.CopyFrom(style);
- Initalize();
+ Initialize();
}
/// <summary>
}
}
- private void Initalize()
+ private void Initialize()
{
if (itemDataStyle == null)
{
PositionUsesPivotPoint = true,
ParentOrigin = Tizen.NUI.ParentOrigin.TopLeft,
PivotPoint = Tizen.NUI.PivotPoint.TopLeft,
+ Name = "checkedImage",
};
Add(mCheck);
}
#region DropDownListBridge
/// <summary>
- /// DropDownListBridge is bridge to contact item data and item view.
+ /// DropDownListBridge is bridge to connect item data and an item View.
/// </summary>
/// <since_tizen> 6 </since_tizen>
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
- public class DropDownListBridge : FlexibleView.Adapter
+ public class DropDownListBridge
{
- private List<DropDownDataItem> mDatas = new List<DropDownDataItem>();
+ private List<DropDownDataItem> itemDataList = new List<DropDownDataItem>();
+
+ internal bool AdapterPurge {get;set;} = false; // Set to true if adapter content changed since last iteration.
/// <summary>
/// Creates a new instance of a DropDownListBridge.
{
if(position == -1)
{
- position = mDatas.Count;
+ position = itemDataList.Count;
}
- mDatas.Insert(position, data);
- NotifyItemInserted(position);
+ itemDataList.Insert(position, data);
+ AdapterPurge = true;
}
/// <summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public void RemoveData(int position)
{
- mDatas.RemoveAt(position);
- NotifyItemRemoved(position);
+ itemDataList.RemoveAt(position);
+ AdapterPurge = true;
}
/// <summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public DropDownDataItem GetData(int position)
{
- return mDatas[position];
+ return itemDataList[position];
}
/// <summary>
/// Get view holder by view type.
/// </summary>
- /// <param name="viewType">Create item view.</param>
/// <since_tizen> 6 </since_tizen>
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
- public override FlexibleView.ViewHolder OnCreateViewHolder(int viewType)
+ public ViewHolder OnCreateViewHolder()
{
- FlexibleView.ViewHolder viewHolder = new FlexibleView.ViewHolder(new DropDownItemView());
+ ViewHolder viewHolder = new ViewHolder(new DropDownItemView());
return viewHolder;
}
/// <summary>
- /// Binder view holder, it can be override.
+ /// Bind ViewHolder with View.
/// </summary>
/// <param name="holder">View holder.</param>
- /// <param name="position">Position index where will be gotten.</param>
+ /// <param name="position">Position index of source data.</param>
/// <since_tizen> 6 </since_tizen>
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
- public override void OnBindViewHolder(FlexibleView.ViewHolder holder, int position)
+ public void BindViewHolder(ViewHolder holder, int position)
{
- DropDownDataItem listItemData = mDatas[position];
+ DropDownDataItem listItemData = itemDataList[position];
if(listItemData == null)
{
return;
listItemView.Name = "Item" + position;
if (listItemData.Size != null)
{
- holder.ItemView.Size = listItemData.Size;
+ if (listItemData.Size.Width > 0)
+ {
+ holder.ItemView.WidthSpecification = (int)listItemData.Size.Width;
+ }
+ else
+ {
+ holder.ItemView.WidthSpecification = LayoutParamPolicies.MatchParent;
+ }
+
+ if (listItemData.Size.Height > 0)
+ {
+ holder.ItemView.HeightSpecification = (int)listItemData.Size.Height;
+ }
+ else
+ {
+ holder.ItemView.HeightSpecification = LayoutParamPolicies.MatchParent;
+ }
}
if (listItemView != null)
}
listItemView.IsSelected = listItemData.IsSelected;
- }
+ }
}
/// <summary>
/// <since_tizen> 6 </since_tizen>
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
- public override void OnDestroyViewHolder(FlexibleView.ViewHolder holder)
+ public void OnDestroyViewHolder(ViewHolder holder)
{
if (holder.ItemView != null)
{
/// <since_tizen> 6 </since_tizen>
/// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
- public override int GetItemCount()
+ public int GetItemCount()
+ {
+ return itemDataList.Count;
+ }
+ }
+ #endregion
+
+ #region ViewHolder
+
+ /// <summary>
+ /// A ViewHolder is a class that holds a View created from DropDownListBridge data.
+ /// </summary>
+ /// <since_tizen> 6 </since_tizen>
+ /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class ViewHolder
+ {
+ /// <summary>
+ /// ViewHolder constructor.
+ /// </summary>
+ /// <param name="itemView">View</param>
+ /// <since_tizen> 6 </since_tizen>
+ /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ViewHolder(View itemView)
+ {
+ if (itemView == null)
+ {
+ throw new ArgumentNullException("itemView may not be null");
+ }
+ this.ItemView = itemView;
+ }
+
+ /// <summary>
+ /// Returns the view.
+ /// </summary>
+ /// <since_tizen> 6 </since_tizen>
+ /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public View ItemView { get; }
+
+ internal bool IsBound
{
- return mDatas.Count;
- }
+ get;
+ set;
+ }
+
}
+
#endregion
+
}
}
--- /dev/null
+/* Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using System.ComponentModel;
+using System.Diagnostics;
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// [Draft] This class provides a View that can scroll a single View with a layout.
+ /// </summary>
+ internal class LayoutScroller : CustomView
+ {
+ static bool LayoutDebugScroller = true; // Debug flag
+
+ private class ScrollerCustomLayout : LayoutGroup
+ {
+ protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
+ {
+ float totalHeight = 0.0f;
+ float totalWidth = 0.0f;
+
+ MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
+ MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
+
+ // measure children
+ foreach( LayoutItem childLayout in LayoutChildren )
+ {
+ if (childLayout != null)
+ {
+ // Get size of child
+ // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
+ MeasureSpecification heightMeasureSpecUnrestricted = new MeasureSpecification( heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
+ MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpecUnrestricted );
+ float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
+ float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
+
+ // Determine the width and height needed by the children using their given position and size.
+ // Children could overlap so find the left most and right most child.
+ Position2D childPosition = childLayout.Owner.Position2D;
+ float childLeft = childPosition.X;
+ float childTop = childPosition.Y;
+
+ // Store current width and height needed to contain all children.
+ Extents padding = Padding;
+ Extents childMargin = childLayout.Margin;
+ totalWidth = childWidth + padding.Start + padding.End + childMargin.Start + childMargin.End;
+ totalHeight = childHeight + padding.Top + padding.Bottom + childMargin.Top + childMargin.Bottom;
+
+ if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
+ {
+ childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
+ }
+ if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
+ {
+ childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
+ }
+ }
+ }
+
+
+ MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
+ MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
+ totalWidth = widthSizeAndState.Size.AsDecimal();
+ totalHeight = heightSizeAndState.Size.AsDecimal();
+
+ // Ensure layout respects it's given minimum size
+ totalWidth = Math.Max( totalWidth, SuggestedMinimumWidth.AsDecimal() );
+ totalHeight = Math.Max( totalHeight, SuggestedMinimumHeight.AsDecimal() );
+
+ widthSizeAndState.State = childWidthState;
+ heightSizeAndState.State = childHeightState;
+
+ SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(totalWidth), widthMeasureSpec, childWidthState ),
+ ResolveSizeAndState( new LayoutLength(totalHeight), heightMeasureSpec, childHeightState ) );
+ }
+
+ protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
+ {
+ foreach( LayoutItem childLayout in LayoutChildren )
+ {
+ if( childLayout != null )
+ {
+ LayoutLength childWidth = childLayout.MeasuredWidth.Size;
+ LayoutLength childHeight = childLayout.MeasuredHeight.Size;
+
+ Position2D childPosition = childLayout.Owner.Position2D;
+ Extents padding = Padding;
+ Extents childMargin = childLayout.Margin;
+
+ LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
+ LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
+
+ childLayout.Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight );
+ }
+ }
+ }
+ }
+
+ private Animation scrollAnimation;
+ private float maxScrollDistance;
+ private float childTargetPosition = 0.0f;
+ private PanGestureDetector mPanGestureDetector;
+ private TapGestureDetector mTapGestureDetector;
+ private View mScrollingChild;
+
+ private bool Scrolling = false;
+
+ /// <summary>
+ /// [Draft] Constructor
+ /// </summary>
+ /// <since_tizen> 6 </since_tizen>
+ public LayoutScroller() : base(typeof(VisualView).FullName, CustomViewBehaviour.ViewBehaviourDefault | CustomViewBehaviour.RequiresTouchEventsSupport)
+ {
+ mPanGestureDetector = new PanGestureDetector();
+ mPanGestureDetector.Attach(this);
+ mPanGestureDetector.Detected += OnPanGestureDetected;
+
+ mTapGestureDetector = new TapGestureDetector();
+ mTapGestureDetector.Attach(this);
+ mTapGestureDetector.Detected += OnTapGestureDetected;
+
+ ClippingMode = ClippingModeType.ClipToBoundingBox;
+
+ mScrollingChild = new View();
+
+ Layout = new ScrollerCustomLayout();
+ }
+
+ public void AddLayoutToScroll(View child)
+ {
+ mScrollingChild = child;
+ Add(mScrollingChild);
+ }
+
+
+ /// <summary>
+ /// Scroll vertically by displacement pixels in screen coordinates.
+ /// </summary>
+ /// <param name="displacement">distance to scroll in pixels. Y increases as scroll position approaches the top.</param>
+ /// <since_tizen> 6 </since_tizen>
+ /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
+ public float ScrollVerticallyBy(float displacement)
+ {
+ Debug.WriteLineIf( LayoutDebugScroller, "ScrollVerticallyBy displacement:" + displacement);
+ return ScrollBy(displacement, false);
+ }
+
+ internal void StopScroll()
+ {
+ if (scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
+ {
+ scrollAnimation.Stop(Animation.EndActions.Cancel);
+ scrollAnimation.Clear();
+ }
+ }
+
+ // static constructor registers the control type (for user can add kinds of visuals to it)
+ static LayoutScroller()
+ {
+ // ViewRegistry registers control type with DALi type registry
+ // also uses introspection to find any properties that need to be registered with type registry
+ CustomViewRegistry.Instance.Register(CreateInstance, typeof(LayoutScroller));
+ }
+
+ internal static CustomView CreateInstance()
+ {
+ return new LayoutScroller();
+ }
+
+ public void OffsetChildVertically(float displacement, bool animate)
+ {
+ float previousTargetPosition = childTargetPosition;
+
+ childTargetPosition = childTargetPosition + displacement;
+ childTargetPosition = Math.Min(0,childTargetPosition);
+ childTargetPosition = Math.Max(-maxScrollDistance,childTargetPosition);
+
+ Debug.WriteLineIf( LayoutDebugScroller, "OffsetChildVertically currentYPosition:" + mScrollingChild.PositionY + "childTargetPosition:" + childTargetPosition);
+
+ if (animate)
+ {
+ if (scrollAnimation == null)
+ {
+ scrollAnimation = new Animation();
+ scrollAnimation.Finished += ScrollAnimationFinished;
+
+ }
+ else if (scrollAnimation.State == Animation.States.Playing)
+ {
+ scrollAnimation.Stop(Animation.EndActions.StopFinal);
+ scrollAnimation.Clear();
+ }
+ scrollAnimation.Duration = 1000;
+ scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine);
+ scrollAnimation.AnimateTo(mScrollingChild, "PositionY", childTargetPosition);
+ scrollAnimation.Play();
+ }
+ else
+ {
+ // Set position of scrolling child without an animation
+ mScrollingChild.PositionY = childTargetPosition;
+ }
+ }
+
+ /// <summary>
+ /// you can override it to clean-up your own resources.
+ /// </summary>
+ /// <param name="type">DisposeTypes</param>
+ /// <since_tizen> 6 </since_tizen>
+ /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void Dispose(DisposeTypes type)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (type == DisposeTypes.Explicit)
+ {
+ StopScroll();
+
+ if (mPanGestureDetector != null)
+ {
+ mPanGestureDetector.Detected -= OnPanGestureDetected;
+ mPanGestureDetector.Dispose();
+ mPanGestureDetector = null;
+ }
+
+ if (mTapGestureDetector != null)
+ {
+ mTapGestureDetector.Detected -= OnTapGestureDetected;
+ mTapGestureDetector.Dispose();
+ mTapGestureDetector = null;
+ }
+ }
+ base.Dispose(type);
+ }
+
+ private float ScrollBy(float displacement, bool animate)
+ {
+ if (GetChildCount() == 0 || displacement == 0)
+ {
+ return 0;
+ }
+
+ int scrollingChildHeight = (int)mScrollingChild.Layout.MeasuredHeight.Size.AsRoundedValue();
+ maxScrollDistance = scrollingChildHeight - CurrentSize.Height;
+
+ Debug.WriteLineIf( LayoutDebugScroller, "ScrollBy maxScrollDistance:" + maxScrollDistance +
+ " parent length:" + CurrentSize.Height +
+ " scrolling child length:" + mScrollingChild.CurrentSize.Height);
+
+ float absDisplacement = Math.Abs(displacement);
+
+ OffsetChildVertically(displacement, animate);
+
+ return absDisplacement;
+ }
+
+ private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
+ {
+ if (e.PanGesture.State == Gesture.StateType.Started)
+ {
+ if(Scrolling)
+ {
+ StopScroll();
+ }
+ }
+ else if (e.PanGesture.State == Gesture.StateType.Continuing)
+ {
+ ScrollVerticallyBy(e.PanGesture.Displacement.Y);
+ }
+ else if (e.PanGesture.State == Gesture.StateType.Finished)
+ {
+ ScrollVerticallyBy(e.PanGesture.Velocity.Y * 600);
+ }
+ }
+
+ private void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
+ {
+ if (e.TapGesture.Type == Gesture.GestureType.Tap)
+ {
+ // Stop scrolling if touch detected
+ if(Scrolling)
+ {
+ StopScroll();
+ }
+ }
+ }
+
+ private void ScrollAnimationFinished(object sender, EventArgs e)
+ {
+ Scrolling = false;
+ }
+
+
+ }
+
+} // namespace