总结来说,自定义控件的实现有三种方式,分别是:组合控件、自绘控件和继承控件。下面将分别对这三种方式进行介绍。
(一)组合控件
组合控件,顾名思义就是将一些小的控件组合起来形成一个新的控件,这些小的控件多是系统自带的控件。比如很多应用中普遍使用的标题栏控件,其实用的就是组合控件,那么下面将通过实现一个简单的标题栏自定义控件来说说组合控件的用法。
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
1 public class CustomListViewAdapter extends ArrayAdapter{ 2 3 public CustomListViewAdapter(Context context, int textViewResourceId, 4 List 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 ListcontentList = new ArrayList (); 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、运行效果如下: