【Android自定义View实战】之你应该明白的事儿

2024-05-22 06:58

本文主要是介绍【Android自定义View实战】之你应该明白的事儿,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/52910443【DylanAndroid的csdn博客】


在Android的实际开发中,我们Android系统本身已经给我们提供了很丰富的UI以及各种实用的控件,例如TextView,Button,ImageView等。用这些基础控件已经能够实现非常优美的界面以及功能。然而在实际的开发中,我们由于客户的各种需求,App开发的各种标新立异,追求个性化,所以,导致我们用这些最基础的控件已经不能够满足我们的各种个性化需求。那么,我们就要考虑去自定义控件来完成我们的要求,要真正去完成一个自定义控件,我们先来看看这个控件的继承体系:

1.View体系

Android 应用中的所有用户界面元素都是使用 View 和 ViewGroup 对象构建而成。View 对象用于在屏幕上绘制可供用户交互的内容。ViewGroup 对象用于储存其他 View(和 ViewGroup)对象,以便定义界面的局部。

Android 提供了一系列 View 和 ViewGroup 子类,可为您提供常用输入控件(如按钮和文本字段)和各种布局模式(如线性布局或相对布局)。

这里写图片描述

2.View的关键绘制流程和关键的生命周期

Created with Raphaël 2.1.0 Constructor------>构造函数 onMeasure------>测量View大小 onSizeChanged------>确定View大小 onLayout------>确定View布局的位置 onDraw------>绘制View中的内容 视图状态是否改变 显示 invalidate yes no

综上所述:View的关键生命周期为:
构造View() –> onMeasure() –> onSizeChanged() –> onLayout() –> onDraw()

3.构造函数的自定义属性

  • 构造函数

    public CustomView(Context context) {super(context);
    }public CustomView(Context context, AttributeSet attrs) {super(context, attrs);
    }public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
    }
    • 首先在res的values文件夹下面建一个attrs.xml文件,如下图所示:
      这里写图片描述
    • 然后在里面定义如下内容
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    <declare-styleable name="CircleImageView"><attr name="border_width" format="dimension" /><attr name="border_color" format="color" />
    </declare-styleable>
    </resources>
  • 首先应该在布局文件的跟布局当中定义命名空间
xmlns:app="http://schemas.android.com/apk/res-auto"
  • 在xml中的用法:如下所示
 <com.bm.dylan.view.CircleImageView
                android:id="@+id/civ_personal_information_head"android:layout_width="50dp"android:layout_height="50dp"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:src="@mipmap/personalcenter_head"app:border_color="@color/white" />app:border_width="2dp" />
  • 最后在构造函数中这样获取:

    
    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);super.setScaleType(SCALE_TYPE);TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);int mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);int  mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);a.recycle();}
  • 在xml中可以定义的属性如下

编号类型format的值在xml中定义值示例
1reference参考指定Theme中资源IDreferenceapp:name=”@string/label”
2color:颜色colorapp:Textcolor=”#ff0000”
3boolean:布尔值booleanapp:isVisible=”false”
4dimension:尺寸值dimensionapp:myWidth=”100dp”
5float:浮点型floatapp:fromAlpha=”0.3”
6integer:整型integerapp:framesCount=”22”
7string:字符串stringapp:name=”My name is yuan dong liang”
8fraction:百分数fractionapp:pivotX=”200%”
9enum:枚举enumapp:borderRadius=”circle”
10flag:位或运算fractionandroid:windowSoftInputMode=”stateUnspecified
11多种符合类型colorapp:background = “@drawable/图片ID|#00FF00”|

4..测量View大小(onMeasure)

  • 代码
    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stub  super.onMeasure(widthMeasureSpec, heightMeasureSpec);  System.out.println(widthMeasureSpec+":"+heightMeasureSpec);  // 如果是自定ViewGroup,计算自定义的ViewGroup中所有子控件的大小  //measureChildren(widthMeasureSpec, heightMeasureSpec);  int w = getMeasureWidth(widthMeasureSpec);    int h = getMeasureHeight(heightMeasureSpec); setMeasuredDimension(w, h);  //必须调用此方法,否则会抛出异常  }private int getMeasureHeight(int heightMeasureSpec) {  int result = 0;  int size = MeasureSpec.getSize(heightMeasureSpec);  //每次调用此方法,测量用到的size会发生变化  int mode = MeasureSpec.getMode(heightMeasureSpec);  //根据定义的Layout_width,Layout_height,会对此值产生影响  if (mode == MeasureSpec.EXACTLY) {  result = size;  } else if (mode == MeasureSpec.UNSPECIFIED) {  result = getPaddingLeft()  + getPaddingRight();  } else {  result = Math.min(result, size);  }  System.out.println("Height size:" + size);    System.out.println("Height mode:" + mode);  return result;  
}  private int getMeasureWidth(int widthMeasureSpec) {  int result = 0;  int size = MeasureSpec.getSize(widthMeasureSpec);  int mode = MeasureSpec.getMode(widthMeasureSpec);  if (mode == MeasureSpec.EXACTLY) {  result = size;  } else if (mode == MeasureSpec.UNSPECIFIED) {  result =getPaddingTop()  + getPaddingBottom();  } else {  result = Math.min(result, size);  }  System.out.println("Width size:" + size);  System.out.println("Width mode:" + mode);  return result;  
}  
  • MeasureSpec

    从上面可以看出 onMeasure 函数中有 widthMeasureSpec 和 heightMeasureSpec 这两个 int 类型的参数, 毫无疑问他们是和宽高相关的, 但它们其实不是宽和高, 而是由宽、高和各自方向上对应的测量模式来合成的一个值:对于详细测量值( measureSpec )需要两样东西来确定它,那就是大小(size)和模式(mode)。 而 measureSpec,size,mode他们三个的关系,都封装在View类中的一个内部类里,名叫 MeasureSpec 。
    测量模式一共有三种, 被定义在 Android 中的 View 类的一个内部类View.MeasureSpec中:

模式二进制数值描述
UNSPECIFIED00默认值,父控件没有给子view任何限制,子View可以设置为任意大小。
EXACTLY01表示父控件已经确切的指定了子View的大小。
AT_MOST10表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。

我们重写onMeasure()所要实现的最终目的。它的作用就是存储我们测量好的宽高值。

5.确定View大小(onSizeChanged)

因为View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。它又四个参数,分别为 宽度,高度,上一次宽度,上一次高度。这个函数比较简单,我们只需关注 宽度(w), 高度(h) 即可,这两个参数就是View最终的大小。

 @Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);}

6.确定子View布局位置(onLayout)

确定布局的函数是onLayout,它用于确定子View的位置,在自定义ViewGroup中会用到,他调用的是子View的layout函数。在自定义ViewGroup中,onLayout一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。

 @Override  protected void onLayout(boolean changed, int l, int t, int r, int b) {  // 记录总高度  int mTotalHeight = 0;  // 遍历所有子视图  int childCount = getChildCount();  for (int i = 0; i < childCount; i++) {  View childView = getChildAt(i);  // 获取在onMeasure中计算的视图尺寸  int measureHeight = childView.getMeasuredHeight();  int measuredWidth = childView.getMeasuredWidth();  childView.layout(l, mTotalHeight, measuredWidth, mTotalHeight  + measureHeight);  mTotalHeight += measureHeight;  }  }  
四个参数分别为:
名称说明对应的函数
lView左侧距父View左侧的距离getLeft();
tView顶部距父View顶部的距离getTop();
rView右侧距父View左侧的距离getRight();
bView底部距父View顶部的距离getBottom();

7.绘制内容(onDraw)

  @Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//利用Canvas 画图}

8.绘图知识

  • Canvas(画布)类

    画笔属性设置好之后,还需要将图像绘制到画布上。Canvas类可以用来实现各种图形的绘制工作,如绘制直线、矩形、圆等等。Canvas绘制常用图形的方法如下:
    绘制直线:canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);

      绘制矩形:canvas.drawRect(float left, float top, float right, float bottom, Paint paint);

      绘制圆形:canvas.drawCircle(float cx, float cy, float radius, Paint paint);

      绘制字符:canvas.drawText(String text, float x, float y, Paint paint);

      绘制图形:canvas.drawBirmap(Bitmap bitmap, float left, float top, Paint paint);

  • Paint(画笔)类
    要绘制图形,首先得调整画笔,按照自己的开发需要设置画笔的相关属性。Pain类的常用属性设置方法如下:
 setAntiAlias();             //设置画笔的锯齿效果setColor();                 //设置画笔的颜色setARGB();                  //设置画笔的A、R、G、B值setAlpha();                 //设置画笔的Alpha值setTextSize();              //设置字体的尺寸setStyle();                  //设置画笔的风格(空心或实心)setStrokeWidth();            //设置空心边框的宽度getColor();                  //获取画笔的颜色

9.对外提供操作方法和监听回调

自定义完View之后,一般会对外暴露一些接口,用于控制View的状态等,或者监听View的变化.也就是定义接口。我们的像Button的点击事件就是这样的原理。

10.自定义View的分类

  • 在原有的控件基础上扩展,比如继承ImageView去实现圆形头像等。
  • 通过组合控件来自定义View,比如标题栏就可以通过组合控件来实现,左边一个返回按钮,中间一个显示文本,右边可有可无的提交按钮。
  • 完全重写onDraw()自定义View。

这篇关于【Android自定义View实战】之你应该明白的事儿的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/996001

相关文章

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

SpringBoot 自定义消息转换器使用详解

《SpringBoot自定义消息转换器使用详解》本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧... 目录一、前言二、SpringBoot 内容协商介绍2.1 什么是内容协商2.2 内容协商机制深入理解2.2.1 内容

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount