Android自定义view详解

从继承开始

懂点面向对象语言知识的都知道:封装,继承和多态,这是面向对象的三个基本特征,所以在自定义View的时候,最简单的方法就是继承现有的View

通过上面这段代码,我定义了一个SketchView,继承自View对象,并且复写了它的三个构造方法,我主要来分析一下这三个构造方法:

  • 第一个构造方法就是我们普通在代码中新建一个view用到的方法,例如

SketchView sketchView = new SketchView(this);

就这样,一个自定义的view就被新建出来了,然后可以根据需求添加到布局里

  • 第二个构造方法就是我们一般在xml文件里添加一个view

<me.shaohui.androidpractise.widget.SketchView

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_marginRight="16dp"

android:layout_marginTop="16dp" />

  • 这样,我们就把一个SketchView添加到布局文件里,并且加了一些布局属性,宽高属性以及margin属性,这些属性会存放在第二个构造函数的AttributeSet参数里
  • 第三个构造函数比第二个构造函数多了一个int型的值,名字叫defStyleAttr,从名称上判断,这是一个关于自定义属性的参数,实际上我们的猜测也是正确的,第三个构造函数不会被系统默认调用,而是需要我们自己去显式调用,比如在第二个构造函数里调用调用第三个函数,并将第三个参数设为0。

Measure->Layout->Draw

在学会如何写一个自定义控件之前,了解一个控件的绘制流程是必要的,在Android里,一个view的绘制流程包括:Measure,Layout和Draw,通过onMeasure知道一个view要占界面的大小,然后通过onLayout知道这个控件应该放在哪个位置,最后通过onDraw方法将这个控件绘制出来,然后才能展现在用户面前,下面我将挨个分析一下这三个方法的作用。

  • onMeasure 测量,通过测量知道一个一个view要占的大小,方法参数是两个int型的值,我们都知道,在java中,int型由4个字节(32bit)组成,在MeasureSpce中,用前两位表示mode,用后30位表示size

MeasureSpce的mode有三种:EXACTLY, AT_MOST,UNSPECIFIED,除却UNSPECIFIED不谈,其他两种mode:当父布局是EXACTLY时,子控件确定大小或者match_parent,mode都是EXACTLY,子控件是wrap_content时,mode为AT_MOST;当父布局是AT_MOST时,子控件确定大小,mode为EXACTLY,子控件wrap_content或者match_parent时,mode为AT_MOST。所以在确定控件大小时,需要判断MeasureSpec的mode,不能直接用MeasureSpec的size。在进行一些逻辑处理以后,调用setMeasureDimension()方法,将测量得到的宽高传进去供layout使用。

需要明白的一点是 ,测量所得的宽高不一定是最后展示的宽高,最后宽高确定是在onLayout方法里,layou(left,top,right,bottom),不过一般都是一样的。

  • onLayout 实际上,我在自定义SketchView的时候是没有重写onLayout方法的,因为SketchView只是一个单纯的view,它不是一个view容器,没有子view,而onLayout方法里主要是具体摆放子view的位置,水平摆放或者垂直摆放,所以在单纯的自定义view是不需要重写onLayout方法,不过需要注意的一点是,子view的margin属性是否生效就要看parent是否在自身的onLayout方法进行处理,而view得padding属性是在onDraw方法中生效的。

其实在onLayout方法里有一个属性我一直关注并且没有弄得很明白,就是第一个参数boolean:changed,标示这个view的大小是否发生改变,后续了解到,会回来补坑。

  • onDraw 终于说到了重头戏,一般自定义控件耗费心思最多的就是这个方法了,需要在这个方法里,用Paint在Canvas上画出你想要的图案,这样一个自定义view才算结束。下面会详细讲如何在画布上画出自己想要的图案。

关于onDraw方法,在补充一句,如果是直接继承的View,那么在重写onDraw的方法是时候完全可以把super.ondraw(canvas)删掉,因为它的默认实现是空。

得到一个正方形的View

上一部分主要说了一下,view的绘制流程,从这三个方法中,我们可以知道如何测量一个控件,如何摆放控件的子元素,如何绘制图案,下面我说一下自己通过onMeasure学到的一点小技巧。

在日常开发中,我们偶尔会需要一个正方形的imageView,一般都是通过指定宽高,但是当宽高不确定时,我们就只能寄希望于Android原声支持定义view的比例,但是现实是残酷的,系统好像是没有提供类似属性的,所以我们就只能自己去实现,其实自己写起来也特别的简单,只需要改一个参数就OK了,

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, widthMeasureSpec);

}

不仔细观察是看不出来其中的奥妙的,虽然这里复写了view的onMeasure,但是貌似没有做任何处理,直接调用了super方法,但是仔细观察的话就会发现,在调用super方法的时候,第二个参数变了,本来应该是heightMeasureSpec却换成了widthMeasureSpec,这样view的高度就是view的宽度,一个SquareView就实现了,甚至如果通过自定义属性实现一个自定义比例view。

自定义属性

自定义view没有自定义属性怎么得了,要给view支持自定义属性,需要在values/attrs.xml 文件里定义一个name为自己定义view名字的declare-styleable

<resources>

<declare-styleable name="SketchView">

<attr name="background_color" format="color"/>

<attr name="size" format="dimension"/>

</declare-styleable>

</resources>

这样就可以在xml文件里使用自己定义的属性了

<me.shaohui.androidpractise.widget.SketchView

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

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_marginRight="16dp"

android:layout_marginTop="16dp"

app:background_color="@color/colorPrimary"

app:size="24dp"/>

别忘了在前面加上自定义的命名空间,到这来看,增加自定义属性并不算什么,不过要自定义属性生效还是要耗费一些功夫的,这时候前面留下的伏笔:第三个构造方法的defStyleAttr参数就要登场了。

public SketchView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SketchView, defStyleAttr, R.style.AppTheme);

custom_size = a.getDimensionPixelSize(R.styleable.SketchView_size, SIZE);

custon_background = a.getColor(R.styleable.SketchView_background_color, DEFAULT_COLOR);

a.recycle();

}

经过好一番操作,才能把xml定义的属性拿出来,具体取得的值为多少,我在前面已经解释过,就不在这里多说,接下来的操作就是拿着这些属性干你想干的事吧。

实战:一个动态view

下面将简单介绍一下如何在onDraw(Canvas canvas) 在画布的中心位置画一个自定义颜色的圆,并且通过一个ValueAnimator让这个圆动起来,废话不多说,直接上代码:

(只贴了核心代码,完整代码会在文章最后给链接)

可以看到在onDraw()方法里,我调用了canvas的drawCircle方法画了一个圆,圆心的位置是又画布的位置决定的,位于画布的中心,width和height参数是在onLayout()方法里拿到的,在此之前取到的height和width都是不准确的,这点要注意。圆的半径是xml文件中定义的size*scale,而这个scale是通过一个ValueAnimator确定的,变化范围是从1到2,ValueAnimator的值发生改变会赋给scale同时调用postInvalidate()方法,这个方法的作用就是重绘,然后圆的半径就会发生改变,这样刷新就会实现动画的效果。

requstLayout和invalidate

在自定义view时,时常用到刷新view的方法,这时候就会有三个方法供我们选择:requestLayout()、invalidate()、postInvalidate(),其实invalidate和postInvalidate这两个方法作用是一样的,唯一不同的是invalidate用在主线程,而postInvalidate用在异步线程,下面对比一下requestLayout和invalidate的内部实现:

从代码可以看出,其实这两个方法内部都是调用的scheduleTraversals()方法,不同的是,requestLayout方法将mLayoutRequested标示置为true,scheduleTraversals这个方法以后找机会再细分析,现在只简单说下结论,

  • requestLayout会调用measure和layout 等一系列操作,然后根据布局是否发生改变,surface是否被销毁,来决定是否调用draw,也就是说requestlayout肯定会调用measure和layout,但不一定调用draw,读者可以试着改下我上面写的那个小程序,将postInvalidate改成requestlayout,动画效果就消失了,因为布局没有发生改变。
  • invalidate 只会调用draw,而且肯定会调,即使什么都没有发生改变,它也会重新绘制。

所以如果有布局需要发生改变,需要调用requestlayout方法,如果只是刷新动画,则只需要调用invalidate方法。

时间: 2024-10-08 19:27:54

Android自定义view详解的相关文章

Android 自定义 View 详解

View 的绘制系列文章: Android View 绘制流程之 DecorView 与 ViewRootImpl Android View 的绘制流程之 Measure 过程详解 (一) Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 的事件分发原理解析 对于 Android 开发者来说,原生控件往往无法满足要求,需要开发者自定义一些控件,因此,需要去了解自定义 view 的实现原理.这样即使碰到需要自定义控件的时候,也可以游刃有

自定义View详解

自定义View详解 虽然之前也分析过View回执过程,但是如果让我自己集成ViewGroup然后自己重新onMeasure,onLayout,onDraw方法自定义View我还是会头疼.今天索性来系统的学习下. onMeasure /** * <p> * Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@l

Android学习之自定义view详解

本文和大家分享的主要是android自定义view相关内容,一起来看看吧,希望对大家学习android有所帮助. 1.自定义View的属性 在 res/values/ 下建立一个 attrs.xml ,在里面定义属性和声明整个样式. <?xml version="1.0" encoding="utf-8"?> <resources> <attr name="titleText" format="string

android自定义View全解

源码在这里:CSDN 本文主要有以下内容: * 自定义View的分类 * 自定义View的注意事项 * 自定义View的实现 * 自定义View使其支持wrap_content和padding * 自定义属性的实现过程 首先,自定义View是为了达到更绚丽的效果.它相对来说也是一个比较难的技术体系,涉及到[View的层次结构].[View事件分发机制].[View的工作原理].[View的弹性滑动].[View的滑动冲突]等技术. 自定义View的分类 1.1. 继承 View重写 onDraw

Android自定义View【实战教程】5??---Canvas详解及代码绘制安卓机器人

友情链接: Canvas API Android自定义View[实战教程]3??--Paint类.Path类以及PathEffect类详解 神马是Canvas 基本概念 Canvas:可以理解为是一个为我们提供了各种工具的画布,我们可以在上面尽情的绘制(旋转,平移,缩放等等).可以理解为系统分配给我们一个一个内存空间,然后提供了一些对这个内存空间操作的方法(API), 实际存储是在下面的bitmap. 两种画布 这里canvas可以绘制两种类型的画图,分别是view和surfaceView. V

Android自定义view教程05--自定义view的基础知识之LayoutInflater详解

前面的教程我们讲了一下android的动画 的使用,尤其是着重讲解了属性动画的使用.那么这章开始我们将会讲一下android 自定义view的一些基础知识.帮助大家理解. 首先我们来关注一下LayoutInflater这个类,经常使用开源控件的人对这个类应该很熟悉了,但是很少有人能把这个类讲明白 用清楚,今天我们就来深挖一下这个类. 首先我们定义一个button.xml 和button2.xml 1 <?xml version="1.0" encoding="utf-8

android源码分析 android toast使用详解 toast自定义

在安卓开发过程中,toast使我们经常使用的一个类,当我们需要向用户传达一些信息,但是不需要和用户交互时,该方式就是一种十分恰当的途径. 我们习惯了这样使用toast:Toast.makeText(Context context, String info, int duration).show();该方法是 系统为我们提供的一个方便的创建toast对象的静态方法,其内部依然是调用toast的相关方法完成.下面 就从其源码对该类的实现做一个分析 在toast类中,最重要的用于显示该toast的sh

Android相机开发详解(一)

Android相机开发详解(一) 请支持原创,尊重原创,转载请注明出处:http://blog.csdn.net/kangweijian(来自kangweijian的csdn博客) Android相机开发能够实现打开相机,前后摄像头切换,摄像预览,保存图片,浏览已拍照图片等相机功能. Android相机开发详解(一)主要实现打开相机,摄像预览,前后置摄像头切换,保存图片等四个功能. Android相机开发详解(二)主要实现翻页浏览相片,触控缩放浏览图片,删除图片,发送图片等四个功能. Andro

Android之canvas详解

首先说一下canvas类: Class Overview The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, t