开发过程中,我们免不了需要用到一些自定义的 View,自定义 View 一般可分为三类:

  ① 继承类 View —— 一般继承系统以后的基本 View,新增/重置一些自定义属性 ,例如两端对齐的TextView;

  ② 组合类 View —— 将系统某几个基本View组合在一起形成一个新的View,例如末尾带 ”ד(清空) 的EditText,就是将EditText和ImageView组合在一起来实现;

  ③ 自绘制 View —— 某些特殊的设计控件,无法通过上两种方式实现时,我们就需要考虑通过自绘制来进行处理,本篇我们将着重介绍此类 View 的实现过程。

下面我们通过自定义一个圆形的Button(DCircleButton)来进行说明:

自定义View的步骤:

① 自定义 View 的属性;

② 在自定义 View 的构造方法中获取 View 的属性值;

③ 重写测量尺寸的方法 onMeasure(int, int); (是否需要重写根据具体根据需求);

④ 重写绘制方法 onDraw(Canvas c);

⑤ 在布局XML文件中,使用自定义 View 的属性。

1. 自定义 View 的属性:

在目录 res/values 下新建 attrs.xml 属性文件。

<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--name 是自定义控件的类名-->
<declare-styleable name="DCircleButton" parent="android.widget.Button">
<attr name="txtSize" format="dimension"/>
<attr name="text" format="string"/>
<attr name="txtColor" format="color"/>
<attr name="txtBackgroundColor" format="color"/>
</declare-styleable>
</resources>
自定义属性分两步:
  ① 定义控件的主题样式;
  ② 定义属性名称及类型。
如上面的 xml 文件是自定义控件DCirclebutton的主题样式,主题样式里为属性定义,有些人可能会纠结format字段后面都有哪些属性单位?如果你是使用AS开发的话IDE会自动有提示,基本包括如下:
dimension(字体大小)string(字符串)color(颜色)boolean(布尔类型)float(浮点型)integer(整型)enmu(枚举)fraction(百分比)等。

2.  在构造方法中获取属性值,并绘制

第一步:继承View,实现(AS会提示)以下四种,

public DCircleButton(Context context) {
super(context);
}
public DCircleButton(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public DCircleButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public DCircleButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

第二步,改写这四种构造,让其逐级递进:

public DCircleButton(Context context) {
super(context, null);
}
public DCircleButton(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public DCircleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr, 0);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public DCircleButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

第三步,我们在最后一个方法获取属性值:

private void initAttrs(Context context, AttributeSet attrs) {
TypedArray tArr = context.obtainStyledAttributes(attrs, R.styleable.DCircleButton);
if (null != tArr) {
txtColor = tArr.getColor(R.styleable.DCircleButton_txtColor, Color.BLACK); // 获取文字颜色
txtSize = tArr.getDimensionPixelSize(R.styleable.DCircleButton_txtSize, 18); // 获取文字大小
txt = tArr.getString(R.styleable.DCircleButton_text); // 获取文字内容
backgroundColor = tArr.getColor(R.styleable.DCircleButton_txtBackgroundColor, Color.GRAY); // 获取文字背景颜色
tArr.recycle();
}
}

第四步,绘制

/** 字体颜色 **/
private int txtColor;
/** 字体背景颜色 **/
private int backgroundColor;
/** 字体大小 **/
private int txtSize;
/** 按钮文字内容 **/
private String txt;
/** 圆半径 **/
private float mDrawableRadius; /** 字体背景画笔 **/
private Paint mBackgroundPaint;
/** 字体画笔 **/
private Paint mTxtPaint; public DCircleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
init();
}
/** 初始化 **/
private void init() {
mBackgroundPaint = new Paint();
mBackgroundPaint.setColor(backgroundColor); mTxtPaint = new Paint();
mTxtPaint.setTextAlign(Paint.Align.CENTER);
mTxtPaint.setColor(txtColor);
mTxtPaint.setTextSize(txtSize);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawableRadius = Math.min(getWidth() >> 1, getHeight() >> 1);
canvas.drawCircle(getWidth() >> 1, getHeight() >> 1, mDrawableRadius, mBackgroundPaint);
if (null != txt)
canvas.drawText(txt, getWidth() >> 1, getHeight() >> 1, mTxtPaint);
}

3. 布局中应用

<dinn.circle.button.DCircleButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:text="我是按钮"
app:txtBackgroundColor="@android:color/holo_orange_dark"
app:txtColor="@android:color/white"
app:txtSize="20sp" />

4. 运行结果

这个时候回发现按钮是充满屏幕的,但是布局中我们设置的尺寸属性为“wrap_content”。其实是由于我们在自定义View的流程中还有一个onMeasure方法没有重写。

5. 重写onMeasure控制View的大小

当你没有重写onMeasure方法时候,系统调用默认的onMeasure方法。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

这个方法的作用是:测量控件的大小。其实Android系统在加载布局的时候是由系统测量各子View的大小来告诉父View我需要占多大空间,然后父View会根据自己的大小来决定分配多大空间给子View。

那么从上面的效果来看,当你在布局中设置View的大小为”wrap_content”时,其实系统测量出来的大小是“match_parent”。为什么会是这样子呢?

那得从MeasureSpec的specMode模式说起了。一共有三种模式:

MeasureSpec.EXACTLY:父视图希望子视图的大小是specSize中指定的大小;一般是设置了明确的值或者是MATCH_PARENT。

MeasureSpec.AT_MOST:子视图的大小最多是specSize中的大小;表示子布局限制在一个最大值内,一般为WARP_CONTENT。

MeasureSpec.UNSPECIFIED:父视图不对子视图施加任何限制,子视图可以得到任意想要的大小;表示子布局想要多大就多大,很少使用。

我们看看系统源码 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 是如何实现的:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

从上面的代码 getDefaultSize() 方法中看出,原来 MeasureSpec.AT_MOST 和 MeasureSpec.EXACTLY 走的是同一个分支,也就是父视图希望子视图的大小是specSize中指定的大小。

得出来的默认值就是填充整个父布局。因此,不管你布局大小是 ”wrap_content” 还是 “match_parent” 效果都是充满整个父布局。那我想要 ”wrap_content” 的效果怎么办?那么只有重写onMeasure方法了。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// 父布局希望子布局的大小,如果布局里面设置的是固定值,这里取布局里面的固定值和父布局大小值中的最小值.
// 如果设置的是match_parent,则取父布局的大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width;
int height;
Rect mBounds = new Rect();
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
mTxtPaint.setTextSize(txtSize);
mTxtPaint.getTextBounds(txt, 0, txt.length(), mBounds);
float textWidth = mBounds.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
} if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = width;
}
// 最后调用父类方法,把View的大小告诉父布局。
setMeasuredDimension(width, height);
}

这样实现的最终效果如下:

Android自绘制控件的更多相关文章

  1. Android 中常见控件的介绍和使用

    1 TextView文本框 1.1 TextView类的结构 TextView 是用于显示字符串的组件,对于用户来说就是屏幕中一块用于显示文本的区域.TextView类的层次关系如下: java.la ...

  2. 【风马一族_Android】第4章Android常用基本控件

    第4章Android常用基本控件 控件是Android用户界面中的一个个组成元素,在介绍它们之前,读者必须了解所有控件的父类View(视图),它好比一个盛放控件的容器. 4.1View类概述 对于一个 ...

  3. Android自己定义控件系列五:自己定义绚丽水波纹效果

    尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...

  4. 带着问题写React Native原生控件--Android视频直播控件

    最近在做的采用React Native项目有一个需求,视频直播与直播流播放同一个布局中,带着问题去思考如何实现,能更容易找到问题关键点,下面分析这个控件解决方法: 现在条件:视频播放控件(开源的ijk ...

  5. Android自己定义控件:进度条的四种实现方式

    前三种实现方式代码出自: http://stormzhang.com/openandroid/2013/11/15/android-custom-loading/ (源代码下载)http://down ...

  6. Android自定义view控件

    转载自: http://blog.163.com/ppy2790@126/blog/static/103242241201382210910473/ 开发自定义控件的步骤: 1.了解View的工作原理 ...

  7. android 自己定义控件

    Android自己定义View实现非常easy 继承View,重写构造函数.onDraw.(onMeasure)等函数. 假设自己定义的View须要有自己定义的属性.须要在values下建立attrs ...

  8. Android笔记---常用控件以及用法

    这篇文章主要记录下Android的常用控件以及使用的方法,Android 给我们提供了大量的UI控件,合理地使用这些控件就可以非常轻松地编写出相当不错的界面,这些是Android学习的基础,没有什么业 ...

  9. Android中ListView控件的使用

    Android中ListView控件的使用 ListView展示数据的原理 在Android中,其实ListView就相当于web中的jsp,Adapter是适配器,它就相当于web中的Servlet ...

随机推荐

  1. Mybatis中的# 与 $

    我们说MyBatis有两种注入参数的方式, - 一种是#{} - 另一种是${} 这两种从使用功能来看差距不大,那为什么会强推使用#? ${}使用的是拼接字符串,#{}使用的是占位符的方法,经过了处理 ...

  2. 为什么scanf(" %c",&c)中%c前要空格?

    空格确实不是必须的,但有了空格就可以忽略你输入的空格. ****例如:scanf(" %c" ,&c),你输入了' a'(a前面有个空格),a就能被c接受. 但控制符前如果 ...

  3. C++中的字符串切片操作

    string str = "hello"; str.substr(0,2); //输出"he", 表示[0,2)

  4. Ring 笔记 - 核心概念

    Ring 是一个在 Clojure 中的对于 HTTP 的抽象,是构建 Web 应用的底层接口和库,类似于 Java 中的 Servlet 核心概念 Handler Handler 是一个定义web应 ...

  5. 【php】PDO

    一.PDO的定义 1.pdo(php data object)是一个数据库的抽象层 二.PDO的特点 1.跨数据库 2.支持预处理 3.支持事务处理 三.PDO的使用 1.基本使用 (1)实例化pdo ...

  6. json的fromjson的方法使用。可以在volley中进行使用

    Gson提供了fromJson()方法来实现从Json相关对象到Java实体的方法. 在日常应用中,我们一般都会碰到两种情况,转成单一实体对象和转换成对象列表或者其他结构. 先来看第一种: 比如jso ...

  7. MTK Android 设置-选择日期格式 [管理和组织首选项,ListPreference,CheckBoxPreference,EditTextPreference,RingtonePreference]

    ###android.preference.ListPreference的一些特性 android:key  选项的名称或键 android:title  选项的标题 android:summary  ...

  8. 2017蓝桥杯Excel地址(C++C组)

    题目:Excel地址Excel单元格的地址表示很有趣,它使用字母来表示列号.比如,A表示第1列,B表示第2列,Z表示第26列,AA表示第27列,AB表示第28列,BA表示第53列,....当然Exce ...

  9. 运行jmeter.bat时 提示 not able to find java executable or version

    安装过好几次,这是第一次遇到运行jmeter.bat时 提示 not able to find java executable or version Please check your Java in ...

  10. ConcurrentHashMap和 CopyOnWriteArrayList提供线程安全性和可伸缩性 以及 同步的集合类 Hashtable 和 Vector Collections.synchronizedMap 和 Collections.synchronizedList 区别缺点

    ConcurrentHashMap和 CopyOnWriteArrayList提供线程安全性和可伸缩性 DougLea的 util.concurrent 包除了包含许多其他有用的并发构造块之外,还包含 ...