为什么我们觉得自定义View是学习Android的一道坎?
为什么那么多Android大神却认为自定义View又是如此的简单?
为什么google随便定义一个View都是上千行的代码?
以上这些问题,相信学Android的同学或多或少都有过这样的疑问。
那么,看完此文,希望对你们的疑惑有所帮助。
回到主题,自定义View ,需要掌握的几个点是什么呢?
我们先把自定义View细分一下,分为两种
1) 自定义ViewGroup
2) 自定义View
其实ViewGroup最终还是继承之View,当然它内部做了许多操作;继承之ViewGroup的View我们一般称之为容器,而今天我们不讲这方面,后续有机会再讲。
来看看自定义View 需要掌握的几点,主要就是两点
一、重写 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}方法。
二、重写 protected void onDraw(Canvas canvas) {}方法
空讲理论很难理解,我们还得用例子来说明
我们可以把View理解为一张白纸,而自定义View就是在这张白纸上画上我们自己绘制的图案,可以在绘制任何图案,也可以在白纸的任何位置绘制,那么问题来了,白纸哪里来?图案哪里来?位置如何计算?
a)白纸好说,只要我们继承之View,在onDraw(Canvas canvas)中的canvas就是我们所说的白纸
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
// canvas 即为白纸
super.onDraw(canvas);
}
}
b)图案呢?这里的图案就是有图片和文字组成,这个也好说,定义一个Bitmap 成员变量,和一个String的成员变量
private Bitmap mBitmap ;
private String mName ;
mName = "这里直接赋值";
mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher) ;
图片可以通过资源文件可以拿到。
c)计算位置
所以最核心的也是我们认为最麻烦的地方就是计算绘制的位置,计算位置就得先测量自身的大小,也就是我们必须掌握的两点中的第一点:需要重写 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}方法
先来看一下google写的TextView的onMeasure()方法是如何实现的
哇!好长!而且方法中还嵌套方法,如果真要算下来,代码量不会低于500行,看到这么多代码,头都大了,我想这也是我们为什么在学习Android自定义View的时候觉得如此困难的原因。大多数情况下,因为我们是自定义的View,可以说是根据我们的需求定制的View,所以很多里面的功能我们完全没必要,只需要几十行代码就能搞定。看到几十行代码就能搞定,感觉顿时信心倍增(^.^)
在重写这个方法之前,得先了解一个类 MeasureSpec ,如果不了解,没关系,下面就一起来了解一下这个类。先把代码贴出来,膜拜一下
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
这里我把里面一些我认为没必要的代码都去掉了,只留了以上几行代码,这样看起来很清晰,也非常容易 理解。 我们先做个转化,把上面几个成员变量转化成二进制 这个就不需要转化了,这里代表的只是一个移动的位置,也就是一个单纯的数字
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 1100 0000 0000 0000 0000 0000 0000 0000;
// 0x3 就是 0b11 左移30位 ,就是补30个0;
public static final int UNSPECIFIED = 0000 0000 0000 0000 0000 0000 0000 0000;
// 0b00 左移30位
public static final int EXACTLY = 0100 0000 0000 0000 0000 0000 0000 0000 ;
//0b01 左移30位
public static final int AT_MOST = 1000 0000 0000 0000 0000 0000 0000 0000 ;
//0b10 左移30位
你就会问了,这样写有什么好处呢? 细心的人看了上面这几个方法就明白了,每个方法中都有一个 & 的操作,所以我们接下来看看这集几个方法的含义是什么,先从下往上看,先易后难
1、 public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } 顾名思义,通过measureSpec这个参数,获取size ,两个都是int类型,怎么通过一个int类型的数 获取另一个int类型的数。我们在学习java的时候知道,一个int类型是32位,任何int类型的数都是有 32位,比如一个int类型的数值3,它也是占有32位,只是高30位全部为0。google 也是利用这一点, 让这个int类型的measureSpec数存了两个信息,一个就是size,保存在int类型的低30位,另一个就 是mode,保存在int类型的高2位。前面我们看到了有几个成员变量,UNSPECIFIED,EXACTLY,AT_MOST 者就是mode的三种选择,目前也只有这三种选择,所以只需要2位就能实现。
2、 ` public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); }` 这也好理解,获取模式,但这些模式有啥用处呢?
1)、EXACTLY 模式: 准确的、精确的;这种模式,是最容易理解和处理的,可以理解为大小固定, 比如在定义layout_width的时候,定义为固定大小 10dp,20dp,或者match_parent(此时父控件是 固定的)这时候,获取出来的mode就是EXACTLY
2)、AT_MOST 模式: 最大的;这种模式稍微难处理些,不过也好理解,就是View的大小最大不能超 过父控件,超过了,取父控件的大小,没有,则取自身大小,这种情况一般都是在layout_width设为 warp_content时。
3)、UNSPECIFIED 模式:不指定大小,这种情况,我们几乎用不上,它是什么意思呢,就是View的 大小想要多大,就给多大,不受父View的限制,几个例子就好理解了,ScrollView控件就是。
3、
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
这个方法也好理解,封装measureSpec的值,在定义一个View的大小时,我们只是固定了大小,你
下次想要获取mode的时候,肯定无法拿到,所以就得自己把模式添加进去,这个方法,在自定义View中
,也基本不需要用到,他所使用的场所,是在设置子View的大小的时候需要用到,所以如果是自定义
ViewGroup的话,就需要用到。 感觉讲了这么多,还是不知道怎么使用,接下来就来重写onMeasure()方法,写完之后,你就明白
了。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//这里方法套路都是一样,不管三七 二十一,上来就先把mode 和 size 获取出来。
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//View 真正需要显示的大小
int width = 0, height = 0;
//这里是去测量字体大小
measureText();
//字体宽度加图片宽度取最大宽度,这里因为字体和图片是上下排列
int contentWidth = Math.max(mBoundText.width(), mIconNormal.getWidth());
// 我们渴望得到的宽度
int desiredWidth = getPaddingLeft() + getPaddingRight() + contentWidth;
//重点来了,判断模式,这个模式哪里来的呢,就是在编写xml的时候,设置的layout_width
switch (widthMode) {
//如果是AT_MOST,不能超过父View的宽度
case MeasureSpec.AT_MOST:
width = Math.min(widthSize, desiredWidth);
break;
//如果是精确的,好说,是多少,就给多少;
case MeasureSpec.EXACTLY:
width = widthSize;
break;
//这种情况,纯属在这里打酱油的,可以不考虑
case MeasureSpec.UNSPECIFIED://我是路过的
width = desiredWidth;
break;
}
int contentHeight = mBoundText.height() + mIconNormal.getHeight();
int desiredHeight = getPaddingTop() + getPaddingBottom() + contentHeight;
switch (heightMode) {
case MeasureSpec.AT_MOST:
height = Math.min(heightSize, desiredHeight);
break;
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
height = contentHeight;
break;
}
//最后不要忘记了,调用父类的测量方法
setMeasuredDimension(width, height);
}
到这里,就算View的大小就已经完成了,自定义View的计算过程和以上方法基本类似。接着就是计算需要显示的图标和字体的位置。这里希望图片和字体垂直排列,并居中显示在View当中,因为当前的View的宽高已经测量好了,接下来的计算也就非常简单了,这里就放在onDraw()方法中计算
d)绘制图标和字体
绘制图标,可以用canvas.drawBitmap(Bitmap bitmap, int left, int top ,Paint paint)方法,bitmap 已经有了,如果不需要对图片作特殊处理 paint 可以传入null表示原图原样的绘制在白纸上,所以就差绘制的位置 left ,top前面已经分析过了,需要把图绘制在View的中间,当然这里还需包含字体,所以可以这样计算left 和top。
int left = (mViewWidth - mIconNormal.getWidth())/2 ;
int top = (mViewHeight - mIconNormal.getHeight() - mBoundText.height()) /2 ;
mViewWidth --->View的宽度,mIconNormal --->图片的宽度, mBoundText.height() --->字体的高度;绘制字体,绘制字体,就比绘制图片稍微麻烦点,因为绘制字体需要用到画笔Paint ,这里定义一个画笔Paint,直接new 一个出来
mTextPaintNormal = new Paint();
//设置字体大小
mTextPaintNormal.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mTextSize, getResources().getDisplayMetrics()));
//设置画笔颜色,也就是字体颜色
mTextPaintNormal.setColor(mTextColorNormal);
//设置抗锯齿
mTextPaintNormal.setAntiAlias(true);
这里也是调用Canvas的方法 canvas.drawText(mTextValue,x,y, mTextPaintNormal);mTextValue需要绘制的字体内容, mTextPaintNormal画笔,x,y需要绘制的位置
float x = (mViewWidth - mBoundText.width())/2.0f ;
float y = (mViewHeight + mIconNormal.getHeight() + mBoundText.height()) /2.0F ;
整体来说代码还是相当少的。下面把onDraw的代码也贴出来
@Override
protected void onDraw(Canvas canvas) {
drawBitmap(canvas) ;
drawText(canvas) ;
}
private void drawBitmap(Canvas canvas) {
int left = (mViewWidth - mIconNormal.getWidth())/2 ;
int top = (mViewHeight - mIconNormal.getHeight() - mBoundText.height()) /2 ;
canvas.drawBitmap(mIconNormal, left, top ,null);
}
private void drawText(Canvas canvas) {
float x = (mViewWidth - mBoundText.width())/2.0f ;
float y = (mViewHeight + mIconNormal.getHeight() + mBoundText.height()) /2.0F ;
canvas.drawText(mTextValue,x,y, mTextPaintNormal);
}
总结:
onMeasure() 方法只要了解了 MeasureSpec 类就不是什么问题,而MeasureSpec 也很简单,onDraw() 方法就需要了解Canvas 类的绘制方法,并且通过简单的Api查询,就基本能实现我们所需的要求。对于自定义View,如果你会重写 测量 和 onDraw 方法,那么就具备了此技能,而如果需要了解更深,自定义有个性,更绚丽的View,就还得深入了解Canvas 、Paint等方法
分享到:
相关推荐
仿真书籍翻页效果BookPageView简介:实现了仿真翻页效果,教程完整地描述了翻页原理分析到性能优化的过程教程博客:Android自定义View——从零开始实现书籍翻页效果(一) Android自定义View——从零开始实现书籍...
在Android开发中,自定义View是一项重要的技能,它允许开发者根据特定需求创建独特且功能丰富的用户界面。这个“android 自定义view比较综合的例子”显然涵盖了多个高级话题,旨在帮助有一定基础的开发者提升自定义...
总的来说,这个项目涵盖了Android自定义View的基本流程,以及图形绘制、颜色处理、动态更新等核心知识点,是学习Android自定义组件的一个良好实践。通过学习和研究,开发者不仅可以掌握自定义View的开发技巧,还能...
Android 自定义 View 实现转盘旋转的效果 Android 的自定义 View 为开发者定义和使用个性化的 View 提供了很好的支持,想要使用自己定义的 View,需要继承 View 类,并重写构造函数和 onDraw() 函数。onDraw 函数...
方便调用,有示例。 Android自定义View图片裁剪,支持自由裁剪、按自定义比例裁剪、圆形裁剪、旋转、镜面翻转,从0到1自定义View。kotlin编写的一个自定义View。
在Android开发中,自定义View是一项重要的技能,它允许开发者根据需求创建独特的用户界面元素,以实现更加丰富和个性化的交互体验。"Android 自定义View界面大合集"的主题涵盖了这个领域的广泛知识点,包括但不限于...
这个过程涉及到了Android自定义View的基本原理,包括绘图、触摸事件处理、动画和布局管理等。了解并掌握这些知识,对于提升Android应用的用户体验具有重要意义。想要了解更多关于Android自定义View的内容,可以访问...
总结来说,创建Android自定义View模板并实现点击事件的回调,主要包括以下步骤: 1. 定义XML资源文件(如attrs.xml),声明自定义属性。 2. 创建自定义View类,继承自适当的父View类,如`RelativeLayout`。 3. 在...
在Android开发中,自定义View是一项重要的技能,它允许开发者突破系统提供的标准组件限制,创造出独特且富有创意的用户界面。本资源“Android 自定义view 大合集源码”由知名开发者雨松MOMO提供,旨在深入探讨并实践...
Android自定义View,实现画直线、折现,拖动点可以随意移动位置。实现横竖屏切换时,坐标根据分辨率进行转换。
本项目"Android自定义view,实现多画面播放器"就是这样一个实例,它旨在提供一个能够同时展示多个视频流并支持交互操作的视图。 首先,自定义View的基本流程包括: 1. 创建一个新的View类,通常继承自View或已有的...
本篇博文源码聚焦于Android自定义View的基础实例,旨在帮助开发者掌握自定义View的基本步骤和技巧。 首先,自定义View通常涉及到以下几个关键点: 1. **创建新的View类**:开发者需要继承一个已有的View类,如View...
在Android开发中,自定义View是一项重要的技能,它允许开发者创造出独特且富有表现力的UI元素,提升用户体验。本文将深度剖析如何实现一个名为“水晶/水滴 波浪球”的自定义View,并通过详细步骤讲解,帮助你掌握...
在博客文章“Android自定义View的事件分发机制(一)”中,作者jsonnan详细阐述了这些概念,并可能通过实例代码展示了如何在自定义View中实现自定义的事件分发逻辑。通过阅读这篇文章,开发者可以更深入地理解...
我的Android进阶之旅------>Android自定义View来实现解析lrc歌词并同步滚动、上下拖动、缩放歌词的功能 http://blog.csdn.net/ouyang_peng/article/details/50813419 1、实现歌词同步滚动的功能,即歌曲播放到哪句...
总结,Android自定义View中的原点坐标问题涉及到视图的测量、布局、绘制和事件处理等多个方面。开发者需要深入理解这些概念,才能灵活地创建满足需求的自定义视图。通过源码学习、使用合适的工具以及不断实践,可以...
本项目"Android自定义View之高仿QQ健康"旨在教你如何模仿流行的QQ健康应用,利用谷歌的Material Design风格来设计自定义界面,提供一个既美观又实用的样式。 首先,我们来了解一下Material Design。它是谷歌推出的...
Android自定义View高仿抖音潜艇大挑战小游戏.zipAndroid自定义View高仿抖音潜艇大挑战小游戏.zipAndroid自定义View高仿抖音潜艇大挑战小游戏.zipAndroid自定义View高仿抖音潜艇大挑战小游戏.zipAndroid自定义View...
在Android开发中,自定义View是一项重要的技能,它允许开发者扩展Android系统提供的基本视图组件,以满足项目中独特的需求。本篇文章将深入探讨如何基于鸿洋大神的博客内容,创建一个自定义的验证码View。 验证码的...
在Android开发中,自定义View是一项重要的技能,它允许开发者根据特定需求创建独特且富有表现力的用户界面。本篇文章将深入探讨如何实现一个环形带刻度的进度条,这个自定义View适用于各种需要展示进度的情况,比如...