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

如果功能布局要求非常定制化,已经不能由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. Css3中的 calc()使用

    http://www.w3cplus.com/css3/how-to-use-css3-calc-function.html

  2. linux makefile字符串操作函数 替换subst、模式替换patsubst、去首尾空格strip、查找字符串findstring、过滤filter、反过滤filter-out、排序函数sort、取单词word、取单词串wordlist、个数统计words

    1.1       字符操作函数使用 在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能.make所支持的函数也不算很多,不过已经足够我们的操作了.函数调用后,函 ...

  3. AppScan扫描结果分析及工具栏使用

    Appscan的窗口大概分三个模块,Application Links(应用链接), Security Issues(安全问题), and Analysis(分析) Application Links ...

  4. java导入项目有红色叹号

    原因:缺少jar包 解决:         选中项目  ->  右键  -> Build Path  -> Configer Builder Path  ->  删除掉有错的J ...

  5. 洛谷 [P1265] 公路修建

    本题的描述:城市联盟,最短距离.. 使人想到了prim求MST,再一看数据范围:完全图!,那么一定得用prim,因为只有5000个点,所以不加优化的prim就能过. #include <iost ...

  6. 夏令营提高班上午上机测试 Day 3 解题报告

    今天的题的确水.T3还是一道NOIP原题. 嘛,多刷点水题也不是什么坏事嘛. 说来也快,夏令营结束了整一星期了呢.大家也都回到了日常的暑假生活呢. 今天学业水平测试出成绩了...嗯结果还算满意呢,至少 ...

  7. 闭包和es6实现循环绑定li输出固定索引值

    首先我们需要一个html结构 <div > <ul> <li>a</li> <li>a</li> <li>a< ...

  8. JDBC【PreparedStatment、批处理、处理二进制、自动主键、调用存储过程、函数】

    1.PreparedStatement对象 PreparedStatement对象继承Statement对象,它比Statement对象更强大,使用起来更简单 Statement对象编译SQL语句时, ...

  9. Dagger2 使用全解析

    Dagger2 使用全解析 Dagger是一个注入工具,何为注入,我们要生产一批机器人,每个机器人都有一个控制器,我们可以在机器人内部 new 出一个控制器: class Robot { val co ...

  10. angular2^ typescript 将 文件和Json数据 合并发送到服务器(2.服务端)

    nodejs 中使用框架 express web框架 multer 文件接受 直接贴代码了,我就不解释了 "use strict"; exports.__esModule = tr ...