自定义view如何分类

  1. 自定义View:只需要重写onMeasure()和onDraw(),在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View
  2. 自定义ViewGroup:只需要重写onMeasure()和onLayout(),一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout

view的构造函数:共有4个

 // 如果View是在Java代码里面new的,则调用第一个构造函数public CustomView(Context context) {super(context);}// 如果View是在.xml里声明的,则调用第二个构造函数// 自定义属性是从AttributeSet参数传进来的public CustomView(Context context, AttributeSet attrs) {super(context, attrs);}// 不会自动调用 // 一般是在第二个构造函数里主动调用 // 如View有style属性时 public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}//API21之后才使用 // 不会自动调用 // 一般是在第二个构造函数里主动调用 // 如View有style属性时 public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}

LayoutInflater 的基本用法

LayoutInflater 其实就是使用 Android 提供的 pull 解析方式 来解析布局文件的
LayoutInflater.from(this).inflate()inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
inflate()方法一般接收两个参数,第一个参数就是要加载的布局 id,第二个参数
是指给该布局的外部再嵌套一层父布局,如果不需要就直接传 null。接收三个参数时:
  • 如果 root 为 null,attachToRoot 将失去作用,设置任何值都没有意义。
  • 如果 root 不为 null,attachToRoot 设为 true,则会给加载的布局文件的指定一个父布局,即 root。
  • 如果 root 不为 null,attachToRoot 设为 false,则会将布局文件最外层的所有 layout 属性进行设置,当该 view 被添加到父 view 当中时,这些 layout 属性会自动生效。
  • 在不设置 attachToRoot 参数的情况下,如果 root 不为 null,attachToRoot 参数默认为 true。

Android两种坐标系

  • View的静态坐标方法
View的静态坐标方法 解释
getLeft() 返回View自身左边到父布局左边的距离
getTop() 返回View自身顶边到父布局顶边的距离
getRight() 返回View自身右边到父布局左边的距离
getBottom() 返回View自身底边到父布局顶边的距离
getX() 返回值为getLeft()+getTranslationX(),当setTranslationX()时getLeft()不变,getX()变。
getY() 返回值为getTop()+getTranslationY(),当setTranslationY()时getTop()不变,getY()变。
  • 手指触摸屏幕时MotionEvent
MotionEvent坐标方法 解释
getX() 当前触摸事件距离当前View左边的距离
getY() 当前触摸事件距离当前View顶边的距离
getRawX() 当前触摸事件距离整个屏幕左边的距离
getRawY() 当前触摸事件距离整个屏幕顶边的距离
  • 获取宽高
View宽高方法 解释
getWidth() layout后有效,返回值是mRight-mLeft,一般会参考measure的宽度(measure可能没用),但不是必须的。
getHeight() layout后有效,返回值是mBottom-mTop,一般会参考measure的高度(measure可能没用),但不是必须的。
getMeasuredWidth() 返回measure过程得到的mMeasuredWidth值,供layout参考,或许没用。
getMeasuredHeight() 返回measure过程得到的mMeasuredHeight值,供layout参考,或许没用。
  • 获取view位置
View的方法 结论描述
getLocalVisibleRect() 获取View自身可见的坐标区域,坐标以自己的左上角为原点(0,0),另一点为可见区域右下角相对自己(0,0)点的坐标,其实View2当前height为550,可见height为470。
getGlobalVisibleRect() 获取View在屏幕绝对坐标系中的可视区域,坐标以屏幕左上角为原点(0,0),另一个点为可见区域右下角相对屏幕原点(0,0)点的坐标。
getLocationOnScreen() 坐标是相对整个屏幕而言,Y坐标为View左上角到屏幕顶部的距离。
getLocationInWindow() 如果为普通Activity则Y坐标为View左上角到屏幕顶部(此时Window与屏幕一样大);如果为对话框式的Activity则Y坐标为当前Dialog模式Activity的标题栏顶部到View左上角的距离。
  • View滑动相关坐标系

View的scrollTo()和scrollBy()是用于滑动View中的内容,而不是改变View的位置;改变View在屏幕中的位置可以使用offsetLeftAndRight()和offsetTopAndBottom()方法,他会导致getLeft()等值改变

View的滑动方法 效果及描述
offsetLeftAndRight(int offset) 水平方向挪动View,offset为正则x轴正向移动,移动的是整个View,getLeft()会变的,自定义View很有用
offsetTopAndBottom(int offset) 垂直方向挪动View,offset为正则y轴正向移动,移动的是整个View,getTop()会变的,自定义View很有用
scrollTo(int x, int y) 将**View中内容(不是整个View)**滑动到相应的位置,参考坐标原点为ParentView左上角,x,y为正则向xy轴反方向移动,反之同理。
scrollBy(int x, int y) 在scrollTo()的基础上继续滑动xy。
setScrollX(int value) 实质为scrollTo(),只是只改变Y轴滑动。
setScrollY(int value) 实质为scrollTo(),只是只改变X轴滑动。
getScrollX()/getScrollY() 获取当前滑动位置偏移量。

Android 视图绘制流程

view的层级结构

一. onMeasure()  决定View的大小;

源码:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
用于测量视图的大小。View 系统的绘制流程会从 ViewRoot 的 performTraversals()方法中开始,在其内部调用 View 的 measure()方法。measure()方法接收两个参数,widthMeasureSpec 和 heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。
MeasureSpec 的值由 specSize 和 specMode 共同组成的,其中 specSize 记
录的是大小,specMode 记录的是规格。specMode 一共有三种类型,如下所
示:
1. EXACTLY(确切的大小,如:100dp)
表示父视图希望子视图的大小应该是由 specSize 的值来决定的,系统默认会按
照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任
意的大小。
2. AT_MOST(大小不可超过某数值,如:matchParent, 最大不能超过父布局大小)
表示子视图最多只能是 specSize 中指定的大小,开发人员应该尽可能小得去设
置这个视图,并且保证不会超过 specSize。系统默认会按照这个规则来设置子
视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED(不对View大小做限制,系统使用)
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这
种情况比较少见,不太会用到。
  public class MyView extends View {  @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(200, 200);}}
上面代码中把 View 默认的测量流程覆盖掉了,不管在布局文件中定义 MyView 这个视图的大小是多少,最终在界面上显示的大小都将会是 200*200。

注意:在 setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和 getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是 0。
由此可见,视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在 XML 文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。

getMeasureWidth与getWidth的区别

  1. getMeasureWidth:在measure()过程结束后就可以获取到对应的值; 通过setMeasuredDimension()方法来进行设置的.
  2. getWidth:在layout()过程结束后才能获取到; 通过视图右边的坐标减去左边的坐标计算出来的.

二. onLayout() 决定View在ViewGroup中的位置;

源码:protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
这个方法是用于给视图进行布局的,也就是确定视图的位置。ViewRoot 的 performTraversals()方法会在 measure 结束后继续执行,并调用 View 的 layout()方法来执行此过程。
从上面的源码中可以看到,View 中的 onLayout()方法就是一个空方法,因为 onLayout()过程是为

了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图 决定子视图的显示位置。既然如此,我们来看下 ViewGroup 中的 onLayout() 方法是怎么写的吧,代码如下:

ViewGroup中的源码:@Overrideprotected abstract void onLayout(boolean changed,int l, int t, int r, int b);
可以看到,ViewGroup 中的 onLayout()方法竟然是一个抽象方法,这就意味着所有 ViewGroup 的子类都必须重写这个方法。
在 onLayout()过程结束后,我们就可以调用 getWidth()方法和 getHeight()方 法来获取视图的宽高了。
 

三. onDraw()  决定绘制这个View。

在这里才真正地开始对视图进行绘制。ViewRoot 中的代码会继续执行并创建出一个 Canvas 对象,然后调用 View 的 draw()方法来 执行具体的绘制工作。draw()方法内部的绘制过程总共可以分为六步,其中第二步和第五步在一般情况下很少用到,因此这里我们只分析简化后的绘制过程。代
码如下所示:

  public void draw(Canvas canvas) {if (ViewDebug.TRACE_HIERARCHY) {ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);}final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;// Step 1, draw the background, if needed int saveCount;if (!dirtyOpaque) {final Drawable background = mBGDrawable;if (background != null) {final int scrollX = mScrollX;final int scrollY = mScrollY;if (mBackgroundSizeChanged) {background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);mBackgroundSizeChanged = false;}if ((scrollX | scrollY) == 0) {background.draw(canvas);} else {canvas.translate(scrollX, scrollY);background.draw(canvas);canvas.translate(-scrollX, -scrollY);}}}final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) {// Step 3, draw the content if (!dirtyOpaque) onDraw(canvas);// Step 4, draw the children dispatchDraw(canvas);// Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas);// we're done...return;}}
第一步的作用是对视图的背景进行绘制。这里会先得到一个 mBGDrawable 对象,然后根据 layout 过程确定的视图位置来设置背景的绘制区域,之后再调用 Drawable 的draw()方法来完成背景的绘制工作。那么这个 mBGDrawable 对象是从哪里来的呢?其实就是在 XML中通过 android:background 属性设置的图片或颜色。当然你也可以在代码中通过 setBackgroundColor()、setBackgroundResource()等方法进行赋值。
第三步的作用是对视图的内容进行绘制。可以看到,这里去调用了一下onDraw()方法,那么 onDraw()方法里又写了什么代码呢?进去一看你会发现,原来又是个空方法啊。其实也可以理解,因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现也是理所当然的。
第四步的作用是对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现 View 中的 dispatchDraw()方法又是一个空方法,而 ViewGroup 的 dispatchDraw()方法中就会有具体的绘制代码。
第六步的作用是对视图的滚动条进行绘制。那么你可能会奇怪,当前的视图又不一定是 ListView 或者ScrollView,为什么要绘制滚动条呢?其实不管是 Button 也好,TextView 也 好,任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已。绘制滚动条的代码逻辑也比较复杂,这里就不再贴出来了,因为我们的重点是第三步过程。

自定义绘制API

  1. Canvas常用方法

    • 绘制图形(点、线、矩形、椭圆、圆等)
    • 绘制文本(文本的居中问题,需要Paint知识)
    • 画布的基本变化(平移、缩放、旋转、倾斜)
    • 画布的裁剪
    • 画布的保存
  2. Paint类主要用于设置绘制风格:包括画笔的颜色画笔触笔粗细、填充风格及文字的特征

  • Paint常用方法

    • 颜色
    • 类型(填充、描边)
    • 字体大小
    • 宽度
    • 对齐方式
    • 文字位置属性测量
    • 文字宽度测量
    • 3.Path常用方法

      • 添加路径
      • 移动起点
      • 贝塞尔(二阶、三阶)
      • 逻辑运算
      • 重置路径
      • PathEffect
      • Matrix
      • PathMeasure
  • PorterDuffXfermode

  • Matrix

  • 平移矩阵

  • 缩放矩阵

  • 旋转矩阵

  • ColorMatrix

类别 API 描述
旋转 setRotate 设置(非输入轴颜色的)色调
饱和度 setSaturation 设置饱和度
缩放 setScale 三原色的取值的比例
设置 set、setConcat 设置颜色矩阵、两个颜色矩阵的乘积
重置 reset 重置颜色矩阵为初始状态
矩阵运算 preConcat、postConcat 颜色矩阵的前乘、后乘
  • 4.动画

    • ObjectAnimator
    • ValueAnimator
    • AnimatorSet
    • 差值器
    • 估值器

自定义View示例:实现类似水波扩散效果一共分为六步

1.

public SpreadView(Context context) {this(context,null,0);
}public SpreadView(Context context, @Nullable AttributeSet attrs) {this(context, attrs,0);
}public SpreadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initPaints();
}private void initPaints() {}@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);
}

2.实现画笔paint类

本文一共两只画笔

private void initPaints() {//画笔1:centerPaint = new Paint();centerPaint.setColor(Color.YELLOW);centerPaint.setAntiAlias(true);//抗锯齿效果//最开始不透明且扩散距离为0alphas.add(255);spreadRadius.add(0);//画笔2:spreadPaint = new Paint();spreadPaint.setAntiAlias(true);spreadPaint.setAlpha(255);spreadPaint.setColor(Color.RED);
}

3.覆写onMeasure(…)方法

实现这个方法告诉了母容器如何放弃自定义View,可以通过提供的measureSpecs来决定你的View的高和宽,以下是一个正方形,确认它的宽和高是一样的。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int w = MeasureSpec.getSize(widthMeasureSpec);int h = MeasureSpec.getSize(heightMeasureSpec);int size = Math.min(w, h);setMeasuredDimension(size, size);
}

注意:

这个方法需要至少保证一个setMeasuredDimension(..)调用,否则会报IllegalStateException错误。

4.实现onSizeChanged(…)方法

这个方法是你获取View现在的宽和高. 这里我们计算的是中心和半径

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);//圆心位置centerX = w / 2;centerY = h / 2;
}

5.实现onDraw(…)方法

这个方法提供了如何绘制view,它提供的Canvas类可以进行绘制。

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);for (int i = 0; i < spreadRadius.size(); i++) {int alpha = alphas.get(i);spreadPaint.setAlpha(alpha);int width = spreadRadius.get(i);//绘制扩散的圆canvas.drawCircle(centerX, centerY, radius + width, spreadPaint);//每次扩散圆半径递增,圆透明度递减if (alpha > 0 && width < 300) {alpha = alpha - distance > 0 ? alpha - distance : 1;alphas.set(i, alpha);spreadRadius.set(i, width + distance);}}//当最外层扩散圆半径达到最大半径时添加新扩散圆if (spreadRadius.get(spreadRadius.size() - 1) > maxRadius) {spreadRadius.add(0);alphas.add(255);}//超过8个扩散圆,删除最先绘制的圆,即最外层的圆if (spreadRadius.size() >= 8) {alphas.remove(0);spreadRadius.remove(0);}//中间的圆canvas.drawCircle(centerX, centerY, radius, centerPaint);//TODO 可以在中间圆绘制文字或者图片//延迟更新,达到扩散视觉差效果postInvalidateDelayed(delayMilliseconds);
}

6.添加你的View

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><com.qinqu.spreadviewdemo.SpreadViewandroid:id="@+id/spreadView"android:layout_width="match_parent"android:layout_height="wrap_content"app:spread_center_color="@color/colorAccent"app:spread_delay_milliseconds="35"app:spread_distance="5"app:spread_max_radius="90"app:spread_radius="100"app:spread_spread_color="@color/colorAccent" /></LinearLayout>

问题

自定义View执行invalidate()方法,为什么有时候不会回调onDraw()?
自定义一个view时,重写onDraw。调用view.invalidate(),会触发onDraw和computeScroll()。前提
是该view被附加在当前窗口.
view.postInvalidate(); //是在非UI线程上调用的
自定义一个ViewGroup,重写onDraw。onDraw可能不会被调用,原因是需要先设置一个背景(颜色或图)。
表示这个group有东西需要绘制了,才会触发draw,之后是onDraw。因此,一般直接重写dispatchDraw来绘制viewGroup.自定义一个ViewGroup,dispatchDraw会调用drawChild.

demo:https://download.csdn.net/download/haoxuhong/10457408

Android自定义view原理及自定义View示例相关推荐

  1. android 仿360浮动,Android仿360悬浮小球自定义view实现示例

    Android仿360悬浮小球自定义view实现示例 效果图如下: 实现当前这种类似的效果 和360小球 悬浮桌面差不错类似.这种效果是如何实现的呢.废话不多说 ,直接上代码. 1.新建工程,添加悬浮 ...

  2. java 贝塞尔曲线_贝塞尔曲线:原理、自定义贝塞尔曲线View、使用!!!

    一.原理 转自:http://www.2cto.com/kf/201401/275838.html Android动画学习Demo(3) 沿着贝塞尔曲线移动的Property Animation Pr ...

  3. Android软件开发之盘点自定义View界面大合集(二)

    Android软件开发之盘点自定义View界面大合集(二) - 雨松MOMO的程序世界 - 51CTO技术博客 雨松MOMO带大家盘点Android 中的自定义View界面的绘制 今天我用自己写的一个 ...

  4. Android初级教程初谈自定义view自定义属性

    有些时候,自己要在布局文件中重复书写大量的代码来定义一个布局.这是最基本的使用,当然要掌握:但是有些场景都去对应的布局里面写对应的属性,就显得很无力.会发现,系统自带的控件无法满足我们的要求,这个时候 ...

  5. Carson带你学Android:源码解析自定义View Draw过程

    前言 自定义View是Android开发者必须了解的基础 网上有大量关于自定义View原理的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化 等 今天,我将全面总结自定义View ...

  6. android录音波浪动画_Android 自定义 view 实现波浪动画进度条

    最近在做项目时需要实现这样一种动画,类似于波浪形的进度动画,粗略的看了一下,发现好像类似于正余弦曲线实现的,但是Android 没有相关的API,所以需要我们动手画出来,所以现在在此记录一下学习过程, ...

  7. Android自定义view之(刻度尺view)

    前言: 最近一直在做h5,感觉学的东西多了还真有点混淆了,再来看anroid的时候,觉得有点点陌生了,难道真的是鱼与熊掌不可兼得吗? 好吧,也罢- 在技术群中看到一个小伙伴有一个这样的需求,所以在不是 ...

  8. android canvas绘制圆角_Android自定义View撸一个渐变的温度指示器(TmepView)

    秦子帅明确目标,每天进步一点点..... 作者 |  andy 地址 |  blog.csdn.net/Andy_l1/article/details/82910061 1.概述 自定义View对需要 ...

  9. android绘制心形_Android自定义View系列(一)——打造一个爱心进度条

    写作原因:Android进阶过程中有一个绕不开的话题--自定义View.这一块是安卓程序员更好地实现功能自主化必须迈出的一步.下面这个系列博主将通过实现几个例子来认识安卓自定义View的方法.从自定义 ...

最新文章

  1. gitlab的搭建与汉化
  2. Node.js 使用axios读写influxDB
  3. 原 Linux搭建SVN 服务器2
  4. php如何查看上传的文件大小,PHP设置最大上传文件大小
  5. maven命令mvn package指定jar包名称
  6. 分布式光伏贷款欲破冰 多家银行推出相关业务
  7. java final对象_java面向对象基础_final详细介绍
  8. 7.8 服务暴露总结
  9. Java中构造函数,静态代码块,构造代码块的执行顺序
  10. CSS基本选择器之类选择器多类名(CSS、HTML)
  11. 分布式系统关注点(3)——过去这几十年,分布式系统的「数据一致性」精华都在这了!...
  12. 计算器是不是电子计算机,计算器和计算机的区别?
  13. oracle中给予权限,Oracle给予用户权限
  14. Nginx不停机升级
  15. android minui fb显示相关函数
  16. 【C#】WinForm 之 SQL Server 服务监控器(避免开机启动服务)
  17. simulink中找不到CarSim S-Function图标
  18. Android开发AndroidStudio与eclipse安装与使用
  19. | + logger
  20. 【机器学习】缺失值的处理方法总结

热门文章

  1. redis漏洞利用总结
  2. linux rpm下载网站,推荐几个rpm下载站点
  3. 26-时尚精品服饰网店响应式网页模板 (HTML css JavaScript)
  4. deallocate mysql_MySQL 预处理语句prepare、execute、deallocate的使用
  5. WebRTC:stun/turn服务器搭建
  6. 氚云1,熟悉氚云并学会如何自建流程
  7. 向mysql数据库发送指令_常用的MySQL数据库命令大全
  8. excel值查找公式 - Vlookup
  9. 马化腾要1千年做成的事他十年完成了,还骗了马云每年投10亿!
  10. 诸暨机器人餐厅价格_诸暨写字楼食堂承包收费标准,承包饭堂