浏览和等待
ImageBrowser程序演示了Image的另一个功能,它允许您浏览本书中某些示例所使用的库存照片。 正如您在下面的XAML文件中看到的那样,Image元素与Label和两个Button视图共享屏幕。 请注意,在Image上设置了PropertyChanged处理程序。 您在第11章“可绑定基础结构”中了解到,PropertyChanged处理程序由BindableObject实现,并在绑定属性更改值时触发。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ImageBrowser.ImageBrowserPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<StackLayout>
<Image x:Name="image"
VerticalOptions="CenterAndExpand"
PropertyChanged="OnImagePropertyChanged" />
<Label x:Name="filenameLabel"
HorizontalOptions="Center" />
<ActivityIndicator x:Name="activityIndicator" />
<StackLayout Orientation="Horizontal">
<Button x:Name="prevButton"
Text="Previous"
IsEnabled="false"
HorizontalOptions="CenterAndExpand"
Clicked="OnPreviousButtonClicked" />
<Button x:Name="nextButton"
Text="Next"
IsEnabled="false"
HorizontalOptions="CenterAndExpand"
Clicked="OnNextButtonClicked" />
</StackLayout>
</StackLayout>
</ContentPage>
此页面上还有一个ActivityIndicator。 当程序等待长操作完成(例如下载位图)但通常无法提供有关操作进度的任何信息时,通常会使用此元素。 如果您的程序知道操作的完成部分,则可以使用ProgressBar。 (ProgressBar将在下一章演示。)
ActivityIndicator有一个名为IsRunning的布尔属性。通常,该财产是
false,ActivityIndicator不可见。将该属性设置为true可使ActivityIn?dicator可见。所有这三个平台都实现了一个动画视觉,表明该程序正在运行,但在每个平台上看起来都有点不同。在iOS上它是一个旋转轮,在Android上它是一个旋转的部分圆圈。在Windows设备上,一系列点在屏幕上移动。
为了提供对库存图像的浏览访问,ImageBrowser需要下载包含所有文件名列表的JSON文件。多年来,各种版本的.NET引入了几个能够通过Web下载对象的类。但是,并非所有这些都可用于可移植类库中的.NET版本,该类库具有与Xamarin.Forms兼容的配置文件。可用的类是WebRequest及其后代类HttpWebRequest。
WebRequest.Create方法基于URI返回WebRequest方法。 (返回值实际上是一个HttpWebRequest对象。)BeginGetResponse方法需要一个回调函数,当引用URI的Stream可用于访问时,该函数被调用。通过调用EndGetResponse和GetResponseStream可以访问Stream。
一旦程序在以下代码中访问Stream对象,它就会使用DataCon?tractJsonSerializer类以及在ImageBrowserPage类顶部附近定义的嵌入式ImageList类,将JSON文件转换为ImageList对象:
public partial class ImageBrowserPage : ContentPage
{
[DataContract]
class ImageList
{
[DataMember(Name = "photos")]
public List<string> Photos = null;
}
WebRequest request;
ImageList imageList;
int imageListIndex = 0;
public ImageBrowserPage()
{
InitializeComponent();
// Get list of stock photos.
Uri uri = new Uri("https://developer.xamarin.com/demo/stock.json");
request = WebRequest.Create(uri);
request.BeginGetResponse(WebRequestCallback, null);
}
void WebRequestCallback(IAsyncResult result)
{
Device.BeginInvokeOnMainThread(() =>
{
try
{
Stream stream = request.EndGetResponse(result).GetResponseStream();
// Deserialize the JSON into imageList;
var jsonSerializer = new DataContractJsonSerializer(typeof(ImageList));
imageList = (ImageList)jsonSerializer.ReadObject(stream);
if (imageList.Photos.Count > 0)
FetchPhoto();
}
catch (Exception exc)
{
filenameLabel.Text = exc.Message;
}
});
}
void OnPreviousButtonClicked(object sender, EventArgs args)
{
imageListIndex--;
FetchPhoto();
}
void OnNextButtonClicked(object sender, EventArgs args)
{
imageListIndex++;
FetchPhoto();
}
void FetchPhoto()
{
// Prepare for new image.
image.Source = null;
string url = imageList.Photos[imageListIndex];
// Set the filename.
filenameLabel.Text = url.Substring(url.LastIndexOf('/') + 1);
// Create the UriImageSource.
UriImageSource imageSource = new UriImageSource
{
Uri = new Uri(url + "?Width=1080"),
CacheValidity = TimeSpan.FromDays(30)
};
// Set the Image source.
image.Source = imageSource;
// Enable or disable buttons.
prevButton.IsEnabled = imageListIndex > 0;
nextButton.IsEnabled = imageListIndex < imageList.Photos.Count - 1;
}
void OnImagePropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "IsLoading")
{
activityIndicator.IsRunning = ((Image)sender).IsLoading;
}
}
}
WebRequestCallback方法的整个主体都包含在lambda函数中,该函数是Device.BeginInvokeOnMainThread方法的参数。 WebRequest下载由辅助执行线程中的URI引用的文件。这可以确保操作不会阻止正在处理用户界面的程序的主线程。回调方法也在此辅助线程中执行。但是,可以访问Xamarin.Forms应用程序中的用户界面对象
仅来自主线程。
Device.BeginInvokeOnMainThread方法的目的是解决此问题。此方法的参数排队等待在程序的主线程中运行,并可以安全地访问用户界面对象。
当您单击这两个按钮时,对FetchPhoto的调用使用UriImageSource来下载新位图。这可能需要一秒钟左右。 Image类定义一个名为IsLoading的Boolean属性,当Image处于加载(或下载)位图的过程中时,该属性为true。 IsLoading由可绑定属性IsLoadingProperty支持。这也意味着每当IsLoading更改值时,都会触发PropertyChanged事件。该程序使用PropertyChanged事件处理程序 - 类的最底部的OnImagePropertyChanged方法 - 将ActivityIndicator的IsRunning prop.erty设置为与Image的IsLoading属性相同的值。
您将在第16章“数据绑定”中看到,您的应用程序如何链接IsLoading和IsRunning等属性,以便它们在没有任何显式事件处理程序的情况下保持相同的值。
这是ImageBrowser的实际应用:
某些图像设置了EXIF方向标志,如果特定平台忽略该标志,则图像会侧向显示。
如果以横向模式运行此程序,您将发现按钮消失。 这个程序的更好的布局选项是Grid,第17章对此进行了演示。