安卓进阶之自定义View
安卓进阶之自定义View
自定义View,可以分为具体的三大类:
- 自定义View(继承系统控件/继承View)
- 自定义Viewgroup(继承系统特定的Viewgroup/继承ViewGround)
- 自定义组合控件
## 自定义View的工作流程和内容
### 工作流程
无论是哪一类View,只要是View,都需要经过以下工作流程:
测量->布局->绘制.
measure->layout->draw.
- measure阶段测量View的宽高
- layout阶段用来确定View的位置
- draw阶段则是用来绘制View.
测量阶段和布局阶段的工作内容
测量阶段(measure):从上到下递归地调用每个 View 或者 ViewGroup 的 measure() 方法,测量他们的尺寸并计算它们的位置;
布局阶段(layout):从上到下递归地调用每个 View 或者 ViewGroup 的 layout() 方法,把测得的它们的尺寸和位置赋值给它们。
View 和 ViewGroup 在测量阶段和布局阶段的区别
- 测量阶段,
measure()方法被父 View 调用,在measure()中做一些准备和优化工作后,调用onMeasure()来进行实际的自我测量。onMeasure()做的事,View和ViewGroup不一样:- View:
View在onMeasure()中会计算出自己的尺寸然后保存; - ViewGroup:
ViewGroup在onMeasure()中会调用所有子 View 的measure()让它们进行自我测量,并根据子 View 计算出的期望尺寸来计算出它们的实际尺寸和位置(实际上 99.99% 的父 View 都会使用子 View 给出的期望尺寸来作为实际尺寸,原因在下期或下下期会讲到)然后保存。同时,它也会根据子 View 的尺寸和位置来计算出自己的尺寸然后保存;
- View:
- 布局阶段,
layout()方法被父 View 调用,在layout()中它会保存父 View 传进来的自己的位置和尺寸,并且调用onLayout()来进行实际的内部布局。onLayout()做的事,View和ViewGroup也不一样:- View:由于没有子 View,所以
View的onLayout()什么也不做。 - ViewGroup:ViewGroup 在 onLayout() 中会调用自己的所有子 View 的 layout() 方法,把它们的尺寸和位置传给它们,让它们完成自我的内部布局。
- View:由于没有子 View,所以
绘制阶段的工作内容
在官方注释中,绘制分为以下步骤:
- 如果需要,就绘制背景--drawBackgrounp()
- 保存当前canvas层
- 绘制View的内容--onDraw()
- 绘制子View--dispatchView()
- 如果需要,就绘制View的褪色边缘,类似于阴影效果
- 绘制装饰,比如滚动条--onDrawForeground()
其中第2,5步可以跳过.具体如何绘制内容将在文末补充.
## 上手:实现继承View的自定义View
我们通过继承View实现一个自定义View,往往需要实现以下内容:
绘制内容(draw)
对Padding进行处理(draw)
对wrap_content进行处理(measure)
创建自定义属性,配置自己的自定义View
重写onTounchEvent()改变触摸反馈
括号为涉及到的工作流程.
例:
在界面中,创建一个可以滑动的矩形
java代码:
public class CustomView extends View {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
int lastx;
int lasty;
int mColor;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x= (int) event.getX();
int y= (int) event.getY(); switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastx=x;
lasty=y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX=x-lastx;
int offsetY=y-lasty;
((View)getParent()).scrollBy(-offsetX,-offsetY);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
} public CustomView(Context context) {
super(context);
} public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
//提取CustomView属性集合的rect_colot属性,如果不设置,默认为红色.
TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.CustomView);
mColor=typedArray.getColor(R.styleable.CustomView_rect_color, Color.RED);
typedArray.recycle();
paint.setColor(mColor);
paint.setStrokeWidth((float)1.5);
} public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
} public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
} /*
widthMeasureSpec和heightMeasureSpec分别压缩了mode和size两个信息.
mode的分类:
UNSPECIFIED:不限制,相当于match_parent
AT_MOST:限制上限,相当于wrap_content
EXACTLY:限制固定值
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//重新 onMeasure(),并计算出 View 的尺寸;
//(可以使用 resolveSize() 来让子 View 的计算结果符合父 View 的限制,也可以用自己的方式来满足父 View 的限制也行), 本例子使用自己方式满足父 View 的限制。
//对wrap_content进行处理
//在onMeasure方法中指定一个默认的宽高,在设置wrap_content属性时设置此默认宽高
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(80,80);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(80,heightSpecSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,80);
}
/*resolveSize(int size, int widthMeasureSpec)
方法内部的实现方式与例子自定义实现方式相似,从widthMeasureSpec得到
mode的类别作判断
UNSPECIFIED:不限制,返回size
AT_MOST:限制上限,size>MeasureSpec.getSize(widthMeasureSpec),返回后者,否则返回直接size
EXACTLY:限制固定值,返回MeasureSpec.getSize(widthMeasureSpec)
*/ /*setMeasuredDimension(
resolveSize(int size,int widthMeasureSpec),
resolveSize(int size,int heightMeasureSpec)
);
*/
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//对padding进行处理
int paddingLeft=getPaddingLeft();
int paddingRight=getPaddingRight();
int paddingTop=getPaddingTop();
int paddingBottom=getPaddingBottom();
int width=getWidth()-paddingLeft-paddingRight;
int height=getHeight()-paddingTop-paddingBottom;
//根据padding绘制矩形
canvas.drawRect(0+paddingLeft,0+paddingTop,width+paddingLeft,height+paddingTop,paint);
} }xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"> <com.example.drawtest.CustomView
app:rect_color="@color/colorAccent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/> </LinearLayout>
以android开头的都是系统自带的属性,自定义属性需要在values目录下创建attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomView">
<attr name="rect_color" format="color"/>
</declare-styleable>
</resources>
上手:自定义ViewGroup
自定义ViewGroup的步骤其实就是对View工作流程中的测量阶段,布局阶段对应方法进行重写:
- 重写onMesure()
- 重写onLayout()
重写 onMeasure() 的步骤:
1.遍历子View,根据子View的LayoutParams属性以及ViewGroup的MeasureSpec模式得到子 View 的 MeasureSpec
(子View的LayoutParams就是开发者对子View宽高等与位置相关属性的要求,而ViewGroup的mode则是开发者对ViewGroup宽高属性的要求.更简单的说,子View的LayoutParams保存了子View的xml代码中的Layout_height,Layout_width等有关的位置信息属性,ViewGroup的mode保存了ViewGroup的xml代码中的Layout_height,Layout_width属性).

2.把计算出的子View的childWidthSpec和childHeightSpec作为参数传入子 View 的 measure()方法 来计算子 View 的尺寸
3.子View在onMeasure()中计算自己最终的位置和尺寸利用setMeasuredDimension()方法保存
4.ViewGroup通过子View的位置和尺寸确定自己的尺寸并用 setMeasuredDimension() 保存
重写 onLayout() 的方式
在 onLayout() 里调用每个子 View 的 layout() ,让它们保存自己的位置和尺寸。
补充: 绘制内容的关键点
自定义绘制的方式最常用的方式是重写onDraw()绘制方法
绘制的关键是 Canvas 的使用
- Canvas 的绘制类方法: drawXXX() (关键参数:Paint)
- Canvas 的辅助类方法:范围裁切和几何变换
Paint:
Paint的 API 大致可以分为 4 类:颜色,效果,drawText() 相关,初始化.颜色类的API作用包括:直接设置颜色的 API 用来给图形和文字设置颜色(纯色,渐变色);加滤镜; 用来处理源图像和 View 已有内容的关系。
效果类的 API 可以实现抗锯齿、填充/轮廓、线条宽度、线头形状,线拐角,线性过滤(使图像过渡平缓)等等。
drawText()与初始化使用较少不作介绍.
Canvas范围裁剪和几何变换:
范围裁剪可以得到对原图像进行裁剪得到各种形状的图像,比如输入方形图片,输出圆形头像.
几何变换可以实现平移,旋转,缩放,错切效果;
可以使用不同的绘制方法来控制遮盖关系
参考学习网站:HenCoderhttps://hencoder.com/
参考学习书籍:Android进阶之光-刘望舒
安卓进阶之自定义View的更多相关文章
- Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)
Android 高手进阶(21) 版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明地址:http://blog.csdn.net/xiaanming/article/detail ...
- Android自定义View学习(二)
绘制顺序 参考:HenCoder Android 开发进阶:自定义 View 1-5 绘制顺序 绘制过程 包括 背景 主体(onDraw()) 子 View(dispatchDraw()) 滑动边缘渐 ...
- Android自定义View学习笔记(一)
绘制基础 参考:HenCoder Android 开发进阶: 自定义 View 1-1 绘制基础 Paint详解 参考:HenCoder Android 开发进阶: 自定义 View 1-2 Pain ...
- 安卓自定义View进阶-Canvas之画布操作 转载
安卓自定义View进阶-Canvas之画布操作 转载 https://www.gcssloop.com/customview/Canvas_Convert 本来想把画布操作放到后面部分的,但是发现很多 ...
- 安卓自定义控件(三)实现自定义View
前面两篇博客,把View绘制的方法说了一下,但是,我们只在onDraw里面做文章,控件都是直接传入一个Context,还不能在布局文件里使用自定义View.这一篇博客,就不再讲绘制,在我们原先的基础上 ...
- 安卓自定义View教程目录
基础篇 安卓自定义View基础 - 坐标系 安卓自定义View基础 - 角度弧度 安卓自定义View基础 - 颜色 进阶篇 安卓自定义View进阶 - 分类和流程 安卓自定义View进阶 - Canv ...
- 安卓自定义View(一)自定义控件属性
自定义View增加属性第一步:定义属性资源文件 在/res/values 文件夹下建立"Values XML layout",按照如下定义一个textview的属性 <?xm ...
- Android自定义View (二) 进阶
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自定义View之旅,前面已经介绍过一个自定义View的基础的例 ...
- Android 自定义View (二) 进阶
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自定义View之旅,前面已经介绍过一个自定义View的基础的例 ...
随机推荐
- Hibernate fetch相关
fetch=FetchType.LAZY 时,spring boot jackson 返回数据时会出错. 可配置使用Hibernate4Module 帮助解决: @Configurationpubli ...
- c++ new bad_alloc
try { for (int i = 0; i<1000; i++) { test1 = new Test(); cout << i << " new dog ...
- ffmpeg 使用 gdb 调试相关技巧
本文说明了,在ffmpeg二次开发或调用库的过程,如何借助于ffmpeg源码进行调试. 注:ffmpeg版本是4.0. 1. 编写代码 编写将pcm数据转换为mp2的代码 pcm_to_mp2.c # ...
- Java工程师学习指南第4部分:Java并发编程指南
本文整理了微信公众号[Java技术江湖]发表和转载过的Java并发编程相关优质文章,想看到更多Java技术文章,就赶紧关注本公众号吧吧. [纯干货]Java 并发进阶常见面试题总结 [Java基本功] ...
- Web模糊测试:WFuzz的坑和快速入门
转载自 FreeBuf.COM 首先说下我对wfuzz这个工具的简单介绍和理解.工具主要是做web模糊测试,最开始做fuzz我是自己写个脚本配合一些常用工具来测,后来看见这款工具因为是比较简单吧,学习 ...
- ADRMS与office的整合(一)
因为微软之前针对客户的RMS加密服务是一种免费的测试服务,虽然用户很多但实质上还是一种“测试服务”. 后来微软把这个服务商业化了,需要继续使用的话需要打下这个补丁 https://support.mi ...
- 【VS开发】MFC中调用C函数模块的解决方案
[VS开发]MFC中调用C函数模块的解决方案 标签(空格分隔): [VS开发] 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 说明:最近调试基于MFC的程序 ...
- spring中@Conditional注解
@Conditional是Spring4新提供的注解,它的作用是根据某个条件加载特定的bean. 我们需要创建实现类来实现Condition接口,这是Condition的源码 public inter ...
- 前端ajax中运用post请求和get请求之于session验证
首先我们来看下ajax两种请求的区别: Ajax中POST和GET的区别Get和Post都是向服务器发送的一种请求,只是发送机制不同. 1. GET请求会将参数跟在URL后进行传递,而POST请求则是 ...
- SQL SERVER2014 加密 备份恢复 学习 (一)
SQL 自2008(还是2005)之后,推出加密功能,可以一定程度上保护数据库的备份安全.以下测试环境为:sql server 2014主要目的:将备份的文件加密,在其它电脑上恢复时必须有证书和密钥才 ...