Skip to content

Instantly share code, notes, and snippets.

@kiliman
Last active February 5, 2016 18:49
Show Gist options
  • Save kiliman/dc2590b40a3bda9291bb to your computer and use it in GitHub Desktop.
Save kiliman/dc2590b40a3bda9291bb to your computer and use it in GitHub Desktop.
Dynamic Layout in MvvmCross for Android
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var tabConfig = ViewModel.TabConfig;
var tabId = tabConfig["id"].ToString();
var ignored = base.OnCreateView(inflater, container, savedInstanceState);
_scrollView = new ScrollView(Activity);
var layout = BuildLayout(inflater, tabConfig);
_scrollView.AddView(layout);
return _scrollView;
}
private View BuildLayout(LayoutInflater inflater, JObject tabConfig)
{
var layout = new LinearLayout(Activity)
{
Orientation = Orientation.Vertical
};
var layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MatchParent, LinearLayout.LayoutParams.MatchParent);
layout.LayoutParameters = layoutParams;
layout.Focusable = true;
layout.FocusableInTouchMode = true;
var set = this.CreateBindingSet<DynamicFragmentView, DynamicViewModel>();
var bindingContext = new MvxAndroidBindingContext(Activity, new LayoutInflaterProvider(inflater), ViewModel);
var controls = new List<View>();
foreach (var control in tabConfig["controls"])
{
var id = (string)control["id"];
var type = (string)control["type"];
var label = (string)control["label"];
switch (type)
{
case "text":
case "number":
var editText = BuildEditText(layout, label);
if (type == "number")
{
editText.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
}
set.Bind(editText).For(x => x.Text).To("Values['" + id + "']");
break;
case "autocomplete":
var listkey = (string)control["list"];
var autoitems = ViewModel.GetAutoCompleteList(listkey);
var autoComplete = BuildAutoComplete(layout, inflater, label, autoitems);
set.Bind(autoComplete).For(x => x.Text).To("Values['" + id + "']");
autoComplete.FocusChange += ControlOnFocusChange;
break;
case "popup":
var url = (string)control["url"];
var popupProperty = (string)control["property"];
var popupButton = BuildPopup(layout, label);
set.Bind(popupButton).For("Click").To(vm => vm.ShowPopupCommand).WithConversion("CommandParameter", new PopupCommandInfo { Url = url, Property = popupProperty});
break;
case "photo":
var photoLayout = BuildPhotoLayout(layout, inflater, id, label);
var cameraButton = photoLayout.FindViewById<ImageButton>(Resource.Id.camera_button);
var galleryButton = photoLayout.FindViewById<ImageButton>(Resource.Id.gallery_button);
var imageView = photoLayout.FindViewById<ImageView>(Resource.Id.photo_view);
set.Bind(cameraButton).For("Click").To(vm => vm.TakePhotoCommand).WithConversion("CommandParameter", id);
set.Bind(galleryButton).For("Click").To(vm => vm.OpenGalleryCommand).WithConversion("CommandParameter", id);
set.Bind(imageView).For("Click").To(vm => vm.ShowPictureCommand).WithConversion("CommandParameter", id);
SetImageView(id);
break;
case "dropdown":
var items = control["values"].ToObject<string[]>();
var spinner = BuildSpinner(layout, label, items);
var position = Array.FindIndex(items, x => x == (string)ViewModel.Values[id]);
spinner.SetSelection(position);
spinner.ItemSelected += (sender, args) =>
{
ViewModel.Values[id] = spinner.GetItemAtPosition(args.Position).ToString();
layout.RequestFocus();
};
spinner.FocusChange += ControlOnFocusChange;
break;
case "switch":
var @switch = BuildSwitch(layout, label, "Yes", "No");
set.Bind(@switch).For(x => x.Checked).To("Values['" + id + "']");
break;
}
}
set.Apply();
return layout;
}
private const int TEXT_SIZE = 20;
private TextView BuildTextView(ViewGroup parentView, string label)
{
var textView = new TextView(parentView.Context)
{
Text = label,
TextSize = TEXT_SIZE - 4,
};
textView.SetTextColor(new Color(59, 140, 212));
textView.SetTypeface(null, TypefaceStyle.Bold);
var textViewParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
textView.LayoutParameters = textViewParams;
var padding = textView.DipsToPixels(8);
textView.SetPadding(padding, padding, padding, 0);
parentView.AddView(textView);
return textView;
}
private EditText BuildEditText(ViewGroup parentView, string hint)
{
BuildTextView(parentView, hint);
var editText = new EditText(parentView.Context)
{
Hint = hint,
TextSize = TEXT_SIZE
};
var editTextParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
editText.LayoutParameters = editTextParams;
var padding = editText.DipsToPixels(8);
editText.SetPadding(padding, padding, padding, padding * 2);
parentView.AddView(editText);
return editText;
}
private AutoCompleteTextView BuildAutoComplete(ViewGroup parentView, LayoutInflater inflater, string hint, string[] items)
{
BuildTextView(parentView, hint);
var autoComplete = new AutoCompleteTextView(parentView.Context)
{
Hint = hint,
TextSize = TEXT_SIZE
};
autoComplete.SetSingleLine(true);
var autoCompleteParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
autoComplete.LayoutParameters = autoCompleteParams;
var padding = autoComplete.DipsToPixels(8);
autoComplete.SetPadding(padding, padding, padding, padding * 2);
var adapter = new AutoCompleteCustomAdapter(inflater, Android.Resource.Layout.SelectDialogItem, items);
autoComplete.Threshold = 1;
autoComplete.Adapter = adapter;
parentView.AddView(autoComplete);
return autoComplete;
}
private Button BuildPopup(ViewGroup parentView, string hint)
{
var button = new Button(parentView.Context)
{
Text = hint,
TextSize = TEXT_SIZE
};
var buttonParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
button.LayoutParameters = buttonParams;
var padding = button.DipsToPixels(8);
button.SetPadding(padding, padding, padding, padding * 2);
parentView.AddView(button);
return button;
}
private Switch BuildSwitch(ViewGroup parentView, string text, string textOn, string textOff)
{
var @switch = new Switch(parentView.Context)
{
Text = text,
TextOn = textOn,
TextOff = textOff,
TextSize = TEXT_SIZE
};
var switchParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
var padding = @switch.DipsToPixels(8);
@switch.SetPadding(padding, padding, padding, padding * 2);
@switch.LayoutParameters = switchParams;
parentView.AddView(@switch);
return @switch;
}
private Spinner BuildSpinner(ViewGroup parentView, string text, string[] items)
{
BuildTextView(parentView, text);
var spinner = new Spinner(Activity);
var spinnerParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
var padding = spinner.DipsToPixels(8);
spinner.SetPadding(padding, padding, padding, padding * 2);
spinner.LayoutParameters = spinnerParams;
var adapter = new ArrayAdapter(parentView.Context, Resource.Layout.SpinnerItem, items);
spinner.Adapter = adapter;
spinner.Focusable = true;
parentView.AddView(spinner);
return spinner;
}
private View BuildPhotoLayout(ViewGroup parentView, LayoutInflater inflater, string name, string caption)
{
var photoLayout = inflater.Inflate(Resource.Layout.photolayout, null);
var photoCaption = photoLayout.FindViewById<TextView>(Resource.Id.photo_caption);
photoCaption.Text = caption;
var imageView = photoLayout.FindViewById<ImageView>(Resource.Id.photo_view);
_imageViews[name] = imageView;
parentView.AddView(photoLayout);
return photoLayout;
}
{
"tabs": [
{
"id": "tab1",
"title": "Tab #1",
"controls": [
{
"id": "text1",
"label": "Text #1",
"type": "text"
},
{
"id": "number1",
"label": "Number #1",
"type": "number"
},
{
"id": "photo1",
"label": "Photo #1",
"type": "photo"
},
{
"id": "autocomplete1",
"label": "Autocomplete #1",
"type": "autocomplete",
"list": "list1"
},
{
"id": "dropdown1",
"label": "Dropdown #1",
"type": "dropdown",
"values": [
"",
"Value 1",
"Value 2",
"Value 3",
]
},
{
"id": "switch1",
"label": "Switch #1",
"type": "switch"
}
]
}
]
}
@kiliman
Copy link
Author

kiliman commented Feb 5, 2016

This is a simple gist demonstrating creating a dynamic layout based on layout defined in a JSON file.

I use manual binding by creating a BindingSet and adding bindings as needed. The ViewModel contains a property named Values of type ObservableDictionary<string, object>.

As shown in BuildPhotoLayout(), in addition to creating the controls manually like, new EditText(), you can also inflate a more complex layout like var photoLayout = inflater.Inflate(Resource.Layout.photolayout, null);

@kiliman
Copy link
Author

kiliman commented Feb 5, 2016

The TabLayout.json file is a simple layout that lists the controls and their types. In our app, we have multiple tabs using a ViewPager.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment