使用图标的Xamarin.Forms列表应用程序(仅限Android)

4

我有一个Xamarin Forms项目,想要列出已安装应用程序的列表,并显示图标。

目前我只有标签,但是没有图像。

我使用依赖服务来获取应用程序列表:

public IEnumerable<IAppListServiceApplication> GetApplications()
        {
            var apps = Android.App.Application.Context.PackageManager.GetInstalledApplications(PackageInfoFlags.MatchAll);
            foreach (var app in apps)
            {
                var label = app.LoadLabel(Android.App.Application.Context.PackageManager);
                if (!label.ToLower().StartsWith("com."))
                    yield return new AppListServiceApplication
                    {
                        Label = label,
                        PackageName = app.PackageName,
                        AppIcon = GetAppIcon(app)
                    };
            }
        }

但是我无法将Android Drawable对象转换为Xamarin Forms项目中的ImageSource。目前我尝试的方法如下:

public ImageSource GetAppIcon(ApplicationInfo app)
        {
            try
            {
                var drawable = app.LoadIcon(Android.App.Application.Context.PackageManager);

                //return ImageSource.FromStream(() => new MemoryStream(bytes));
                Bitmap icon = drawableToBitmap(drawable);

                return ImageSource.FromStream(() => MemoryStreamFromBitmap(icon));
            }
            catch (Exception ex)
            {
                var message = ex.ToString();
                return null;
            }


        }
        MemoryStream MemoryStreamFromBitmap(Bitmap bmp)
        {
            MemoryStream stream = new MemoryStream();
            bmp.Compress(Bitmap.CompressFormat.Png, 0, stream);
            return stream;
        }
        Bitmap drawableToBitmap(Drawable drawable)
        {
            Bitmap bitmap = null;

            if (drawable is BitmapDrawable) {
                BitmapDrawable bitmapDrawable = (BitmapDrawable)drawable;
                if (bitmapDrawable.Bitmap != null)
                {
                    return bitmapDrawable.Bitmap;
                }
            }
            if (drawable is AdaptiveIconDrawable)
            {
                AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable)drawable;
                if (!(adaptiveIconDrawable.IntrinsicWidth <= 0 || adaptiveIconDrawable.IntrinsicHeight <= 0))
                {
                    bitmap = Bitmap.CreateBitmap(drawable.IntrinsicWidth, drawable.IntrinsicHeight, Bitmap.Config.Argb8888);
                    Canvas c = new Canvas(bitmap);
                    drawable.SetBounds(0, 0, c.Width, c.Height);
                    drawable.Draw(c);
                    return bitmap;

                }
            }

            if (drawable.IntrinsicWidth <= 0 || drawable.IntrinsicHeight <= 0)
            {
                bitmap = Bitmap.CreateBitmap(1, 1, Bitmap.Config.Argb8888); // Single color bitmap will be created of 1x1 pixel
            }
            else
            {
                bitmap = Bitmap.CreateBitmap(drawable.IntrinsicWidth, drawable.IntrinsicHeight, Bitmap.Config.Argb8888);
            }

            Canvas canvas = new Canvas(bitmap);
            drawable.SetBounds(0, 0, canvas.Width, canvas.Height);
            drawable.Draw(canvas);
            return bitmap;
        }

如您所见,我是一个新手,正在学习Xamarin和Android ^^。 我在Google上没有找到如何将Android中的Drawable传递到Xamarin Forms以显示它的方法。

如果您能指导我正确的方向来了解如何实现它,我将不胜感激。

谢谢

编辑:添加输出错误

我在输出窗口中看到以下错误:

ImageLoaderSourceHandler: Image data was invalid: Xamarin.Forms.StreamImageSource
11-05 04:31:19.287 D/skia    (10270): --- SkAndroidCodec::NewFromStream returned null

**编辑:** 添加项目

这是GitHub上的问题: https://github.com/werddomain/Xamarin-Android-Laucher/tree/master/Forms/AndroidCarLaucher


我创建了一个简单的Xamarin表单应用程序并添加了您的代码。但是我在AppListServiceApplication中遇到了困难。因此,如果您可以发布完整的代码或至少与服务相关的代码,那么解决问题会更容易些。 - nevermore
谢谢 @jackHua,我已经将项目推送到Github上了。我正在测试图片,所以现在可能处于另一种状态。我会在周末再次处理它。 - Benoit
1个回答

6

创建一个新的ImageSource,加载原始Drawable作为它的图标比转换为位图、字节数组等方式更快速、更节省内存。此外,在ListView等重复使用单元格的情况下,这种方法可以正确地工作...

ImageSource实现(存在于.NetStd/Forms库中):

[TypeConverter(typeof(PackageNameSourceConverter))]
public sealed class PackageNameSource : ImageSource
{
    public static readonly BindableProperty PackageNameProperty = BindableProperty.Create(nameof(PackageName), typeof(string), typeof(PackageNameSource), default(string));

    public static ImageSource FromPackageName(string packageName)
    {
        return new PackageNameSource { PackageName = packageName };
    }

    public string PackageName
    {
        get { return (string)GetValue(PackageNameProperty); }
        set { SetValue(PackageNameProperty, value); }
    }

    public override Task<bool> Cancel()
    {
        return Task.FromResult(false);
    }

    public override string ToString()
    {
        return $"PackageName: {PackageName}";
    }

    public static implicit operator PackageNameSource(string packageName)
    {
        return (PackageNameSource)FromPackageName(packageName);
    }

    public static implicit operator string(PackageNameSource packageNameSource)
    {
        return packageNameSource != null ? packageNameSource.PackageName : null;
    }

    protected override void OnPropertyChanged(string propertyName = null)
    {
        if (propertyName == PackageNameProperty.PropertyName)
            OnSourceChanged();
        base.OnPropertyChanged(propertyName);
    }
}

TypeConverter实现(存在于.NetStd / Forms库中):

[TypeConversion(typeof(PackageNameSource))]
public sealed class PackageNameSourceConverter : TypeConverter
{
    public override object ConvertFromInvariantString(string value)
    {
        if (value != null)
            return PackageNameSource.FromPackageName(value);

        throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(PackageNameSource)));
    }
}

IImageSourceHandler, IImageViewHandler的实现(存在于Xamarin.Android项目中):

public class PackageNameSourceHandler : IImageSourceHandler, IImageViewHandler
{
    public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
    {
        var packageName = ((PackageNameSource)imagesource).PackageName;
        using (var pm = Application.Context.PackageManager)
        using (var info = pm.GetApplicationInfo(packageName, PackageInfoFlags.MetaData))
        using (var drawable = info.LoadIcon(pm))
        {
            Bitmap bitmap = null;
            await Task.Run(() =>
            {
                bitmap = Bitmap.CreateBitmap(drawable.IntrinsicWidth, drawable.IntrinsicHeight, Bitmap.Config.Argb8888);
                using (var canvas = new Canvas(bitmap))
                {
                    drawable.SetBounds(0, 0, canvas.Width, canvas.Height);
                    drawable.Draw(canvas);
                }
            });
            return bitmap;
        }
    }

    public Task LoadImageAsync(ImageSource imagesource, ImageView imageView, CancellationToken cancellationToken = default(CancellationToken))
    {
        var packageName = ((PackageNameSource)imagesource).PackageName;
        using (var pm = Application.Context.PackageManager)
        {
            var info = pm.GetApplicationInfo(packageName, PackageInfoFlags.MetaData);
            imageView.SetImageDrawable(info.LoadIcon(pm));
        }
        return Task.FromResult(true);
    }
}

注意:在汇编级别上注册此内容:
[assembly: ExportImageSourceHandler(typeof(PackageNameSource), typeof(PackageNameSourceHandler))]

现在在您的依赖服务中返回一个包含至少PackageName的Android应用程序列表,类似于一个 IList<Package>
public class Package
{
    public string Name { get; set; }
    public string PackageName { get; set; }
}

Xaml示例:

现在,您可以使用PackageNameSourceIList<Package>绑定到ListView自定义单元格:

<Image WidthRequest="60" HeightRequest="60">
    <Image.Source>
        <local:PackageNameSource PackageName="{Binding PackageName}" />
    </Image.Source>
</Image>

enter image description here


哇,感谢这个例子。我不确定在哪里放置 [assembly: ExportImageSourceHandler]。我需要把它放在特殊的文件中还是放在我的服务依赖类中就可以了? - Benoit
@Benoit 很多人将汇编级别的特性放置在 Properties/AssemblyInfo.cs 文件中,而其他人则将其放置在与类相关的同一.cs文件中(i.e. ExportImageSourceHandler.cs) (在 using 段落之后,在 namespace 声明之前)。只要它在 Xamarin.Android 项目中,那么这是您的选择... - SushiHangover
它完美地运行了。我会接受您的答案。但我希望了解为什么我的代码没有起作用。我没有理解你的所有代码以及它所做的事情。 - Benoit
@Benoit Grad 很高兴听到你的代码可以工作。但是,就我个人而言,当我从你的GitHub库中拉取代码时,它无法编译。当我看到你在将MonoDroid程序集引用到.NetStd/Forms库中,并从那里使用JNI等内容时,我没有时间进行调试...也许其他人可以帮忙。如果你继续沿着这条路走,我建议你使用共享代码项目,而不是添加依赖于特定平台的库引用,这些库应该独立于任何基于平台的代码,或者使用基于Xamarin.Android库的项目。 - SushiHangover
@SushiHangover非常感谢您的帮助!我一直在按照您最初说的创建新图像源,然后将其转换为字节数组并发送回我的PCL,但是会得到一个空的斜角图像。 - tkefauver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接