在毕设项目中多处用到自定义控件,一直打算总结一下自定义控件的实现方式,今天就来总结一下吧。在此之前学习了郭霖大神博客上面关于自定义View的几篇博文,感觉受益良多,本文中就参考了其中的一些内容。
总结来说,自定义控件的实现有三种方式,分别是:组合控件、自绘控件和继承控件。下面将分别对这三种方式进行介绍。
(一)组合控件
组合控件,顾名思义就是将一些小的控件组合起来形成一个新的控件,这些小的控件多是系统自带的控件。比如很多应用中普遍使用的标题栏控件,其实用的就是组合控件,那么下面将通过实现一个简单的标题栏自定义控件来说说组合控件的用法。
1、新建一个Android项目,创建自定义标题栏的布局文件title_bar.xml:
1 <?xml version="1.0" encoding="utf-8"?>
2
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 android:background="#0000ff" >
6
7
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
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
可见这个标题栏控件还是比较简单的,其中在左边有一个返回按钮,背景是一张事先准备好的图片back1_64.png,标题栏中间是标题文字。
2、创建一个类TitleView,继承自RelativeLayout:
1 public class TitleView extendsRelativeLayout {2
3 //返回按钮控件
4 privateButton mLeftBtn;5 //标题Tv
6 privateTextView mTitleTv;7
8 publicTitleView(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 voidsetLeftButtonListener(OnClickListener listener) {22 mLeftBtn.setOnClickListener(listener);23 }24
25 //设置标题的方法
26 public voidsetTitleText(String title) {27 mTitleTv.setText(title);28 }29 }
在TitleView中主要是为自定义的标题栏加载了布局,为返回按钮添加事件监听方法,并提供了设置标题文本的方法。
3、在activity_main.xml中引入自定义的标题栏:
1
2 android:id="@+id/main_layout"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:orientation="vertical" >
6
7
9 android:layout_width="match_parent"
10 android:layout_height="wrap_content" >
11
12
13
4、在MainActivity中获取自定义的标题栏,并且为返回按钮添加自定义点击事件:
1 privateTitleView mTitleBar;2 mTitleBar =(TitleView) findViewById(R.id.title_bar);3
4 mTitleBar.setLeftButtonListener(newOnClickListener() {5
6 @Override7 public voidonClick(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 implementsOnClickListener {2
3 //定义画笔
4 privatePaint mPaint;5 //用于获取文字的宽和高
6 privateRect mBounds;7 //计数值,每点击一次本控件,其值增加1
8 private intmCount;9
10 publicCounterView(Context context, AttributeSet attrs) {11 super(context, attrs);12
13 //初始化画笔、Rect
14 mPaint = newPaint(Paint.ANTI_ALIAS_FLAG);15 mBounds = newRect();16 //本控件的点击事件
17 setOnClickListener(this);18 }19
20 @Override21 protected voidonDraw(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 @Override42 public voidonClick(View v) {43 mCount ++;44
45 //重绘
46 invalidate();47 }48
49 }
2、在activity_main.xml中引入该自定义布局:
1
2 android:id="@+id/main_layout"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:orientation="vertical" >
6
7
9 android:layout_width="100dp"
10 android:layout_height="100dp"
11 android:layout_gravity="center_horizontal|top"
12 android:layout_margin="20dp" />
13
14
3、运行效果如下:
(三)继承控件
就是继承已有的控件,创建新控件,保留继承的父控件的特性,并且还可以引入新特性。下面就以支持横向滑动删除列表项的自定义ListView的实现来介绍。
1、创建删除按钮布局delete_btn.xml,这个布局是在横向滑动列表项后显示的:
1 <?xml version="1.0" encoding="utf-8"?>
2
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
2、创建CustomListView类,继承自ListView,并实现了OnTouchListener和OnGestureListener接口:
1 public class CustomListView extends ListView implementsOnTouchListener,2 OnGestureListener {3
4 //手势动作探测器
5 privateGestureDetector mGestureDetector;6
7 //删除事件监听器
8 public interfaceOnDeleteListener {9 void onDelete(intindex);10 }11
12 privateOnDeleteListener mOnDeleteListener;13
14 //删除按钮
15 privateView mDeleteBtn;16
17 //列表项布局
18 privateViewGroup mItemLayout;19
20 //选择的列表项
21 private intmSelectedItem;22
23 //当前删除按钮是否显示出来了
24 private booleanisDeleteShown;25
26 publicCustomListView(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 voidsetOnDeleteListener(OnDeleteListener listener) {38 mOnDeleteListener =listener;39 }40
41 //触摸监听事件
42 @Override43 public booleanonTouch(View v, MotionEvent event) {44 if(isDeleteShown) {45 hideDelete();46 return false;47 } else{48 returnmGestureDetector.onTouchEvent(event);49 }50 }51
52 @Override53 public booleanonDown(MotionEvent e) {54 if (!isDeleteShown) {55 mSelectedItem = pointToPosition((int) e.getX(), (int) e.getY());56 }57 return false;58 }59
60 @Override61 public boolean onFling(MotionEvent e1, MotionEvent e2, floatvelocityX,62 floatvelocityY) {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(newOnClickListener() {69
70 @Override71 public voidonClick(View v) {72 mItemLayout.removeView(mDeleteBtn);73 mDeleteBtn = null;74 isDeleteShown = false;75 mOnDeleteListener.onDelete(mSelectedItem);76 }77 });78
79 mItemLayout =(ViewGroup) getChildAt(mSelectedItem80 -getFirstVisiblePosition());81
82 RelativeLayout.LayoutParams params = newRelativeLayout.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 voidhideDelete() {96 mItemLayout.removeView(mDeleteBtn);97 mDeleteBtn = null;98 isDeleteShown = false;99 }100
101 public booleanisDeleteShown() {102 returnisDeleteShown;103 }104
105 /**
106 * 后面几个方法本例中没有用到107 */
108 @Override109 public voidonShowPress(MotionEvent e) {110
111 }112
113 @Override114 public booleanonSingleTapUp(MotionEvent e) {115 return false;116 }117
118 @Override119 public boolean onScroll(MotionEvent e1, MotionEvent e2, floatdistanceX,120 floatdistanceY) {121 return false;122 }123
124 @Override125 public voidonLongPress(MotionEvent e) {126
127 }128
129 }
3、定义列表项布局custom_listview_item.xml,它的结构很简单,只包含了一个TextView:
1 <?xml version="1.0" encoding="utf-8"?>
2
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:descendantFocusability="blocksDescendants" >
6
7
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
4、定义适配器类CustomListViewAdapter,继承自ArrayAdapter:
1 public class CustomListViewAdapter extends ArrayAdapter{2
3 public CustomListViewAdapter(Context context, inttextViewResourceId,4 Listobjects) {5 super(context, textViewResourceId, objects);6 }7
8 @Override9 public View getView(intposition, 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 returnview;23 }24
25 }
5、在activity_main.xml中引入自定义的ListView:
1
2 android:id="@+id/main_layout"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:orientation="vertical" >
6
7
9 android:layout_width="match_parent"
10 android:layout_height="wrap_content" />
11
12
6、在MainActivity中对列表做初始化、设置列表项删除按钮点击事件等处理:
1 public class MainActivity extendsActivity {2
3 //自定义Lv
4 privateCustomListView mCustomLv;5 //自定义适配器
6 privateCustomListViewAdapter mAdapter;7 //内容列表
8 private List contentList = new ArrayList();9
10 @Override11 protected voidonCreate(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(newOnDeleteListener() {20
21 @Override22 public void onDelete(intindex) {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 voidinitContentList() {34 for (int i = 0; i < 20; i++) {35 contentList.add("内容项" +i);36 }37 }38
39 @Override40 public voidonBackPressed() {41 if(mCustomLv.isDeleteShown()) {42 mCustomLv.hideDelete();43 return;44 }45 super.onBackPressed();46 }47
48 }
7、运行效果如下: