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.
I have two Samsung tablets (Samsung SM-T580 Android 7.0 and SM-T280 Android 5.1).
I have a list view with a custom adapter. It works fine on the SM-T580 but it crashes on the first iteration of method:
public override View GetView(int position, View convertView, ViewGroup parent)
on the SM-T280 tablet. I do not understand how it works perfect on the SM-T580 and crashes on the SM-T280. The error message when it crashes is:
The application is in break mode.
Your app has entered a break state, but there is no code to show because all threads were executing external code (typically system or framework code.)
I have the compiler set to:
Compile using Android version: (Target Framework)
Android 5.1 (Lollipop)
I am using Visual Studio 2017 with Xamarin.
The following is my code.
The layout code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TableLayout
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="2"
android:id="#+id/tableLayout1">
<TableRow
android:id="#+id/tableRow1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_weight="1">
<TextView
android:id="#+id/CompanyNameLabel"
android:text=""
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_column="1"
android:layout_gravity="left"
android:textStyle="bold"
android:textSize="16sp"
android:padding="8dip" />
<TextView
android:id="#+id/CompanySalesLabel"
android:text=""
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_column="2"
android:layout_gravity="right"
android:padding="8dip" />
</TableRow>
</TableLayout>
<TextView
android:id="#+id/CompanyAddressLabel"
android:text=""
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="20dip" />
<TextView
android:id="#+id/CompanyCityStateZipLabel"
android:text=""
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="20dip" />
</LinearLayout>
The view holder code:
public class MyMainCompanyRowHolder : Java.Lang.Object
{
public TextView CompanyName { get; set; }
public TextView CompanyAddress { get; set; }
public TextView CompanySales { get; set; }
public TextView CompanyCityStateZipLabel { get; set; }
public static T Cast<T>(Java.Lang.Object obj) where T : MyMainCompanyRowHolder
{
var propInfo = obj.GetType().GetProperty("Instance");
return propInfo == null ? null : propInfo.GetValue(obj, null) as T;
}
}
The custom adapter:
public class ListAdapter : BaseAdapter<Company>
{
List<Company> company;
Activity context;
public ListAdapter(Activity currentContext, List<Company> companyList) : base()
{
this.company = companyList;
this.context = currentContext;
}
public override Company this[int position]
{
get { return company.ToArray()[position]; }
}
public override int Count
{
get { return company.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
Models.ViewHolder.MyMainCompanyRowHolder myViewHolder = null;
View view = convertView;
if (view != null)
myViewHolder = Models.ViewHolder.MyMainCompanyRowHolder.Cast<Models.ViewHolder.MyMainCompanyRowHolder>(view.Tag);
if (myViewHolder == null)
{
myViewHolder = new Models.ViewHolder.MyMainCompanyRowHolder();
//view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
view = context.LayoutInflater.Inflate(Resource.Layout.MainCompanyListRow, null);
myViewHolder.CompanyName = view.FindViewById<TextView>(Resource.Id.CompanyNameLabel);
myViewHolder.CompanyAddress = view.FindViewById<TextView>(Resource.Id.CompanyAddressLabel);
myViewHolder.CompanySales = view.FindViewById<TextView>(Resource.Id.CompanySalesLabel);
myViewHolder.CompanyCityStateZipLabel = view.FindViewById<TextView>(Resource.Id.CompanyCityStateZipLabel);
view.Tag = myViewHolder;
}
myViewHolder.CompanyName.Text = company.ToArray()[position].CompanyName;
myViewHolder.CompanyAddress.Text = company.ToArray()[position].Address;
myViewHolder.CompanySales.Text = company.ToArray()[position].ActualSales.ToString("C0");
myViewHolder.CompanyCityStateZipLabel.Text = company.ToArray()[position].City + ", " + company.ToArray()[position].State + " " + company.ToArray()[position].Zip;
return view;
}
}
The call in the Activity module:
myCompany = myDBF.getCompany("");
var listView = FindViewById<ListView>(Resource.Id.OrderMethodListView);
listView.Adapter = new Models.Adapters.ListAdapter(this, myCompany);
Thanks! I figured it out. Android 5.1 does not like:
android:layout_weight="1"
android:layout_column="1"
android:layout_gravity="left"
Once I removed those lines from my Layout Code, the app works correctly.
I found many similar questions on StackOverflow, but can't seem to figure out this issue.
I'm trying to bind an ObservableCollection to a ListView so that when the contents of the collection change the ListView will automatically update. Unfortunately, when elements are added to the collection (via button click), the ListView isn't being updated. How can I make the ListView reflect the current contents of the collection?
Note: It seems that if I force layout with listView.RequestLayout(); the listView will contain the correct number of items. However, it won't necessarily contain the right items. For example, if I delete the first item and add a new one at the end, forcing a layout has no effect. If I force the layout after deleting the first item, then again after adding one at the end, the contents are correct.
Activity code
public class MainActivity : Activity
{
int count = 1;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
ObservableCollection<UserTask> allTasksCollection = new ObservableCollection<UserTask>();
while(count < 6)
{
allTasksCollection.Add(new UserTask("Task number " + count));
count++;
}
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Bind listview to all tasks
ListView listView = FindViewById<ListView>(Resource.Id.allTasksListView);
UserTaskListAdapter adapter = new UserTaskListAdapter(this, allTasksCollection);
listView.Adapter = adapter;
// Button click should add a new task and remove the first task.
Button button = FindViewById<Button>(Resource.Id.myButton);
button.Click += delegate {
allTasksCollection.Add(new UserTask("Task number " + count));
button.Text = string.Format("{0} tasks!", count++);
};
}
}
Adapter
public class UserTaskListAdapter : BaseAdapter<UserTask>
{
Activity context;
ObservableCollection<UserTask> list;
public UserTaskListAdapter(Activity _context, ObservableCollection<UserTask> _list)
: base()
{
this.context = _context;
this.list = _list;
}
public override int Count
{
get { return list.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override UserTask this[int index]
{
get { return list[index]; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
view = context.LayoutInflater.Inflate(Resource.Layout.UserTaskRowItem, parent, false);
UserTask item = this[position];
view.FindViewById<TextView>(Resource.Id.Title).Text = item.Name;
return view;
}
}
Main XAML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/rootLayout">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/allTasksContainer">
<Button
android:id="#+id/myButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/hello" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/allTasksListView" />
</LinearLayout>
</LinearLayout>
ListItem XAML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/linearLayoutHorizontal">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/CheckboxContainer">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/checkboxSelect" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/TextContainer">
<TextView
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/Title" />
<TextView
android:text="Small Text"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/DueDate" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
Try to call NotifyDataSetChanged() on your adapter after updating its items, AFAIK ListView in Android doesn't observe the changes on your ObservableCollection:
Try this code:
button.Click += delegate {
allTasksCollection.Add(new UserTask("Task number " + count));
button.Text = string.Format("{0} tasks!", count++);
adapter.NotifyDataSetChanged();
};
You can also try to add a litener to your ObservableCollection, in your adapter, change the constructor to this:
this.list.CollectionChanged += (sender,args) => { NotifyDataSetChanged(); };
Take a look at the INotifyPropertyChanged interface. It will warn the listview that variables within an object within the listview have changed.
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/
Edit: misread your question. My bad.
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);
}
}
I have a ListView which generates its ListItems dynamically via a CustomAdapter in the view. The ListView holds different InputControls, say EditTexts, DatePickers, MvxSpinners and so on.
When a User selects an Item from an MvxSpinner the Focus is set to the first focusable InputControl beeing displayed on the screen.
How can I make sure that after the user selects an item from the Spinner the Spinner keeps or gets the focus?
This is the xml from the SpinnerListViewItem:
<?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="wrap_content"
android:gravity="left"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text"
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="false"
local:MvxBind="Text Beschriftung"
style="#style/CardLabelMediumStyle" />
<Mvx.MvxSpinner
local:MvxBind="ItemsSource ItemsSource;SelectedItem SelectedItem"
local:MvxItemTemplate="#layout/item_text_spinner"
local:MvxDropDownItemTemplate="#layout/item_text_spinner_dropdown"
android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
code of the custom adapter:
public class PruefpunkteAdapter : MvxAdapter
{
IDictionary<Type, TypeDescriptor> types = new Dictionary<Type, TypeDescriptor>();
public PruefpunkteAdapter(Context context, IMvxAndroidBindingContext bindingContext)
: base(context, bindingContext)
{
types.Add(typeof(FormularpunktTextBox), new TypeDescriptor() { TempldateId = Resource.Layout.ListItem_PruefberichtFormularpunktTextBox, ViewType = 5 });
types.Add(typeof(FormularpunktSpinner), new TypeDescriptor() { TempldateId = Resource.Layout.ListItem_PruefberichtFormularpunktSpinner, ViewType = 15 });
/* about 15 more types added */
}
public override int GetItemViewType(int position)
{
var item = GetRawItem(position);
var descriptor = types[item.GetType()];
return descriptor.ViewType;
}
public override int ViewTypeCount
{
get { return types.Count; }
}
protected override View GetBindableView(View convertView, object source, int templateId)
{
return base.GetBindableView(convertView, source, types[source.GetType()].TempldateId);
}
private class TypeDescriptor
{
public int ViewType;
public int TempldateId;
}
}
In your custom adapter you could add code like this for the Spinner:
spinner.setFocusable(true);
spinner.setFocusableInTouchMode(true);
spinner.FocusChangeListener += (s, e) =>
{
bool hasFocus = e.HasFocus;
if (hasFocus) {
YourActivity.this.spinner.performClick();
}
}
Might be a little off on the "YourActivity.this.spinner" part of this without seeing how your code currently looks but basically just need to run performClick(); on your spinner.