安卓进阶之自定义View

自定义View,可以分为具体的三大类:

  • 自定义View(继承系统控件/继承View)
  • 自定义Viewgroup(继承系统特定的Viewgroup/继承ViewGround)
  • 自定义组合控件

## 自定义View的工作流程和内容
### 工作流程

无论是哪一类View,只要是View,都需要经过以下工作流程:

测量->布局->绘制.

measure->layout->draw.

  1. measure阶段测量View的宽高
  2. layout阶段用来确定View的位置
  3. draw阶段则是用来绘制View.

测量阶段和布局阶段的工作内容

测量阶段(measure):从上到下递归地调用每个 View 或者 ViewGroup 的 measure() 方法,测量他们的尺寸并计算它们的位置;

布局阶段(layout):从上到下递归地调用每个 View 或者 ViewGroup 的 layout() 方法,把测得的它们的尺寸和位置赋值给它们。

View 和 ViewGroup 在测量阶段和布局阶段的区别

  1. 测量阶段,measure() 方法被父 View 调用,在 measure() 中做一些准备和优化工作后,调用 onMeasure() 来进行实际的自我测量。 onMeasure() 做的事,ViewViewGroup 不一样:

    1. ViewViewonMeasure() 中会计算出自己的尺寸然后保存;
    2. ViewGroupViewGrouponMeasure() 中会调用所有子 View 的 measure() 让它们进行自我测量,并根据子 View 计算出的期望尺寸来计算出它们的实际尺寸和位置(实际上 99.99% 的父 View 都会使用子 View 给出的期望尺寸来作为实际尺寸,原因在下期或下下期会讲到)然后保存。同时,它也会根据子 View 的尺寸和位置来计算出自己的尺寸然后保存;
  2. 布局阶段,layout() 方法被父 View 调用,在 layout() 中它会保存父 View 传进来的自己的位置和尺寸,并且调用 onLayout() 来进行实际的内部布局。onLayout() 做的事, ViewViewGroup 也不一样:
    1. View:由于没有子 View,所以 ViewonLayout() 什么也不做。
    2. ViewGroup:ViewGroup 在 onLayout() 中会调用自己的所有子 View 的 layout() 方法,把它们的尺寸和位置传给它们,让它们完成自我的内部布局。

绘制阶段的工作内容

在官方注释中,绘制分为以下步骤:

  1. 如果需要,就绘制背景--drawBackgrounp()
  2. 保存当前canvas层
  3. 绘制View的内容--onDraw()
  4. 绘制子View--dispatchView()
  5. 如果需要,就绘制View的褪色边缘,类似于阴影效果
  6. 绘制装饰,比如滚动条--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工作流程中的测量阶段,布局阶段对应方法进行重写:

  1. 重写onMesure()
  2. 重写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的更多相关文章

  1. Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条)

      Android 高手进阶(21)  版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明地址:http://blog.csdn.net/xiaanming/article/detail ...

  2. Android自定义View学习(二)

    绘制顺序 参考:HenCoder Android 开发进阶:自定义 View 1-5 绘制顺序 绘制过程 包括 背景 主体(onDraw()) 子 View(dispatchDraw()) 滑动边缘渐 ...

  3. Android自定义View学习笔记(一)

    绘制基础 参考:HenCoder Android 开发进阶: 自定义 View 1-1 绘制基础 Paint详解 参考:HenCoder Android 开发进阶: 自定义 View 1-2 Pain ...

  4. 安卓自定义View进阶-Canvas之画布操作 转载

    安卓自定义View进阶-Canvas之画布操作 转载 https://www.gcssloop.com/customview/Canvas_Convert 本来想把画布操作放到后面部分的,但是发现很多 ...

  5. 安卓自定义控件(三)实现自定义View

    前面两篇博客,把View绘制的方法说了一下,但是,我们只在onDraw里面做文章,控件都是直接传入一个Context,还不能在布局文件里使用自定义View.这一篇博客,就不再讲绘制,在我们原先的基础上 ...

  6. 安卓自定义View教程目录

    基础篇 安卓自定义View基础 - 坐标系 安卓自定义View基础 - 角度弧度 安卓自定义View基础 - 颜色 进阶篇 安卓自定义View进阶 - 分类和流程 安卓自定义View进阶 - Canv ...

  7. 安卓自定义View(一)自定义控件属性

    自定义View增加属性第一步:定义属性资源文件 在/res/values 文件夹下建立"Values XML layout",按照如下定义一个textview的属性 <?xm ...

  8. Android自定义View (二) 进阶

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自定义View之旅,前面已经介绍过一个自定义View的基础的例 ...

  9. Android 自定义View (二) 进阶

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自定义View之旅,前面已经介绍过一个自定义View的基础的例 ...

随机推荐

  1. Eclipse添加Android library 错误的原因

    这两天把项目从本地转移到GIT上,本来我的Workspace是在D盘,现在因为感觉D盘不够用,就把GIT到的项目放到E盘了 按照以往的用法,GIT下来以后再往属性里添加依赖库就OK了,但是这次怎么也无 ...

  2. ubuntu docker 环境安装

    转载:https://www.cnblogs.com/blog-rui/p/9946382.html 1. 在Ubuntu中安装Docker 更新ubuntu的apt源索引 sudo apt-get ...

  3. Java工程师学习指南第2部分:JavaWeb技术世界

    本文整理了微信公众号[Java技术江湖]发表和转载过的Java Web优质文章,想看到更多Java技术文章,就赶紧关注吧. IDEA中的Maven实战 老师,免费版的IDEA为啥不能使用Tomcat? ...

  4. JAVA数据结构和算法 6 递归

    递归:直接或者间接地调用自己.比如计算连续数的阶乘,计算规律:n!=(n-1)!*n. 每个递归方法都有一个基值(终止)条件,以防止无线地递归下去,以及由此引发的程序崩溃. 采用递归是因为它可以从概念 ...

  5. 【转】MySql 三大知识点——索引、锁、事务

    索引 索引,类似书籍的目录,可以根据目录的某个页码立即找到对应的内容. 索引的优点:1. 天生排序.2. 快速查找. 索引的缺点:1. 占用空间.2. 降低更新表的速度. 注意点:小表使用全表扫描更快 ...

  6. Guava源码阅读-base-CharMatcher

    package com.google.common.base; (部分内容摘自:http://blog.csdn.net/idealemail/article/details/53860439) 之前 ...

  7. redis的发布和订阅操作

  8. luogu P1115 最大子段和 (dp)

    链接: https://www.luogu.org/problemnew/show/P1115 题面: 题目描述 给出一段序列,选出其中连续且非空的一段使得这段和最大. 输入输出格式 输入格式: 第一 ...

  9. spark 执行报错 java.io.EOFException: Premature EOF from inputStream

    使用spark2.4跟spark2.3 做替代公司现有的hive选项. 跑个别任务spark有以下错误 java.io.EOFException: Premature EOF from inputSt ...

  10. HTTP协议的简单了解

    1. 用于服务端和客户端通信 客户端发送请求,服务端提供资源: 通过URI定位资源. 2. 通过请求和响应交换进行通信 客户端发送请求,服务端响应请求并返回数据: 请求报文:请求方法.URI.协议版本 ...