热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[Android技术专题]自定义View从入门到上天

一、前言标题起得屌了点,文章只能给大家带来理论知识,能不能上天还是得各位亲自实践。文中涉及到很多自己的理解,能力有限,有问题的地方还请指正,文中的参考资料都是精心筛选的,非常值得阅
一、前言

标题起得屌了点,文章只能给大家带来理论知识,能不能上天还是得各位亲自实践。文中涉及到很多自己的理解,能力有限,有问题的地方还请指正,文中的参考资料都是精心筛选的,非常值得阅读。

很多人把自定义View想得复杂了,以为有多高深,主要还是没有实践过,没有足够的自信;但也有很多人把自定义View想得简单了,以为摸清View的几个关键回调、知道自定义属性和Android的消息分发机制就算是老司机了,其实对于自定义View来讲,设计、排版、效率都是很费脑筋的,我在github上到现在都没发现一个像样的图文混排自定义View。

常见的Android自定义View主要有两种类型:

  • 组合控件:通过Android的基础控件(TextView、CheckBox、Button、ProgressBar等)组合而成,比如试题控件(TextView+VideoGroup)、下拉刷新、瀑布流控件、带左/右滑功能的控件、视频控件等,这种自定义View的难点在于程序的逻辑处理;

  • 完全自定义控件:继承自View、TextureView或SurfaceView,然后重写核心的回调方法,以View为例,按需复写其构造、onMeasure、onLayout、onTouchEvent、onDraw、onAttachedToWindow、onDetachedFromWindow等方法,这种自定义View的难点在于程序的设计、效率优化和排版,比如输入法中的手写控件、图文混排控件(现在很多都是通过webview加载网页实现了)、词典取词控件、图表控件、个性化进度条、弹幕显示控件、Markdown控件、IDE代码编辑控件等。

按照上面这种方式分只是便于理解,很多时候有些控件既有组合,又需要复写所继承类的回调方法。

二、自定义View的价值
  • 能够做到基础控件无法做到的效果,为应用的表现增色;

  • 在多个应用并行开发的团队,将公用的交互效果提取成自定义控件,方便复用,减少不必要的重复劳动;

  • 将控件的内部逻辑封装在自定义View中,便于应用内解耦;

三、有必要了解的核心知识点

View、SurfaceView、TextureView的区别

  • View:普通的View,与宿主窗口共享同一个绘图表面,UI在主线程中绘制,在有无硬件加速的情况下都能工作(没有硬件加速的情况下,canvas的有些方法会失效);

  • SurfaceView:继承自View,绘制和显示效率高,因为拥有独立的绘图表面,UI在一个独立的线程中进行绘制,不会占用主线程的资源。SurfaceView的使用和普通的View不一样,需要结合SurfaceHodler一起使用。因为和宿主窗口不是共享同一个绘图表面的原因,笔者在实际使用SurfaceView的过程中发现对其做动画操作会达不到想要的效果(一坨黑色);

  • TextureView:继承自View,与SurfaceView相比,TextureView不会创建一个单独的绘图表面,这使得它可以像一般的View一样执行一些变换操作,比如移动、动画等等,但TextureView必须在硬件加速开启的窗口中才能正常工作;

View的三大核心方法onMeasure、onLayout、onDraw

  • onMeasure:用于测量视图的大小;

  • onLayout:用于给视图进行布局;

  • onDraw:用于对视图进行绘制;

自定义属性

对于自定义View的一些属性设置,除了可以在自定义View中提供公开接口外,还可以通过自定义属性,在对自定义View布局时就指定,这样可以简化用户使用控件的复杂度,实现自定义属性的步骤如下:

  • 在自定义View工程的res/values文件夹下新建一个attrs.xml的文件,在里面定义自定义属性的ID、属性和属性对应的类型,eg:











  • 在自定义View带attrs参数的构造方法中解析自定义属性值:

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DictView, defStyle, 0);
    int n = a.getIndexCount();
    for(int i = 0; i {
    int attr = a.getIndex(i);
    if(attr == com.test.dict.R.styleable.DictView_textSize){
    textSize = a.getDimensionPixelSize(attr, textSize);
    }else if(attr == com.test.dict.R.styleable.DictView_textColor){
    textColor = a.getColorStateList(attr);
    }else if(attr == com.test.dict.R.styleable.DictView_typeface){
    typefaceIndex = a.getInt(attr, typefaceIndex);
    }else if(attr == com.test.dict.R.styleable.DictView_width){
    setWidth(a.getDimensionPixelSize(attr, mWidth));
    }else if(attr == com.test.dict.R.styleable.DictView_height){
    setHeight(a.getDimensionPixelSize(attr, mHeight));
    }
    }
    a.recycle();

对自定义属性的解析需要注意两点:

1.TypedArray使用完成后一定要调用其recycle方法,否则会有内存泄露的问题;

2.如果自定义View在一个单独的module中(不属于主工程),对attr的获取不能使用switch-case语句,要用if…else,具体原因之前有介绍过,详见:在Android library中不能使用switch-case语句访问资源ID的原因分析及解决方案

完成自定义属性的定义后,就可以在布局自定义View的过程中使用自定义属性了,具体步骤如下:

  • 在xml布局文件的根标签或者需要使用自定义属性的标签中指定自定义属性的命名空间,其中这里的dictview就是命名空间,是可以随意指定的:

    xmlns:dictview="http://schemas.android.com/apk/res-auto"

  • 在自定义View的布局中使用自定义属性,所有自定义属性的设置都是在指定的命名空间下的,因为是自定义,所以不能用android这个命名空间:

    android:layout_
    android:layout_
    android:layout_centerHorizOntal="true"
    android:layout_centerVertical="true"
    android:text="@string/hello_world"
    dictview:textColor="@android:color/white"
    dictview:typeface="sans" />

双缓冲

在移动设备中很容易出现效率问题,对于效率问题的处理,主要方法是时间换空间或者空间换时间;自定义View可能存在显示的效率问题,可以通过双缓冲来解决这个问题,双缓冲就是用空间换时间的典型例子,同一个View在内存中创建了两份同样大小的内存,一份用于绘制,一份用于显示,绘制是绘制在Bitmap上,显示就是将这张bitmap显示在画布上。

硬件加速

在Android设备中,硬件加速默认是开启的,有些应用出于内存占用(开启硬件加速会占用更多的内存)和应用特征的考虑(没什么动画,基本没有涉及到需要GPU的操作),会在AndroidManifest.xml中关掉硬件加速,这会导致自定义View时,canvas的某些方法不能正常使用,为了让自定义View达到更好的表现效果,建议不要关掉有用到自定义View界面的硬件加速(因为在View层面只能关闭硬件加速,无法开启硬件加速,所以只能控制Activity和Window层面的硬件加速)。

图文混排:

涉及到图文混排的自定义View,一定要将排版和显示这两件事情分开,因为排版耗时但不涉及到UI的更新,可以在线程中处理,但显示必须要更新UI,所以在onDraw方法里面尽量不要做耗时和逻辑处理,只纯粹做显示操作。对于排版可以考虑异步,或者先完成排版,后续只需要直接显示即可,这得具体问题具体分析。

同时显示也有技巧,为了节省内存,可以考虑做缓存,一个控件可能不只一页内容,可以在内存中缓存当前页和当前页的前、后两页,当滑动时,始终按照这种策略更新缓存内容就可以了,这样既达到了节省内存、又提高效率的目的。

getHistorySize

对于有涉及到触摸操作的自定义View(比如手写控件),是在onTouchEvent方法中接收触摸消息的,但限于Android系统和设备本身的情况,底层上报的点信息不一定能够实时通过MotionEvent回调到上层,底层1秒钟可能传了几百个点,但onTouchEvent方法中接收到的可能只有几十个点,如果需要更为平滑地点信息,可以借助MotionEvent的getHistorySize方法获取底层上报的更多点信息,关于getHistorySize的解释,请参见参考资料中对平滑手写签名效果的介绍。

SpannableString

使用过SpannableString的都知道,可以通过它将同一串字符中的不同文字做不同的处理,比如某些文字的颜色、字体、背景色、大小等有变化,都可以通过它来设置,熟练掌握SpannableString对于灵活自定义View会有很大地帮助。

四、参考资料
  • Creating Custom Views

  • 推翻自己和过往,重学自定义View

  • Android LayoutInflater原理分析,带你一步步深入了解View(一)

  • Android视图绘制流程完全解析,带你一步步深入了解View(二)

  • Android视图状态及重绘流程分析,带你一步步深入了解View(三)

  • Android自定义View的实现方法,带你一步步深入了解View(四)

  • Android 深入理解Android中的自定义属性

  • 公共技术点之 View 事件传递

  • Android 触摸及手势操作GestureDetector

  • 通过Spannable对象设置textview的样式

  • Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

  • Android视图SurfaceView的实现原理分析

  • Android硬件加速

  • Android手写优化-平滑的签名效果实现

  • Android手写优化-更为平滑的签名效果实现

五、优质开源项目
  • awesome-android-ui

  • Android 开源项目分类汇总

  • Android平台下的原生Markdown解析器

  • MarkdownEditors

  • Android开源弹幕引擎

  • DraweeTextView

  • Android图文混排控件MixtureTextView

  • markers

  • Android系统应用源码中的各种自定义控件

六、忠告

千万别一言不合就自定义,能够用Android基础控件解决的问题就尽量用基础控件,其次是用基础控件的组合,如果是确实有必要自定义才考虑自定义。自定义的控件既需要耗费较长的开发时间,又不一定能保证有基础控件那么高的效率(基础控件都是谷歌优化过了的)。

更多原创文章和优质资源请关注公众号:

《[Android技术专题]自定义View从入门到上天》 open_dev


推荐阅读
  • 本文详细介绍了在 CentOS 7 系统中配置 fstab 文件以实现开机自动挂载 NFS 共享目录的方法,并解决了常见的配置失败问题。 ... [详细]
  • MicrosoftDeploymentToolkit2010部署培训实验手册V1.0目录实验环境说明3实验环境虚拟机使用信息3注意:4实验手册正文说 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 本文详细解析了使用C++实现的键盘输入记录程序的源代码,该程序在Windows应用程序开发中具有很高的实用价值。键盘记录功能不仅在远程控制软件中广泛应用,还为开发者提供了强大的调试和监控工具。通过具体实例,本文深入探讨了C++键盘记录程序的设计与实现,适合需要相关技术的开发者参考。 ... [详细]
  • 在《Linux高性能服务器编程》一书中,第3.2节深入探讨了TCP报头的结构与功能。TCP报头是每个TCP数据段中不可或缺的部分,它不仅包含了源端口和目的端口的信息,还负责管理TCP连接的状态和控制。本节内容详尽地解析了TCP报头的各项字段及其作用,为读者提供了深入理解TCP协议的基础。 ... [详细]
  • 本文介绍了如何利用Shell脚本高效地部署MHA(MySQL High Availability)高可用集群。通过详细的脚本编写和配置示例,展示了自动化部署过程中的关键步骤和注意事项。该方法不仅简化了集群的部署流程,还提高了系统的稳定性和可用性。 ... [详细]
  • 在Android平台中,播放音频的采样率通常固定为44.1kHz,而录音的采样率则固定为8kHz。为了确保音频设备的正常工作,底层驱动必须预先设定这些固定的采样率。当上层应用提供的采样率与这些预设值不匹配时,需要通过重采样(resample)技术来调整采样率,以保证音频数据的正确处理和传输。本文将详细探讨FFMpeg在音频处理中的基础理论及重采样技术的应用。 ... [详细]
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
  • 掌握Android UI设计:利用ZoomControls实现图片缩放功能
    本文介绍了如何在Android应用中通过使用ZoomControls组件来实现图片的缩放功能。ZoomControls提供了一种简单且直观的方式,让用户可以通过点击放大和缩小按钮来调整图片的显示大小。文章详细讲解了ZoomControls的基本用法、布局设置以及与ImageView的结合使用方法,适合初学者快速掌握Android UI设计中的这一重要功能。 ... [详细]
  • 基于iSCSI的SQL Server 2012群集测试(一)SQL群集安装
    一、测试需求介绍与准备公司计划服务器迁移过程计划同时上线SQLServer2012,引入SQLServer2012群集提高高可用性,需要对SQLServ ... [详细]
  • 在尝试对 QQmlPropertyMap 类进行测试驱动开发时,发现其派生类中无法正常调用槽函数或 Q_INVOKABLE 方法。这可能是由于 QQmlPropertyMap 的内部实现机制导致的,需要进一步研究以找到解决方案。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • 在Android开发中,实现多点触控功能需要使用`OnTouchListener`监听器来捕获触摸事件,并在`onTouch`方法中进行详细的事件处理。为了优化多点触控的交互体验,开发者可以通过识别不同的触摸手势(如缩放、旋转等)并进行相应的逻辑处理。此外,还可以结合`MotionEvent`类提供的方法,如`getPointerCount()`和`getPointerId()`,来精确控制每个触点的行为,从而提升用户操作的流畅性和响应性。 ... [详细]
  • 如何正确配置Log4j以优化日志记录效果? ... [详细]
  • jQuery插件验证与屏幕键盘功能的集成解决方案
    本文介绍了一种集成了验证功能和屏幕键盘的jQuery插件解决方案。该插件不仅提供了强大的表单验证功能,还引入了一个高度可定制的屏幕键盘,以增强用户体验。通过这一集成方案,开发者可以轻松实现复杂的表单验证逻辑,并为用户提供便捷的输入方式,特别适用于移动设备或特殊输入场景。 ... [详细]
author-avatar
有志者来袭
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有