在安卓中使用Coil进行图像处理
安卓开发者必须知道的基本技能之一是如何正确处理图像,特别是来自远程源的图像。这是因为许多应用程序以某种方式使用图像。
图像处理可以通过许多不同的方式进行,这取决于你想要实现的功能。在大多数情况下,你可能不仅需要进行加载,还需要进行内存和磁盘缓存,对内存中的图像进行下采样,重新使用位图,并自动暂停/取消请求。
在本教程中,我们将学习如何使用一个快速、轻量级、易于使用的库Coil ,来执行上述任务以及更多的任务。
Coil 它是用Kotlin制作的,并且有嵌入式的coroutine支持,这使得它适用于现代的Android开发。
前提条件
在开始学习本教程之前,请确保你已经熟悉了。
- [在Android开发中]使用[Kotlin]。
- 约束布局的Flow辅助部件
- 视图绑定和/或[数据绑定]
- [Kotlin Coroutines]的基础知识
开始吧
让我们从创建一个Android项目开始,我们将使用这个教程。
为了在我们的应用程序中包含Coil,我们需要以下已经发布在mavenCentral() 的依赖项。
注意,在写这篇文章的时候,使用的
Coil库是version 1.2.2。
implementation("io.coil-kt:coil:1.2.2")
这是默认的依赖关系,与ImageView 扩展函数和Coil 单元一起。它高度依赖于io.coil-kt:coil-base 。这个依赖项支持静态图像的加载。
implementation("io.coil-kt:coil-base:1.2.2")
这是一个基本的工件,主要是由其他依赖关系所依赖的。与io.coil-kt:coil 不同,它不包括ImageView 的扩展函数和Coil 的单子。
implementation("io.coil-kt:coil-gif:1.2.2")
包括GIF 解码器,允许你在你的应用程序中显示GIF 图片。Animated WEBP 需要Android 9.0+ ,而animated HEIF 图片需要Android 11.0+ 。
implementation("io.coil-kt:coil-svg:1.2.2")
上述依赖性支持SVG的解码。
implementation("io.coil-kt:coil-video:1.2.2")
io.coil-kt:coil-video:1.2.2 插件为Android中支持的视频编解码器提供视频框架。编解码器是对数字数据流进行解码和编码的软件。
选择正确的工件是基于你的应用需求。幸运的是,在同一个项目中可以使用一个以上的依赖关系。
说到这里,将以下内容添加到模块级的build.gradle 文件中,因为我们只对纯图像进行工作。
dependencies{
implementation("io.coil-kt:coil:1.2.2")
}
注意,Coil 依赖于Java-8 。要启用这一点,在同一个build.gradle 文件中包括以下内容。
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
最后,Coil需要互联网许可,因为我们将获取远程图像。在Manifest 文件中加入以下一行。
<uses-permission android:name="android.permission.INTERNET"/>
我们现在可以同步该项目了。
构建用户界面
在这个项目中,我们将使用一个单一的ImageView 和几个Buttons 。每个按钮将代表一个修改加载图像的功能。
在你的activity_main.xml 文件中,添加下面的代码。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_margin="16dp"
android:src="@drawable/ic_launcher_foreground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/stateText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="State: Default"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/flow"
app:layout_constraintEnd_toEndOf="@+id/imageView"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
app:constraint_referenced_ids="circle,rounded,grayScale,crossFade,blur,crop,placeholder,error,combined"
app:flow_horizontalGap="8dp"
app:flow_horizontalStyle="packed"
app:flow_verticalStyle="spread"
app:flow_wrapMode="chain"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/imageView"
app:layout_constraintVertical_bias="1" />
<Button
android:id="@+id/circle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Circle Trans" />
<Button
android:id="@+id/rounded"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="rounded" />
<Button
android:id="@+id/grayScale"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Gray scale" />
<Button
android:id="@+id/crossFade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="cross fade" />
<Button
android:id="@+id/blur"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="blur" />
<Button
android:id="@+id/crop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="crop" />
<Button
android:id="@+id/placeholder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Placeholder" />
<Button
android:id="@+id/error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Error Image" />
<Button
android:id="@+id/combined"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="combined trans" />
</androidx.constraintlayout.widget.ConstraintLayout>
Flow是一个辅助部件,它允许我们在其内部对齐视图,而不约束每个被引用的视图。
上面的代码在一个水平包装、垂直传播的链式流中添加了按钮。在imageview 和flow 之间是一个TextView ,表示图片的当前状态。
请看下面的预览。

图片来源
Coil支持三种主要的图像来源。一个load() 方法被用来显示图像。这是一个扩展函数,扩展了ImageView 类。它提供了一个请求生成器lambda,大部分的操作功能在这里被应用。
下面是主要的图片来源。
URL
这使用了一个指向你想加载到目标中的远程图像的链接。
// for instance
imageView.load("https://example.images/example.png")
可绘资源
它从项目文件中加载图像。
imageView.load(R.drawable.image)
文件资源
文件资源利用来自主机设备的给定图像。
imageView.load(File("/path/to/image"))
启用视图绑定
viewBinding 允许我们以一种更简化的方式访问UI视图。它利用了由 库生成的绑定类。viewBinding
为了启用viewBinding ,在模块级的gradle.build 文件中添加以下代码,并进行同步。
android{
buildFeatures{
viewBinding true
}
}
现在,我们就可以开始了!
图像处理功能
在这里,我们监听button 的点击并执行相应的动作。首先,打开MainActivity.kt ,添加以下启动代码。
class MainActivity : AppCompatActivity() {
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
supportActionBar?.hide()
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// we'll handle clicks here
}
}
在上面的代码中,我们已经使用viewBinding膨胀了用户界面。
向目标加载图片
在这一步中,我们将从一个URL加载一张图片到目标(ImageView)。由于这将包括几个操作,最好将样本链接与主代码分开。
因此,创建一个名为ImageLink 的Kotlin类,并在其中添加以下代码。
class ImageLinks {
// array of links to free images on the internet
private val links = arrayListOf<String>(
"https://images.freeimages.com/images/large-previews/825/linked-hands-1308777.jpg",
"https://images.unsplash.com/photo-1541443131876-44b03de101c5?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&dl=mathieu-renier-4WBvCqeMaDE-unsplash.jpg",
"https://images.unsplash.com/photo-1549399542-7e3f8b79c341?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&dl=roberto-nickson-zu95jkyrGtw-unsplash.jpg",
"https://www.cnet.com/a/img/XtH050ErlMIQxKn_HYUx2plJnDc=/940x528/2020/12/17/c9a829c8-69d6-4299-b2d0-cf9624aa7556/2021-acura-tlx-a-spec-65.jpg",
"https://cdn.jdpower.com/JDPA_2021%20Acura%20TLX%20Advance%20Red%20Front%20View.jpg",
"https://s3-us-east-2.amazonaws.com/matter-blog/2020/09/People_Person_Cover_Image.png",
"https://images.fandango.com/ImageRenderer/0/0/redesign/static/img/default_poster.png/0/images/masterrepository/other/ant_man_ver5.jpg"
)
fun randomLink(): String {
return links.random()
}
}
上面的randomLink() 函数,每当我们调用它时,都会返回一个随机选择的链接。这确保了我们不会在每个进程中使用相同的图片。
让我们来管理我们应用程序中的状态。
打开MainActivity.kt 文件,在onCreate() 方法的下面添加以下代码。
private fun updateState(newState: String){
binding.stateText.text = "State: $newState"
}
上面的函数设置了新的状态,以帮助我们识别对图像进行的最新操作。
过渡
过渡是切换状态时产生的持续时间。
交叉渐变
这是一个动画过渡,它将imageView上的变化可视化。它是通过在给定的时间内从0 t0 1 ,改变不透明度来实现的。
// triggered when crossfade button is clicked...(this applies to other snippets as well)
binding.crossFade.setOnClickListener {
val link = ImageLinks().randomLink()
binding.imageView.load(link) {
crossfade(750) // 75th percentile of a second
build()
}
// set current state
updateState("cross fade")
}
交叉渐变的持续时间是由crossfade() 函数中传递的时间(微秒)决定的。
变换
图像变换是可以应用于ImageView 的操作技术。Coil支持以下变换。
圆角
这可以在图像上创建弯曲的角落。圆角的程度是由RoundedCornersTransformation() 函数中传递的浮动值决定的。
binding.rounded.setOnClickListener {
val link = ImageLinks().randomLink()
binding.imageView.load(link) {
transformations(
RoundedCornersTransformation(8F)
)
build()
}
updateState("Rounded")
}
模糊化
这涉及到对图像质量的操作。
binding.blur.setOnClickListener {
val link = ImageLinks().randomLink()
binding.imageView.load(link) {
transformations(
BlurTransformation(this@MainActivity,radius = 8f),
)
build()
}
updateState("Blur")
}
模糊半径越大,图像就越模糊。当我们不希望用户看到某些图像或内容时,模糊会有帮助。例如,当服务只要求高级访问时。
圆形裁剪
圆形裁剪是在可用的尺寸范围内形成一个圆形的图像。
binding.circle.setOnClickListener {
val link = ImageLinks().randomLink()
binding.imageView.load(link) {
transformations(
CircleCropTransformation()
)
build()
}
updateState("Circle Crop")
}
灰度(Grayscale
灰度是指一种脱色的图像格式。所有颜色都变成了黑色或白色。
binding.grayScale.setOnClickListener {
val link = ImageLinks().randomLink()
binding.imageView.load(link) {
transformations(
GrayscaleTransformation(),
)
build()
}
updateState("Grayscale")
}
占位符
顾名思义,placeholders ,是指在设置实际图像之前,预先设置在ImageView 的图像。
当一个请求完成后,占位符会立即被替换成预定的图像。由于这个原因,它可能不会被视觉检测到,特别是当一个图像请求完成得非常快的时候。
binding.placeholder.setOnClickListener {
val link = ImageLinks().randomLink()
binding.imageView.load(link){
/* create a drawable resource to use here */
placeholder(R.drawable.ic_placeholder)
build()
}
updateState("Placeholder")
}
裁剪
它允许我们硬编码尺寸或图像大小。请注意,在使用Coil的size() 功能时,我们不指定测量单位,因为默认单位是像素。
binding.crop.setOnClickListener {
val link = ImageLinks().randomLink()
binding.imageView.apply {
scaleType = ImageView.ScaleType.CENTER_CROP
load(link) {
size(300, 300)
}
}
}
组合变换
在某些情况下,你可能想在同一图像上同时应用一个以上的变换。这可以通过以下方式实现。
binding.combined.setOnClickListener {
val link = ImageLinks().randomLink()
binding.imageView.load(link) {
crossfade(750)
transformations(
CircleCropTransformation(),
GrayscaleTransformation() // e.t.c
)
build()
}
updateState("Combined State")
}
注意使用逗号来分隔变换。
取消请求
在某些情况下,一个网络请求需要被取消,特别是当它需要太长的时间来响应时。请求取消也被称为处置,如下面的片段所示。
imageView.load(link){
...
}.dispose()
这通过取消任何正在进行的工作来释放与该请求相关的资源。这种方法是empotent的,意味着多个应用程序不会影响第一个应用程序的结果。
内存缓存
缓存使显示远程图像成为可能,即使在互联网连接中断时也是如此。要做到这一点,图像必须至少在一个实例中被取走。
Coil利用了OkHttp ,这是一个支持SPDY 协议的HTTP client 。
异常处理
跟踪一个请求是很重要的,因为它有助于处理过程中遇到的错误。
Coil使用一个接受两个lambdas的onSuccess 和onError 的listener() 函数,如下图所示。
// handle error button click
binding.error.setOnClickListener {
binding.imageView.load("https://a/bad/link") {
listener(
// pass two arguments
onSuccess = { _, _ ->
Toast.makeText(this@MainActivity, "Success", Toast.LENGTH_SHORT).show()
},
onError = { request: ImageRequest, throwable: Throwable ->
request.error
Toast.makeText(this@MainActivity, "$throwable", Toast.LENGTH_SHORT).show()
})
// setup error image
error(R.drawable.ic_error_image)
}
}
一个错误图像被用来表示在解决请求时出了问题。
总结
恭喜你完成了本教程。你现在可以自如地使用Coil 来处理Android中的图像。因此,你可以利用这些知识来构建更强大的应用程序。