Xamarin.Forms swipe gesture recognizer - android

Xamarin.Forms is very new and very exciting, but for now I see that it has limited documentation and a few samples. I'm trying to make an app with an interface similar to the "MasterDetailPage" one, but also having a right Flyout view, not only the left one.
I've seen that this is not possible using the current API, and so my approach was this:
Create a shared GestureRecognizer interface.
In Android app and iOS in bind this interface to the UIGestureRecognizer on iOS or the OnTouch method on the android.
For iOS this is working but for Android the touch listener over the activity doesn't seem to work.
Is my approach good? Maybe there is another good method to capture touch events directly from the shared code? Or do you have any ideas why the public override bool OnTouchEvent doesn't work in an AndroidActivity?

For Xamarin.Forms swipe gesture recognizer add SwipeGestureRecognizer
<BoxView Color="Teal" ...>
<BoxView.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Swiped="OnSwiped"/>
</BoxView.GestureRecognizers>
</BoxView>
Here is the equivalent C# code:
var boxView = new BoxView { Color = Color.Teal, ... };
var leftSwipeGesture = new SwipeGestureRecognizer { Direction = SwipeDirection.Left };
leftSwipeGesture.Swiped += OnSwiped;
boxView.GestureRecognizers.Add(leftSwipeGesture);
For more check here : https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/gestures/swipe

The MasterDetailPage, and other shared elements, are just containers for view renderers to pick up. You best option would be to create a custom LRMasterDetailPage (left-right..) and give it properties for both the DetailLeft and DetailRight. Then you implement a custom ViewRenderer per platform for this custom element.
The element:
public class LRMasterDetailPage {
public View LeftDetail;
public View RightDetail;
public View Master;
}
The renderer:
[assembly:ExportRenderer (typeof(LRMasterDetailPage), typeof(LRMDPRenderer))]
namespace App.iOS.Renderers
{
public class LRMDPRenderer : ViewRenderer<LRMasterDetailPage,UIView>
{
LRMasterDetailPage element;
protected override void OnElementChanged (ElementChangedEventArgs<LRMasterDetailPage> e)
{
base.OnElementChanged (e);
element = e.NewElement;
// Do someting else, init for example
}
protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Renderer")
return;
base.OnElementPropertyChanged (sender, e);
if (e.PropertyName == "LeftDetail")
updateLeft();
if (e.PropertyName == "RightDetail")
updateRight();
}
private void updateLeft(){
// Insert view of DetailLeft element into subview
// Add button to open Detail to parent navbar, if not yet there
// Add gesture recognizer for left swipe
}
private void updateRight(){
// same as for left, but flipped
}
}
}

I advise you to use the CarouselView approach, f.e. you can use already existing solutions with carousel view which are supports swipe gestures. So in result your view will be wrapped into this carousel view control

Related

Correct way to hide and show widgets?

In my app, I need to show and hide widgets like button and textview at a certain time.
and how I am doing is as the following:
private void hideviews() {
image.setVisibility(View.GONE); ///ImageView
title1.setVisibility(View.GONE);///TextView
title2.setVisibility(View.GONE);///TextView
title3.setVisibility(View.GONE);///TextView
title4.setVisibility(View.GONE);///TextView
title5.setVisibility(View.GONE);///TextView
}
private void showviews() {
image.setVisibility(View.VISIBLE);
title1.setVisibility(View.VISIBLE);///TextView
title2.setVisibility(View.VISIBLE);///TextView
title3.setVisibility(View.VISIBLE);///TextView
title4.setVisibility(View.VISIBLE);///TextView
title5.setVisibility(View.VISIBLE);///TextView
}
I don't think this is the correct way to do this.
Because I don't know how many widgets there will be.
Any guidance on how to correctly show widgets is really appreciated.
Get the reference to root layout, iterate through the childs, check if the view at certain index is instance of EditText(or View that you dont need to hide), if not hide it
RelativeLayout root = findViewById(R.id.root)
for(i=0,i<root.getChildCount()-1,i++){
if(! (root.getChildAt(i) instance of EditText)){
root.getChildAt(i).setVisibility(View.GONE)
}
}
Since you don't know how many testviews will be attached, then I believe that the best approach will be to:
get the reference of the parent view group (that contains all the
textviews),
loop through all the childs using getChildAt,
verify whether the object is an instance of TextView/ImageView and if so set its visibility according to your logic
Instead of hiding every widget separately hide the root layout.
RelativeLayout rootLayout;
rootLayout= (RelativeLayout) findViewById(R.id.root_layout);
and use something like this to control the visibility.
public void setLayoutInvisible() {
if (rootLayout.getVisibility() == View.VISIBLE) {
rootLayout.setVisibility(View.GONE);
}
}
public void setLayoutVisible() {
if (rootLayout.getVisibility() == View.GONE) {
rootLayout.setVisibility(View.VISIBLE);
}
}
Make an array of all the views that you want to show/hide:
View[] views = {image, title1, title2, title3, title4, title5};
and then use this to hide them:
for (View view : views) {
view.setVisibility(View.GONE);
}
and use this to show them:
for (View view : views) {
view.setVisibility(View.VISIBLE);
}
although you can combine the 2 code parts in a single procedure:
void fixViews(int state) {
for (View view : views) {
view.setVisibility(state);
}
}
and call it:
fixViews(View.GONE); or fixViews(View.VISIBLE);

Replicating the drop-down ToolbarItem "More" in Xamarin.Forms

I am struggling how I could replicate the drop-down ToolbarItem from Xamarin.Forms when a ToolbarItem's order is set to Secondary for IOS, in order for it to look like it does for Android.
Here are some images to better explain what I am looking for:
How it works on Android:
Code:
ToolbarItem toolbarItem = new ToolbarItem()
{
Text = "ToolbarItem",
Order = ToolbarItemOrder.Secondary
};
Images on how it looks on Android:
Image showing the "More" icon
Image showing the "More" icon expanded to show more toolbar items
There is no default "More" icon on the toolbar when setting the Order to Secondary in iOS. Instead what happens, is that a bar below the navigation bar is created, which includes all of the toolbar items - something I do not wish to have for my Application.
This is an example of how it has been achieved before on IOS:
A screenshot I took from one of my Apps that implements this
effect
In native iOS, you can use UIPopoverController to achieve your effect. But please notice that this control can only be used in iPad.
Since you are using Xamarin.Forms, we can create a custom renderer in iOS platform to get this.
Firstly, create a page renderer to display the UIPopoverController. We can show it from a UIBarButtonItem or a UIView depending on your request. Here I use UIBarButtonItem like:
//I defined the navigateItem in the method ViewWillAppear
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
rightItem = new UIBarButtonItem("More", UIBarButtonItemStyle.Plain, (sender, args) =>
{
UIPopoverController popView = new UIPopoverController(new ContentViewController());
popView.PopoverContentSize = new CGSize(200, 300);
popView.PresentFromBarButtonItem(rightItem, UIPopoverArrowDirection.Any, true);
});
NavigationController.TopViewController.NavigationItem.SetRightBarButtonItem(leftItem, true);
}
Secondly, construct the content ViewController in the UIPopoverController(just like the secondary list in android):
public class ContentViewController : UIViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
UITableView tableView = new UITableView(new CGRect(0, 0, 200, 300));
tableView.Source = new MyTableViewSource();
View.AddSubview(tableView);
}
}
public class MyTableViewSource : UITableViewSource
{
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell(new NSString("Cell"));
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, new NSString("Cell"));
}
cell.TextLabel.Text = "Item" + indexPath.Row;
return cell;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return 10;
}
}
At last we can show it on the screen by calling PresentFromBarButtonItem.

Use group in ConstraintLayout to listen for click events on multiple views

Basically I'd like to attach a single OnClickListener to multiple views inside a ConstraintLayout.
Before migrating to the ConstraintLayout the views where inside one layout onto which I could add a listener. Now they are on the same layer with other views right under the ConstraintLayout.
I tried adding the views to a android.support.constraint.Group and added a OnClickListener to it programmatically.
group.setOnClickListener {
Log.d("OnClick", "groupClickListener triggered")
}
However this does not seem to work as of the ConstraintLayout version 1.1.0-beta2
Have I done something wrong, is there a way to achieve this behaviour or do I need to attach the listener to each of the single views?
The Group in ConstraintLayout is just a loose association of views AFAIK. It is not a ViewGroup, so you will not be able to use a single click listener like you did when the views were in a ViewGroup.
As an alternative, you can get a list of ids that are members of your Group in your code and explicitly set the click listener. (I have not found official documentation on this feature, but I believe that it is just lagging the code release.) See documentation on getReferencedIds here.
Java:
Group group = findViewById(R.id.group);
int refIds[] = group.getReferencedIds();
for (int id : refIds) {
findViewById(id).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// your code here.
}
});
}
In Kotlin you can build an extension function for that.
Kotlin:
fun Group.setAllOnClickListener(listener: View.OnClickListener?) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener(listener)
}
}
Then call the function on the group:
group.setAllOnClickListener(View.OnClickListener {
// code to perform on click event
})
Update
The referenced ids are not immediately available in 2.0.0-beta2 although they are in 2.0.0-beta1 and before. "Post" the code above to grab the reference ids after layout. Something like this will work.
class MainActivity : AppCompatActivity() {
fun Group.setAllOnClickListener(listener: View.OnClickListener?) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener(listener)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Referenced ids are not available here but become available post-layout.
layout.post {
group.setAllOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
val text = (v as Button).text
Toast.makeText(this#MainActivity, text, Toast.LENGTH_SHORT).show()
}
})
}
}
}
This should work for releases prior to 2.0.0-beta2, so you can just do this and not have to do any version checks.
The better way to listen to click events from multiple views is to add a transparent view as a container on top of all required views. This view has to be at the end (i.e on top) of all the views you need to perform a click on.
Sample container view :
<View
android:id="#+id/view_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="#+id/view_bottom"
app:layout_constraintEnd_toEndOf="#+id/end_view_guideline"
app:layout_constraintStart_toStartOf="#+id/start_view_guideline"
app:layout_constraintTop_toTopOf="parent"/>
Above sample contains all four constraint boundaries within that, we can add views that to listen together and as it is a view, we can do whatever we want, such as ripple effect.
To complement the accepted answer for Kotlin users create an extension function and accept a lambda to feel more like the API group.addOnClickListener { }.
Create the extension function:
fun Group.addOnClickListener(listener: (view: View) -> Unit) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener(listener)
}
}
usage:
group.addOnClickListener { v ->
Log.d("GroupExt", v)
}
The extension method is great but you can make it even better by changing it to
fun Group.setAllOnClickListener(listener: (View) -> Unit) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener(listener)
}
}
So the calling would be like this
group.setAllOnClickListener {
// code to perform on click event
}
Now the need for explicitly defining View.OnClickListener is now gone.
You can also define your own interface for GroupOnClickLitener like this
interface GroupOnClickListener {
fun onClick(group: Group)
}
and then define an extension method like this
fun Group.setAllOnClickListener(listener: GroupOnClickListener) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener { listener.onClick(this)}
}
}
and use it like this
groupOne.setAllOnClickListener(this)
groupTwo.setAllOnClickListener(this)
groupThree.setAllOnClickListener(this)
override fun onClick(group: Group) {
when(group.id){
R.id.group1 -> //code for group1
R.id.group2 -> //code for group2
R.id.group3 -> //code for group3
else -> throw IllegalArgumentException("wrong group id")
}
}
The second approach has a better performance if the number of views is large since you only use one object as a listener for all the views!
While I like the general approach in Vitthalk's answer I think it has one major drawback and two minor ones.
It does not account for dynamic position changes of the single views
It may register clicks for views that are not part of the group
It is not a generic solution to this rather common problem
While I'm not sure about a solution to the second point, there clearly are quite easy ones to the first and third.
1. Accounting position changes of element in the group
This is actually rather simple. One can use the toolset of the constraint layout to adjust the edges of the transparent view.
We simply use Barriers to receive the leftmost, rightmost etc. positions of any View in the group.
Then we can adjust the transparent view to the barriers instead of concrete views.
3. Generic solution
Using Kotlin we can extend the Group-Class to include a method that adds a ClickListener onto a View as described above.
This method simply adds the Barriers to the layout paying attention to every child of the group, the transparent view that is aligned to the barriers and registers the ClickListener to the latter one.
This way we simply need to call the method on the Group and do not need to add the views to the layout manually everytime we need this behaviour.
in Constraintlayout 2.0.0,you can use Layer to resolve multiple views click event,and also support scale animation
For the Java people out there like me:
public class MyConstraintLayoutGroup extends Group {
public MyConstraintLayoutGroup(Context context) {
super(context);
}
public MyConstraintLayoutGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyConstraintLayoutGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setOnClickListener(OnClickListener listener) {
for (int id : getReferencedIds()) {
getRootView().findViewById(id).setOnClickListener(listener);
}
}
}
This is not propagating click states to all other children however.
fun ConstraintLayout.setAllOnClickListener(listener: (View) -> Unit) {
children.forEach { view ->
rootView.findViewById<View>(view.id).setOnClickListener { listener.invoke(this) }
}
}
And then
.setAllOnClickListener {
do something
}

Xamarin.Forms NavigationPage PushAsync not scrolling

I'm using a MasterDetail page and the content pages reacheable from the Master are quite long. I'm trying the app on Android devices, and if I access one of those pages creating a new NavigationPage they show up correctly, but if I push them into the navigation stack, or if I go back to them from another page into the stack, the scroll doesn't work anymore.
This is the code for the creation of NavigationPage:
void OnItemSelected(object sender, SelectedItemChangedEventArgs e) {
var item = e.SelectedItem as IMenuItem;
if (item != null && item.SubmitPageType != null) {
Detail = new NavigationPage((Page)Activator.CreateInstance(item.SubmitPageType));
masterPage.ListView.SelectedItem = null;
IsPresented = false;
}
}
and this is the code that I use to push the same page into the navigation stack
this.Navigation.PushAsync(new MyPage());
In Xaml, I've tried using ScrollView into the ContentPage.Content element, but it doesn' work and it cuts out part of my layout at the bottom of the page.
Has anyone experienced similar problems with NavigationPage and content scrolling? Can anyone help me?
Thanks
When I face simillar problem, this code in custom NavigationPageRenderer helps me
[assembly: ExportRenderer(typeof(YOUR_CUSTOM_NAV_PAGE), typeof(NoAnimationNavigationPageRenderer))]
namespace CanPay.Mobile.Droid.Renderers
{
public class NoAnimationNavigationPageRenderer : NavigationPageRenderer
{
protected override void SetupPageTransition(FragmentTransaction transaction, bool isPush)
{
transaction.SetCustomAnimations(0, 0, 0, 0);
}
}
}

Existance of Android ViewAttachedToActivity event?

I'm looking in the documentation for some kind of an event that would allow me to detect when either a view is created or when the view is attached to the activity, anywhere in the view hierarchy, whether it's one level deep or multiple levels deep.
A method like this would be ideal, either at the Activity level, Window level, or Window.DecorView level:
void ViewAttachedToActivity(View view)
{
... //triggered each time an individual view is added to activity
}
The important part is that I want to be able to detect this event from the context of the Activity, not from the context of the child view itself.
Below is a rough demo of what I'm trying to accomplish. I'm wondering if a more efficient method exists:
P.S. I know I can accomplish the custom font part by subclassing all the text controls like TextView, EditText, Button, etc, and use them instead of the stock controls, but I'm looking for a simple workaround that might help me to avoid that.
(Please excuse the fact that this code is written in C# using Mono for Android, it should be simple to understand and mentally convert to Java)
public class BaseActivity : SherlockFragmentActivity
{
public Typeface Voltaire { get; set; }
bool pendingLayout = false;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Voltaire = Typeface.CreateFromAsset(Assets, "fonts/voltaire-regular.ttf");
Window.DecorView.ViewTreeObserver.GlobalLayout += new EventHandler(ViewTreeObserver_GlobalLayout);
Window.DecorView.ViewTreeObserver.PreDraw += new EventHandler<ViewTreeObserver.PreDrawEventArgs>(ViewTreeObserver_PreDraw);
}
void ViewTreeObserver_GlobalLayout(object sender, EventArgs e)
{
pendingLayout = true;
}
void ViewTreeObserver_PreDraw(object sender, ViewTreeObserver.PreDrawEventArgs e)
{
if (pendingLayout)
{
pendingLayout = false;
SetTypeFace(Window.DecorView, Voltaire);
}
}
public void SetTypeFace(View view, Typeface typeface)
{
if (view is TextView)
{
((TextView)view).Typeface = typeface;
}
if (view is ViewGroup)
{
ViewGroup viewgroup = (ViewGroup)view;
for (int i = 0; i < viewgroup.ChildCount; i++)
{
SetTypeFace(viewgroup.GetChildAt(i), typeface);
}
}
}
}
I dont think an event like this exists

Categories

Resources