kotlin 隐藏 组件

Hello there, and welcome.

您好,欢迎光临。

系列路线图 (Series Roadmap)

  1. Part 1

    第1部分

  2. Part 2

    第2部分

  3. Part 3 (you’re here)

    第3部分(您在这里)

This is the third part in a three-part series tutorial on using Compound Components in Android. Confused? you might want to check out the previous articles for more clarity.

这是由三部分组成的系列教程的第三部分,该系列教程涉及在Android中使用复合组件。 困惑? 您可能想查看前面的文章以更清楚。

FileDescriptor (FileDescriptor)

We have successfully completed the core functionality our Compound Component is supposed to exhibit, which happens to be, File Selection, File Metadata Retrieval. At this point, we are done, but I would like to introduce you to one more concept in Custom Views on Android, and that is Custom Attributes.

我们已经成功完成了我们的复合组件应该展现的核心功能,恰好是文件选择,文件元数据检索。 至此,我们已经完成了,但是我想向您介绍Android上“自定义视图”中的另一个概念,即“ 自定义属性”。

自定义属性 (Custom Attributes)

The concept of attributes in Android is one that is widely common, we use attributes to define specific behaviour for Views present in the Android design system. Attributes help us to set customize our Views to look different from the defaults given to us by the plain old View itself. Examples are the textColor, textSize, background, backgroundTint attributes and so much more.

Android中的属性概念非常普遍,我们使用属性来定义Android设计系统中存在的View的特定行为。 属性可帮助我们设置自定义视图,使其外观与普通旧视图本身为我们提供的默认视图有所不同。 示例包括textColortextSizebackgroundbackgroundTint属性等等。

The concept is not so different when it comes to Custom Views in android, just that we would create and manage its’ usability ourselves. Without further ado, let’s jump right in!

当涉及到android中的“ 自定义视图”时,该概念并没有太大不同,只是我们可以自己创建和管理其“可用性”。 事不宜迟,让我们直接进入吧!

What we are going to be doing is defining attributes to specify if the info section of our compound component is to be displayed by default (it’s visible by default), this gives the user the liberty to specify if the view should be visible or hidden. The second attribute would specify the background of the compound component itself, this would be a drawable resource. Up until this point, we used a rectangular drawable with a grey-like background.

我们要做的是定义属性以指定默认情况下是否显示复合组件的info部分(默认情况下可见),这使用户可以自由指定视图是可见还是隐藏。 第二个属性将指定复合组件本身的背景,这将是可绘制的资源。 到目前为止,我们使用了带有灰色背景的矩形可绘制对象。

定义自定义属性 (Defining Custom Attributes)

Start by navigating to res > values folder, and create a new Values Resource File called attr.xml to store the attributes to be defined for this Compound Component. Below is the code defining the attributes for the compound component.

首先导航到res > values 文件夹,然后创建一个名为attr.xml的新值资源文件来存储要为此复合组件定义的属性。 下面的代码定义了复合组件的属性。

<?xml version="1.0" encoding="utf-8"?>
<resources>


    <declare-styleable name="FileDescriptor">
        <attr name="showInfoByDefault" format="boolean"/>
        <attr name="viewBackground" format="reference"/>
    </declare-styleable>


</resources>

In the attr.xml file, we create a declare-styleable tag, this tag is used to define, or in this case, “declare” attributes for a View that can be styled, or a View that is styleable, get it?. So, this tag picks up a certain Custom View and takes note of the attributes declared in here and allows the Custom View to be styled using those attributes in XML, and also retrievable in code. The declare-styleable tag has a name attribute that defines the styleable Views whose attributes are to be declared. In this case, we go with FileDescriptor.

在attr.xml文件中,我们创建一个declare-styleable 标签,此标签用于定义(或在这种情况下)“声明”可样式化的View或可样式化的View的属性 ,明白吗? 因此,此标记拾取某个自定义视图,并记录在此声明的属性,并允许使用XML中的这些属性来对自定义视图进行样式设置,并且还可以在代码中进行检索。 declare-styleable 标记具有名称属性,该属性定义了要声明其属性的可样式化视图。 在这种情况下,我们使用FileDescriptor

Inside the tag, we have to define the attributes that would be usable on our Custom View, we make use of the attr tag. The attr tag has two very important attributes, the name and format. The name attribute accepts the name we want to give the attribute (we should follow android’s naming convention for naming identifiers/attributes defined here), and the format specifies the type of value the attribute would accept, this can be a color, string, integer, reference (to a resource), boolean, dimension or a float. Specifying the format this attribute requires enables android to know the set of values that can be entered into this attribute.

在标签内部,我们必须定义可在Custom View上使用的attr ,我们利用attr标签。 attr标记具有两个非常重要的属性,即nameformatname属性接受我们要赋予属性的名称(对于此处定义的命名标识符/属性,我们应遵循android的命名约定),格式指定该属性接受的值的类型,可以是颜色,字符串,整数,(对资源的)引用,布尔值,尺寸或浮点数。 指定此属性要求的格式,可使android知道可以输入此属性的值集。

Two attributes are declared, showInfoByDefault and viewBackground. The former’s format is set as a Boolean since we would be needing a true or false value to determine the info section’s visibility. The latter demands a reference to a resource which is expected to be a drawable.

声明了两个属性showInfoByDefaultviewBackground 。 前者的格式设置为布尔值,因为我们需要一个true或false值来确定信息部分的可见性。 后者要求参考预期可绘制的资源。

Now that all of that attribute setup is done, we need to get the attributes in code and use them as needed. That’s done in the code below.

现在完成了所有属性设置,我们需要在代码中获取属性并根据需要使用它们。 这是在下面的代码中完成的。

class FileDescriptor @JvmOverloads
    constructor(private val ctx: Context, private val attributeSet: AttributeSet? = null, private val defStyleAttr: Int = 0)
    : ConstraintLayout(ctx, attributeSet, defStyleAttr) {
    
    // ...
    var showInfoByDefault = false
    @DrawableRes var viewBackground: Int = R.drawable.round_file_descriptor_background
    
    init {


        // get the inflater service from the android system
        val inflater = ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater


        // the array of attributes
        val attributes = ctx.obtainStyledAttributes(attributeSet, R.styleable.FileDescriptor)


        showInfoByDefault = attributes.getBoolean(R.styleable.FileDescriptor_showInfoByDefault, showInfoByDefault) // can be set to false
        viewBackground = attributes.getResourceId(R.styleable.FileDescriptor_viewBackground, viewBackground) // can be set to R.drawable.round_file_descriptor_background


        attributes.recycle()
        
        // inflate the layout into "this" layout
        inflater.inflate(R.layout.file_descriptor, this)


        // finally, we set the background of our component
        setBackgroundResource(viewBackground)
        file_info.visibility = if (showInfoByDefault) View.VISIBLE else View.GONE
        
        info_file_image.setOnClickListener {
            showInfoByDefault = !showInfoByDefault
            file_info.visibility = if (showInfoByDefault) View.VISIBLE else View.GONE
        }


    }
    
    // ...
    
    
}

In the code above, we start by declaring variables that hold the default values for the attributes to be used, even when they are not specified explicitly. We assign false to showInfoByDefault and R.drawable.round_file_descriptor_background to viewBackground. By doing this, we have our default values for the attributes set up.

在上面的代码中,我们首先声明变量,这些变量包含要使用的属性的默认值,即使未明确指定它们也是如此。 我们分配到假showInfoByDefaultR.drawable.round_file_descriptor_backgroundviewBackground 。 这样,我们就为属性设置了默认值。

Normally, attributes are set up when the view is created, meaning, the attributes provided to a View are applied immediately. Since that’s the goal, we can set them up in the init block. Android’s Context object ctx provides an obtainStyledAttributes method that takes in two parameters, an AttributeSet object and a reference pointing to the style declaration for our Custom View.

通常,属性是在创建视图时设置的,也就是说,提供给视图的属性会立即应用。 既然是目标,我们可以在init块中进行设置。 Android的Context对象ctx提供了一个obtainStyledAttributes方法,该方法obtainStyledAttributes两个参数,一个AttributeSet对象和一个指向自定义视图的样式声明的引用。

Clarifying further, it’s important to understand that every attribute declared for a View is packaged into the AttributeSet object, keep in mind that this includes our layout_height, layout_width, layout_weight, textSize and what not. While our view is being inflated, Android retrieves all the defined attributes, packages them and send them over to the inflated object for proper instantiation. As you can see in FileDescriptor class definition, we pass that same attributeSet object into the ConstraintLayout class, this is so that the class can also manage attributes specific to it, or it’s parent.

进一步说明,了解为View声明的每个属性都打包到AttributeSet对象中是很重要的,请记住,这包括我们的layout_heightlayout_widthlayout_weighttextSize和其他内容。 当我们的视图膨胀时,Android会检索所有定义的属性,将它们打包并将其发送到膨胀的对象以进行正确的实例化。 如您在FileDescriptor类定义中所看到的,我们将该同一个attributeSet对象传递到ConstraintLayout类中,这样一来,该类还可以管理特定于其或其父级的属性。

But one thing is missing, the ConstraintLayout, ViewGroup or View implementations don’t know of our attribute declarations, so it is useless thinking that they can manage the attributes for us. Well, since we declared them for this view, it has to be managed in this view.

但是缺少一件事, ConstraintLayoutViewGroupView实现不知道我们的属性声明,因此认为它们可以为我们管理属性是无用的。 好吧,由于我们为此视图声明了它们,因此必须在视图中对其进行管理。

Essentially, passing in the attributeSet and a reference to the styleable declaration denotes that all attributes present in the attributeSet object and also happens to be defined in R.styleable.FileDescriptor should be retrieved. This means if attributes xyz is defined in XML (layout) and XML (attr), it should be returned by Android. Whatever is returned is stored in a local attributes object.

本质上,传递attributeSet和对可样式化声明的引用表示应该检索存在于attributeSet对象中并且也恰好在R.styleable.FileDescriptor定义的所有属性。 这意味着,如果xyz属性是在XML(布局)和XML(属性)中定义的,则应由Android返回。 返回的所有内容都存储在本地属性对象中。

Now that we have the attributes, we have to identify which attribute is which. To achieve this every attribute has an ID attached to it, but we did not define any, so where does this come from?

现在我们有了属性,我们必须确定哪个属性是哪个。 为了实现这一点,每个属性都附加了一个ID,但是我们没有定义任何ID,那么它是从哪里来的呢?

It so happens that by default, the ID for every declared attribute is the name of the Custom View and the name of the attribute separated by an underscore. So, the ID for our name attribute is FileDescriptor_name, this is so that we don’t end up having clashes between attribute names (namespacing), of course one Custom View cannot have two attributes of the same name declared.

碰巧的是,默认情况下,每个声明的属性的ID都是“ 自定义视图”的名称,而属性的名称由下划线分隔。 因此,我们的名称属性的ID为FileDescriptor_name ,这样我们就不会在属性名称之间发生冲突(命名空间),当然,一个“ 自定义视图”不能声明两个具有相同名称的属性。

There are helper methods on the attributes object that help in getting the values of the set attributes as long as the ID is passed in. We call getBoolean on the attributes object passing the ID of the name attribute, and a default value. The default value is returned if the attribute was not set initially. We then return the Boolean value and store in showInfoByDefault. The same is done for viewBackground, just that this time, we are getting a resource. Now that we have gotten and stored the values of our attributes, we have to call recycle() on the attributes object to avoid memory leaks.

只要传入ID,属性对象上就可以使用帮助程序方法来获取设置属性的值。我们在传递名称属性ID和默认值的属性对象上调用getBoolean 。 如果属性最初未设置,则返回默认值。 然后,我们返回布尔值并将其存储在showInfoByDefault 。 对viewBackground进行相同的操作,只是这次,我们正在获取资源。 现在我们已经获取并存储了属性的值,我们必须在属性对象上调用recycle()以避免内存泄漏。

We then go ahead to set the background of the compound component to viewBackground (remember this defaults to R.blah.blah if none is set), and the visibility of the info section is determined by the showInfoByDefault variable. We can also use this time to set a click listener on the file_info toggle, when this is clicked, we invert the value of showInfoByDefault, and reset the visibility of the info section. Great!

然后,我们继续将复合组件的背景设置为viewBackground (请R.blah.blah如果未设置,则默认为R.blah.blah ),而info部分的可见性则由showInfoByDefault变量确定。 我们也可以利用这段时间在file_info开关上设置一个点击监听器,当点击它时,我们将反转showInfoByDefault的值,并重置info部分的可见性。 大!

The next step is to set the attributes up in the activity_main itself. So we go…

下一步是在activity_main本身中设置属性。 所以我们去...

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


    <dev.olaore.compoundcmpts_final.FileDescriptor
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        app:showInfoByDefault="true"
        app:viewBackground="@drawable/round_file_descriptor_background_three"
        android:id="@+id/file_descriptor"
        android:layout_marginRight="20dp"
        app:layout_constraintTop_toBottomOf="@id/select_file_button" />


</androidx.constraintlayout.widget.ConstraintLayout>

In the code above, we set the showInfoByDefault attribute to true, this automatically shows the info section and the viewBackground is set to a drawable resource. Running the app, we come up with this.

在上面的代码中,我们将showInfoByDefault属性设置为true,这会自动显示info部分,并且viewBackground设置为可绘制资源。 运行该应用程序,我们想到了这一点。

Image for post
demo for compound components after add different drawable for background as attributes and different state for the info section
在为背景添加不同的背景可绘制属性作为属性并为信息部分添加不同状态后,对复合组件进行演示

In the image above, the background drawable set as an attribute takes effect and the extra space towards the bottom is the info section showing by default.

在上图中,设置为属性的背景可绘制对象生效,默认情况下,显示在底部的额外空间是信息部分。

class FileDescriptor @JvmOverloads
    constructor(private val ctx: Context, private val attributeSet: AttributeSet? = null, private val defStyleAttr: Int = 0)
    : ConstraintLayout(ctx, attributeSet, defStyleAttr) {
      
      // ...
      
      init {
        
        // other initialization
        share_file_image.setOnClickListener { shareDescription() }
        
      }
      
      private fun shareDescription() {
        file?.let {


            val shareIntent = Intent().apply {
                action = Intent.ACTION_SEND
                putExtra(Intent.EXTRA_TEXT, file_info.text)
                type = "text/plain"
            }
            ctx.startActivity(Intent.createChooser(shareIntent, null))


            return
        }


        Toast.makeText(ctx, "You have to select a File first", Toast.LENGTH_LONG).show()
    }
      
      
      
      // ...
      
}

Finally, we need to add support to share the file’s information to other applications, we go on to add a new method shareDescription which makes sure the file object isn’t null before creating a share intent object. This is just a regular intent object with action set to Intent.ACTION_SEND and an extra attached to it which is the text to be shared. We then need to state the type of the data being shared to be text/plain, then the intent is launched, and we return from the block of code, if it happens to be null, we display a toast requiring a file to be selected initially.

最后,我们需要添加支持以将文件信息共享给其他应用程序,然后继续添加新的方法shareDescription ,以确保在创建共享意图对象之前文件对象不为null。 这只是一个常规的intent对象,其action设置为Intent.ACTION_SEND并附加了一个附加对象,即要共享的文本。 然后,我们需要将共享数据的类型声明为text/plain ,然后启动该意图,然后从代码块返回,如果碰巧为空,则显示一个祝酒,要求选择一个文件原来。

Running the app, we go on to select a video from the File system and the data is retrieved as programmed, clicking on the share icon brings up the sharing options android provides for the type of data that is to be shared, and selecting any of those consumers shares the data appropriately.

运行该应用程序,我们继续从“文件系统”中选择一个视频,然后按编程方式检索数据,单击“共享”图标将显示android为要共享的数据类型提供的共享选项,然后选择这些消费者适当地共享数据。

Image for post
Image for post
Left: demo app showing details about a selected video and the info section visible, Right: demo showing the collapsed version of the compound component
左:演示应用程序,显示有关所选视频的详细信息以及可见的信息部分,右:演示程序,显示复合组件的折叠版本

And when the share icon is clicked?

当单击share图标时?

Image for post
Image for post
Left: Android’s sharing provider giving a grid of options to which text/plain can be shared to, Right: The data being shared to Whatsapp
左:Android的共享提供程序,提供了可以与之共享文本/纯文本的选项网格,右:与Whatsapp共享的数据

下一步 (Next Steps)

We’ve come to the end of the series on Compound Components on Android. Well, it was a roller coaster ride and not so easy, new concepts flying here and there, but if you have any bugging questions, feel free to ask in the comments section or send me a DM on Twitter, or tag me in any of your tweets relating to the subject matter. Well, this component is not perfect as you can keep adding features to it. Some of them you can experiment with are:

我们已经结束了关于Android上的Composite Components的系列文章的结尾。 好吧,这只是坐过山车,而不是那么容易,新概念到处都是,但是,如果您有任何bug的问题,请随时在评论部分提问,或在Twitter上给我发送DM ,或在任何您与主题相关的推文。 好吧,这个组件并不完美,因为您可以不断为其添加功能。 您可以尝试的其中一些是:

  1. Hide the share and info icons if no file has been selected

    如果未选择文件,则隐藏共享和信息图标
  2. Embed the file selection process inside the component — we do it from outside the component with the Select File button in the activity_main file, you can find a way to put the functionality inside the component itself.

    将文件选择过程嵌入到组件内部–我们通过使用activity_main文件中的Select File按钮从组件外部进行选择,您可以找到一种将功能放入组件内部的方法。
  3. Resetting the component, a text or icon button that resets the compound component back to an empty state.

    重置组件时,可以使用文本或图标按钮将复合组件重置为空状态。
  4. Add more attributes, they are never too much

    添加更多属性,它们永远不会太多

Code for this project can be found here, thanks for reading! Cheers!!

该项目的代码可以在这里找到,感谢您的阅读! 干杯!!

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

kotlin 隐藏 组件

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐