当有的设计布局使用的地方比较多,还涉及到需要变化视图数据内容,现有的组件无法满足设计需求的时候,常常需要自定义组件。本文介绍在Android日常开发中如何自定义组件,从使用原生代码到使用databinging绑定页面数据来定义自定义的View。
1.使用原生方法自定义组件
1.1 自定义属性
res/values文件中创建属性资源文件attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AppComponent">
<attr name="customSrc" format="integer" />
<attr name="customText" format="string" />
</declare-styleable>
</resources>
1.2 定义item布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<TextView
android:id="@+id/icon_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp" />
</LinearLayout>
1.3.绑定属性到视图
提供java和kotlin的两种写法
java写法:
package com.tosmart.launcher.myfile.component;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.tosmart.launcher.myfile.R;
public class AppComponent extends LinearLayout {
private ImageView mIcon;
private TextView mTitle;
public AppComponent(Context context) {
super(context);
}
public AppComponent(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(attrs);
}
public AppComponent(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(attrs);
}
private void initView(AttributeSet attributeSet) {
LayoutInflater.from(getContext()).inflate(R.layout.item_layout, this);
TypedArray array = getContext().obtainStyledAttributes(attributeSet, R.styleable.AppComponent);
mIcon = findViewById(R.id.icon);
mIcon.setImageDrawable(array.getDrawable(R.styleable.AppComponent_customSrc));
mTitle = findViewById(R.id.icon_title);
mTitle.setText(array.getText(R.styleable.AppComponent_customText));
array.recycle();
}
}
kotlin写法:
package com.tosmart.launcher.myfile.component
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.tosmart.launcher.myfile.R
class AppComponent (context: Context,attrs: AttributeSet ?):
LinearLayout(context,attrs){
private val customSrc : ImageView
private val customText : TextView
init {
LayoutInflater.from(getContext()).inflate(R.layout.item_layout, this)
val array = context.obtainStyledAttributes(attrs, R.styleable.AppComponent)
customSrc = findViewById(R.id.icon)
customSrc.setImageDrawable(array.getDrawable(R.styleable.AppComponent_customSrc))
customText = findViewById(R.id.icon_title)
customText.text = array.getText(R.styleable.AppComponent_customText)
array.recycle()
}
}
可以看到两种写法的不同,本自定义布局都继承LinearLayout并使用了构造函数,而kotlin构造函数中有两个参数,事实上布局中有4种构造方法,实现继承式实现的自定义布局必须要使用至少2个参数的构造方法,也就是上面用的这种。
- Context context:这是 Android 应用程序环境的上下文对象。提供访问应用程序资源和类的方法。
- AttributeSet attrs:这是一个 AttributeSet 对象,用于传递 XML 布局文件中定义的属性集合。在创建自定义 View 时,系统会将 XML 文件中定义的属性转化为 AttributeSet 对象传递给构造函数。这样可以在构造函数中获取 XML 中定义的属性值。
- int defStyleAttr:这是指向一个默认风格资源的引用,用于应用到此组件上。当 XML 布局文件中没有明确指定样式,则会使用这个默认风格资源。
- int defStyleRes:功能同defStyleAttr,只是引用风格资源的类型不一样。
![PDWKRHS%KR$F]}@_DJ7M028.png](https://img-blog.csdnimg.cn/img_convert/4d7354f29b956e0c4ffd2c433e5d63e5.png)
1.4.主布局引入自定义组件布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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">
<com.example.spotless.AppComponent
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:customSrc="@drawable/home"
app:customText="Your Custom Text" />
</LinearLayout>
2.使用databing
Data Binding 是一种用于在 Android 应用程序中实现声明式数据绑定的库。它允许您将 UI 组件与应用程序的数据模型直接绑定,从而简化了 UI 的更新和管理过程。
为什么要使用Databinding,以下有几点对比可以看出Databinding的优势所在。
对比属性 | 原始方式** ** | databinding |
---|---|---|
查找速度 | 使用findViewById进行深度优先查找,需要消耗一定时间,对于深层复杂布局性能低 | Databinding在编译时生成绑定类,将视图和数据模型进行静态绑定。运行时无需重复查找视图,速度更快。 |
数据更新 | 需手动查找视图后设置数据 | 设置数据绑定表达式自动绑定数据到视图,并且支持双向绑定 |
代码简洁性 | 存在大量样板代码查找和设置视图 | 布局文件中设置绑定表达式即可 |
类型安全性 | 手动设置数据容易运行时异常 | 提供类型安全的绑定表达式,可以在编译时就能检测错误,减少运行出错 |
自定义组件类中使用databing绑定视图,这里使用的是单向的绑定,对于双向绑定后面的博客再详细补充其具体使用。
package com.tosmart.launcher.myfile
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import com.tosmart.launcher.myfile.databinding.ItemLayoutBinding
class BindAppComponent(context: Context, attrs: AttributeSet?) :
LinearLayout(context, attrs) {
private val customSrc: ImageView
private val customText: TextView
init {
val binding: ItemLayoutBinding =
DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.item_layout, this, true)
val ta = context.obtainStyledAttributes(attrs, R.styleable.AppComponent)
customSrc = binding.icon
customSrc.setImageDrawable(ta.getDrawable(R.styleable.AppComponent_customSrc))
customText = binding.iconTitle
customText.text = ta.getText(R.styleable.AppComponent_customText)
ta.recycle()
}
}
对于单向绑定,自定义的布局和引用方式和原始的一样,延续使用之前的属性配置文件。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<com.tosmart.launcher.myfile.BindAppComponent
android:layout_width="100dp"
android:layout_height="100dp"
app:customSrc="@drawable/music"
app:customText="Music"
android:layout_centerInParent="true"/>
</RelativeLayout>
其实还可以通过组合的方式实现自定义组件,相比于继承方式会灵活一点,但是不能直接通过类名使用组件,感兴趣的话可以了解一下。
以上自定义实现的效果如下,主要实现可以设置不同的图标和标题的布局。
自定义绑定属性
可以通过注解@BindingAdapter创建自定义的绑定属性,可以设置一些绑定属性是全局可用的,就不再需要通过属性配置资源文件中设置属性。
public class CustomBindingAdapters {
@JvmStatic
@BindingAdapter("isCollapseGroup") //使用注解定义一个自定义属性
public static void setIsCollapseGroup(View view, boolean isCollapse) {
if (isCollapse) {
view.setVisibility(View.GONE);
} else {
view.setVisibility(View.VISIBLE);
}
}
}
在视图中通过自定义数据绑定数据:注意的是使用该注解创建的属性只能用于绑定数据,也就是等号右边一定是"@{viewModel.isGroupCollapsed}"类似的绑定表达式,不能是固定值。
<LinearLayout
android:id="@+id/myLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isCollapseGroup="@{viewModel.isGroupCollapsed}">
</LinearLayout>