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


相关推荐: Laravel如何清理系统缓存命令_Laravel清除路由配置及视图缓存的方法【总结】  浅谈redis在项目中的应用  北京的网站制作公司有哪些,哪个视频网站最好?  Android自定义控件实现温度旋转按钮效果  Laravel N+1查询问题如何解决_Eloquent预加载(Eager Loading)优化数据库查询  如何在万网开始建站?分步指南解析  ai格式如何转html_将AI设计稿转换为HTML页面流程【页面】  使用C语言编写圣诞表白程序  Laravel的路由模型绑定怎么用_Laravel Route Model Binding简化控制器逻辑  如何在Windows 2008云服务器安全搭建网站?  HTML 中如何正确使用模板变量为元素的 name 属性赋值  php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】  Laravel如何发送系统通知_Laravel Notifications实现多渠道消息通知  Win11怎么查看显卡温度 Win11任务管理器查看GPU温度【技巧】  Laravel怎么进行数据库回滚_Laravel Migration数据库版本控制与回滚操作  Linux网络带宽限制_tc配置实践解析【教程】  Laravel如何使用Seeder填充数据_Laravel模型工厂Factory批量生成测试数据【方法】  教你用AI将一段旋律扩展成一首完整的曲子  Laravel Seeder怎么填充数据_Laravel数据库填充器的使用方法与技巧  如何在建站之星网店版论坛获取技术支持?  小米17系列还有一款新机?主打6.9英寸大直屏和旗舰级影像  图册素材网站设计制作软件,图册的导出方式有几种?  家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?  Laravel如何实现RSS订阅源功能_Laravel动态生成网站XML格式订阅内容【教程】  Laravel如何实现多对多模型关联?(Eloquent教程)  javascript中的try catch异常捕获机制用法分析  成都品牌网站制作公司,成都营业执照年报网上怎么办理?  JS实现鼠标移上去显示图片或微信二维码  零基础网站服务器架设实战:轻量应用与域名解析配置指南  详解Huffman编码算法之Java实现  iOS中将个别页面强制横屏其他页面竖屏  浅谈javascript alert和confirm的美化  如何在浏览器中启用Flash_2025年继续使用Flash Player的方法【过时】  如何在阿里云虚拟机上搭建网站?步骤解析与避坑指南  如何用狗爹虚拟主机快速搭建网站?  如何确认建站备案号应放置的具体位置?  Laravel怎么写单元测试_PHPUnit在Laravel项目中的基础测试入门  网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?  PHP 500报错的快速解决方法  Laravel如何使用Eloquent进行子查询  php读取心率传感器数据怎么弄_php获取max30100的心率值【指南】  简单实现Android文件上传  如何在云主机上快速搭建网站?  JS去除重复并统计数量的实现方法  作用域操作符会触发自动加载吗_php类自动加载机制与::调用【教程】  图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?  Firefox Developer Edition开发者版本入口  高端智能建站公司优选:品牌定制与SEO优化一站式服务  Python企业级消息系统教程_KafkaRabbitMQ高并发应用  Laravel如何实现用户角色和权限系统_Laravel角色权限管理机制