Mac
Mac

Reputation: 373

Picker with down icon on the right and over the text - Android

I successfully implemented the picker with down icon on the right from this wonderful post in Xamarin Forms project, but when the text in picker is long (than the picker control's width), the text overlaps the down image, which looks very bad.

Basically the down icon drawable image is set as background drawable, so I tried using Control.Foreground in the custom renderer, but I got this error - "Java.Lang.LinkageError: no non-static method".

...
if (Control != null && this.Element != null && !string.IsNullOrEmpty(element.Image))
              Control.Background = AddPickerStyles(element.Image);
...

Please assist. It would be very helpful, if you point out any solution for text ellipsis also (ie.., dots for long texts in picker like iOS) Thanks in advance

NOTE: This is obviously not a problem in iOS.

Upvotes: 0

Views: 3778

Answers (2)

Mac
Mac

Reputation: 373

I ended up using a Grid that has a picker and image in each column as given in a xamarin forums discussion. This works fine!

<Grid>
   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="1*"/>
   </Grid.ColumnDefinitions>
   <userControl:BindablePicker Grid.Row="0" Grid.Column="0" ItemsSource="{Binding x, Mode=TwoWay}" SelectedItem="{Binding x}"/>
   <Image Grid.Row="0" Grid.Column="0" Source="arrow.png" HeightRequest="x" WidthRequest="x" InputTransparent="True" HorizontalOptions="End" VerticalOptions="Center"/>
</Grid>

NOTE : I used this above XAML code for my case as I have very little number of dropdowns, also because I didnt have any other way to overcome this problem.

Upvotes: 0

Junior Jiang
Junior Jiang

Reputation: 12723

Yeah , as LandLu said , you can add SetPadding to sovle this problem:

if (Control != null && this.Element != null && !string.IsNullOrEmpty(element.Image))
{
    Control.Background = AddPickerStyles(element.Image);

    Control.SetPadding(5, 0, 70, 0); //Add code here
}

Or modify in AddPickerStyles method :

public LayerDrawable AddPickerStyles(string imagePath)
    {
        ShapeDrawable border = new ShapeDrawable();
        border.Paint.Color = Android.Graphics.Color.Gray;
        border.SetPadding(10,10,10,10);
        border.Paint.SetStyle(Paint.Style.Stroke);

        Drawable[] layers = { border , GetDrawable(imagePath) };
        LayerDrawable layerDrawable = new LayerDrawable(layers);
        layerDrawable.SetLayerInset(0, 0, 0, 0, 0);

        layerDrawable.SetPadding(5,0,70,0); // Add code here

        return layerDrawable;
    }

But this is hiding a lot of text for smaller resolution devices like Moto E and larger resolution device tablets like Nexus 9.. Any solution..

If you mean pop dialog view hide part text, bellow is a way to solve it. You can custom pop dialog , let dialog be center.

IElementController ElementController => Element as IElementController;
private AlertDialog _dialog;

To modify dialog, need to custom Control click method:

if (Control != null && this.Element != null && !string.IsNullOrEmpty(element.Image))
{
  ...
  Control.Click += Control_Click
}
...
 private void Control_Click(object sender, EventArgs e)
 {
     var picker = new NumberPicker(Context);
     picker.DescendantFocusability = DescendantFocusability.BlockDescendants;
     if (model.Items != null && model.Items.Any())
     {
        // set style here
        picker.MaxValue = model.Items.Count - 1;
        picker.MinValue = 0;
        //picker.SetBackgroundColor(Android.Graphics.Color.Yellow);
        picker.SetDisplayedValues(model.Items.ToArray());
        picker.WrapSelectorWheel = false;
        picker.Value = model.SelectedIndex;
      }

      var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
      layout.AddView(picker);

     ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, true);

      var builder = new AlertDialog.Builder(Context);
      builder.SetView(layout);

      builder.SetTitle(model.Title ?? "");
      builder.SetNegativeButton("Cancel  ", (s, a) =>
      {
     ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
         // It is possible for the Content of the Page to be changed when Focus is changed.
         // In this case, we'll lose our Control.
         Control?.ClearFocus();
        _dialog = null;
       });
       builder.SetPositiveButton("Ok ", (s, a) =>
       {
        ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);
        // It is possible for the Content of the Page to be changed on SelectedIndexChanged.
        // In this case, the Element & Control will no longer exist.
          if (Element != null)
           {
               if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
                Control.Text = model.Items[Element.SelectedIndex];

     ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
                // It is also possible for the Content of the Page to be changed when Focus is changed.
                // In this case, we'll lose our Control.
                Control?.ClearFocus();
             }
            _dialog = null;
        });

     _dialog = builder.Create();
     _dialog.DismissEvent += (ssender, args) =>
      {
    ElementController?.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
      };
     _dialog.Show();
     var metrics = Resources.DisplayMetrics;
     Window dialogWindow = _dialog.Window;
     WindowManagerLayoutParams p = dialogWindow.Attributes; 
        // set width
     p.Width = metrics.WidthPixels;
     p.Gravity = GravityFlags.Center;
     p.Alpha = 0.8f;
     dialogWindow.Attributes = p;
 }

Last is the full solution code of above two problems:

public class CustomPickerRenderer : PickerRenderer
{
    CustomPicker element;

    IElementController ElementController => Element as IElementController;
    private AlertDialog _dialog;
    protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
    {
        base.OnElementChanged(e);

        element = (CustomPicker)this.Element;

        if (Control != null && this.Element != null && !string.IsNullOrEmpty(element.Image))
        {
            Control.Background = AddPickerStyles(element.Image);
            //Control.SetPadding(5, 0, 70, 0);
            Control.Click += Control_Click;

        }
    }

    private void Control_Click(object sender, EventArgs e)
    {
        //throw new NotImplementedException();
        Picker model = Element;

        var picker = new NumberPicker(Context);
        picker.DescendantFocusability = DescendantFocusability.BlockDescendants;
        if (model.Items != null && model.Items.Any())
        {
            // set style here
            picker.MaxValue = model.Items.Count - 1;
            picker.MinValue = 0;
            //picker.SetBackgroundColor(Android.Graphics.Color.Yellow);
            picker.SetDisplayedValues(model.Items.ToArray());
            picker.WrapSelectorWheel = false;
            picker.Value = model.SelectedIndex;
        }

        var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
        layout.AddView(picker);

        ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, true);

        var builder = new AlertDialog.Builder(Context);
        builder.SetView(layout);

        builder.SetTitle(model.Title ?? "");
        builder.SetNegativeButton("Cancel  ", (s, a) =>
        {
            ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
            // It is possible for the Content of the Page to be changed when Focus is changed.
            // In this case, we'll lose our Control.
            Control?.ClearFocus();
            _dialog = null;
        });
        builder.SetPositiveButton("Ok ", (s, a) =>
        {
            ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);
            // It is possible for the Content of the Page to be changed on SelectedIndexChanged.
            // In this case, the Element & Control will no longer exist.
            if (Element != null)
            {
                if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
                    Control.Text = model.Items[Element.SelectedIndex];
                ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
                // It is also possible for the Content of the Page to be changed when Focus is changed.
                // In this case, we'll lose our Control.
                Control?.ClearFocus();
            }
            _dialog = null;
        });

        _dialog = builder.Create();
        _dialog.DismissEvent += (ssender, args) =>
        {
            ElementController?.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
        };
        _dialog.Show();


        var metrics = Resources.DisplayMetrics;
        Window dialogWindow = _dialog.Window;
        WindowManagerLayoutParams p = dialogWindow.Attributes; 
        // set width
        p.Width = metrics.WidthPixels;
        p.Gravity = GravityFlags.Center;
        p.Alpha = 0.8f;
        dialogWindow.Attributes = p;
    }


    public LayerDrawable AddPickerStyles(string imagePath)
    {
        ShapeDrawable border = new ShapeDrawable();
        border.Paint.Color = Android.Graphics.Color.Gray;
        border.SetPadding(10,10,10,10);
        border.Paint.SetStyle(Paint.Style.Stroke);

        Drawable[] layers = { border , GetDrawable(imagePath) };
        LayerDrawable layerDrawable = new LayerDrawable(layers);
        layerDrawable.SetLayerInset(0, 0, 0, 0, 0);

        layerDrawable.SetPadding(5,0,80,0);

        return layerDrawable;
    }

    private BitmapDrawable GetDrawable(string imagePath)
    {
        int resID = Resources.GetIdentifier(imagePath, "drawable", this.Context.PackageName);
        var drawable = ContextCompat.GetDrawable(this.Context, resID);
        var bitmap = ((BitmapDrawable)drawable).Bitmap;

        var result = new BitmapDrawable(Resources, Bitmap.CreateScaledBitmap(bitmap, 70, 70, true));
        result.Gravity = Android.Views.GravityFlags.Right;

        return result;
    }

    protected override void Dispose(bool disposing)
    {
        Control.Click -= Control_Click;
        base.Dispose(disposing);
    }

}

Upvotes: 0

Related Questions