I have started exploring the Spinner control just now. I have pretty much achieved what I wanted but only the last step is missing. Here is what I have done so far.
I have a very simple class for this example:
[Serializable]
public class Merchant
{
public Int64 MerchantId { get; set; }
public String ShopName { get; set; }
public override string ToString()
{
return ShopName;
}
}
Here is the axml where I placed the Spinner:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="1280dip"
android:layout_height="800dip">
<TextView
android:text="Select a merchant:"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/lblSelect"
android:layout_marginLeft="30dip"
android:layout_marginTop="30dip"
android:textSize="42dip" />
<Spinner
android:id="#+id/spinMerchant"
android:layout_width="1000dip"
android:layout_height="wrap_content"
android:layout_below="#id/lblSelect"
android:prompt="#string/spinner_prompt"
android:layout_centerHorizontal="true"
android:minHeight="20dip" />
</RelativeLayout>
And my code is:
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Create your application here
this.SetContentView(Resource.Layout.MerchantSelect);
List<Merchant> lstMerchant = new List<Merchant> ();
lstMerchant.Add (new Merchant() { ShopName = "First Shop", MerchantId = 11 });
lstMerchant.Add (new Merchant() { ShopName = "Second Shop", MerchantId = 12 });
Spinner spinner = this.FindViewById<Spinner>(Resource.Id.spinMerchant);
ArrayAdapter adapter = new ArrayAdapter (this, Android.Resource.Layout.SimpleSpinnerItem, lstMerchant);
spinner.ItemSelected += new EventHandler<AdapterView.ItemSelectedEventArgs> (spinner_ItemSelected);
spinner.Adapter = adapter;
}
private void spinner_ItemSelected (object sender, AdapterView.ItemSelectedEventArgs e)
{
Spinner spinner = (Spinner)sender;
//Merchant merch = (Merchant)spinner.SelectedItem;
string toast = string.Format ("Selected text is {0}", spinner.GetItemAtPosition (e.Position));
Toast.MakeText (this, toast, ToastLength.Long).Show ();
}
I want to get the selected text as well as the ID behind it as soon as the selection is made. I get the text by spinner.GetItemAtPosition (e.Position) but I can't seem to find anything that can give me the ID. If I try to do Merchant merch = (Merchant)spinner.SelectedItem; I get an exception: Cannot convert type 'Java.Lang.Object' to 'Merchant'.
Please let me know how it can be achieved.
Thanks.
The spinner only knows about the text because that's what you have in your ToString() method it. The adapter that's providing the spinner data has no idea it is dealing with Merchant objects.
To get to the actual Merchant object, youll have to make your List<Merchant> lstMerchant a member of your class.
Then change your selection handler:
private void spinner_ItemSelected (object sender, AdapterView.ItemSelectedEventArgs e)
{
Spinner spinner = (Spinner)sender;
// Get the ID from your model.
Merchant merch = this.lstMerchant[e.Position];
var id = merch.MerchantId;
string toast = string.Format ("Selected text is {0}", spinner.GetItemAtPosition (e.Position));
Toast.MakeText (this, toast, ToastLength.Long).Show ();
}
Related
I got a simple layout with TabLayout and PageViewer but can't get them to work together
<com.google.android.material.tabs.TabLayout
android:id="#+id/sliding_tabs_emoji"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:tabMode="scrollable"
app:tabGravity="fill"
app:tabIndicatorColor="#color/gray"
app:tabIndicatorHeight="1dp"
android:paddingBottom="2dp"/>
<androidx.viewpager.widget.ViewPager
android:id="#+id/viewpager_emoji"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/red"/>
In my Activity OnCreate initializing the following
//Fragment array
var fragments = new AndroidX.Fragment.App.Fragment[]
{
Library.Fun.Emoji.Fragments.Recent.NewInstance(),
Library.Fun.Emoji.Fragments.People.NewInstance(),
};
//Tab title array
var titles = Android.Runtime.CharSequence.ArrayFromStringArray(new[] {
"Recent" ,
"People"
});
var viewPager = FindViewById<ViewPager>(Resource.Id.viewpager_emoji);
//viewpager holding fragment array and tab title text
viewPager.Adapter = new EmojiTabsPagerAdapter(SupportFragmentManager, fragments, titles);
// Give the TabLayout the ViewPager
TAB_Layout.SetupWithViewPager(viewPager);
Toast.MakeText(this, "SET", ToastLength.Short);
TAB_Layout.GetTabAt(0).SetIcon(Resource.Drawable.ic_camera);
TAB_Layout.GetTabAt(1).SetIcon(Resource.Drawable.ic_camera);
Where the Adapter is simple as it could be as following
public class EmojiTabsPagerAdapter : FragmentPagerAdapter
{
private readonly AndroidX.Fragment.App.Fragment[] fragments;
private readonly ICharSequence[] titles;
public EmojiTabsPagerAdapter(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment[] fragments, ICharSequence[] titles) : base(fm)
{
this.fragments = fragments;
this.titles = titles;
}
public override int Count
{
get
{
return fragments.Length;
}
}
public override AndroidX.Fragment.App.Fragment GetItem(int position)
{
return fragments[position];
}
public override ICharSequence GetPageTitleFormatted(int position)
{
//return titles[position];
return null;
}
}
Tabs are appearing but Fragments are not loading, The results are the following
Where it should load RECENT and PEOPLE fragments on each tab, my Fragment XML is the following
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="#color/green">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="RECENTS"/>
</RelativeLayout>
And fragment's code is the following (for RECENT)
public class Recent : AndroidX.Fragment.App.Fragment
{
public static Recent NewInstance()
{
var frag = new Recent { Arguments = new Bundle() };
return frag;
}
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.Inflate(Resource.Layout.fragment_emoji_recent, container, false);
return view;
}
}
Any Idea what am I doing wrong?
I use two Fragments: TabFragment1, TabFragment2 to test. In your code, you did provide the code about SetupWithViewPager.
The code below works well for me.
void setupViewPager(Android.Support.V4.View.ViewPager viewPager)
{
var adapter = new Adapter(SupportFragmentManager);
adapter.AddFragment(new TabFragment1(), "First Fragment");
adapter.AddFragment(new TabFragment2(), "Second Fragment");
viewPager.Adapter = adapter;
viewpager.Adapter.NotifyDataSetChanged();
//viewpager.OffscreenPageLimit(4);
}
In MainActivity, you could set like below.
var tabLayout = FindViewById<TabLayout>(Resource.Id.tabs);
tabLayout.SetupWithViewPager(viewpager);
You could download the source file from the link: https://www.c-sharpcorner.com/article/xamarin-android-create-viewpager-tablayout-floatingactionbutton-supportacti/
Did this work before moving to AndroidX?
I think you may need to pass ChildFragmentManager instead of FragmentManager to your PagerAdapter.
In my test app I disable expansion of AppBarLayout when it is collapsed (by scrolling RecycleView). I do that by adding addOnOffsetChangedListener to AppBarLayout.
However, when I click EditText it expands again but, I do not want it to expand.
How to disable the expansion of AppBarLayout when I click EditText?
I have put the whole code, so that everyone can copy/paste the code, create new project and test this fast.
Here is how the .gif looks
Here is XML code:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="400dp"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleTextAppearance="#android:color/transparent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="#drawable/beachcroatia"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycleView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
<LinearLayout
android:id="#+id/messages_linear_layout"
android:layout_width="match_parent"
android:layout_height="58dp"
android:layout_gravity="bottom"
android:background="#E1F5FE"
android:orientation="horizontal"
tools:layout_editor_absoluteY="453dp">
<EditText
android:id="#+id/editText_messageBox"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_gravity="center"
android:layout_weight="0.85"
android:hint="Enter a message"/>
<ImageView
android:id="#+id/messages_sendArrow"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_weight="0.15"
android:src="#drawable/ic_send_message_" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
Here is full MainActivity code:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerChat;
private AdapterChat mAdapterChat;
CollapsingToolbarLayout collapsingToolbarLayout;
AppBarLayout appBarLayout;
ImageView sendMessageImageViewButton;
EditText editTextMessage;
private List<Message> messagesList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
collapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
appBarLayout = findViewById(R.id.app_bar_layout);
sendMessageImageViewButton = findViewById(R.id.messages_sendArrow);
editTextMessage = findViewById(R.id.editText_messageBox);
messagesList = new ArrayList<>();
messagesList.addAll(getMessagesList());
mRecyclerChat = findViewById(R.id.recycleView);
LinearLayoutManager manager = new LinearLayoutManager(this);
mRecyclerChat.setLayoutManager(manager);
mAdapterChat = new AdapterChat(this, messagesList);
mRecyclerChat.setAdapter(mAdapterChat);
//move to the last item in recycleview
mRecyclerChat.getLayoutManager().scrollToPosition(mAdapterChat.getMessagesObekt().size() - 1);
editTextMessage.requestFocus();
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (Math.abs(verticalOffset) == appBarLayout.getTotalScrollRange()) {
// Collapsed
Toast.makeText(MainActivity.this, "Collapsed", Toast.LENGTH_SHORT).show();
// disable expanding
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
} else if (verticalOffset == 0) {
Toast.makeText(MainActivity.this, "Extend", Toast.LENGTH_SHORT).show();
// Expanded
} else {
// Somewhere in between
}
}
});
//sending new message and updating recycleview
sendMessageImageViewButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Message Send", Toast.LENGTH_SHORT).show();
mAdapterChat.updateLastMessage(new Message(editTextMessage.getText().toString()));
editTextMessage.getText().clear();
mRecyclerChat.getLayoutManager().scrollToPosition(mAdapterChat.getMessagesObekt().size() - 1);
}
});
}
//ading some items to a list
private List<Message> getMessagesList() {
List<Message> mMessages = new ArrayList<>();
for (int i = 0; i < 200; i++) {
mMessages.add(new Message("message " + i));
}
return mMessages;
}
}
Here is my message class where i add dummy data to show in RecycleView
public class Message {
private String message;
public Message(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Here is RecycleView Adapter code:
package com.example.petar.collapsingtolbartestiramo;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class AdapterChat extends RecyclerView.Adapter<AdapterChat.ChatHolder> {
private LayoutInflater mInflater;
private List<Message> messagesObekt;
public AdapterChat(Context context, List<Message> listOfMassages) {
mInflater = LayoutInflater.from(context);
messagesObekt = new ArrayList<>();
messagesObekt.addAll(listOfMassages);
notifyDataSetChanged();
}
public void updateChat(List<Message> porukeObjekt){
this.messagesObekt.addAll(porukeObjekt);
notifyDataSetChanged();
}
public void updateLastMessage(Message porukeObjekt) {
this.messagesObekt.add(porukeObjekt);
//notifyDataSetChanged();
notifyItemInserted(messagesObekt.size());
}
public List<Message> getMessagesObekt() {
return messagesObekt;
}
#Override
public ChatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.list_message, parent, false);
ChatHolder holder = new ChatHolder(view, messagesObekt);
return holder;
}
#Override
public void onBindViewHolder(ChatHolder holder, int position) {
holder.textViewMessage.setText(messagesObekt.get(position).getMessage());
}
#Override
public int getItemCount() {
return messagesObekt.size();
}
public static class ChatHolder extends RecyclerView.ViewHolder {
TextView textViewMessage;
public ChatHolder(View itemView, List<Message> messagesObekt) {
super(itemView);
textViewMessage = itemView.findViewById(R.id.rv_message);
}
}
}
Here is RecycleView item XML code (list_message.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:orientation="vertical">
<TextView
android:id="#+id/rv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Poruka Ovdje"
android:textSize="14sp"/>
</LinearLayout>
:
EDIT
I tried adding focus listener to EditText but, that did not helped. This is how i try:
editTextMessage.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if(hasFocus){
appBarLayout.setExpanded(false);
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
}
}
});
I do not know is the best solution, but it works nice. If someone has better solution, feel free to write it.
There is indeed a better, cleaner and simpler solution.
The culprit that leads to this "issue" is actually a feature that was added by Google inside AppBarLayout.ScrollingViewBehavior. That's the one you set via app:layout_behavior="#string/appbar_scrolling_view_behavior". Inside that class, onRequestChildRectangleOnScreen calls setExpanded(false, !immediate) whenever the keyboard is shown. You simply override this method and return false to disable this default behavior. Add this class to your project:
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.appbar.AppBarLayout;
public class FixedScrollingViewBehavior extends AppBarLayout.ScrollingViewBehavior {
public FixedScrollingViewBehavior() {
super();
}
public FixedScrollingViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onRequestChildRectangleOnScreen(#NonNull CoordinatorLayout parent,
#NonNull View child, #NonNull Rect rectangle, boolean immediate) {
return false;
}
}
Then just use this new class by changing app:layout_behavior="#string/appbar_scrolling_view_behavior" to app:layout_behavior="your.source.package.FixedScrollingViewBehavior".
I have find the answer. I do not know is the best solution, but it works nice. If someone has better solution, feel free to write it.
So, what i done is i put the whole AppBarLayout params to 0 when i click EditText or when EditText get focus.
So, when EditText is clicked, AppBarLayout is still there but, now his height is 0 and it is not visible.
This do the trick:
//Collaps AppBarLayout
public void collapsAppBarLayout(){
CoordinatorLayout.LayoutParams params =(CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
params.height = 0; // setting new param height to 0
//setting params to appbarLayout (now AppBarLayout is 0)
appBarLayout.setLayoutParams(params);
appBarLayout.setExpanded(false);
}
Also, if you want to have expand option, you need to save height of previous params and then just add that height again.
So, this is a code how i done it:
Fist, when i scroll RecycleView i disable expand of appBarLayout using addOnOffsetChangedListener. However i also save params height for future use.
int sizeParams; //save params height
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (Math.abs(verticalOffset) == appBarLayout.getTotalScrollRange()) {
// Collapsed
Toast.makeText(MainActivity.this, "Collapsed", Toast.LENGTH_SHORT).show();
// disable expanding and scroll
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
//saving height for future use
sizeParams = params.height;
} else if (verticalOffset == 0) {
Toast.makeText(MainActivity.this, "Extend", Toast.LENGTH_SHORT).show();
// Expanded
} else {
// Somewhere in between
}
}
});
Now i make this 2 method which i call when i need expand/collaps.
//Collaps AppBarLayout
public void collapsAppBarLayout(){
CoordinatorLayout.LayoutParams params =(CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
sizeParams = params.height;//save params height for future use
params.height = 0; // setting new param height to 0
//setting params to appbarLayout (now AppBarLayout is 0
appBarLayout.setLayoutParams(params);
appBarLayout.setExpanded(false);
}
//Expand AppBarLayout
public void expandAppBarLayout(){
CoordinatorLayout.LayoutParams params =(CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
params.height = 3*sizeParams; // HEIGHT
appBarLayout.setLayoutParams(params); //add height to appbarlayout
//enable scroll and expand
AppBarLayout.LayoutParams params1 = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params1.setScrollFlags(1);
appBarLayout.setExpanded(true);//expand
}
I am using a spinner and an arrayAdapter to populate items in spinner. I want to make the spinner multi-selectable but I just select one row.I searched in Google, solution available in Java, but i don't have any idea on how to implement it in Xamarin.
My code is as below,
adapterList= new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItemMultipleChoice);
spnMultiTest.Adapter = adapterList;
I want to make the spinner multi-selectable but I just select one row.I searched in Google, solution available in Java, but i don't have any idea on how to implement it in Xamarin.
Basically I just translate the codes from Android Spinner with multiple choice
to Xamarin codes. I have tested it and it works fine:
MultiSpinner.cs:
public interface MultiSpinnerListener
{
void onItemsSelected(bool[] selected);
}
public class MultiSpinner : Spinner, IDialogInterfaceOnMultiChoiceClickListener, IDialogInterfaceOnCancelListener
{
Context _context;
private List<String> items;
private bool[] selected;
private String defaultText;
private MultiSpinnerListener listener;
public MultiSpinner(Context context) : base(context)
{
_context = context;
}
public MultiSpinner(Context context, IAttributeSet arg1) : base(context, arg1)
{
_context = context;
}
public MultiSpinner(Context context, IAttributeSet arg1, int arg2) : base(context, arg1, arg2)
{
_context = context;
}
public void OnClick(IDialogInterface dialog, int which, bool isChecked)
{
if (isChecked)
selected[which] = true;
else
selected[which] = false;
}
public override void OnClick(IDialogInterface dialog, int which)
{
dialog.Cancel();
}
public override bool PerformClick()
{
AlertDialog.Builder builder = new AlertDialog.Builder(_context);
builder.SetMultiChoiceItems(
items.ToArray(), selected, this);
builder.SetPositiveButton("OK",this);
builder.SetOnCancelListener(this);
builder.Show();
return true;
}
public void SetItems(List<String> items, String allText,
MultiSpinnerListener listener)
{
this.items = items;
this.defaultText = allText;
this.listener = listener;
// all selected by default
selected = new bool[items.Count];
for (int i = 0; i < selected.Length; i++)
selected[i] = true;
ArrayAdapter<string> adapter = new ArrayAdapter<string>(_context,Resource.Layout.simple_spinner_item,Resource.Id.tv_item,new string[] { allText });
// all text on the spinner
//ArrayAdapter<String> adapter = new ArrayAdapter<String>(_context,Resource.Layout.simple_spinner_item, new String[] { allText });
Adapter = adapter;
}
public void OnCancel(IDialogInterface dialog)
{
Java.Lang.StringBuffer spinnerBuffer = new Java.Lang.StringBuffer();
bool someUnselected = false;
for (int i = 0; i < items.Count; i++)
{
if (selected[i] == true)
{
spinnerBuffer.Append(items[i]);
spinnerBuffer.Append(", ");
}
else
{
someUnselected = true;
}
}
String spinnerText;
if (someUnselected)
{
spinnerText = spinnerBuffer.ToString();
if (spinnerText.Length > 2)
spinnerText = spinnerText.Substring(0, spinnerText.Length - 2);
}
else
{
spinnerText = defaultText;
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(_context,Resource.Layout.simple_spinner_item,Resource.Id.tv_item,new string[] { spinnerText });
Adapter = adapter;
if (listener != null)
{
listener.onItemsSelected(selected);
}
}
}
simple_spinner_item.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tv_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Update:
Here is the codes for using this MultiSpinner:
Main.axml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<MultiSpinner.MultiSpinner
android:id="#+id/mSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
MainActivity.cs:
public class MainActivity : Activity
{
MultiSpinner mSpinner;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
mSpinner = FindViewById<MultiSpinner>(Resource.Id.mSpinner);
List<string> items = new List<string> {
"Android",
"iOS",
"UWP"
};
mSpinner.SetItems(items, "AllText", null);
}
}
I need to add pictures to dropdown items.
In the dropdown I need to see the icon first and the text below. But for some reason, I can't see images, though I see the empty place instead.
My project is Xamarin.Android with MVVMCross. I'm missing something, maybe I need some plugins?
I have MypageView.axml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/toolbar" />
<TextView
android:id="#+id/city"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginBottom="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textSize="12sp"
android:layout_below="#id/toolbar"
local:MvxBind="Text Strings[CityTextView]" />
<MvxSpinner
android:id="#+id/select_city"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginBottom="24dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:spinnerMode="dropdown"
android:layout_below="#id/city"
local:MvxItemTemplate="#layout/item_city"
local:MvxDropDownItemTemplate="#layout/item_city"
local:MvxBind="ItemsSource Cities; SelectedItem SelectedCity" />
And item_city.axml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<ImageView
android:id="#+id/cityImage"
android:layout_width="35dp"
android:layout_height="20dp"
android:layout_marginLeft="16dp"
android:scaleType="fitXY"
local:MvxBind="DrawableName Name, Converter=IconsConverter" /> />
<TextView
android:id="#+id/cityName"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginLeft="8dp"
android:layout_toRightOf="#id/cityImage"
android:textSize="16sp"
local:MvxBind="Text Name" />
</RelativeLayout>
In the Core, I have City.cs:
namespace My.cities.Core.Models
{
public class City
{
public City(string Id, string Name, string Flag)
{
this.Id = Id;
this.Name = Name;
this.Flag = Flag;
}
public string Id { get; set; }
public string Name { get; set; }
public string Flag { get; set; }
}
}
And MypageViewModel.cs:
using MvvmCross.Core.ViewModels;
using System.Collections.Generic;
using My.cities.Core.Models;
namespace My.cities.Core.ViewModels
{
public class MypageViewModel : BaseViewModel
{
private City _selectedCity;
private List<City> _cities = new List<City>()
{
new City("1", "London", "England")
new City("2", "Paris", "France")
};
public List<City> Cities
{
get { return _cities; }
}
public City SelectedCity
{
get
{
return _selectedCity;
}
set
{
_selectedCity = value;
RaisePropertyChanged(() => SelectedCity);
}
}
}
}
So I add the converter to MypageView.cs:
using Android.App;
using MvvmCross.Platform.Converters;
using System;
using System.Globalization;
namespace My.cities.Droid.Views
{ [Activity]
class MypageView : BaseView
{
protected override int LayoutResource => Resource.Layout.MypageView;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
}
public static class CrossDeviceInfoHelper
{
public static string GetLocalImageUrlByPlatform(string name)
{
return CrossDeviceInfo.Current.Platform == Platform.Android ? $"#drawable/{name}" : name;
}
}
public class IconsConverter : MvxValueConverter<string, string>
{
protected override string Convert(string value, Type targetType, object parameter, CultureInfo culture)
{
if (value == "London")
return CrossDeviceInfoHelper.GetLocalImageUrlByPlatform("england");
if (value == "Paris")
return CrossDeviceInfoHelper.GetLocalImageUrlByPlatform("france");
}
}
Images England.png and France.png live here: My.sities.Droid\Resources\drawable-hdpi, My.sities.Droid\Resources\drawable-mdpi, and etc.
The build is successful, but I can't see my images, only empty rectangles. Why?
UPD.
I installed Xam.Plugin.DeviceInfo and changed my code. But I still can't see icons.
In debug it says:
[0:] MvxBind:Warning: 36,05 Value '' could not be parsed as a valid string identifier
[0:] MvxBind:Warning: 36,06 Value '' could not be parsed as a valid string identifier
[0:] MvxBind:Warning: 36,06 Value 'London' was not a known drawable name
[0:] MvxBind:Warning: 36,08 Value 'Paris' was not a known drawable name
UPD2.
Also, I tried to do it this way:
public class IconsConverter : MvxValueConverter<string, int>
{
protected override int Convert(string value, Type targetType,
object parameter, CultureInfo culture)
{
switch (value)
{
case "London":
return Resource.Mipmap.England;
case "Paris":
return Resource.Mipmap.France;
}
}
}
But the error is the same:
[0:] MvxBind:Warning: 9,54 Value '' could not be parsed as a valid string identifier
[0:] MvxBind:Warning: 9,63 Value '' could not be parsed as a valid string identifier
[0:] MvxBind:Warning: 9,64 Value 'London' was not a known drawable name
[0:] MvxBind:Warning: 9,65 Value 'Paris' was not a known drawable name
I think you are lacking the android:scaleType in your ImageView. Try using android:scaleType="fitXY".
If it is not that, try using the DrawableName binding instead of DrawableId and it's better because you can use a converter in your PCL making it cross-platform with Xam.Plugin.DeviceInfo and a helper method like this one:
public static class CrossDeviceInfoHelper
{
public static string GetLocalImageUrlByPlatform(string name)
{
// Assuming we are working with Android and iOS
return CrossDeviceInfo.Current.Platform == Platform.Android ? $"#drawable/{name}" : name;
}
}
So you'll endup with a Converter like this one:
public class StringToIntValueConverter : MvxValueConverter<string, string>
{
protected override string Convert(string value, Type targetType, object parameter, CultureInfo culture)
{
if (value == "London")
return CrossDeviceInfoHelper.GetLocalImageUrlByPlatform("england");
if (value == "Paris")
return CrossDeviceInfoHelper.GetLocalImageUrlByPlatform("france");
}
}
Also if I were you I'll add an enum or something that tells you the country in your City class so that you don't have to compare strings in the converter and you could also have a Dictionary in your converter that has which city goes with which country image so that you can just call CrossDeviceInfoHelper.GetLocalImageUrlByPlatform(myDictionary[value]); and therefore not using ifs
The binding expression DrawableName Name Converter=IconsConverter is wrong. You are missing a comma:
DrawableName Name, Converter=IconsConverter
Alternatively you can write it like:
DrawableName Icons(Name)
I'm currently creating an app for Android using MvvmCross. Part of that app requires a MvxListView, where each item has 2 TextViews. One of these TextViews are hidden by default. I'm looking to implement an accordion like functionality, where clicking on the first TextView will show/hide the other TextView.
I've currently gotten most of this to work with the MvvmCross Visibility Plugin, but the click event is bound to the MvxListView instead of the TextView inside it. What I've currently gotten to work looks like this:
FirstViewModel:
public class FirstViewModel
: MvxViewModel
{
public FirstViewModel(IListService listService)
{
Interests = new ObservableCollection<Interest>();
List<Interest> tempInterests = listService.GetInterestFeeds("");
foreach (var interest in tempInterests)
{
interest._parent = this;
Interests.Add(interest);
}
var pluginLoader = new PluginLoader();
pluginLoader.EnsureLoaded();
}
private ObservableCollection<Interest> _interests;
public ObservableCollection<Interest> Interests
{
get { return _interests; }
set { _interests = value; RaisePropertyChanged(() => Interests); }
}
public ICommand ItemVisibleCommand
{
get
{
return new MvxCommand<Interest>(item => item.IsVisible = !item.IsVisible);
}
}
}
FirstView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
...>
<Mvx.MvxListView
...
local:MvxBind="ItemsSource Interests"
local:MvxItemTemplate="#layout/item_interests" />
</LinearLayout>
item_interests:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="30dp"
local:MvxBind="Text InterestName" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
local:MvxBind="Text InterestDescription; Visibility IsVisible, Converter=Visibility" />
</LinearLayout>
In order to bind it to the TextView inside the MvxListView, I've been trying to modify my code to something similar to How to bind ItemClick in MvxListView in MvxListView as per the anwser by Stuart, resulting in the following code:
item_interest:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="30dp"
local:MvxBind="Text InterestName; Click ItemVisibleCommand" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
local:MvxBind="Text InterestDescription; Visibility IsVisible, Converter=Visibility" />
</LinearLayout>
FirstViewModel:
public class FirstViewModel
: MvxViewModel
{
public FirstViewModel(IListService listService)
{
Interests = new ObservableCollection<Interest>();
List<Interest> tempInterests = listService.GetInterestFeeds("");
foreach (var interest in tempInterests)
{
interest._parent = this;
Interests.Add(interest);
}
var pluginLoader = new PluginLoader();
pluginLoader.EnsureLoaded();
}
private ObservableCollection<Interest> _interests;
public ObservableCollection<Interest> Interests
{
get { return _interests; }
set { _interests = value; RaisePropertyChanged(() => Interests); }
}
public void MakeItemVisible(bool isVisible)
{
isVisible = !isVisible;
}
Interest:
public class Interest : INotifyPropertyChanged
{
public string InterestId { get; set; }
public string InterestName { get; set; }
public string InterestDescription { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public FirstViewModel _parent { get; set; }
private bool _isVisible;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
onPropertyChanged(this, "IsVisible");
}
}
private void onPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
public Interest(string id, string name, string description)
{
//Initialisers
}
private MvxCommand<bool> _itemVisible;
public ICommand ItemVisibleCommand
{
get
{
_itemVisible = _itemVisible ?? new MvxCommand<bool>(IsVisible => _parent.MakeItemVisible(IsVisible));
return _itemVisible;
}
}
}
resulting in the following exception:
04-04 15:05:40.575 I/MonoDroid(18011): UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object
04-04 15:05:40.575 I/MonoDroid(18011): at Cirrious.MvvmCross.ViewModels.MvxCommand`1<bool>.Execute (object) <IL 0x00010, 0x00088>
04-04 15:05:40.575 I/MonoDroid(18011): at Cirrious.MvvmCross.Binding.Droid.Target.MvxViewClickBinding.ViewOnClick (object,System.EventArgs) <IL 0x0001f, 0x000fb>
04-04 15:05:40.575 I/MonoDroid(18011): at Android.Views.View/IOnClickListenerImplementor.OnClick (Android.Views.View) [0x0000d] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.12-series/a1e3982a/source/monodroid/src/Mono.Android/platforms/android-15/src/generated/Android.Views.View.cs:1615
04-04 15:05:40.575 I/MonoDroid(18011): at Android.Views.View/IOnClickListenerInvoker.n_OnClick_Landroid_view_View_ (intptr,intptr,intptr) [0x00011] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.12-series/a1e3982a/source/monodroid/src/Mono.Android/platforms/android-15/src/generated/Android.Views.View.cs:1582
04-04 15:05:40.575 I/MonoDroid(18011): at (wrapper dynamic-method) object.a963c1ac-b573-4022-b41d-f0f002438c84 (intptr,intptr,intptr) <IL 0x00017, 0x00043>
Unhandled Exception:
System.NullReferenceException: Object reference not set to an instance of an object
Thanks in advance to anyone who's taken the time to read all that :)
UPDATE - I tried to do as Stuart suggested, and got the following solution:
First off, to preserve the original Interest entity, it got wrapped in an InterestWrapper.
public class InterestWrapper : INotifyPropertyChanged
{
private Interest _interest;
private InterestAndroidViewModel _parent; //TO-DO
public Interest Item { get { return _interest; } }
public event PropertyChangedEventHandler PropertyChanged;
private bool _isVisible;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
onPropertyChanged(this, "IsVisible");
}
}
private void onPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
public InterestWrapper(Interest interest, InterestAndroidViewModel parent)
{
IsVisible = false;
_interest = interest;
_parent = parent;
}
public IMvxCommand ItemVisibleCommand
{
get
{
return new MvxCommand(() => _parent.MakeItemVisible(_interest));
}
}
}
FirstViewModel
public class FirstViewModel
: MvxViewModel
{
public FirstViewModel(IListService listService)
{
Interests = new ObservableCollection<InterestWrapper>();
List<Interest> tempInterests = listService.GetInterestFeeds("");
foreach (var interest in tempInterests)
{
InterestWrapper wrapper = new InterestWrapper(interest, this);
Interests.Add(wrapper);
}
}
private ObservableCollection<InterestWrapper> _interests;
public ObservableCollection<InterestWrapper> Interests
{
get { return _interests; }
set { _interests = value; RaisePropertyChanged(() => Interests); }
}
public void MakeItemVisible(Interest interest)
{
if (interest.IsVisible)
{
interest.IsVisible = !interest.IsVisible;
}
else
{
foreach (var _interest in _interests)
{
_interest.Item.IsVisible = false;
}
interest.IsVisible = !interest.IsVisible;
}
}
}
item_interest:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
...
<RelativeLayout
...
<TextView
...
local:MvxBind="Text Item.InterestName; Click ItemVisibleCommand" />
<Mvx.MvxImageView
...
local:MvxBind="Visibility Item.IsVisible, Converter=InvertedVisibility; Click ShowEducationsCommand" />
</RelativeLayout>
</RelativeLayout>
The local:MvxBind="Text InterestName; Click ItemVisibleCommand" can only really call a non-parameterized MvxCommand - it can't call MvxCommand<bool> as it doesn't know what the bool value is.
If you wanted to, you could use the CommandParameter converter to pass in the value - e.g. local:MvxBind="Text InterestName; Click CommandParameter(ItemVisibleCommand, IsVisible)"
But overall, in this case I'd probably recommend rewriting ItemVisibleCommand as just a "toggle visible" command instead
Look through this question it's the as your one and has the solution suggested by Stuart, so think it's what you are looking for.
Binding button click in ListView template MvvMCross