前言
上一章《Android View繪製及事件(三)自定義View及View繪製流程》介紹了自定義View的實現方式大致有三種:自定義組合控件、繼承系統控件(如TextView、ImageView等)、繼承View或ViewGroup。這章專門學習自定義組合控件實現方式,將多個控件組合成爲一個新的控件,能夠解決多次重複使用同一類型的佈局的問題。
另外介紹一下,本章將使用主流的約束佈局ConstraintLayout來實現。關於約束佈局我們要知道,從 Android Studio 2.3 起,官方的模板默認使用 ConstraintLayout,可以在Api9以上的Android系統使用它。由於佈局嵌套過多的問題會給導致系統繪製視圖所需的時間和計算功耗加重,所以它的出現主要作用是爲了解決佈局嵌套過多的問題。同時它可以進行靈活的方式定位和調整控件,因爲它結合了RelativeLayout和LinearLayout兩者的優點,即設置控件的相對位置並按照比例約束控件位置和尺寸,能夠更好地適配屏幕大小不同的機型。
廢話少說,上車!
提示:記得在Gradle中添加約束佈局ConstraintLayout依賴:
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
一、編寫ConstraintLayout佈局文件
1、TextView居中顯示
一般使用RelativeLayout會這樣寫,如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AAA" android:textSize="sp18"
android:textColor="@color/black"
android:layout_centerInParent="true"/>
</RelativeLayout>
而,使用ConstraintLayout會這樣寫,如下所示:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/view_order_item_layout"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AAA"
android:textSize="sp18"
android:textColor="@color/black"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
*約束佈局的相對位置常用屬性:
- layout_constraintLeft_toLeftOf
- layout_constraintLeft_toRightOf
- layout_constraintRight_toLeftOf
- layout_constraintRight_toRightOf
- layout_constraintTop_toTopOf
- layout_constraintTop_toBottomOf
- layout_constraintBottom_toTopOf
- layout_constraintBottom_toBottomOf
- layout_constraintBaseline_toBaselineOf
- layout_constraintStart_toEndOf
- layout_constraintStart_toStartOf
- layout_constraintEnd_toStartOf
- layout_constraintEnd_toEndOf
*上面屬性中有一個比較有趣的layout_constraintBaseline_toBaselineOf ,它是橫向與其他佈局或控件平衡。
*如果居中了,再想向左右偏移該怎麼辦?一般使用 layout_marginLeft="100dp" 就可以向右偏移了100dp。另一個方法是水平、垂直偏移屬性是:layout_constraintHorizontal_bias 、layout_constraintVertical_bias。
假如現在要實現水平偏移,給TextView1的layout_constraintHorizontal_bias賦一個範圍爲 0-1 的值,假如賦值爲0,則TextView1在佈局的最左側,假如賦值爲1,則TextView1在佈局的最右側,假如假如賦值爲0.5,則水平居中,假如假如賦值爲0.3,則更傾向於左側。垂直偏移同理。
2、鏈式縱橫
如果兩個或以上控件成橫向或豎向約束在一起,就可以認爲是他們是一條鏈。一條鏈的第一個控件是這條鏈的鏈頭,我們可以在鏈頭中設置 layout_constraintHorizontal_chainStyle、layout_constraintVertical_chainStyle來改變整條鏈的樣式。
chains提供了三種樣式值:
CHAIN_SPREAD :展開元素 (默認);
CHAIN_SPREAD_INSIDE : 兩端貼近parent;
CHAIN_PACKED :鏈的元素將被打包在一起。
然後,將控件首尾鏈接起來:
鏈頭鏈尾:
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
鏈中:
app:layout_constraintEnd_toStartOf="@+id/textView3"
app:layout_constraintStart_toEndOf="@+id/textView"
代碼如下:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/view_order_item_layout"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/view_order_item_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AAA"
android:textSize="@dimen/sp18"
android:textColor="@color/black"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="@id/view_order_item_right"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/view_order_item_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BBB"
android:textSize="@dimen/sp18"
android:textColor="@color/black"
app:layout_constraintLeft_toLeftOf="@id/view_order_item_left"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
3、權重
除了樣式鏈外,還可以創建一個權重鏈。可以留意到上面所用到的TextView寬度都爲wrap_content,如果我們把寬度都設爲0dp,這個時候可以在每個TextView中設置橫向權重layout_constraintHorizontal_weight、layout_constraintVertical_weight爲來創建一個權重鏈,作用於LinearLayout的 android:layout_weight="1" 效果一樣。
4、邊距
在其他佈局中的寫法一般都是直接 android:layout_margin="dp" ;但是如果沒有約束該控件在ConstraintLayout佈局裏的位置那就會失效。
邊距常用屬性如下:
android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/view_order_item_layout"
android:layout_width="match_parent"android:layout_height="50dp">
<TextView
android:id="@+id/view_order_item_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AAA"
android:textSize="@dimen/sp18"
android:textColor="@color/black"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="@id/view_order_item_right"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="20dp"/>
<TextView
android:id="@+id/view_order_item_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BBB"
android:textSize="@dimen/sp18"
android:textColor="@color/black"
app:layout_constraintLeft_toLeftOf="@id/view_order_item_left"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="20dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
二、構造函數
public class CombineTextReveal extends ConstraintLayout {
TextView mTextLeft;
TextView mTextRight;
ImageView mImgIcon;
public CombineTextReveal(Context context) {
super(context);
initView(context);
}
public CombineTextReveal(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public CombineTextReveal(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
//初始化UI,可根據業務需求設置默認值。
private void initView(Context context) {
LayoutInflater.from(context).inflate(R.layout.view_view_order, this, true);
mTextLeft = (TextView) findViewById(R.id.view_order_item_left);
mTextRight = (TextView) findViewById(R.id.view_order_item_right);
mImgIcon = (ImageView) findViewById(R.id.go_in_img);
}
//設置標題文字的方法
private void setmTextLeft(String str) {
if (!TextUtils.isEmpty(str)) {
mTextLeft.setText(str);
}
}
private void setmTextRight(String title) {
if (!TextUtils.isEmpty(title)) {
mTextRight.setText(title);
}
}
//對左邊按鈕設置事件的方法
private void setLeftListener(OnClickListener onClickListener) {
mImgIcon.setOnClickListener(onClickListener);
}
}
接下來,就可以在其他佈局當中引用該控件了,然後通過id可以拿到組合控件的實例調用內部的方法。
<?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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.daojia.sheeprestaurant.widgets.CombineTextReveal
android:id="@+id/combineText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</com.daojia.sheeprestaurant.widgets.CombineTextReveal>
</androidx.constraintlayout.widget.ConstraintLayout>
到這裏組合控件基本就算完成了。除了這些基礎功能外,我們還可以做一些功能擴展,比如可以在佈局時設置View顯示或隱藏,因爲可能有某些元素不需要進行顯示,那麼這時候就需要用到自定義屬性來解決了。
三、自定義屬性控件
1、屬性值的類型format
(1). reference:資源ID
屬性定義:
<declare-styleable name = "名稱">
<attr name = "background" format = "reference" />
</declare-styleable>
屬性使用:
<ImageView android:background = "@drawable/圖片ID"/>
(2). color:顏色值
屬性定義:
<attr name = "textColor" format = "color" />
屬性使用:
<TextView android:textColor = "#00FF00" />
(3). boolean:布爾值
屬性定義:
<attr name = "focusable" format = "boolean" />
屬性使用:
<Button android:focusable = "true"/>
(4). dimension:尺寸值
屬性定義:
<attr name = "layout_width" format = "dimension" />
屬性使用:
<Button android:layout_width = "42dip"/>
(5). float:浮點值
屬性定義:
<attr name = "fromAlpha" format = "float" />
屬性使用:
<alpha android:fromAlpha = "1.0"/>
(6). integer:整型值
屬性定義:
<attr name = "framesCount" format="integer" />
屬性使用:
<animated-rotate android:framesCount = "12"/>
(7). string:字符串
屬性定義:
<attr name = "text" format = "string" />
屬性使用:
<TextView android:text = "我是文本"/>
(8). fraction:百分數
屬性定義:
<attr name = "pivotX" format = "fraction" />
屬性使用:
<rotate android:pivotX = "200%"/>
(9). enum:枚舉值
屬性定義:
<declare-styleable name="名稱">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
屬性使用:
<LinearLayout
android:orientation = "vertical">
</LinearLayout>
注意:枚舉類型的屬性在使用的過程中只能同時使用其中一個,不能 android:orientation = “horizontal|vertical"
(10). flag:位或運算
屬性定義:
<declare-styleable name="名稱">
<attr name="gravity">
<flag name="top" value="0x01" />
<flag name="bottom" value="0x02" />
<flag name="left" value="0x04" />
<flag name="right" value="0x08" />
<flag name="center_vertical" value="0x16" />
...
</attr>
</declare-styleable>
屬性使用:
<TextView android:gravity="bottom|left"/>
注意:位運算類型的屬性在使用的過程中可以使用多個值
(11). 混合類型:屬性定義時可以指定多種類型值
屬性定義:
<declare-styleable name = "名稱">
<attr name = "background" format = "reference|color" />
</declare-styleable>
屬性使用:
<ImageView
android:background = "@drawable/圖片ID" />
或者:
<ImageView
android:background = "#00FF00" />
四、實例
Android自定義屬性可分爲以下幾步:
- 創建自定義View
- 在values/attrs.xml文件中聲明自定義屬性
- 在要調用自定義view的佈局文件中設置它的自定義屬性值
- 在自定義View的構造方法中通過TypedArray獲取
-
創建自定義View
<?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"
android:id="@+id/view_order_item_layout"
android:layout_width="match_parent"
android:layout_height="70dp">
<TextView
android:id="@+id/view_order_item_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AAA"
android:textSize="@dimen/sp18"
android:textColor="@color/black"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="@id/view_order_item_right"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="20dp"/>
<TextView
android:id="@+id/view_order_item_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BBB"
android:textSize="@dimen/sp18"
android:textColor="@color/black"
app:layout_constraintLeft_toLeftOf="@id/view_order_item_left"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@id/go_in_img"
app:layout_constraintEnd_toStartOf="@id/go_in_img"
android:layout_marginEnd="20dp"/>
<ImageView
android:id="@+id/go_in_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_cloes"
android:scaleType="fitXY"
app:layout_constraintLeft_toRightOf="@id/view_order_item_right"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
></ImageView>
<View android:layout_width="match_parent"
android:layout_height="@dimen/dp1"
android:background="@color/gap_line"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="10dp" android:layout_marginEnd="10dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
-
自定義屬性的聲明(declare)
<declare-styleable name="CombineTextReveal">
<attr name="setLeftText" format="string"></attr>
<attr name="setRighText" format="string"></attr>
<attr name="setVisible" format = "boolean"/>
</declare-styleable>
-
自定義View類
public CombineTextReveal(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
initAttrs(context,attrs);
}
public CombineTextReveal(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
initAttrs(context,attrs);
}
//在View的構造方法中通過TypedArray獲取
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.CombineTextReveal);
String leftStr = mTypedArray.getString(R.styleable.CombineTextReveal_setLeftText);
if (!TextUtils.isEmpty(leftStr)) {
mTextLeft.setText(leftStr);
}
String rightStr = mTypedArray.getString(R.styleable.CombineTextReveal_setRighText);
if (!TextUtils.isEmpty(rightStr)) {
mTextRight.setText(rightStr);
}
boolean isVisible =mTypedArray.getBoolean(R.styleable.CombineTextReveal_setVisible,true);
if (isVisible){
mImgIcon.setVisibility(VISIBLE);
}else{
mImgIcon.setVisibility(GONE);
}
mTypedArray.recycle();
}
-
佈局文件中使用
<xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.daojia.sheeprestaurant.widgets.CombineTextReveal
android:id="@+id/combineText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:setLeftText="訂單金額"
app:setRighText="¥0.00"
app:setVisible="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</com.daojia.sheeprestaurant.widgets.CombineTextReveal>
</androidx.constraintlayout.widget.ConstraintLayout>
-
效果圖
參考鏈接:https://www.jianshu.com/p/17ec9bd6ca8a