我有一个带有显示卡片 View 的 GridLayoutManager 的 RecyclerView。我希望卡片根据屏幕大小重新排列(Google Play 应用程序使用它的应用程序卡片做这种事情)。这是一个例子:
这是我的应用目前的外观:
如您所见,卡片只是拉伸(stretch),不适合因方向更改而产生的空白空间。那么我该怎么做呢?
代码:
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; }
}
}
}
fragment 布局:
<?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>
fragment 项目布局:
<?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>
最佳答案
您可以计算可用的列数,给定所需的列宽,并按计算加载图像。定义一个静态函数来计算:
public class Utility {
public static int calculateNoOfColumns(Context context, float columnWidthDp) { // For example columnWidthdp=180
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float screenWidthDp = displayMetrics.widthPixels / displayMetrics.density;
int noOfColumns = (int) (screenWidthDp / columnWidthDp + 0.5); // +0.5 for correct rounding to int.
return noOfColumns;
}
}
然后在 Activity 或 fragment 中使用它时,您可以这样做:
int mNoOfColumns = Utility.calculateNoOfColumns(getApplicationContext());
............
mGridLayoutManager = new GridLayoutManager(this, mNoOfColumns);
关于android - GridLayoutManager - 如何自动调整列?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33575731/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为