在畢設項目中多處用到自定義控件,一直打算總結一下自定義控件的實現方式,今天就來總結一下吧。在此之前學習了郭霖大神博客上面關於自定義View的幾篇博文,感覺受益良多,本文中就參考了其中的一些內容。
總結來說,自定義控件的實現有三種方式,分別是:組合控件、自繪控件和繼承控件。下面將分別對這三種方式進行介紹。
(一)組合控件
組合控件,顧名思義就是將一些小的控件組合起來形成一個新的控件,這些小的控件多是系統自帶的控件。比如很多應用中普遍使用的標題欄控件,其實用的就是組合控件,那么下面將通過實現一個簡單的標題欄自定義控件來說說組合控件的用法。
1、新建一個Android項目,創建自定義標題欄的布局文件title_bar.xml:
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:background="#0000ff" > 6 7 <Button 8 android:id="@+id/left_btn" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:layout_centerVertical="true" 12 android:layout_margin="5dp" 13 android:background="@drawable/back1_64" /> 14 15 <TextView 16 android:id="@+id/title_tv" 17 android:layout_width="wrap_content" 18 android:layout_height="wrap_content" 19 android:layout_centerInParent="true" 20 android:text="這是標題" 21 android:textColor="#ffffff" 22 android:textSize="20sp" /> 23 24 </RelativeLayout>
可見這個標題欄控件還是比較簡單的,其中在左邊有一個返回按鈕,背景是一張事先准備好的圖片back1_64.png,標題欄中間是標題文字。
2、創建一個類TitleView,繼承自RelativeLayout:
1 public class TitleView extends RelativeLayout {
2
3 // 返回按鈕控件
4 private Button mLeftBtn;
5 // 標題Tv
6 private TextView mTitleTv;
7
8 public TitleView(Context context, AttributeSet attrs) {
9 super(context, attrs);
10
11 // 加載布局
12 LayoutInflater.from(context).inflate(R.layout.title_bar, this);
13
14 // 獲取控件
15 mLeftBtn = (Button) findViewById(R.id.left_btn);
16 mTitleTv = (TextView) findViewById(R.id.title_tv);
17
18 }
19
20 // 為左側返回按鈕添加自定義點擊事件
21 public void setLeftButtonListener(OnClickListener listener) {
22 mLeftBtn.setOnClickListener(listener);
23 }
24
25 // 設置標題的方法
26 public void setTitleText(String title) {
27 mTitleTv.setText(title);
28 }
29 }
在TitleView中主要是為自定義的標題欄加載了布局,為返回按鈕添加事件監聽方法,並提供了設置標題文本的方法。
3、在activity_main.xml中引入自定義的標題欄:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:id="@+id/main_layout" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <com.example.test.TitleView 8 android:id="@+id/title_bar" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" > 11 </com.example.test.TitleView> 12 13 </LinearLayout>
4、在MainActivity中獲取自定義的標題欄,並且為返回按鈕添加自定義點擊事件:
1 private TitleView mTitleBar;
2 mTitleBar = (TitleView) findViewById(R.id.title_bar);
3
4 mTitleBar.setLeftButtonListener(new OnClickListener() {
5
6 @Override
7 public void onClick(View v) {
8 Toast.makeText(MainActivity.this, "點擊了返回按鈕", Toast.LENGTH_SHORT)
9 .show();
10 finish();
11 }
12 });
5、運行效果如下:
這樣就用組合的方式實現了自定義標題欄,其實經過更多的組合還可以創建出功能更為復雜的自定義控件,比如自定義搜索欄等。
(二)自繪控件
自繪控件的內容都是自己繪制出來的,在View的onDraw方法中完成繪制。下面就實現一個簡單的計數器,每點擊它一次,計數值就加1並顯示出來。
1、創建CounterView類,繼承自View,實現OnClickListener接口:
1 public class CounterView extends View implements OnClickListener {
2
3 // 定義畫筆
4 private Paint mPaint;
5 // 用於獲取文字的寬和高
6 private Rect mBounds;
7 // 計數值,每點擊一次本控件,其值增加1
8 private int mCount;
9
10 public CounterView(Context context, AttributeSet attrs) {
11 super(context, attrs);
12
13 // 初始化畫筆、Rect
14 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
15 mBounds = new Rect();
16 // 本控件的點擊事件
17 setOnClickListener(this);
18 }
19
20 @Override
21 protected void onDraw(Canvas canvas) {
22 super.onDraw(canvas);
23
24 mPaint.setColor(Color.BLUE);
25 // 繪制一個填充色為藍色的矩形
26 canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
27
28 mPaint.setColor(Color.YELLOW);
29 mPaint.setTextSize(50);
30 String text = String.valueOf(mCount);
31 // 獲取文字的寬和高
32 mPaint.getTextBounds(text, 0, text.length(), mBounds);
33 float textWidth = mBounds.width();
34 float textHeight = mBounds.height();
35
36 // 繪制字符串
37 canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2
38 + textHeight / 2, mPaint);
39 }
40
41 @Override
42 public void onClick(View v) {
43 mCount ++;
44
45 // 重繪
46 invalidate();
47 }
48
49 }
2、在activity_main.xml中引入該自定義布局:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:id="@+id/main_layout" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <com.example.test.CounterView 8 android:id="@+id/counter_view" 9 android:layout_width="100dp" 10 android:layout_height="100dp" 11 android:layout_gravity="center_horizontal|top" 12 android:layout_margin="20dp" /> 13 14 </LinearLayout>
3、運行效果如下:

(三)繼承控件
就是繼承已有的控件,創建新控件,保留繼承的父控件的特性,並且還可以引入新特性。下面就以支持橫向滑動刪除列表項的自定義ListView的實現來介紹。
1、創建刪除按鈕布局delete_btn.xml,這個布局是在橫向滑動列表項后顯示的:
1 <?xml version="1.0" encoding="utf-8"?> 2 <Button xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="wrap_content" 4 android:layout_height="wrap_content" 5 android:background="#FF0000" 6 android:padding="5dp" 7 android:text="刪除" 8 android:textColor="#FFFFFF" 9 android:textSize="16sp" > 10 11 </Button>
2、創建CustomListView類,繼承自ListView,並實現了OnTouchListener和OnGestureListener接口:
1 public class CustomListView extends ListView implements OnTouchListener,
2 OnGestureListener {
3
4 // 手勢動作探測器
5 private GestureDetector mGestureDetector;
6
7 // 刪除事件監聽器
8 public interface OnDeleteListener {
9 void onDelete(int index);
10 }
11
12 private OnDeleteListener mOnDeleteListener;
13
14 // 刪除按鈕
15 private View mDeleteBtn;
16
17 // 列表項布局
18 private ViewGroup mItemLayout;
19
20 // 選擇的列表項
21 private int mSelectedItem;
22
23 // 當前刪除按鈕是否顯示出來了
24 private boolean isDeleteShown;
25
26 public CustomListView(Context context, AttributeSet attrs) {
27 super(context, attrs);
28
29 // 創建手勢監聽器對象
30 mGestureDetector = new GestureDetector(getContext(), this);
31
32 // 監聽onTouch事件
33 setOnTouchListener(this);
34 }
35
36 // 設置刪除監聽事件
37 public void setOnDeleteListener(OnDeleteListener listener) {
38 mOnDeleteListener = listener;
39 }
40
41 // 觸摸監聽事件
42 @Override
43 public boolean onTouch(View v, MotionEvent event) {
44 if (isDeleteShown) {
45 hideDelete();
46 return false;
47 } else {
48 return mGestureDetector.onTouchEvent(event);
49 }
50 }
51
52 @Override
53 public boolean onDown(MotionEvent e) {
54 if (!isDeleteShown) {
55 mSelectedItem = pointToPosition((int) e.getX(), (int) e.getY());
56 }
57 return false;
58 }
59
60 @Override
61 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
62 float velocityY) {
63 // 如果當前刪除按鈕沒有顯示出來,並且x方向滑動的速度大於y方向的滑動速度
64 if (!isDeleteShown && Math.abs(velocityX) > Math.abs(velocityY)) {
65 mDeleteBtn = LayoutInflater.from(getContext()).inflate(
66 R.layout.delete_btn, null);
67
68 mDeleteBtn.setOnClickListener(new OnClickListener() {
69
70 @Override
71 public void onClick(View v) {
72 mItemLayout.removeView(mDeleteBtn);
73 mDeleteBtn = null;
74 isDeleteShown = false;
75 mOnDeleteListener.onDelete(mSelectedItem);
76 }
77 });
78
79 mItemLayout = (ViewGroup) getChildAt(mSelectedItem
80 - getFirstVisiblePosition());
81
82 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
83 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
84 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
85 params.addRule(RelativeLayout.CENTER_VERTICAL);
86
87 mItemLayout.addView(mDeleteBtn, params);
88 isDeleteShown = true;
89 }
90
91 return false;
92 }
93
94 // 隱藏刪除按鈕
95 public void hideDelete() {
96 mItemLayout.removeView(mDeleteBtn);
97 mDeleteBtn = null;
98 isDeleteShown = false;
99 }
100
101 public boolean isDeleteShown() {
102 return isDeleteShown;
103 }
104
105 /**
106 * 后面幾個方法本例中沒有用到
107 */
108 @Override
109 public void onShowPress(MotionEvent e) {
110
111 }
112
113 @Override
114 public boolean onSingleTapUp(MotionEvent e) {
115 return false;
116 }
117
118 @Override
119 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
120 float distanceY) {
121 return false;
122 }
123
124 @Override
125 public void onLongPress(MotionEvent e) {
126
127 }
128
129 }
3、定義列表項布局custom_listview_item.xml,它的結構很簡單,只包含了一個TextView:
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:descendantFocusability="blocksDescendants" > 6 7 <TextView 8 android:id="@+id/content_tv" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:layout_centerVertical="true" 12 android:layout_margin="30dp" 13 android:gravity="center_vertical|left" /> 14 15 </RelativeLayout>
4、定義適配器類CustomListViewAdapter,繼承自ArrayAdapter<String>:
1 public class CustomListViewAdapter extends ArrayAdapter<String> {
2
3 public CustomListViewAdapter(Context context, int textViewResourceId,
4 List<String> objects) {
5 super(context, textViewResourceId, objects);
6 }
7
8 @Override
9 public View getView(int position, View convertView, ViewGroup parent) {
10 View view;
11
12 if (convertView == null) {
13 view = LayoutInflater.from(getContext()).inflate(
14 R.layout.custom_listview_item, null);
15 } else {
16 view = convertView;
17 }
18
19 TextView contentTv = (TextView) view.findViewById(R.id.content_tv);
20 contentTv.setText(getItem(position));
21
22 return view;
23 }
24
25 }
5、在activity_main.xml中引入自定義的ListView:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:id="@+id/main_layout" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <com.example.test.CustomListView 8 android:id="@+id/custom_lv" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" /> 11 12 </LinearLayout>
6、在MainActivity中對列表做初始化、設置列表項刪除按鈕點擊事件等處理:
1 public class MainActivity extends Activity {
2
3 // 自定義Lv
4 private CustomListView mCustomLv;
5 // 自定義適配器
6 private CustomListViewAdapter mAdapter;
7 // 內容列表
8 private List<String> contentList = new ArrayList<String>();
9
10 @Override
11 protected void onCreate(Bundle savedInstanceState) {
12 super.onCreate(savedInstanceState);
13 requestWindowFeature(Window.FEATURE_NO_TITLE);
14 setContentView(R.layout.activity_main);
15
16 initContentList();
17
18 mCustomLv = (CustomListView) findViewById(R.id.custom_lv);
19 mCustomLv.setOnDeleteListener(new OnDeleteListener() {
20
21 @Override
22 public void onDelete(int index) {
23 contentList.remove(index);
24 mAdapter.notifyDataSetChanged();
25 }
26 });
27
28 mAdapter = new CustomListViewAdapter(this, 0, contentList);
29 mCustomLv.setAdapter(mAdapter);
30 }
31
32 // 初始化內容列表
33 private void initContentList() {
34 for (int i = 0; i < 20; i++) {
35 contentList.add("內容項" + i);
36 }
37 }
38
39 @Override
40 public void onBackPressed() {
41 if (mCustomLv.isDeleteShown()) {
42 mCustomLv.hideDelete();
43 return;
44 }
45 super.onBackPressed();
46 }
47
48 }
7、運行效果如下:

Refer:http://blog.csdn.net/guolin_blog/article/details/17357967

