每天我们都会使用很多的应用程序,尽管他们有不同的约定,但大多数应用的设计是非常相似的。这就是为什么许多客户要求使用一些其他应用程序没有的设计,使得应用程序显得独特和不同。

如果功能布局要求非常定制化,已经不能由Android内置的View创建 —这时候就需要使用自定义View了。而这意味着在大多数情况下,我们将需要相当长的时间来完成它。但这并不意味着我们不应该这样做,因为实现它是非常令人兴奋和有趣的。

我最近面临了类似的情况:我的任务是使用ViewPager实现Android应用引导页。不同于iOS,Android并没有提供这样的View,所以我不得不编写一个自定义View来实现它。

我花了一些时间来实现它。幸运的是,时下很多开源项目都有类似可复用的View,这节省了我和其他开发者的时间。我决定基于这种View创建一个公共库。如果你有类似的功能需求并且缺乏时间实现它,可以在github repo发现它。

Sample of using PageIndicatorView

绘制!

因为编写自定义View比起普通的View更耗时,你应该只在为了实现特定的功能但没有更简单的方法情况下使用自定义View,或者你希望通过自定义View解决以下问题:

  1. 性能。如果你布局里面有很多View,你想通自定义View优化它,使其更轻量。

  2. 视图层次结构复杂。

  3. 一个完全自定义的View,需要手动绘制才能实现。

如果你还没有尝试过编写自定义View,这篇文章将教会你绘制扁平的自定义View的一些技巧。我将会告诉你整体的视图结构,如何实现具体的功能,不要重犯常见的错误,以及实现动画效果!

我们需要知道的第一件事 –View的生命周期。不知出于某种原因,谷歌并没有提供View生命周期的图表,由于开发者普遍对其有误解,导致了一些意想不到的错误和问题,所以我们要认清这过程。

构造函数

每个View的生命都是从构造函数开始。而且这是一个绘制初始化,进行各种计算,设定默认值或做任何我们需要的事情很好的地方。

但是,为了使我们的View更易于使用和配置,Android提供了很有用的AttributeSet接口。它很容易实现,而且绝对值得花时间去了解和实现它,因为它会帮助你(和你的团队)通过静态参数来设置View,对于以后新特性加入或者新屏幕拓展性支持也更好。

首先,创建一个新的文件attrs.xml。所有不同的自定义View属性都可以放在该文件中。正如你看到的这个例子,我们有一个PageIndicatorView和它的唯一属性piv_count。

Custom Attributes sample

紧接着在View的构造函数中,你需要获取这个属性并使用它,如下图所示。

public PageIndicatorView(Context context, AttributeSet attrs) {

super(context, attrs);

TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.PageIndicatorView);

int count = typedArray.getInt(R.styleable.PageIndicatorView_piv_count,0);

typedArray.recycle();

}

注意:

  • 在创建自定义属性使用一个简单的前缀,以避免与其它View类似的属性名称冲突。一般我们使用View名称缩写,就像例子中的piv_。

  • 如果你使用的是Android Studio,一旦你使用完属性,lint会建议你调用recycle()方法 。The reason is just to get rid of inefficiently bound data that’s not gonna be used again。[译者注:翻译有点拗口,其实就是回收TypedArray,以便后面重用]

onAttachedToWindow

父View调用addView(View)后,这个View将被依附到一个窗口。在这个阶段,我们的View会知道它被包围的其他view。如果你的View和其他View在相同的layout.xml,这是通过id找到他们的好地方(你可以通过属性进行设置),同时可以保存为全局(如果需要)的引用。

onMeasure

这意味着我们的自定义View到了处理自己的大小的时候。这是非常重要的方法,因为在大多数情况下,你的View需要有特定的大小以适应你的布局。

当你重写此方法,需要记得的是,最终要设置setMeasuredDimension(int width, int height) 。

onMeasure

当处理自定义View的大小时候,使用者可能通过layout.xml或者动态设置了具体的大小。要正确地计算它,我们需要做几件事情。

1.计算你的View内容所需的大小(宽度和高度)。

2.获取你的View MeasureSpec大小和模式(宽度和高度)。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);

}

3.检查MeasureSpec 设置和调整View(宽度和高度)的尺寸模式。

int width;

if (widthMode == MeasureSpec.EXACTLY) {

width = widthSize;

} else if (widthMode == MeasureSpec.AT_MOST) {

width = Math.min(desiredWidth, widthSize);

} else {

width = desiredWidth;

}

注意:

看看MeasureSpec的值:

  • MeasureSpec.EXACTLY 意味着硬编码大小值,所以你应该设置指定的宽度或高度。

  • MeasureSpec.AT_MOST 用于表明你的View匹配父View的大小,

    所以它应该和他想要的大小一样大。

    [译者注:此时View尺寸只要不超过父View允许的最大尺寸即可]

  • MeasureSpec.UNSPECIFIED 实际上是视图包装尺寸。因此,你可以使用上面计算所需的大小。

在通过setMeasuredDimension设置最终值之前,以防万一,可以检查这些值不为负数。这可以避免在布局预览时一些问题。

onLayout

此方法分配大小和位置给它的每一个子View。正因为如此,我们正在研究一个扁平的自定义视图(继承简单的View)不具有任何子View,那么就没有理由重写此方法。[译者注:实现可以参考Custom Layouts on Android]

onDraw

这就是发生魔法的地方。在这里,使用Canvas和Paint对象你将可以画任何你需要的东西。

一个Canvas实例从onDraw参数得来,它一般用于绘制不同形状,而Paint对象定义形状颜色。简单地说,Canvas用于绘制对象,而Paint用于造型。它们无处不在,无论绘制的是一个直线,圆或长方形。

onDraw() — methods example

使自定义View,要始终牢记onDraw会花费大量的时间。当布局有一些变化,滚动、快速滑动都会导致重新绘制。所以这就是为什么Android Studio也建议:避免在onDraw中进行对象分配的操作,对象应该只创建一次并在将来重用。

onDraw() — Paint object recreation

onDraw() — Paint object reuse

注意:

  • 在执行绘制时始终牢记重用对象,而不创建新的。不要依赖于IDE高亮一个潜在的问题,而是自己有意识地去做这件事,因为在onDraw调用一个内部会创建对象的方法时,IDE无法识别它。

  • 同时请不要硬编码View大小。其他开发者在使用时可以定义不同的大小,所以View大小应该取决于它有什么尺寸。

View 更新

从View的生命周期图可以得知,可以重绘View自身有两种方法。invalidate()和requestLayout()方法会帮助你在运行时动态改变View状态。但为什么需要两个方法?

  • invalidate()用来简单重绘View。例如更新其文本,色彩或触摸交互性。View将只调用onDraw()方法再次更新其状态。

  • requestLayout()方法,你可以看到其将会从`onMeasure()开始更新View。这意味着你的View更新后,它改变它的大小,你需要再次测量它,并依赖于新的大小来重新绘制。

动画

在自定义View中,动画是一帧一帧的过程。这意味着,如果你想使一个圆半径从小变大,你将需要逐步增加半径并调用invalidate()来重绘它。

在自定义View动画中,ValueAnimator是你的好朋友。下面这个类将帮助你从任何值开始执行动画到最后,甚至支持Interpolator(如果需要)。

ValueAnimator animator = ValueAnimator.ofInt(0, 100);

animator.setDuration(1000);

animator.setInterpolator(new DecelerateInterpolator());

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

public void onAnimationUpdate(ValueAnimator animation) {

int newRadius = (int) animation.getAnimatedValue();

}

});

注意:

当每一次新的动画值出来时,不要忘记调用invalidate()。

android自定义View的绘制原理的更多相关文章

  1. Android自定义View之绘制虚线

    现在实现一个效果,有个虚线分割和阴影效果.一个一个实现. 分为2中方式. 1.设计出图,我们SRC引入进来(最简单,但是需要其他资源支持). 2.code实现,有些难度,需要查资料. 现在把第2种方式 ...

  2. 【朝花夕拾】Android自定义View篇之(一)View绘制流程

    前言 转载请申明转自[https://www.cnblogs.com/andy-songwei/p/10955062.html]谢谢! 自定义View.多线程.网络,被认为是Android开发者必须牢 ...

  3. Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 onMeasure方法简述 附有自定义View例子 Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android fr ...

  4. 【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布 ...

  5. Android 自定义 View 绘制

    在 Android 自定义View 里面,介绍了自定义的View的基本概念.同时在 Android 控件架构及View.ViewGroup的测量 里面介绍了 Android 的坐标系 View.Vie ...

  6. android自定义View绘制天气温度曲线

    原文:android自定义View绘制天气温度曲线 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u012942410/article/detail ...

  7. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  8. Android自定义View

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View ...

  9. android 自定义view 前的基础知识

    本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了 ...

随机推荐

  1. docker挂载NVIDIA显卡运行pytorch

    本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃   写在前面: 请参考之前的文章安装好CentOS.NVIDIA相关驱动及软件.docker及 ...

  2. Spring MVC执行的流程

    1.Spring MVC应用的开发步骤 a.在web.xml文件中定义前端控制器DispatcherServlet来拦截用户请求.由于Web应用是基于请求/响应架构的应用,所以 不管哪个MVC Web ...

  3. spring-boot 使用 main函数 无法启动的问题完美 解决方案。

    首先 是启动之后 ,直接回exit code  0,网址 里面输入localhost:8080显示站点未启动.网上查 了多种 方式 ,日志 也 打了,都没发现问题,最后到这篇文章里 找到了答案.但是这 ...

  4. 数据库MySQL的基本操作

    1.MySQL数据库的安装: CentOS6上mysql服务端和客户端的安装和启动: #使用yum安装mysql数据库的服务端和客户端 yum install -y mysql mysql-serve ...

  5. ABP官方文档翻译 6.6 Javascript API

    JavaScript API AJAX 通知 消息 UI Block和Busy 事件总线 日志 其他实用功能 ABP提供了一套对象和函数,用来简化.标准化javascript的开发. 这里是ABP提供 ...

  6. Go笔记-继承

    [Go中继承的实现]     当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这在效果上等同于外层类型 继承 了这些方法:将父类型放在子类型中来实现亚型 package main i ...

  7. 本地Linux服务器上配置Git

    当我们需要拉取远程服务器代码到本地服务器时,我们首先要确定已经配置了正确的Git账号,可以从~/.gitconfig文件(为隐藏文件,需要使用ls -a查看),以及~/.ssh下的id_rsa.pub ...

  8. SaltStack 的远程执行机制

    html,body { font-size: 15px } body { font-family: Helvetica, "Hiragino Sans GB", "微软雅 ...

  9. mysql DML DDL DCL

    DML(data manipulation language):  它们是SELECT.UPDATE.INSERT.DELETE,就象它的名字一样,这4条命令是用来对数据库里的数据进行操作的语言  D ...

  10. MySQL5.7 group by新特性,报错1055

    项目中本来使用的是mysql5.6进行开发,切换到5.7之后,突然发现原来的一些sql运行都报错,错误编码1055,错误信息和sql_mode中的"only_full_group_by&qu ...