前言
Android 項目中避免不了會使用自定義控件,主要能夠避免代碼的冗余,使用起來也很靈活,而且也方便后期移植入其他項目。自定義控件也是面試中經(jīng)常問到的東西,寫過挺多自定義控件但是一直沒有系統(tǒng)的總結(jié)過,今天再重新系統(tǒng)的學(xué)習(xí)一下。
自定義控件主要分為三類,自定義組合控件,自定義繪制控件,自定義繼承控件。
1.自定義組合控件是項目中經(jīng)常會用到,像標題欄,復(fù)用率較高的布局。
2.自定義繪制控件是面試中經(jīng)常會問到的,如果需求復(fù)雜,繪制的流程也會難度加大(后期會詳細介紹自定義控件的繪制)
3.自定義繼承控件主要是針對Android 原生的控件進行擴展,像自定義listView 。
下面我們來詳細說一下這三種自定義控件
一.自定義組合控件
組合控件的意思就是,我們并不需要自己去繪制視圖上顯示的內(nèi)容,而只是用系統(tǒng)原生的控件就好了,但我們可以將幾個系統(tǒng)原生的控件組合到一起,這樣創(chuàng)建出的控件就被稱為組合控件。
場景:一般一個項目中所有頁面的標題欄大體都差不多,所以我們沒有必要去每個頁面都重寫寫布局,這個時候我們就可以用自定義組合控件
1.第一步:新建一個title.xml布局文件,代碼如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/lay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<!--返回鍵-->
<ImageView
android:id="@+id/title_icon"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:paddingLeft="15dp"
android:src="@mipmap/black_back" />
<!--關(guān)閉按鈕-->
<TextView
android:id="@+id/tv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingLeft="15dp"
android:text="關(guān)閉"
android:textColor="#333333"
android:textSize="15sp"
android:visibility="gone" />
<!--標題-->
<TextView
android:id="@+id/title_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="13dp"
android:layout_marginTop="13dp"
android:ellipsize="end"
android:maxLength="13"
android:singleLine="true"
android:textColor="#010101"
android:textSize="17sp" />
<!--右側(cè)提示字和圖標-->
<LinearLayout
android:id="@+id/ll_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<!--圖標-->
<ImageView
android:id="@+id/iv_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="3dp" />
<!--提示文字-->
<TextView
android:id="@+id/tv_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="15dp"
android:textColor="#333333"
android:textSize="15sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@+id/title_name"
android:background="@color/line_d7" />
</RelativeLayout>
UI效果如圖

2.第二步:接下來我們來創(chuàng)建一個View 集成RelativeLayout 如
public class WhitePublicTitleView extends RelativeLayout {
View mView;
ImageView title_icon;
TextView title_name;
RelativeLayout lay;
ImageView iv_right;
TextView tv_right;
LinearLayout ll_right;
TextView tv_close;
public WhitePublicTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WhitePublicTitleView(Context context) {
super(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mView = LayoutInflater.from(getContext()).inflate(R.layout.public_title_view_white, this);
initView();
}
public void setTitleBg() {
lay.setBackgroundColor(Color.parseColor("#de3031"));
}
public void initView() {
title_icon = (ImageView) mView.findViewById(R.id.title_icon);
title_name = (TextView) mView.findViewById(R.id.title_name);
lay = (RelativeLayout) mView.findViewById(R.id.lay);
iv_right = (ImageView) mView.findViewById(R.id.iv_right);
tv_right = (TextView) mView.findViewById(R.id.tv_right);
tv_close = (TextView) mView.findViewById(R.id.tv_close);
ll_right = (LinearLayout) mView.findViewById(R.id.ll_right);
}
public void setBackListener(OnClickListener clickListener) {
title_icon.setOnClickListener(clickListener);
tv_close.setOnClickListener(clickListener);
}
public void setBackState(int state) {
title_icon.setVisibility(state);
}
public void setTitleNam(String name) {
title_name.setText(name);
}
public void setRight(String state, int icon) {
tv_right.setText(state);
iv_right.setImageResource(icon);
}
public void setRightListener(OnClickListener clickListener) {
ll_right.setOnClickListener(clickListener);
}
//展示文字關(guān)閉按鈕
public void showTextClose() {
tv_close.setVisibility(View.VISIBLE);
title_icon.setVisibility(View.GONE);
}
}
3.第三步:我們使用自定義組合控件
布局文件引入
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/f2"
android:divider="@drawable/divider"
android:orientation="vertical">
<!--標題-->
<com.jyjt.ydyl.Widget.WhitePublicTitleView
android:id="@+id/title_collect"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
代碼初始化具體數(shù)據(jù)
title_collect= (ImageView) mView.findViewById(R.id.title_collect);
title_collect.setTitleNam("我的收藏");
title_collect.setBackListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SwitchActivityManager.exitActivity(MyCollectActivity.this);
}
});
總結(jié):自定義控件在創(chuàng)建和使用起來都很簡單。
二.自定義繪制控件
自定義繪制控件的意思就是,這個View上所展現(xiàn)的內(nèi)容全部都是我們自己繪制出來的。繪制的代碼是寫在onDraw()方法中的
場景:自定義一個計數(shù)器View,這個View可以響應(yīng)用戶的點擊事件,并自動記錄一共點擊了多少次。新建一個CounterView繼承自View,代碼如下所示:
1.第一步:創(chuàng)建一個類集成View 進行繪制
public class CounterView extends View implements OnClickListener {
private Paint mPaint;
private Rect mBounds;
private int mCount;
public CounterView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBounds = new Rect();
setOnClickListener(this);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(Color.BLUE);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
mPaint.setColor(Color.YELLOW);
mPaint.setTextSize(30);
String text = String.valueOf(mCount);
mPaint.getTextBounds(text, 0, text.length(), mBounds);
float textWidth = mBounds.width();
float textHeight = mBounds.height();
canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2+ textHeight / 2, mPaint);
}
@Override
public void onClick(View v) {
mCount++;
invalidate();
}
}
可以看到,首先我們在onClick()方法調(diào)用調(diào)用invalidate()方法會導(dǎo)致視圖進行重繪,因此onDraw()方法在稍后就將會得到調(diào)用。
主要的邏輯當(dāng)然就是寫在onDraw()方法里首先是將Paint畫筆設(shè)置為藍色,然后調(diào)用Canvas的drawRect()方法繪制了一個矩形背景,接著將畫筆設(shè)置為黃色,繪制當(dāng)前的計數(shù),注意這里先是調(diào)用了getTextBounds()方法來獲取到文字的寬度和高度,然后調(diào)用了drawText()方法去進行繪制。
2.第二步:使用自定義繪制控件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.customview.CounterView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true" />
</RelativeLayout>
效果如圖:

可以看到,這里我們將CounterView放入了一個RelativeLayout中,然后可以像使用普通控件來給CounterView指定各種屬性,比如通過layout_width和layout_height來指定CounterView的寬高,通過android:layout_centerInParent來指定它在布局里居中顯示。只不過需要注意,自定義的View在使用的時候一定要寫出完整的包名,不然系統(tǒng)將無法找到這個View。
三.自定義繼承控件
繼承控件,我們并不需要自己重頭去實現(xiàn)一個控件,只需要去繼承一個現(xiàn)有的控件,然后在這個控件上增加一些新的功能,就可以形成一個自定義的控件了。這種自定義控件的特點就是不僅能夠按照我們的需求加入相應(yīng)的功能,還可以保留原生控件的所有功能。
場景:我們做一個簡單的自定義一個diaolog ,點擊確定按鈕消失,(主要集成具體的原生控件)
第一步:創(chuàng)建一個布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/confirm_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_corners_white"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="5dip"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="18dp"
android:layout_marginTop="24dp"
android:src="@mipmap/ic_build_dialog" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dip"
android:background="@color/line" />
<Button
android:id="@+id/ok_btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/commenttext"
android:gravity="center"
android:text="知道了"
android:textColor="@color/de30"
android:textSize="16sp" />
</LinearLayout>
第二部:創(chuàng)建一個類繼承Dialog
public class BuildingDialog extends Dialog {
public View mView;
LayoutInflater inflater;
Button ok_btn;
public Context mContext;
DialogCallBack mDialogCallBack;
public BuildingDialog(Context context) {
this(context, R.style.dialog, null);
this.mContext = context;
}
public BuildingDialog(Context context, int themeResId, String content) {
super(context, themeResId);
this.mContext = context;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
inflater = LayoutInflater.from(mContext);
mView = inflater.inflate(R.layout.dialog_build, null);
setContentView(mView);
initView();
ok_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
// mDialogCallBack.clickokBtn();
}
});
}
public void initView() {
ok_btn = (Button) mView.findViewById(R.id.ok_btn);
}
public void setDialogCallBack(DialogCallBack dialogCallBack) {
this.mDialogCallBack = dialogCallBack;
}
public interface DialogCallBack {
void clickokBtn();
}
}
第三步:使用自定義繼承控件
BuildingDialog mBuildingDialog= new BuildingDialog(mContext);
mBuildingDialog.show(); //展示
mBuildingDialog.setDialogCallBack(this); //回調(diào)監(jiān)聽
四.自定義屬性
使用自定義控件的時候有時會創(chuàng)建自己的控件的屬性,下面我們來講講自定義屬性怎么用
主要步驟
1.定義declare-styleable,添加attr
2.使用TypedArray獲取自定義屬性
3.設(shè)置到View上
第一步:定義declare-styleable,添加attr
第二步:稍后更新
第三步:稍后更新
總結(jié):
雖然每個例子都很簡單,但是萬變不離其宗,復(fù)雜的View也是由這些簡單的原理堆積出來的。后期會寫深入了解View系列的文章,感謝大家有耐心看到最后。