gridlayout android layout xamarin android-recyclerview android-gridlayout

android - GridLayoutManager: ¿cómo ajustar automáticamente las columnas?



gridlayout android (4)

El constructor de GridLayoutManager tiene un argumento spanCount que es

El número de columnas en la grilla

Puede inicializar el administrador con un valor de recurso entero y proporcionar diferentes valores para diferentes pantallas (es decir, values-w600 , values-large , values-land ).

Tengo un RecyclerView con un GridLayoutManager que muestra vistas de tarjeta. Quiero que las tarjetas se reordenen de acuerdo con el tamaño de la pantalla (la aplicación Google Play hace este tipo de cosas con sus tarjetas de aplicaciones). Aquí hay un ejemplo:

Así es como se ve mi aplicación en este momento:

Como puede ver, las tarjetas simplemente se estiran y no se ajustan al espacio vacío creado a partir del cambio de orientación. Entonces, ¿cómo puedo hacer esto?

Código:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Json; using System.Threading; using System.Threading.Tasks; using Android.Media; using Android.App; using Android.Support.V4.App; using Android.Support.V4.Content.Res; using Android.Support.V4.Widget; using Android.Support.V7.Widget; using Android.Content; using Android.OS; using Android.Runtime; using Android.Util; using Android.Views; using Android.Widget; using Android.Net; using Android.Views.Animations; using Android.Graphics; using Android.Graphics.Drawables; using Newtonsoft.Json; using *******.Adapters; using *******.Models; namespace *******.Fragments { public class Dashboard : GridLayoutBase { private ISharedPreferences pref; private SessionManager session; private string cookie; private DeviceModel deviceModel; private RecyclerView recyclerView; private RecyclerView.Adapter adapter; // private RecyclerView.LayoutManager layoutManager; private GridLayoutManager gridLayoutManager; private List<ItemData> itemData; private Bitmap lastPhotoBitmap; private Drawable lastPhotoDrawable; private static Activity activity; private ProgressDialog progressDialog; private TextView noData; private const string URL_DASHBOARD = "http://192.168.1.101/appapi/getdashboard"; private const string URL_DATA = "http://192.168.1.101/appapi/getdata"; public override void OnCreate(Bundle bundle) { base.OnCreate(bundle); activity = Activity; session = new SessionManager(); pref = activity.GetSharedPreferences("UserSession", FileCreationMode.Private); cookie = pref.GetString("PHPSESSID", string.Empty); } public async override void OnStart() { base.OnStart(); progressDialog = ProgressDialog.Show(activity, String.Empty, GetString(Resource.String.loading_text)); progressDialog.Window.ClearFlags(WindowManagerFlags.DimBehind); await GetDevicesInfo(); if (deviceModel.Error == "true" && deviceModel.ErrorType == "noSensors") { recyclerView.Visibility = ViewStates.Gone; noData.Visibility = ViewStates.Visible; progressDialog.Hide(); return; } else { recyclerView.Visibility = ViewStates.Visible; noData.Visibility = ViewStates.Gone; await PopulateSensorStates(); } // DisplayLastPhoto(); adapter = new ViewAdapter(itemData); new System.Threading.Thread(new System.Threading.ThreadStart(() => { activity.RunOnUiThread(() => { recyclerView.SetAdapter(adapter); }); })).Start(); progressDialog.Hide(); } public async Task GetDevicesInfo() { var jsonFetcher = new JsonFetcher(); JsonValue jsonDashboard = await jsonFetcher.FetchDataWithCookieAsync(URL_DASHBOARD, cookie); deviceModel = new DeviceModel(); deviceModel = JsonConvert.DeserializeObject<DeviceModel>(jsonDashboard); } // Shows sensor states public async Task PopulateSensorStates() { itemData = new List<ItemData>(); string lastValue = String.Empty; foreach (var sensor in this.deviceModel.Sensors) { var sensorImage = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.smoke_red, null); switch (sensor.Type) { case "2": var jsonFetcher = new JsonFetcher(); JsonValue jsonData = await jsonFetcher.FetchSensorDataAsync(URL_DATA, sensor.Id, "DESC", "1", cookie); var deviceModel = new DeviceModel(); deviceModel = JsonConvert.DeserializeObject<DeviceModel>(jsonData); lastValue = deviceModel.SensorData.Last().Value; break; case "4": await RenderLastCameraPhoto(); sensorImage = new BitmapDrawable(Resources, lastPhotoBitmap); break; } itemData.Add(new ItemData() { id = sensor.Id, value = lastValue, type = sensor.Type, image = sensorImage, title = sensor.Name.First().ToString().ToUpper() + sensor.Name.Substring(1).ToLower(), }); } } // Shows the last camera photo public async Task RenderLastCameraPhoto() { if (deviceModel.Error == "true" && deviceModel.ErrorType == "noPhoto") { //TODO: Show a "No photo" picture } else { string url = deviceModel.LastPhotoLink; lastPhotoBitmap = await new ImageDownloader().GetImageBitmapFromUrlAsync(url, activity, 300, 300); } } public async void UpdateData(bool isSwipeRefresh) { await GetDevicesInfo(); if (deviceModel.Error == "true" && deviceModel.ErrorType == "noSensors") { recyclerView.Visibility = ViewStates.Gone; noData.Visibility = ViewStates.Visible; return; } else { recyclerView.Visibility = ViewStates.Visible; noData.Visibility = ViewStates.Gone; await PopulateSensorStates(); } adapter = new ViewAdapter(itemData); new System.Threading.Thread(new System.Threading.ThreadStart(() => { activity.RunOnUiThread(() => { recyclerView.SetAdapter(adapter); }); })).Start(); adapter.NotifyDataSetChanged(); } public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.Inflate(Resource.Layout.Dashboard, container, false); noData = view.FindViewById<TextView>(Resource.Id.no_data_title); SwipeRefreshLayout swipeRefreshLayout = view.FindViewById<SwipeRefreshLayout>(Resource.Id.swipe_container); // swipeRefreshLayout.SetColorSchemeResources(Color.LightBlue, Color.LightGreen, Color.Orange, Color.Red); // On refresh button press/swipe, updates the recycler view with new data swipeRefreshLayout.Refresh += (sender, e) => { UpdateData(true); swipeRefreshLayout.Refreshing = false; }; var gridLayoutManager = new GridLayoutManager(activity, 2); recyclerView = view.FindViewById<RecyclerView>(Resource.Id.dashboard_recycler_view); recyclerView.HasFixedSize = true; recyclerView.SetLayoutManager(gridLayoutManager); recyclerView.SetItemAnimator(new DefaultItemAnimator()); recyclerView.AddItemDecoration(new SpaceItemDecoration(15)); return view; } public class ViewAdapter : RecyclerView.Adapter { private List<ItemData> itemData; public string sensorId; public string sensorType; private ImageView imageId; private TextView sensorValue; private TextView sensorTitle; public ViewAdapter(List<ItemData> itemData) { this.itemData = itemData; } public class ItemView : RecyclerView.ViewHolder { public View mainView { get; set; } public string id { get; set; } public string type { get; set; } public ImageView image { get; set; } // public TextView value { get; set; } public TextView title { get; set; } public ItemView(View view) : base(view) { mainView = view; } } public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) { View itemLayoutView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.DashboardItems, null); CardView cardView = itemLayoutView.FindViewById<CardView>(Resource.Id.dashboard_card_view); imageId = itemLayoutView.FindViewById<ImageView>(Resource.Id.sensor_image); // sensorValue = itemLayoutView.FindViewById<TextView>(Resource.Id.sensor_value); sensorTitle = itemLayoutView.FindViewById<TextView>(Resource.Id.sensor_title); var viewHolder = new ItemView(itemLayoutView) { id = sensorId, type = sensorType, image = imageId, // value = sensorValue, title = sensorTitle }; return viewHolder; } public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { ItemView itemHolder = viewHolder as ItemView; itemHolder.image.SetImageDrawable(itemData[position].image); if (itemData[position].type == "2") { // Temperature itemHolder.title.Text = itemData[position].title + ": " + itemData[position].value; } else { itemHolder.title.Text = itemData[position].title; } var bundle = new Bundle(); var dualColumnList = new DualColumnList(); var gallery = new Gallery(); EventHandler clickUpdateViewEvent = ((sender, e) => { bundle.PutString("id", itemData[position].id); gallery.Arguments = bundle; dualColumnList.Arguments = bundle; if (itemData[position].type == "4") { // Camera ((FragmentActivity)activity).ShowFragment(gallery, itemData[position].title, itemData[position].type, true); } else { ((FragmentActivity)activity).ShowFragment(dualColumnList, itemData[position].title, itemData[position].type, true); } }); itemHolder.image.Click += clickUpdateViewEvent; // itemHolder.value.Click += clickUpdateViewEvent; itemHolder.title.Click += clickUpdateViewEvent; } public override int ItemCount { get { return itemData.Count; } } } public class ItemData { public string id { get; set; } public string type { get; set; } public Drawable image { get; set; } public string value { get; set; } public string title { get; set; } } } }

Diseño de Fragmento:

<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/swipe_container" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:weightSum="1"> <RelativeLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="0.9" android:scrollbars="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/dashboard_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:text="@string/no_data_text" android:id="@+id/no_data_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="30sp" android:gravity="center" android:layout_centerInParent="true" /> </RelativeLayout> </LinearLayout> </android.support.v4.widget.SwipeRefreshLayout>

Diseño de elementos de fragmento:

<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/dashboard_card_view" android:layout_width="wrap_content" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" android:foreground="?android:attr/selectableItemBackground"> <ImageView android:id="@+id/sensor_image" android:layout_width="120dp" android:layout_height="120dp" android:paddingTop="5dp" android:layout_alignParentTop="true" /> <!-- <TextView android:id="@+id/sensor_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30sp" android:layout_below="@id/sensor_image" android:gravity="center" />--> <TextView android:id="@+id/sensor_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="23sp" android:layout_below="@id/sensor_image" android:gravity="center" android:layout_alignParentBottom="true" /> </LinearLayout> </android.support.v7.widget.CardView>


El constructor new GridLayoutManager(activity, 2) trata sobre GridLayoutManager(Context context, int spanCount) donde spanCount es el número de columnas en la grilla.

La mejor manera es verificar el ancho de la ventana / vista y la base en este ancho, cuente cuántos espacios desea mostrar.


Intenté @Riten responder y funcionó funtastic !! Pero no estaba contento con el "180" codificado, así que modifiqué esto:

public class ColumnQty { private int width, height, remaining; private DisplayMetrics displayMetrics; public ColumnQty(Context context, int viewId) { View view = View.inflate(context, viewId, null); view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); width = view.getMeasuredWidth(); height = view.getMeasuredHeight(); displayMetrics = context.getResources().getDisplayMetrics(); } public int calculateNoOfColumns() { int numberOfColumns = displayMetrics.widthPixels / width; remaining = displayMetrics.widthPixels - (numberOfColumns * width); // System.out.println("/nRemaining/t" + remaining + "/nNumber Of Columns/t" + numberOfColumns); if (remaining / (2 * numberOfColumns) < 15) { numberOfColumns--; remaining = displayMetrics.widthPixels - (numberOfColumns * width); } return numberOfColumns; } public int calculateSpacing() { int numberOfColumns = calculateNoOfColumns(); // System.out.println("/nNumber Of Columns/t"+ numberOfColumns+"/nRemaining Space/t"+remaining+"/nSpacing/t"+remaining/(2*numberOfColumns)+"/nWidth/t"+width+"/nHeight/t"+height+"/nDisplay DPI/t"+displayMetrics.densityDpi+"/nDisplay Metrics Width/t"+displayMetrics.widthPixels); return remaining / (2 * numberOfColumns); } }

Donde "viewId" es el diseño que se utilizará como vistas en el RecyclerView como en R.layout.item_for_recycler

Aunque no estoy seguro del impacto de View.inflate ya que solo lo uso para obtener el ancho, nada más.

Luego en el GridLayoutManager hago:

GridLayoutManager gridLayoutManager = new GridLayoutManager(this, Utility.columnQty(this, R.layout.item_for_recycler));

ACTUALIZACIÓN : agregué más líneas al código cuando lo uso para obtener un espaciado de ancho mínimo en la cuadrícula. Calcular espaciado:

recyclerPatternsView.addItemDecoration(new GridSpacing(columnQty.calculateSpacing()));


Puede calcular el número de columnas y cargar la imagen según lo calculado. Defina una función estática para calcular como:

public class Utility { public static int calculateNoOfColumns(Context context) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); float dpWidth = displayMetrics.widthPixels / displayMetrics.density; int noOfColumns = (int) (dpWidth / 180); return noOfColumns; } }

Y luego al usarlo en la actividad o fragmento puedes hacer esto:

int mNoOfColumns = Utility.calculateNoOfColumns(getApplicationContext()); ............ mGridLayoutManager = new GridLayoutManager(this, mNoOfColumns);