How to display an image with MvxImageView into a MvxListView - android

I have a MvxListView in Xamarin.Android app, and an ItemTemplate to display a name and a picture retrieve from WS. If the name is displayed, it is not the case of the picture ...
View_Main.axml
<?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="match_parent"
android:layout_height="match_parent"
android:background="#00007f"
android:textColor="#ffffff"
android:textSize="24dp"
android:layout_margin="30dp"
android:padding="20dp"
android:layout_marginTop="10dp">
<Mvx.MvxListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxBind="ItemsSource ItemsModels"
local:MvxItemTemplate="#layout/item_list" />
</LinearLayout>
item_list.axml
<?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:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="22dp"
local:MvxBind="Text Name" />
<Mvx.MvxImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
local:MvxBind="ImageUrl Image" />
</LinearLayout>
I have the internet permission in AndroidManifest.xml, and I installed DownloadCache and File plugins.
Urls are good, I display them in Visual Studio Console and when I click on them, image display.
I followed this video to try to display image, but I don't know why it works for him and not for me ...
https://www.youtube.com/watch?v=JBzj_nkeLFI
Maybe because he uses MvvmCross 3.0 and I'm using MvvmCross 4.0 ...
FIRST EDIT
My ItemModel class
public class ItemModel
{
public string Name { get; set; }
public string Image { get; set; }
public string Text { get; set; }
public bool IsFavorite { get; set; }
}
and a typical value of Image property : http://inu.tapptic.com/test/image.php?text=%E5%8D%81%E5%85%AB
The problem is not the URL, I tried with a URL found on google image, and it doesn't work too
SECOND EDIT
public class MainViewModel : MvxViewModel
{
private readonly IDataProviderService _dataProviderService;
#region Fields
private ObservableCollection<ItemModel> _itemsModels;
#endregion
#region Properties
public ObservableCollection<ItemModel> ItemsModels
{
get { return _itemsModels; }
set
{
_itemsModels = value;
RaisePropertyChanged(() => ItemsModels);
}
}
#endregion
#region Constructors
public MainViewModel(IDataProviderService dataProviderService)
{
_dataProviderService = dataProviderService;
}
#endregion
public override void Start()
{
TimeWatcher.BeforeExecution();
Task.Run(() => LoadData()); // A voir car ça lance un autre "faux thread", mais ça bloque pas l'UI
base.Start();
TimeWatcher.ExecutionEnd();
}
private async void LoadData()
{
ItemsModels = await _dataProviderService.GetItemsAsync("http://dev.tapptic.com/", "test/json.php");
// Just to see if URLs are good
foreach(var itemsModel in ItemsModels)
Debug.WriteLine(itemsModel.Image);
}
}

Related

Horizontal RecyclerView with dynamic item’s Height

I’m trying to implement a RecyclerView with horizontal scrolling, so I’m using this a LinearLayoutManager with horizontal orientation. The problem is that I’m populating the RecyclerView using 2 different types of items, with different heights. This is the layout I’m using for the item:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp">
<LinearLayout
android:id="#+id/document_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#drawable/ic_rounded"
android:backgroundTint="#color/ms_black_ms_gray"
android:gravity="center"
android:layout_gravity="bottom"
android:padding="5dp"
android:paddingStart="15dp">
<TextView
android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/white"
android:textSize="13sp"
android:singleLine="true"
android:maxWidth="80dp"
tools:text="example_form"/>
<TextView
android:id="#+id/format"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/white"
android:textSize="13sp" />
…
</LinearLayout>
<android.support.v7.widget.CardView
android:id="#+id/image_view"
android:layout_width="120dp"
android:layout_height="80dp"
android:layout_gravity="bottom"
app:cardCornerRadius="25dp"
app:cardElevation="0dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/preview_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"/>
…
</RelativeLayout>
</android.support.v7.widget.CardView>
and this is the layout that contains the RecyclerView, which is basically like this:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="14dp"
android:paddingEnd="14dp">
<ImageView
android:id="#+id/attach"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_gravity="bottom"
android:layout_marginBottom="19dp"
android:visibility="visible"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginBottom="10dp"
android:layout_marginTop="5dp"
android:padding="3dp"
android:foreground="#drawable/ic_rounded_stroke"
android:foregroundTint="#color/white">
<android.support.constraint.ConstraintLayout
android:id="#+id/chatEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/ic_rounded"
android:foreground="#drawable/ic_rounded_stroke"
android:padding="6dp"
android:visibility="visible">
<EditText
android:id="#+id/editText"
android:textSize="17sp"
android:textColor="#121212"
android:letterSpacing="-0.02"
android:lineSpacingExtra="0sp"
android:padding="10dp"
android:paddingStart="15dp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxLines="5"
android:hint="#string/chat_hint"
android:inputType="textCapSentences|textMultiLine"
android:maxLength="2500"
android:background="#null"
app:layout_constraintRight_toLeftOf="#id/buttonsContainer"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/send"
android:layout_gravity="bottom"
android:visibility="visible"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingBottom="10dp"
android:paddingTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="#ffffff"
android:letterSpacing="-0.02"
android:gravity="center_horizontal"
android:text="#string/send"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
<android.support.v7.widget.RecyclerView
android:id="#+id/filesList"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:paddingTop="5dp"
android:paddingEnd="5dp"
android:visibility="gone"
app:layout_constraintRight_toLeftOf="#id/send"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#id/editText"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
</LinearLayout>
</LinearLayout>
I’m using a single ViewHolder, I just change the visibility of the 2 child views.
The result I expect to get is this one:
But what I’m getting is this; the CardView being cut in half, using the height of the second type of item:
I saw this post, which is similar to my problem. It recommends using Google’s Flexbox. So, I tried to implement FlexboxLayoutManager:
FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(getContext());
layoutManager.setFlexDirection(FlexDirection.ROW);
layoutManager.setFlexWrap(FlexWrap.NOWRAP);
I’m using row direction and It is showing items on next lines if it does not fit in single line. So, I also added No_wrap. And now it is showing items in a single line but do not provide scrolling. Also in this case it tries to fit all items in a single line by decreasing width of items.
I also played with the flex box sample app, but I couldn’t get the result I want.
Is there a way I can achieve horizontal scrolling with the Flexbox integrated with RecyclerView? Or should I use a different approach?
Thanks
EDIT
Thanks for the tips and everything, but it is not solving it. So, I stripped down the code to bare minimum to reproduce this.
MainActivity:
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE = 1;
private RecyclerView recyclerView;
private FilesAdapter filesAdapter;
private List<File> filesList = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
LinearLayoutManager filesLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(filesLayoutManager);
filesAdapter = new FilesAdapter(filesList);
recyclerView.setAdapter(filesAdapter);
ImageView attach = findViewById(R.id.attach);
attach.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(Intent.createChooser(intent,"Select Files"), REQUEST_CODE);
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
try {
if (data != null) {
List<File> uriList = new ArrayList<>();
if (data.getClipData() != null) { // Multiple files
for (int i = 0; i < data.getClipData().getItemCount(); i++) {
Uri uri = data.getClipData().getItemAt(i).getUri();
Pair<Boolean, File> isValid = isFileValid(uri);
if (isValid.first) {
uriList.add(isValid.second);
}
}
} else { // Single file
Uri uri = data.getData();
Pair<Boolean, File> isValid = isFileValid(uri);
if (isValid.first) {
uriList.add(isValid.second);
}
}
if (uriList.size() > 0) {
for (File file : uriList) {
filesList.add(filesList.size(), file);
filesAdapter.notifyItemInserted(filesList.size());
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
super.onActivityResult(requestCode, resultCode, data);
}
private Pair<Boolean, File> isFileValid(Uri uri) throws NullPointerException {
Pair<Boolean, File> defaultResponse = Pair.create(false, null);
Cursor c = getContentResolver().query(uri, null, null, null, null);
if (c != null) {
c.moveToFirst();
String filename = c.getString(c.getColumnIndex(OpenableColumns.DISPLAY_NAME));
if (isSupported(filename)) {
c.close();
return Pair.create(true, new File(StringUtils.endsWithIgnoreCase(filename, ".pdf") ? DOCUMENT : IMAGE));
} else {
Toast.makeText(this, "File format not supported", Toast.LENGTH_SHORT).show();
c.close();
return defaultResponse;
}
}
return defaultResponse;
}
private boolean isSupported(String filename) {
String[] supportedFormats = { ".pdf", ".jpg", ".gif", ".png" };
for (String format : supportedFormats) {
if (StringUtils.endsWithIgnoreCase(filename, format)) {
return true;
}
}
return false;
}
}
Main activity layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="#+id/attach"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:layout_marginBottom="19dp"
android:padding="10dp"
android:src="#drawable/ic_attach" />
</LinearLayout>
File:
public class File {
public enum Type {
DOCUMENT,
IMAGE
}
private Type type;
public File(Type type) {
this.type = type;
}
public Type getType() {
return type;
}
}
File Adapter:
public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.BaseViewHolder> {
private List<File> files;
public FilesAdapter(List<File> files) {
this.files = files;
}
#NonNull
#Override
public FilesAdapter.BaseViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(viewType == 0 ? R.layout.document_item : R.layout.image_item, parent, false);
if (viewType == 0) {
return new DocumentViewHolder(view);
} else {
return new ImageViewHolder(view);
}
}
#Override
public void onBindViewHolder(#NonNull FilesAdapter.BaseViewHolder viewHolder, int position) {
viewHolder.bind(files.get(position));
}
#Override
public int getItemViewType(int position) {
if (files.get(position).getType() == File.Type.DOCUMENT) {
return 0;
} else {
return 1;
}
}
#Override
public int getItemCount() {
return files.size();
}
abstract static class BaseViewHolder extends RecyclerView.ViewHolder {
public BaseViewHolder(#NonNull View itemView) {
super(itemView);
}
abstract void bind(File file);
}
static class ImageViewHolder extends BaseViewHolder {
public ImageViewHolder(#NonNull View itemView) {
super(itemView);
}
#Override
void bind(File file) { }
}
static class DocumentViewHolder extends BaseViewHolder {
public DocumentViewHolder(#NonNull View itemView) {
super(itemView);
}
public void bind(File file) { }
}
}
document item:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="150dp"
android:layout_height="40dp"
android:background="#drawable/ic_rounded"
android:backgroundTint="#888888"
android:layout_margin="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="PDF"
android:textColor="#android:color/white"/>
</LinearLayout>
image item:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="120dp"
android:layout_height="80dp"
android:layout_margin="5dp"
app:cardBackgroundColor="#000000"
app:cardCornerRadius="10dp"
app:cardElevation="0dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="IMAGE"
android:textColor="#android:color/white"/>
</androidx.cardview.widget.CardView>
if I select an image first, and the several pdfs, it works fine:
But if I first select 3 pdfs, and then an image, this happens:
Any idea how to solve this?
I had a similar issue in another project and i solved it by using the Google library FlexboxLayoutManager.
Get the latest FlexboxLayoutManager Library (https://github.com/google/flexbox-layout) and add it into your grandle dependencies (implementation 'com.google.android:flexbox:2.0.1')
In your Activity add the below lines of code: FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(this);
layoutManager.setFlexDirection(FlexDirection.ROW);
layoutManager.setFlexWrap(FlexWrap.NOWRAP);
recyclerView.setLayoutManager(layoutManager);
To make FlexboxLayoutManager work with horizontal scroll add the below code in your adapter (FilesAdapter) in BaseViewHolder class: abstract static class BaseViewHolder extends RecyclerView.ViewHolder {
public BaseViewHolder(#NonNull View itemView) {
super(itemView);
ViewGroup.LayoutParams lp = itemView.getLayoutParams();
if (lp instanceof FlexboxLayoutManager.LayoutParams) {
FlexboxLayoutManager.LayoutParams flexboxLp = (FlexboxLayoutManager.LayoutParams) lp;
flexboxLp.setFlexShrink(0.0f);
flexboxLp.setAlignSelf(AlignItems.FLEX_START); //this will align each itemView on Top or use AlignItems.FLEX_END to align it at Bottom
}
}
abstract void bind(File file);
}
In case it helps anyone else, Kotlin version of MariosP's answer with minor refactors below, but 100% kudos to #MariosP. His answer saved the day for us!
RecyclerView setup (this was from a fragment, called in onViewCreated):
private fun setupRecyclerView() {
val flexBoxLayoutManager = FlexboxLayoutManager(requireContext(), FlexDirection.ROW, FlexWrap.NOWRAP)
with(recycler_view) {
layoutManager = flexBoxLayoutManager
adapter = myAdapter
}
}
Adapter setup:
var items : List<Item>
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bindItem(items[position])
}
In the ViewHolder:
class MyViewHolder(private val itemView: View): RecyclerView.ViewHolder(itemView) {
fun bindItem(item: Item) {
// Do things with item
updateLayoutParamsToAllowHorizontalScrolling()
}
private fun updateLayoutParamsToAllowHorizontalScrolling() {
(itemView.layoutParams as? FlexboxLayoutManager.LayoutParams)?.let {
it.flexShrink = 0.0f
it.alignSelf = AlignItems.FLEX_START
}
}
}
try this for your RecyclerView:
android:layout_height="wrap_content"
Since the XML file that contains your RecyclerView is not complete here I cannot be sure but if your RecyclerView is inside another parent view that is limiting it, then i guess using wrap_content as the height for RecyclerView plus some tweaks should solve it.
Also, note that you are limiting you RecyclerView to the bottom of "editText" from top side so that may be preventing your RecyclerView from expanding too.
All you have to do, is to set recyclerview's height to the height of the biggest item, in your case the image item.
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="wrap_content"
android:layout_height="80dp" />
The reason Image is getting cropped when you choose pdf files first is because the height of recycleView is 40dp which is the height of pdf item. When you try to add a new item without modifying the existing ones, recycleView height remains the same i.e. 40dp. To enforce a minimum height of 80dp (which is the current height of the image layout), we can use minHeight as follows:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="80dp"
tools:listitem="#layout/document_item"
/>
You can also modify your pdf item layout to align the pdfs center_vertically with image items as follows:
<?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="match_parent"
android:layout_gravity="center_vertical"
android:layout_margin="5dp">
<TextView
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:gravity="center"
android:background="#drawable/ic_round"
android:backgroundTint="#888888"
android:text="PDF"
android:textColor="#android:color/white"/>
</LinearLayout>
Cheers :)
First, I think your main layout is a bit overcomplicated. You could do the whole thing in a single ConstraintLayout (if you need framed background around specific items, I recommend to use pure View instances layed out using Barriers and Guidelines - see https://medium.com/better-programming/essential-components-of-constraintlayout-7f4026a1eb87)
Another addition and/or improvement would be to not use right/left constraints, rather start/end. This prepares your layout for RTL display too.
Also, I highly recommend to use separate layout files and ViewHolders for distinct items in a RecyclerView.
As others pointed out in comments, your RecyclerView is layed out using match_parent which can in turn crop your view. You may want to set this wrap_content.
In the meanwhile, you may also want to update dependencies to use Android Jetpack and ditch support libraries.

How to create a recyclerview using xamarin android mvvmcross?

I have followed the example from https://github.com/xamarin/monodroid-samples/tree/master/android5.0/RecyclerViewSample/RecyclerViewSample for displaying the data in recyclerview in xamarin android. It is working fine for me.
But as per our project requirement, i need to display a recyclerview using xamarin android mvvmcross. I have searched for examples but could not find a working one.
Code:
main.xml
<MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
android:id="#+id/recylerView"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginLeft="#dimen/margin_normal"
android:layout_marginRight="#dimen/margin_normal"
android:layout_marginBottom="#dimen/margin_normal"
android:layout_marginTop="#dimen/margin_normal"
android:background="#FFFF00"
android:orientation="horizontal"
android:divider="#null"
android:dividerHeight="#dimen/divider_height_medium"
local:MvxItemTemplate="#layout/item_list"
local:MvxBind="ItemsSource CoffeeList" />
Fragment.cs
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.Inflate(Resource.Layout.main, null);
return v;
}
item_list.xml
<LinearLayout
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="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text FirstName" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text LastName" />
</LinearLayout>
ViewModel.cs
public class CoffeeViewModel : MvxViewModel
{
public CoffeeViewModel ()
{
}
private string _firstName;
public string FirstName
{
get => _firstName;
set => SetProperty(ref _firstName, value);
}
private string _lastName;
public string LastName
{
get => _lastName;
set => SetProperty(ref _lastName, value);
}
}
Now when i run the application, the recyclerview is displaying empty. Can you please let me know if I am missing anything while binding the data.
First of all install the package MvvmCross.Droid.Support.V7.RecyclerView.
Here you have an example of a simple MvxRecyclerView:
<mvvmcross.droid.support.v7.recyclerview.MvxRecyclerView
...
local:MvxItemTemplate="#layout/item_template"
local:MvxBind="ItemsSource Items; ItemClick ItemClickCommand; ItemLongClick ItemLongClickCommand" />
So Items is your ObservableCollection<T> of your ViewModel, ItemClickCommand is the command of your ViewModel that will be executed when you click on a row and ItemLongClick is the command of your ViewModel that will be executed when you do a long click on a row.
Let's say you have a public ObservableCollection<PersonItemViewModel> Items {get;set;}
And your PersonItemViewModel has the next properties:
private string _firstName;
public string FirstName
{
get => _firstName;
set => SetProperty(ref _firstName, value);
}
private string _lastName;
public string LastName
{
get => _lastName;
set => SetProperty(ref _lastName, value);
}
So your item_template.axml that will be renderized as each row of the MvxRecyclerView. It's DataContext (to which you do the bindings) is each of the items of the ObservableCollection<PersonItemViewModel>, i.e. a PersonItemViewModel. Here an example of the layout:
<LinearLayout
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="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text FirstName" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text LastName" />
</LinearLayout>
More info in Android MvxRecyclerView
HIH
If you are using Mvvmcross 6. Put that code in your Setup.cs at your .Droid project:
protected override IEnumerable<Assembly> AndroidViewAssemblies =>
new List<Assembly>(base.AndroidViewAssemblies)
{
typeof(MvxRecyclerView).Assembly,
};
Now you don't have to use the full path of MvxRecyclerView (MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView).
Now you can use the MvxRecyclerView just like de RecyclerView from Android.

MvxRecyclerView with button in each row

I am making an Android application with Xamarin.Android and MvvmCross. I have a MvxRecyclerView:
<MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#null"
local:MvxItemTemplate="#layout/auto_complete_search_item"
local:MvxBind="ItemsSource SearchAutoCompleteItems; ItemClick SearchAutoCompleteItemClick" />
And my auto_complete_search_item.xml:
<?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:layout_width="match_parent"
android:layout_height="#dimen/auto_complete_search_item_height"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/margin_large"
local:MvxBind="Text Title" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/margin_large"
local:MvxBind="Text Category" />
</LinearLayout>
<TextView
android:id="#+id/fill_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textSize="#dimen/text_medium"
android:textColor="#color/black"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
local:MvxLang="Text fa-arrow-up"
local:MvxBind="Style ., Converter=String, ConverterParameter=fonts/fontawesome.ttf" />
</LinearLayout>
My click handler ItemClick SearchAutoCompleteItemClick works, but I need to handle the click event for when the user clicks on my fill_button separately. How can I achieve this?
I figured out how to do this using a custom MvxRecyclerView and custom MvxRecyclerAdapter:
public class TwoPieceMvxRecyclerView : MvxRecyclerView
{
private ICommand _itemClickPiece1;
private ICommand _itemClickPiece2;
public ICommand ItemClickPiece1
{
get { return _itemClickPiece1; }
set
{
if (ReferenceEquals(_itemClickPiece1, value))
{
return;
}
_itemClickPiece1 = value;
}
}
public ICommand ItemClickPiece2
{
get { return _itemClickPiece2; }
set
{
if (ReferenceEquals(_itemClickPiece2, value))
{
return;
}
_itemClickPiece2 = value;
}
}
public TwoPieceMvxRecyclerView(Context context, IAttributeSet attr) : base(context, attr)
{
}
public TwoPieceMvxRecyclerView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
}
public class TwoPieceMvxRecyclerAdapter : MvxRecyclerAdapter, IOnClickListener
{
public TwoPieceMvxRecyclerAdapter(IMvxAndroidBindingContext context) : base(context)
{
}
public TwoPieceMvxRecyclerView TwoPieceMvxRecyclerView { get; set; }
protected override Android.Views.View InflateViewForHolder(Android.Views.ViewGroup parent, int viewType, MvvmCross.Binding.Droid.BindingContext.IMvxAndroidBindingContext bindingContext)
{
var view = base.InflateViewForHolder(parent, viewType, bindingContext);
var clickablePiece1 = view.FindViewById<View>(Resource.Id.clickable_piece1);
var clickablePiece2 = view.FindViewById<View>(Resource.Id.clickable_piece2);
clickablePiece1.SetOnClickListener(this);
clickablePiece2.SetOnClickListener(this);
return view;
}
public void OnClick(View v)
{
var viewHolder = this.TwoPieceMvxRecyclerView.FindContainingViewHolder(v);
var item = GetItem(viewHolder.LayoutPosition); // What different is viewHolder.AdapterPosition? I tested it with 100 items and it's always the same, but I'm not sure if this will never break...
if (v.Id == Resource.Id.clickable_piece1)
{
TwoPieceMvxRecyclerView.ItemClickPiece1.Execute(item);
}
else if (v.Id == Resource.Id.clickable_piece2)
{
TwoPieceMvxRecyclerView.ItemClickPiece2.Execute(item);
}
}
}
And then in the containing activity (or fragment):
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.activity_search);
var r = FindViewById<TwoPieceMvxRecyclerView>(Resource.Id.my_twopiecemvxrecyclerview);
var adapter = new TwoPieceMvxRecyclerAdapter(((IMvxAndroidBindingContext)BindingContext));
adapter.TwoPieceMvxRecyclerView = r;
r.Adapter = adapter;
}
And use it like this:
<My.Namespace.TwoPieceMvxRecyclerView
android:id="#+id/my_twopiecemvxrecyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#null"
local:MvxItemTemplate="#layout/my_two_piece_layout"
local:MvxBind="ItemsSource MyItems; ItemClickPiece1 MyItemClick1; ItemClickPiece2 MyItemClick2" />
my_two_piece_layout.xml needs to have views with ids clickable_piece1 and clickable_piece2
You can add a second binding to the TextView. That will bind a Click event to a command you will define in the SearchAutoCompleteItem type (the command will be declared on the level of the individual item).
local:MvxBind="Click FillCommand; Style ., Converter=String, ConverterParameter=fonts/fontawesome.ttf"

Visibility Converters in Xamarin Android

I am creating a android application using MvvmCross, in which I have to show and hide some controls in listview depending upon the value. For that I have created a visibility converter in PCL like this
public class VisibilityValueConverter : MvxValueConverter<bool, MvxVisibility>
{
protected override MvxVisibility Convert(bool value, Type targetType, object parameter, CultureInfo culture)
{
return (value == true) ? MvxVisibility.Visible : MvxVisibility.Collapsed;
}
}
and I am using this value converter in my layout page like this
<?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="20dp"
local:MvxBind="Text QuestionText"
android:layout_marginTop="15dp" />
<RadioGroup
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/radioGroup1"
android:layout_marginTop="5dp">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/radioButton1"
local:MvxBind="Text OptionA" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text OptionB"
android:id="#+id/radioButton2" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text OptionC"
android:id="#+id/radioButton3" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Text OptionD"
android:id="#+id/radioButton4" />
</RadioGroup>
<EditText
android:layout_width="fill_parent"
android:layout_height="159.0dp"
android:textSize="20dp"
android:layout_marginTop="2dp"
local:MvxBind="Visibility TexboxVisible,Converter=Visibility" />
</LinearLayout>
But it's not working. It's not hitting the breakpoints in PCL value converter.
I have also tried to use MvxVisibility plugin but it's also not working.
I think I am doing something wrong. Can someone help and let me know how to use visibilty converters inside listview in android.
ViewModel
public class Question
{
public string Type { get; set; }
public bool RadioVisible { get; set; }
public bool TexboxVisible { get; set; }
}
private List<Question> _questionList;
public List<Question> QuestionList
{
get { return _questionList; }
set
{
_questionList = value;
RaisePropertyChanged(() => QuestionList);
}
}
private async void ShowQuestionsList(int assignmentId)
{
QuestionList = await _service.GetQuestionListByAssignmentAsync(assignmentId);
if (QuestionList != null)
{
foreach (Question q in QuestionList)
{
if (q.Type != null)
{
if (q.Type == "S")
{
q.RadioVisible = false;
q.TexboxVisible = true;
}
else if (q.Type == "O")
{
q.RadioVisible = true;
q.TexboxVisible = false;
}
}
}
}
}
My breakpoint in my Testconverter is fired as it should. My code:
public class TestMethodValueConverter : MvxValueConverter<bool, MvxVisibility>
{
protected override MvxVisibility Convert(bool value, Type targetType, object parameter, CultureInfo culture)
{
return value ? MvxVisibility.Visible : MvxVisibility.Collapsed;
}
}
And the View-Xaml:
local:MvxBind="Visibility MyBoolProperty, Converter=TestMethod"
But there is another problem. The android view elements can't change the visibility with the MvxVisibility enum. They need a Android.Vioews.ViewStates value.
So you need to add the converter in the Android project. Thats why we use the MvxVisibility-Plugin.
Edit
Your viewmodels should all inherit from MvxViewModel and the properties, which are used for binding need to implement the property-changed call RaisePropertyChanged(() => Property);. Otherwise nobody knows about changes. Thats the first point.
But the Converter should work at the first time without that (as far as I know). So I don't see anything other which can go wrong.. so try to create a simple clean project only with that problem and one single View-Element to reproduce what can go wrong..

Bind button click inside customlayout using mvvmcross and mvxlistview

i wanted to bind a button click event inside my customlayout, below is my customlayout
<?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="63dp">
<LinearLayout
android:orientation="horizontal"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:text="Accept"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/acceptBtnOnList"
android:background="#color/green_color"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold"
android:layout_weight="1"
android:textColor="#android:color/background_light"
local:MvxBind="Click AcceptCommand" />
</LinearLayout>
</RelativeLayout>
below is my ListView layout
<?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"
android:background="#android:color/background_light">
<Mvx.MvxListView
android:id="#+id/ListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#FFDAFF7F"
local:MvxBind="ItemsSource MyList; ItemClick ShowDetailCommand"
local:MvxItemTemplate="#layout/customlayout" />
</LinearLayout>
as you can see above i have called my customlayout inside the listview layout
Below is my ViewModelClass
public class ListViewModel : BaseViewModel
{
public IListService ListService { get; set; }
private MvxCommand _acceptCommand;
private ListAcceptedResult _accepted;
private MvxCommand _detailsCommand;
private ObservableCollection<MyCustomClass> _myList = new ObservableCollection<MyCustomClass>();
public ListViewModel(IListService listService)
{
ListService = listService;
}
public ObservableCollection<MyCustomClass> MyList
{
get { return _myList; }
set
{
_myList = value;
RaisePropertyChanged(() => MyList);
}
}
public ListAcceptedResult Accepted
{
get { return _accepted; }
set
{
_accepted = value;
RaisePropertyChanged(() => Accepted);
Update();
}
}
public ICommand AcceptCommand
{
get
{
IsLoading = true;
return
new MvxCommand<MyCustomClass>(
item =>
//On Success assigning the returned value from service to Accepted Property,
error => { IsLoading = false; ReportError(error.Message); }));
}
}
public async System.Threading.Tasks.Task Update()
{
//update logic
}
}
But i am not able to bind the AcceptCommand command to my button.
i am aware that this will not work because inside my customlayout view i do not get the AcceptCommand command as it is not a part of object MyCustomClass
please help me with some example.
Thanks in advance
As you know, data binding works by binding directly to the current ViewModel/Model. You're not able to access properties or commands of the parent view model, unless you provide a property to access the parent.
Anyway, one option is to create a value converter that you use in your binding. This converter will return a MvxCommand object that when executed will use MvxMessenger to publish a message.
The parent view model will subscribe to this message and then execute the command that you want.
I've created a sample based on Stuart Lodge's N=02 example.
https://github.com/kiliman/MvxCommandToMessage
EDIT: I modified the sample to use a generic MessageToCommandValueConverter. You can now pass in the message type in the binding. You still need specific message types though since MvxMessenger.Publish() is global to your app. See the code on GitHub for the changes.
Here's the value converter:
public class KittenAcceptedMessageValueConverter : MvxValueConverter<Kitten, ICommand>
{
protected override ICommand Convert(Kitten kitten, Type targetType, object parameter, CultureInfo culture)
{
return new MvxCommand(() =>
{
var messenger = Mvx.Resolve<IMvxMessenger>();
var message = new KittenAcceptedMessage(this, kitten);
messenger.Publish(message);
});
}
}
And here's how you bind to it in your layout. Use . to pass the current object to the converter.
<Mvx.MvxImageView
android:layout_width="75dp"
android:layout_height="75dp"
android:layout_margin="10dp"
local:MvxBind="ImageUrl ImageUrl; Click KittenAcceptedMessage(.)" />
And then finally in your ViewModel, you would subscribe to this message, and call your command:
_messenger.Subscribe<KittenAcceptedMessage>(message =>
{
KittenAcceptedCommand.Execute(message.Kitten);
});
private MvxCommand<Kitten> _kittenAcceptedCommand;
public ICommand KittenAcceptedCommand
{
get
{
_kittenAcceptedCommand = _kittenAcceptedCommand ?? new MvxCommand<Kitten>(kitten =>
{
var toast = Mvx.Resolve<IToastPlugin>();
toast.Show(string.Format("You accepted {0}", kitten.Name));
});
return _kittenAcceptedCommand;
}
}
Hope this helps.

Categories

Resources