简介

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

如果功能布局要求非常定制化,已经不能由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 lifecycle

构造函数

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

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

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

Custom Attributes sample

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

  1. public PageIndicatorView(Context context, AttributeSet attrs) {
  2. super(context, attrs);
  3. TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.PageIndicatorView);
  4. int count = typedArray.getInt(R.styleable.PageIndicatorView_piv_count,0);
  5. typedArray.recycle();
  6. }
注意:
  • 在创建自定义属性使用一个简单的前缀,以避免与其它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大小和模式(宽度和高度)。

    1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    2. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    3. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    4. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    5. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    6. }
  3. 检查MeasureSpec 设置和调整View(宽度和高度)的尺寸模式。

    1. int width;
    2. if (widthMode == MeasureSpec.EXACTLY) {
    3. width = widthSize;
    4. } else if (widthMode == MeasureSpec.AT_MOST) {
    5. width = Math.min(desiredWidth, widthSize);
    6. } else {
    7. width = desiredWidth;
    8. }

注意:

看看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(如果需要)。

  1. ValueAnimator animator = ValueAnimator.ofInt(0, 100);
  2. animator.setDuration(1000);
  3. animator.setInterpolator(new DecelerateInterpolator());
  4. animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  5. public void onAnimationUpdate(ValueAnimator animation) {
  6. int newRadius = (int) animation.getAnimatedValue();
  7. }
  8. });
注意:

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

Sample of animation via ValueAnimator

希望这篇文章可以帮助你实现你的第一个自定义View,如果你想更多地了解它,可以看看这个视频

文/adison(简书作者)
原文链接:http://www.jianshu.com/p/29bb35a4860e

转载:【译】Android: 自定义View的更多相关文章

  1. 转载:android自定义view实战(温度控制表)!

    效果图 package cn.ljuns.temperature.view; import com.example.mvp.R; import android.content.Context;impo ...

  2. Android为TV端助力 转载:android自定义view实战(温度控制表)!

    效果图 package cn.ljuns.temperature.view; import com.example.mvp.R; import android.content.Context;impo ...

  3. Android自定义View之CircleView

    Android自定义View之CircleView 版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请表明出处:http://www.cnblogs.com/cavalier-/p/5999 ...

  4. Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...

  5. Android 自定义View及其在布局文件中的使用示例(二)

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3530213.html From crash_coder linguowu linguowu0622@gami ...

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

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

  7. Android自定义View

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

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

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

  9. Android 自定义View (一)

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

  10. Android 自定义 View 圆形进度条总结

    Android 自定义圆形进度条总结 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 微信公众号:牙锅子 源码:CircleProgress 文中如有纰漏,欢迎大家留言指出. 最近 ...

随机推荐

  1. Linux&UNIX上卸载GoldenGate的方法

    1. Log on to the database server (as oracle) where the GoldenGate software is installed. [root@oracl ...

  2. Windows 8.1 (64bit) 下搭建 Scrapy 0.22 环境

    我的Windows 8.1 环境 1.下载安装Python 2.7.6 在Python官方网站中下载Python2.7.6的Windows安装包,根据默认配置安装到C:\Python27目录. 安装完 ...

  3. Log Parser 2.2

    Log Parser 2.2 是一个功能强大的通用工具,它可对基于文本的数据(如日志文件.XML 文件和 CSV 文件)以及 Windows 操作系统上的重要数据源(如事件日志.注册表.文件系统和 A ...

  4. Excel REPT函数使用

    需要制作1K大小的数据 使用Excel REPT函数可以迅速制造 Excel REPT 函数 =REPT(1,1024) 结果直接黏贴进txt文件,注意删除尾空格.

  5. NGUI3.5系列教程之 UILabel

    此NGUI版本为:3.5.1 NGUI 的UILabel脚本下的文字框可以用BBCode设置:[b]Bold[/b] 粗体 [i]italic[/i] 斜体 [u]underline[/u]下划线 [ ...

  6. Django 学习笔记之三 数据库输入数据

    假设建立了django_blog项目,建立blog的app ,在models.py里面增加了Blog类,同步数据库,并且建立了对应的表.具体的参照Django 学习笔记之二的相关命令. 那么这篇主要介 ...

  7. Python Socket File Transfer

    I have a RPi which I intented to use it to crawl data. The development environment in RPi is very ba ...

  8. unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor

    eclipse启动项目时,提示超时: 解决方案: 修改 workspace\.metadata\.plugins\org.eclipse.wst.server.core\servers.xml文件.  ...

  9. Sublime key bindings使用

    开启vi mode后,可以使用很多的VI快捷方式,所以我的sublime已经不是单纯的st了,st的VI模式不完全支持所有的快捷键.我们来看一段官网的key bindings示例: { "k ...

  10. git撤销删除

    问题描述:     使用git时本地文件删除了,提交至github,希望撤销修改,找回源文件 问题解决: (1)查看git log,查看日志信息 注:     使用 git log 可以查看提交的日志 ...