Android自定义View的三种实现方式总结
发布时间 - 2026-01-10 23:07:07 点击率:次在毕设项目中多处用到自定义控件,一直打算总结一下自定义控件的实现方式,今天就来总结一下吧。在此之前学习了郭霖大神博客上面关于自定义View的几篇博文,感觉受益良多,本文中就参考了其中的一些内容。

总结来说,自定义控件的实现有三种方式,分别是:组合控件、自绘控件和继承控件。下面将分别对这三种方式进行介绍。
(一)组合控件
组合控件,顾名思义就是将一些小的控件组合起来形成一个新的控件,这些小的控件多是系统自带的控件。比如很多应用中普遍使用的标题栏控件,其实用的就是组合控件,那么下面将通过实现一个简单的标题栏自定义控件来说说组合控件的用法。
1、新建一个Android项目,创建自定义标题栏的布局文件title_bar.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#0000ff" >
<Button
android:id="@+id/left_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_margin="5dp"
android:background="@drawable/back1_64" />
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="这是标题"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>
可见这个标题栏控件还是比较简单的,其中在左边有一个返回按钮,背景是一张事先准备好的图片back1_64.png,标题栏中间是标题文字。
2、创建一个类TitleView,继承自RelativeLayout:
public class TitleView extends RelativeLayout {
// 返回按钮控件
private Button mLeftBtn;
// 标题Tv
private TextView mTitleTv;
public TitleView(Context context, AttributeSet attrs) {
super(context, attrs);
// 加载布局
LayoutInflater.from(context).inflate(R.layout.title_bar, this);
// 获取控件
mLeftBtn = (Button) findViewById(R.id.left_btn);
mTitleTv = (TextView) findViewById(R.id.title_tv);
}
// 为左侧返回按钮添加自定义点击事件
public void setLeftButtonListener(OnClickListener listener) {
mLeftBtn.setOnClickListener(listener);
}
// 设置标题的方法
public void setTitleText(String title) {
mTitleTv.setText(title);
}
}
在TitleView中主要是为自定义的标题栏加载了布局,为返回按钮添加事件监听方法,并提供了设置标题文本的方法。
3、在activity_main.xml中引入自定义的标题栏:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.test.TitleView
android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</com.example.test.TitleView>
</LinearLayout>
4、在MainActivity中获取自定义的标题栏,并且为返回按钮添加自定义点击事件:
private TitleView mTitleBar;
mTitleBar = (TitleView) findViewById(R.id.title_bar);
mTitleBar.setLeftButtonListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "点击了返回按钮", Toast.LENGTH_SHORT)
.show();
finish();
}
});
5、运行效果如下:
这样就用组合的方式实现了自定义标题栏,其实经过更多的组合还可以创建出功能更为复杂的自定义控件,比如自定义搜索栏等。
(二)自绘控件
自绘控件的内容都是自己绘制出来的,在View的onDraw方法中完成绘制。下面就实现一个简单的计数器,每点击它一次,计数值就加1并显示出来。
1、创建CounterView类,继承自View,实现OnClickListener接口:
public class CounterView extends View implements OnClickListener {
// 定义画笔
private Paint mPaint;
// 用于获取文字的宽和高
private Rect mBounds;
// 计数值,每点击一次本控件,其值增加1
private int mCount;
public CounterView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化画笔、Rect
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(50);
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();
}
}
2、在activity_main.xml中引入该自定义布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.test.CounterView
android:id="@+id/counter_view"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal|top"
android:layout_margin="20dp" />
</LinearLayout>
3、运行效果如下:
(三)继承控件
就是继承已有的控件,创建新控件,保留继承的父控件的特性,并且还可以引入新特性。下面就以支持横向滑动删除列表项的自定义ListView的实现来介绍。
1、创建删除按钮布局delete_btn.xml,这个布局是在横向滑动列表项后显示的:
<?xml version="1.0" encoding="utf-8"?> <Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#FF0000" android:padding="5dp" android:text="删除" android:textColor="#FFFFFF" android:textSize="16sp" > </Button>
2、创建CustomListView类,继承自ListView,并实现了OnTouchListener和OnGestureListener接口:
public class CustomListView extends ListView implements OnTouchListener,
OnGestureListener {
// 手势动作探测器
private GestureDetector mGestureDetector;
// 删除事件监听器
public interface OnDeleteListener {
void onDelete(int index);
}
private OnDeleteListener mOnDeleteListener;
// 删除按钮
private View mDeleteBtn;
// 列表项布局
private ViewGroup mItemLayout;
// 选择的列表项
private int mSelectedItem;
// 当前删除按钮是否显示出来了
private boolean isDeleteShown;
public CustomListView(Context context, AttributeSet attrs) {
super(context, attrs);
// 创建手势监听器对象
mGestureDetector = new GestureDetector(getContext(), this);
// 监听onTouch事件
setOnTouchListener(this);
}
// 设置删除监听事件
public void setOnDeleteListener(OnDeleteListener listener) {
mOnDeleteListener = listener;
}
// 触摸监听事件
@Override
public boolean onTouch(View v, MotionEvent event) {
if (isDeleteShown) {
hideDelete();
return false;
} else {
return mGestureDetector.onTouchEvent(event);
}
}
@Override
public boolean onDown(MotionEvent e) {
if (!isDeleteShown) {
mSelectedItem = pointToPosition((int) e.getX(), (int) e.getY());
}
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// 如果当前删除按钮没有显示出来,并且x方向滑动的速度大于y方向的滑动速度
if (!isDeleteShown && Math.abs(velocityX) > Math.abs(velocityY)) {
mDeleteBtn = LayoutInflater.from(getContext()).inflate(
R.layout.delete_btn, null);
mDeleteBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mItemLayout.removeView(mDeleteBtn);
mDeleteBtn = null;
isDeleteShown = false;
mOnDeleteListener.onDelete(mSelectedItem);
}
});
mItemLayout = (ViewGroup) getChildAt(mSelectedItem
- getFirstVisiblePosition());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.CENTER_VERTICAL);
mItemLayout.addView(mDeleteBtn, params);
isDeleteShown = true;
}
return false;
}
// 隐藏删除按钮
public void hideDelete() {
mItemLayout.removeView(mDeleteBtn);
mDeleteBtn = null;
isDeleteShown = false;
}
public boolean isDeleteShown() {
return isDeleteShown;
}
/**
* 后面几个方法本例中没有用到
*/
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
}
3、定义列表项布局custom_listview_item.xml,它的结构很简单,只包含了一个TextView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants" >
<TextView
android:id="@+id/content_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_margin="30dp"
android:gravity="center_vertical|left" />
</RelativeLayout>
4、定义适配器类CustomListViewAdapter,继承自ArrayAdapter<String>:
public class CustomListViewAdapter extends ArrayAdapter<String> {
public CustomListViewAdapter(Context context, int textViewResourceId,
List<String> objects) {
super(context, textViewResourceId, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(
R.layout.custom_listview_item, null);
} else {
view = convertView;
}
TextView contentTv = (TextView) view.findViewById(R.id.content_tv);
contentTv.setText(getItem(position));
return view;
}
}
5、在activity_main.xml中引入自定义的ListView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.test.CustomListView
android:id="@+id/custom_lv"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
6、在MainActivity中对列表做初始化、设置列表项删除按钮点击事件等处理:
public class MainActivity extends Activity {
// 自定义Lv
private CustomListView mCustomLv;
// 自定义适配器
private CustomListViewAdapter mAdapter;
// 内容列表
private List<String> contentList = new ArrayList<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
initContentList();
mCustomLv = (CustomListView) findViewById(R.id.custom_lv);
mCustomLv.setOnDeleteListener(new OnDeleteListener() {
@Override
public void onDelete(int index) {
contentList.remove(index);
mAdapter.notifyDataSetChanged();
}
});
mAdapter = new CustomListViewAdapter(this, 0, contentList);
mCustomLv.setAdapter(mAdapter);
}
// 初始化内容列表
private void initContentList() {
for (int i = 0; i < 20; i++) {
contentList.add("内容项" + i);
}
}
@Override
public void onBackPressed() {
if (mCustomLv.isDeleteShown()) {
mCustomLv.hideDelete();
return;
}
super.onBackPressed();
}
}
7、运行效果如下:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# android中自定义view
# android的自定义view
# android
# 自定义view
# android TextView设置中文字体加粗实现方法
# android listview优化几种写法详细介绍
# android imageview图片居中技巧应用
# android开发教程之listview使用方法
# Android下拉刷新ListView——RTPullListView(demo)
# Android自定义Adapter的ListView的思路及代码
# android自定义进度条渐变色View的实例代码
# android实现上下滚动的TextView
# Android中实现Webview顶部带进度条的方法
# Android中实现监听ScrollView滑动事件
# 自定义
# 标题栏
# 还可以
# 都是
# 这是
# 实现了
# 加载
# 几个
# 是在
# 在此
# 大神
# 很简单
# 顾名思义
# 就用
# 就来
# 中对
# 中就
# 就以
# 创建一个
# 多处
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
浅述节点的创建及常见功能的实现
如何在阿里云购买域名并搭建网站?
如何构建满足综合性能需求的优质建站方案?
免费视频制作网站,更新又快又好的免费电影网站?
敲碗10年!Mac系列传将迎来「触控与联网」双革新
Laravel如何创建自定义Facades?(详细步骤)
如何在IIS中新建站点并配置端口与物理路径?
Laravel如何使用模型观察者?(Observer代码示例)
如何在七牛云存储上搭建网站并设置自定义域名?
七夕网站制作视频,七夕大促活动怎么报名?
Laravel如何实现数据导出到CSV文件_Laravel原生流式输出大数据量CSV【方案】
HTML5空格在Angular项目里怎么处理_Angular中空格的渲染问题【详解】
Laravel distinct去重查询_Laravel Eloquent去重方法
如何用PHP工具快速搭建高效网站?
Laravel如何发送系统通知?(Notification渠道示例)
谷歌Google入口永久地址_Google搜索引擎官网首页永久入口
悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音
如何快速搭建FTP站点实现文件共享?
Laravel如何配置.env文件管理环境变量_Laravel环境变量使用与安全管理
标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?
Laravel怎么自定义错误页面_Laravel修改404和500页面模板
Laravel怎么连接多个数据库_Laravel多数据库连接配置
晋江文学城电脑版官网 晋江文学城网页版直接进入
php在windows下怎么调试_phpwindows环境调试操作说明【操作】
香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化
小米17系列还有一款新机?主打6.9英寸大直屏和旗舰级影像
深圳网站制作培训,深圳哪些招聘网站比较好?
C#如何调用原生C++ COM对象详解
如何续费美橙建站之星域名及服务?
Laravel数据库迁移怎么用_Laravel Migration管理数据库结构的正确姿势
Laravel如何实现全文搜索_Laravel Scout集成Algolia或Meilisearch教程
JavaScript模板引擎Template.js使用详解
宙斯浏览器视频悬浮窗怎么开启 边看视频边操作其他应用教程
SQL查询语句优化的实用方法总结
UC浏览器如何切换小说阅读源_UC浏览器阅读源切换【方法】
如何在 React 中条件性地遍历数组并渲染元素
Android仿QQ列表左滑删除操作
Laravel如何实现数据库事务?(DB Facade示例)
WEB开发之注册页面验证码倒计时代码的实现
如何在万网ECS上快速搭建专属网站?
网站建设保证美观性,需要考虑的几点问题!
Laravel如何实现全文搜索功能?(Scout和Algolia示例)
手机软键盘弹出时影响布局的解决方法
如何获取PHP WAP自助建站系统源码?
高端企业智能建站程序:SEO优化与响应式模板定制开发
香港服务器部署网站为何提示未备案?
如何在VPS电脑上快速搭建网站?
Laravel API路由如何设计_Laravel构建RESTful API的路由最佳实践
如何在云服务器上快速搭建个人网站?
JavaScript数据类型有哪些_如何准确判断一个变量的类型

