kotlin 隐藏 组件_基于Kotlin的Android复合组件简介-第2部分

kotlin 隐藏 组件

系列路线图 (Series Roadmap)

  1. Part 1

    第1部分

  2. Part 2 (you’re here)

    第2部分(您在这里)
  3. Part 3

    第三部分

Hello there, and welcome back. This is the second part in a three-part series tutorial on using Compound Components in Android. If this is your first time here, you might want to check out the first part here.

您好,欢迎回来。 这是由三部分组成的系列教程的第二部分,该系列教程涉及在Android中使用复合组件。 如果这是您第一次来这里,您可能想在这里签出第一部分。

In the previous part, we successfully displayed our custom view on-screen and we came up with this.

在上一部分中,我们成功地在屏幕上显示了自定义视图,并且我们想到了这一点。

Last point in the previous part, compound component was displayed.

FileDescriptor (FileDescriptor)

In the previous article, we explained the component we would be building, a file descriptor that extracts basic information from a selected file present on the Android File System. So the next step would be adding the functionality to actually select the file.

在上一篇文章中,我们解释了我们将要构建的组件,一个文件描述符,该描述符从Android文件系统上存在的选定文件中提取基本信息。 因此,下一步将是添加功能以实际选择文件。

To that effect, we would be adding a button to the MainActivity that triggers Android’s default file chooser, which allows the user to select a file of interest. The selected file is then passed into the FileDescriptor. Let’s jump right in!

为此,我们将向MainActivity添加一个按钮,该按钮将触发Android的默认文件选择器,该按钮允许用户选择感兴趣的文件。 然后将所选文件传递到FileDescriptor 。 让我们跳进去吧!

<?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">


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/select_file_button"
        app:layout_constraintTop_toTopOf="parent"
        android:text="@string/select_file_text"/> // "Select File"


    <dev.olaore.compoundcmpts_final.FileDescriptor
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        android:layout_marginRight="20dp"
        app:layout_constraintTop_toBottomOf="@id/select_file_button" />


</androidx.constraintlayout.widget.ConstraintLayout>

The above layout produces the output below:

上面的布局产生以下输出:

demo showing the button added to top of the FileDescriptor
demo showing the button added to the top of our file descriptor
演示显示添加到文件描述符顶部的按钮

Next, we want to set up a click listener on the button in the MainActivity, we then define the method that launches the file chooser.

接下来,我们要在MainActivity的按钮上设置一个单击侦听器,然后定义启动文件选择器的方法。

class MainActivity : AppCompatActivity() {


    private val REQUEST_FILE_CODE = 1
    private lateinit var fileDescriptor: FileDescriptor


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
            
        fileDescriptor = findViewById(R.id.file_descriptor);
        select_file_button.setOnClickListener {
            selectFile()
        }


    }


}

Below is the implementation of the selectFile() method invoked on-click of the button.

下面是selectFile() 单击按钮时调用的方法。

// imports...


class MainActivity : AppCompatActivity() {
  
    // ...


    private fun selectFile() {
        val selectFileIntent = Intent(Intent.ACTION_GET_CONTENT)
        selectFileIntent.type = "*/*"
        startActivityForResult(selectFileIntent, 1)
    }


}

In the implementation above, we essentially construct an Intent with an action of ACTION_GET_CONTENT. This action informs the android system that content of some kind is to be retrieved by this intent. Next, we set the type of the content to be gotten. Most times, you might want to get a specific type of file, be it a document, media of some sort, or whatever.

在上面的实现中,我们实质上是使用ACTION_GET_CONTENT动作构造一个Intent 。 此操作通知android系统此意图将检索某种内容。 接下来,我们设置要获取的内容的类型。 大多数时候,您可能想要获取一种特定类型的文件,无论是文档,某种形式的媒体还是其他。

These are all MIME types, and they do well to differentiate between the file types and the extensions of the file type. For example, image/* refers to every type (extension) of image, while image/jpeg, image/png and image/gif refers to specific extensions of the image file type.

这些都是MIME类型 ,它们很好地区分了file typesfile types的扩展名。 例如, image/*image/*每种类型(扩展名),而image/jpegimage/pngimage/gif指图像文件类型的特定扩展名。

In essence, using */* says to the android file system: Get me any file type with any extension. So, this dictates to the android file system that this intent is interested in every type of file present on the file system. After setting the type, we can then launch the activity to get the content by calling startActivityForResult and await the arrival of the content.

本质上,使用*/*对android文件系统说: 获取具有扩展名的任何文件类型 。 因此,这指示android文件系统此意图对文件系统上存在的每种文件类型都感兴趣。 设置类型之后,我们可以通过调用startActivityForResult来启动活动以获取内容,然后等待内容到达。

class MainActivity : AppCompatActivity() {


    private lateinit var fileDescriptor: FileDescriptor


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        fileDescriptor = findViewById(R.id.file_descriptor)
        file_select_button.setOnClickListener {
            selectFile()
        }
    }


    private fun selectFile() {
        val selectFileIntent = Intent(Intent.ACTION_GET_CONTENT)
        selectFileIntent.type = "*/*"
        startActivityForResult(selectFileIntent, 1)
    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
            if (data != null) {
                val fileUri = data.data
            }
        }
    }


}

In onActivityResult, we perform a check to see if the requestCode is the same as the one the intent was launched with 1, and if the result is in good condition. After that, we make sure the data returned is non-null, and if so, we retrieve the URI of the file by getting the data property of the data returned. The data is then stored in our fileUri local variable.

onActivityResult ,我们执行一次检查,以查看requestCode是否与使用1启动的intent相同,以及结果是否处于良好状态。 之后,我们确保返回的数据为非null,如果是,则通过获取返回数据的data属性来检索文件的URI。 然后将数据存储在我们的fileUri局部变量中。

Everything looks good, but we have no way to inform the FileDesriptor of the file that was selected. Well, we can do that by passing the variable into the FileDescriptor to notify the component of the file’s existence.

一切看起来都不错,但是我们无法将所选文件通知FileDesriptor 。 好吧,我们可以通过将变量传递到FileDescriptor来通知组件该文件的存在来实现。

// file types
enum class FileType {
    IMAGE, TEXT, PDF, DOCX, MP4, MP3, UNKNOWN
}


class FileDescriptor @JvmOverloads
    constructor(private val ctx: Context, private val attributeSet: AttributeSet? = null, private val defStyleAttr: Int = 0)
    : ConstraintLayout(ctx, attributeSet, defStyleAttr) {


      var fileUri: Uri? = null
        set(value) {
            field = value
            setUpFileDescriptor()
        }


    var file: File? = null
    var fileType: FileType? = null
      
    private fun setUpFileDescriptor() {
    }
      
}

Firstly, we start by creating an enum class that holds constants for the types of files we would want to manage with our FileDescriptor. We have constants for most file types and an UNKNOWN constant for files we would not be accounting for.

首先,我们从创建一个枚举类开始,该枚举类包含要使用FileDescriptor管理的文件类型的常量。 对于大多数文件类型,我们都有一个常量;对于我们不会考虑的文件,我们有一个UNKNOWN常量。

We created a mutable fileUri property that allows us to set the URI of the file we would be describing. A custom setter is then set up to immediately call the setUpFileDescriptor function once a fileUri is set. A File and FileType object is also created, the former is used to store the file located at the URI set up by the file descriptor locally in our component so every needed piece of data can be gotten at one source, while the former denotes the type of file we have set up. Then the setUpFileDescriptor method is also declared.

我们创建了一个可变的fileUri属性,该属性允许我们设置将要描述的文件的URI。 然后设置自定义设置程序,以在设置fileUri立即调用setUpFileDescriptor函数。 还创建了一个FileFileType对象,前者用于存储位于文件描述符在我们组件中本地设置的URI处的文件,因此每一个需要的数据都可以从一个源获取,而前者表示类型已建立的档案。 然后,还声明了setUpFileDescriptor方法。

class FileDescriptor @JvmOverloads
    constructor(private val ctx: Context, private val attributeSet: AttributeSet? = null, private val defStyleAttr: Int = 0)
    : ConstraintLayout(ctx, attributeSet, defStyleAttr) {


    // ...variable declarations
    
    private fun setUpFileDescriptor() {
       setFile()
    }
      
    private fun setFile() {
        // columns to be retrieved from the media content provider
        val columns = arrayOf(MediaStore.Images.Media.DATA)
        // query the content resolver for the data associated with the fileUri passed in
        val cursor = ctx.contentResolver.query(
            fileUri!!, columns, null, null, null
        )


        cursor?.let {
            it.moveToFirst()
            val dataColumnIndex = it.getColumnIndex(columns[0])
            val filePath = cursor.getString(dataColumnIndex)
            it.close()
            filePath?.let { path ->
                file = File(path)
            }
        }


    }


}

So, we create a new method called setFile, and call it from the setUpFileDescriptor method, this method is used to query the Android’s content provider for the whole File object associated with the fileUri in the FileDescriptor. This allows us to get accurate data about the File to be displayed in the component.

因此,我们创建了一个名为setFile的新方法,并从setUpFileDescriptor方法中对其进行了setUpFileDescriptor ,该方法用于向Android的内容提供程序查询与FileDescriptorfileUri关联的整个File对象。 这使我们可以获得有关要在组件中显示的File准确数据。

This method starts by setting up the columns to be queried from the Content Provider, in this case, we would be going with just one — MediaStore.Images.Media.DATA, this gives us basic descriptive data present in the file. We then run a query on the content provider using the fileUri passed in the columns of data to be retrieved. We pass null into the rest of the parameters since we would not be providing any selection clauses or arguments into the query. The query then returns a cursor that holds a reference to the File’s data.

此方法首先设置要从Content Provider查询的列,在这种情况下,我们将只处理其中一个— MediaStore.Images.Media.DATA ,这为我们提供了文件中存在的基本描述性数据。 然后,我们使用传递到要检索的数据列中的fileUri在内容提供程序上运行查询。 我们将null传递给其余参数,因为我们不会在查询中提供任何选择子句或参数。 然后查询将返回一个游标,其中包含对文件数据的引用。

After making sure the cursor isn’t null, we move to the first entry in the cursor. We then need to get the index of the data column we requested for in the whole entry, hence the call to getColumnIndex(), that should return the index of the column that contains our data. To correctly instantiate our File object, we need to get the fully-qualified path, the path is present in the DATA column, so we can get the String value present at the DATA column index in the entry. With that, we get the filePath and the File object is then instantiated with the filePath as a parameter. And that’s it for the File setup.

确保光标不为空后,我们移至光标的第一项。 然后,我们需要获取整个条目中所需的数据列的索引,因此需要调用getColumnIndex() ,该调用应返回包含我们的数据的列的索引。 为了正确地实例化我们的File对象,我们需要获取完全限定的路径,该路径位于DATA列中,因此我们可以获取该条目中DATA列索引处存在的String值。 这样,我们得到了filePath ,然后使用filePath作为参数实例化File对象。 这就是文件设置。

Now that we have our file, we need to set up the correct image (thumbnail) to be displayed depending on the file type, but let us start by defining the logic for the thumbnail selection.

现在我们有了文件,我们需要根据文件类型设置要显示的正确图像(缩略图),但让我们首先定义缩略图选择的逻辑。

The required images for thumbnails are already in the drawables folder.

缩略图所需的图像已经在drawables文件夹中。

class FileDescriptor @JvmOverloads
    constructor(private val ctx: Context, val attributeSet: AttributeSet? = null, defStyleAttr: Int = 0)
    : ConstraintLayout(ctx, attributeSet, defStyleAttr) {
      
  // variable declarations
      
  // function declarations


  private fun setUpFileTypeImage() {
      file_type_image.setImageResource(when(fileType) {
          FileType.DOCX -> R.drawable.docx
          FileType.IMAGE -> R.drawable.image
          FileType.MP3 -> R.drawable.mp3
          FileType.MP4 -> R.drawable.video
          FileType.PDF -> R.drawable.pdf
          FileType.TEXT -> R.drawable.txt
          FileType.UNKNOWN -> R.drawable.no_file_selected
          else -> R.drawable.no_file_selected
      })
  }
      
}

Essentially, we declare a private function called setUpFileTypeImage and using a when statement, we correctly determine the drawable to be displayed by the compound component depending on the FileType set by the File object. And if the FileType isn’t recognized, we use the same drawable used when it is in an UNKNOWN state.

本质上,我们声明一个名为setUpFileTypeImage的私有函数,并使用when语句,根据File对象设置的FileType正确确定复合组件要显示的可绘制对象。 如果无法识别FileType ,我们将使用处于UNKNOWN状态的同一个drawable。

That done, we need to actually retrieve the FileType. Fortunately, Android’s content resolver API provides a helper getType method that returns the MIME type associated with our file.

完成后,我们需要实际检索FileType 。 幸运的是,Android的内容解析器API提供了一个助手getType方法,该方法返回与我们的文件关联的MIME类型。

class FileDescriptor @JvmOverloads
    constructor(private val ctx: Context, private val attributeSet: AttributeSet? = null, private val defStyleAttr: Int = 0)
    : ConstraintLayout(ctx, attributeSet, defStyleAttr) {
      
      // variable declarations
      
      // function declarations


    private fun setUpFileType() {
        // get a reference to the android's content resolver
        val resolver = ctx.contentResolver
        // get the type of the file
        val type = resolver.getType(fileUri!!)!!


        // run check
        fileType = if (type.contains("image")) {
            FileType.IMAGE
        } else if (type.contains("video")) {
            FileType.MP4
        } else if (type.contains("application/msword") || type.contains("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) {
            FileType.DOCX
        } else if (type.contains("audio")) {
            FileType.MP3
        } else if (type.contains("application/pdf")) {
            FileType.PDF
        } else if (type.contains("text/plain")) {
            FileType.TEXT
        } else {
            FileType.UNKNOWN
        }


        setUpFileTypeImage()
    }
      
}

We create a new method that starts by getting a reference to the content resolver android provides, then we run getType on the resolver while passing in the fileUri to denote what file whose type we want to retrieve, the returned type is then stored in the variable type.

我们创建一个新方法,首先获取对android提供的内容解析器的引用,然后在解析器上运行getType ,同时传入fileUri来表示要检索其类型的文件,然后将返回的类型存储在变量中type

Using a basic if statement, we run through the MIME types that we support and if the type returned by the content resolver contains any of the MIME types for the supported files, we go on to set the FileType appropriately.

使用基本的if语句,我们将遍历我们支持的MIME类型,并且如果内容解析器返回的类型包含受支持文件的任何MIME类型,我们将继续适当地设置FileType

When that is done, we call setUpFileImage, which does the job of setting up the mini-image denoting the type of file that was selected, then we can call setUpFileType from our setFile() function. So our setFile function looks like this.

完成后,我们调用setUpFileImage ,它会完成设置表示所选文件类型的微型图像的工作,然后可以从setFile()函数中调用setUpFileType 。 所以我们的setFile函数看起来像这样。

private fun setUpFileDescriptor() {
    setFile()
    setUpFileType()
}

Before we move on, now that we have put up the logic to get the file, process and store it’s reference, we can now link the fileUri returned by the FileChooser to the FileDescriptor by doing:

在继续之前,现在我们已经建立了获取文件,处理和存储引用的逻辑,现在我们可以通过执行以下操作将FileChooser返回的fileUri链接到FileDescriptor

class MainActivity : AppCompatActivity() {


  // variable declarations
  
  // onCreate callback
  
  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_FILE_CODE && resultCode == Activity.RESULT_OK) {
            if (data != null) {
                val fileUri = data.data
                // set the fileUri retrieved from the FileChooser to the fileUri of the FileDescriptor
                fileDescriptor.fileUri = fileUri
            }
        }
    }


}

Since our File object is set up, we would need to set up the part of our Compound Component that holds description about the selected File. There exists very useful properties on the File object that provides us with most of the descriptive data that we need. Below is the code that sets up data in the views present in the compound components.

由于已经设置了File对象,因此我们需要设置复合组件中包含有关所选File的描述的部分。 File对象上存在非常有用的属性,可为我们提供所需的大多数描述性数据。 以下是在复合组件中存在的视图中设置数据的代码。

class FileDescriptor @JvmOverloads
    constructor(private val ctx: Context, val attributeSet: AttributeSet? = null, defStyleAttr: Int = 0)
    : ConstraintLayout(ctx, attributeSet, defStyleAttr) {
      
      init {
        // layout inflation and setup
        
        // set the visibility of the info section to visible for testing purposes
        file_info.visibility = View.VISIBLE
      }
      
      private fun setUpFileDescriptor() {
        setFile()
        setUpFileType()


        file?.let {
            file_name.text = it.name
            file_info.text = """
                File Name: ${ it.name }
                Path To File: ${ it.path }
                Last Modified: ${ it.lastModified() }
                Size: ${ it.length() }B
            """.trimIndent()
        }


    }
      
}

Initially, we start by making sure the File object is not null, and if so, we construct a multi-line string that describes certain details about the file by calling required methods on the File object. After running the app, and selecting the file we are interested in, we get this as a result.

最初,我们首先确保File对象不为null,如果是,我们将通过在File对象上调用所需的方法来构造多行字符串,以描述有关文件的某些详细信息。 运行该应用程序并选择我们感兴趣的文件后,我们得到了此结果。

demo displaying the file’s info and name and also selecting correct icon for the file selected from the file system.
demo showing the file selection after initial setup
演示显示初始设置后的文件选择

Now, our compound component is looking pretty good, but the details in the component don’t really provide optimal user experience, the size of the file can be viewed in bytes and the last modified date is a Long value. Therefore, we can define extension functions on the Long data type to modify the data as needed.

现在,我们的复合组件看起来很不错,但是组件中的细节并不能真正提供最佳的用户体验,文件大小可以按字节查看,最后修改日期为Long值。 因此,我们可以在Long数据类型上定义扩展函数,以根据需要修改数据。

fun Long.format(format: String): String {
    return SimpleDateFormat(format).format(this.absoluteValue)
}


fun Long.valueInKb(): Double {
    val kb: Double = this.div(1024).toDouble()
    return kb
}

The implementation for the date formatting and bytes conversion is provided above, after doing this, our setUpFileDescriptor method can be modified as such:

上面提供了日期格式和字节转换的实现,完成此操作后,可以如下修改setUpFileDescriptor方法:

private fun setUpFileDescriptor() {
  
  // ...
  
  file?.let {
    file_name.text = it.name
    file_info.text = """
        File Name: ${ it.name }
        Path To File: ${ it.path }
        Last Modified: ${ it.lastModified().format("MMM dd, YYYY hh:mm") }
        Size: ${ it.length().valueInKb() }KB
    """.trimIndent()
  }


}

Running the app, and reproducing the steps for the file selection gives this result:

运行该应用程序,并重新生成文件选择步骤,将得到以下结果:

Image for post
compound component displaying file data after date and size values have been properly formatted.
正确格式化日期和大小值之后显示文件数据的复合组件。

Next up, we would need to display a small preview of the image (we surely don’t want the big unknown image on all our files right?). To achieve this, there are varying approaches depending on the type of file whose thumbnail we try to retrieve. So as to not add to the complexity of this series, we would be manually getting the thumbnail of images and videos, but just displaying plain images for other types of file. To that effect, we create an object called ThumbnailGenerator that handles creation of thumbnails for images and videos. Well, for video thumbnail creation, we would need the Glide image loading library, so you can start by adding this dependency in your app-level build.gradle file.

接下来,我们将需要显示该图像的一个小预览(我们当然不希望所有文件中的未知大图像都正确吗?)。 为实现此目的,根据我们尝试检索其缩略图的文件类型,有不同的方法。 为了不增加本系列的复杂性,我们将手动获取图像和视频的缩略图,而仅显示其他类型文件的纯图像。 为此,我们创建了一个名为ThumbnailGenerator的对象,该对象处理图像和视频缩略图的创建。 好了,对于创建视频缩略图,我们需要Glide图像加载库,因此您可以从在应用程序级别build.gradle文件中添加此依赖关系开始。

// Glide dependencyimplementation 'com.github.bumptech.glide:glide:4.11.0'kapt 'com.github.bumptech.glide:compiler:4.11.0'

// Glide dependencyimplementation 'com.github.bumptech.glide:glide:4.11.0'kapt 'com.github.bumptech.glide:compiler:4.11.0'

We can now define our ThumbnailGenerator object as such:

现在,我们可以这样定义ThumbnailGenerator对象:

package dev.olaore.compoundcmpts_final


import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.provider.MediaStore
import android.widget.ImageView
import com.bumptech.glide.Glide


object ThumbnailGenerator {


    fun createVideoThumbnail(context: Context, imageUri: Uri?, imageView: ImageView) {
        // load the video thumbnail with glide
        Glide.with(context)
            .asBitmap()
            .load(imageUri)
            .into(imageView)


    }


    fun createImageThumbnail(context: Context, imageUri: Uri?, imageView: ImageView) {


        imageView.setImageBitmap(
            MediaStore.Images.Media.getBitmap(context.contentResolver, imageUri)
        )
        
    }


}

The helper functions simply use the Glide library in case of videos, and android’s provided getBitmap method for images.

辅助功能仅在视频情况下使用Glide库,而android提供了针对图像的getBitmap方法。

Back in our FileDescriptor, we need to create the method that effectively retrieves the thumbnail. We then define a retrieveThumbnail method and call if from the setUpFileDescriptor method like this:

回到FileDescriptor ,我们需要创建有效检索缩略图的方法。 然后,我们定义一个retrieveThumbnail方法,并从setUpFileDescriptor方法中调用if,如下所示:

class FileDescriptor @JvmOverloads
    constructor(private val ctx: Context, private val attributeSet: AttributeSet? = null, private val defStyleAttr: Int = 0)
    : ConstraintLayout(ctx, attributeSet, defStyleAttr) {
    
      // ...
      
      private fun setUpFileDescriptor() {
        setFile()
        setUpFileType()


        file?.let {
            file_name.text = it.name
            file_info.text = """
                File Name: ${ it.name }
                Path To File: ${ it.path }
                Last Modified: ${ it.lastModified().format("MMM dd, YYYY hh:mm") }
                Size: ${ it.length().valueInKb() }KB
            """.trimIndent()
        }


        // a call to retrieveThumbnail()
        retrieveThumbnail()


    }
      
    private fun retrieveThumbnail() {


      try {


          when(fileType) {
              FileType.IMAGE -> ThumbnailGenerator.createImageThumbnail(ctx, fileUri, file_preview_image)
              FileType.MP4 -> ThumbnailGenerator.createVideoThumbnail(ctx, fileUri, file_preview_image)
              FileType.DOCX -> file_preview_image.setImageResource(R.drawable.docx)
              FileType.MP3 -> file_preview_image.setImageResource(R.drawable.mp3)
              FileType.PDF -> file_preview_image.setImageResource(R.drawable.pdf)
              FileType.TEXT -> file_preview_image.setImageResource(R.drawable.txt)
              FileType.UNKNOWN -> file_preview_image.setImageResource(R.drawable.no_file_selected)
          }


      } catch (e: Exception) {
          Log.d("FileDescriptor", "Error occured: ${ e.message }")
      }


  }
      
      
   // ...
      
}

What this method does is, depending on the type of file, it either forwards the request to get the thumbnail (image/video) from the ThumbnailGenerator or sets the provided image if the file type is none of the aforementioned. After running the application, and selecting various file types, we are presented with the following:

此方法的作用是,根据文件类型的不同,它转发请求以从ThumbnailGenerator获取缩略图(图像/视频)的请求,或者如果文件类型不是上述任何一种,则设置提供的图像。 运行该应用程序并选择各种文件类型之后,我们将看到以下内容:

Image for post
Image for post
compound component displaying details for an audio and an image file
复合组件,显示音频和图像文件的详细信息
Image for post
Image for post
compound component displaying details for a PDF document and a video.
复合组件,显示PDF文档和视频的详细信息。

Finally, we have achieved the main aim of our compound component, and we correctly display details about a selected file no matter the file type, pretty cool!. You do deserve a round of applause for coming this far.

最终,我们达到了复合组件的主要目的,无论文件类型如何,我们都能正确显示有关所选文件的详细信息,非常酷! 您应该为此鼓掌。

👏

👏

Well, you’d notice that we still have two main features to implement: sharing of the file’s data through various media, and toggling the visibility of the information section of the compound component, head on to the third part of this article where we deal with Custom Attributes and Sharing In Android.

好了,您注意到我们仍然要实现两个主要功能:通过各种媒体共享文件数据,以及切换复合组件信息部分的可见性,请转到本文的第三部分。在Android中使用自定义属性和共享。

Big Ups!!

大UPS!

`

`

翻译自: https://medium.com/android-dev-hacks/a-kotlin-based-introduction-to-compound-components-on-android-part-2-f33d7179b5c8

kotlin 隐藏 组件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值