由于之前在实习生面试的时候,被面试官问到有关自定义控件的问题,但没有回答上来,于是回来后便学习了关于自定义控件的相关知识。

自定义控件介绍


自定义控件,按我的理解,大体上分为两种。一种是自己绘图或者加入动画,产生的单一的自定义控件。一种是利用已有的控件进行组合,产生的组合控件。这篇博文主要介绍第一种。

在进行单一的自定义控件编写时,主要需要重写三个方法:onMeasure(),onLayout()和onDraw(),在介绍这三个方法之前,先来展示一下我自己设计的一个简单的自定义控件,然后根据图片,依次对这三个方法进行讲解

这个自定义控件是一个2048方块的模型,对于一个2048方块来说,我们需要能够设置他的大小、方块上的数字以及方块的颜色。对于Android的自带控件来说,我们可以通过XML文件来静态设置这些属性,那么对于自定义控件来说,当然也可以这样做,接下来先来介绍自定义控件如何设置自定义属性

自定义属性


1.创建自定义属性

在values文件中,创建attrs.xml(如果有多个自定义控件,可以创建多个XML文件来定义自定义属性),在XML中按如下格式,声明自定义属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Code"><!--自定义控件的类名-->
<attr name="size" format="dimension"/> <!--属性名以及属性的类型-->
<attr name="text" format="string"/>
<attr name="codecolor" format="color"/>
<attr name="textcolor" format="color"/>
<attr name="textsize" format="dimension"/>
<attr name="gravity">
<flag name="left" value="0"/>
<flag name="top" value="1"/>
<flag name="right" value="2"/>
<flag name="bottom" value="3"/>
<flag name="center" value="4"/>
</attr>
</declare-styleable>
</resources>

2.使用自定义属性

在使用普通控件的属性时,我们的格式为android:*****=*****,在自定义控件中,我们需要人为地定义一个名称,来表示我们的自定义属性,在XML文件的头部进行声明,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:code="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:gravity="center"
tools:context="com.example.administrator.service.MainActivity"> <com.example.administrator.service.Code
android:layout_width="wrap_content"
android:layout_height="wrap_content"
code:gravity="center"
code:text="2048"
code:textsize="20sp"
code:size="100dp"
code:codecolor="@color/colorPrimaryDark"
/>
</RelativeLayout>

其中RelativeLayout下的xmlns:code=http://schemas.android.com/apk/res-auto,即为声明的自定义属性的名称

3.在Java文件中获取自定义属性

属性在存储过程中,实际上利用的是键值对存储方式,因此,在Java文件中获取相关属性时,只需要根据声明的属性名称作为key值,获取对应的values值即可。其中,用户获取键值对的类为TypedArray,代码如下:

private void initParams(Context context, AttributeSet attrs) {
TypedArray typedArray =context.obtainStyledAttributes(attrs,R.styleable.Code);
if(typedArray!=null)
{
codecolor=typedArray.getColor(R.styleable.Code_codecolor,Color.YELLOW);
text=typedArray.getString(R.styleable.Code_text);
textsize=typedArray.getDimension(R.styleable.Code_textsize,20);
length=typedArray.getDimension(R.styleable.Code_size,100);
textcolor=typedArray.getColor(R.styleable.Code_textcolor,Color.GRAY);
position=typedArray.getInt(R.styleable.Code_gravity,LEFT); typedArray.recycle();
}
}

在获取的属性中,有些需要设置默认值,有些则不需要,这点需要注意。其次,在使用完TypedArray后,不要忘记回收。

onMeasure()


接下来开始介绍文章开头提到的三个方法,先来介绍onMeasure()方法。我们知道,在声明一个控件时,我们需要声明该控件的宽、高,而onMeasure()方法的作用,便是根据用户声明的宽高,计算出相应的宽高,并把该数值传递给View。

1.MeasureSpec类

在介绍具体的计算方法前,我们先了解一下MeasureSpec类,找到其源代码,提取出我们需要的部分:

public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT; /**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT; /**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT; /**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size,
int mode) {
return size + mode; } public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
} public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

在这个代码中,我们可以看到,一个View的尺寸值是一个32位的二进制数,并由两部分组成。其中,高两位,表示的是尺寸值的模式,分为三种:EXACTLY、UNSPECIFIED和AT_MOST,而低30位,则表示这个View的大小。所以,当我们获取一个View的尺寸值时,便可以利用MeasureSpec类的getMode方法和getSize方法,获取对应的模式和大小。那么这三种模式又分别代表了什么呢,继续查看如下源码:

privatestaticintgetRootMeasureSpec(intwindowSize,introotDimension){
int measureSpec;
switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

由这段代码可以看到,当我们定义的宽高为MATCH_PARENT时,模式为EXACTLY,即根据父布局剩余的可填充大小,返回一个准确值。当宽高为WRAP_CONTENT时,模式为AT_MOST,即最大限度地填充父布局。而当为其他值时,如100dp,则为EXACTLY,为用户自定义的准确值。而UNSPECIFIED,则是当该控件大小不确定时(如在ScrollView的子控件),返回的模式值。

2.onMeasure方法的编写

在看了前一段介绍后,大家可能会有疑问,既然系统已经根据XML中定义的属性,给出了相应的尺寸值,那么onMeasure又有什么作用呢。实际上是这样,由于自定义控件使我们自己定义的控件,因此我们想让他多大就多大,有时候系统给出的尺寸值,并不是我们实际想要的尺寸值。如当宽高为WRAP_CONTENT时,系统让我们的模式为AT_MOST,即完全填充父布局,但显然,我们并不一定希望是这样。甚至,我们有时候会希望,无论用户定义的宽高为多少,我们都希望我们的View只会显示出一种给定好的宽高。这个时候,onMeasure的作用就显示出来了。换言之,系统给出的尺寸值,只是系统推荐的值,而我们真正希望这个View的尺寸值为多少,则是我们在onMeasure方法中,自己实现的。下面,以我的这个自定义控件举例。代码如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
//////////设定控件的长宽
switch (widthMode)
{
case MeasureSpec.EXACTLY:
break;
case MeasureSpec.UNSPECIFIED:
widthMeasureSpec=MeasureSpec.makeMeasureSpec((int)length,MeasureSpec.EXACTLY);
break;
case MeasureSpec.AT_MOST:
widthMeasureSpec=MeasureSpec.makeMeasureSpec((int)length,MeasureSpec.EXACTLY);
break;
}
switch (heightMode)
{
case MeasureSpec.EXACTLY:
break;
case MeasureSpec.UNSPECIFIED:
heightMeasureSpec=MeasureSpec.makeMeasureSpec((int)length,MeasureSpec.EXACTLY);
break;
case MeasureSpec.AT_MOST:
heightMeasureSpec=MeasureSpec.makeMeasureSpec((int)length,MeasureSpec.EXACTLY);
break;
} super.onMeasure(widthMeasureSpec, heightMeasureSpec); //传输长宽数据

这段代码很容易读懂,我们首先利用MeasureSpec的getMode方法,获取到相应的模式值。我希望,当宽高为WRAP_CONTENT时,View的大小和我的方块大小一样,而其他时刻,则和用户定义的宽高相同,因此,便可以在CASE语句中,对尺寸值进行修改,并用makeMeasureSpec重新生成32位数字,最后,通过super.onMeasure方法,传输相应的尺寸值。

3.在onMeasure进行相应的初始化操作

当然,onMeasure方法,除了可以传输尺寸值以外,还可以进行相应的初始化操作,如在我的控件中,有gravity属性,那么在设置方块中心的位置时,便自然需要用到整个控件的宽高属性,这个操作自然也就需要在onMeasure中实现,具体代码如下:

int width=MeasureSpec.getSize(widthMeasureSpec);
int height=MeasureSpec.getSize(heightMeasureSpec);
X=width/2;
Y=height/2;
Log.i("X1",X+"");
switch (position)
{
case LEFT:
X=length/2+getPaddingLeft();
Log.i("X2",X+"");
break;
case RIGHT:
X=width-getPaddingLeft()-length/2;
break;
case TOP:
Y=length/2+getPaddingTop();
break;
case BOTTOM:
Y=height-getPaddingBottom()-length/2;
break;
case CENTER:
break;
}
float left=X-length/2;
float right=X+length/2;
float top=Y-length/2;
float bottom=Y+length/2;
rectf.set(left,top,right,bottom); //获取绘图区域
}

onLayout()


onlayout方法,主要作用是设置该View在父布局中的位置,比如你在该View控件中声明了自定义属性layout_gravity,那么你便需要在onLayout中指定该控件的位置。由于这个自定义控件中,未涉及相关属性,故在这个不多介绍这个方法。

onDraw()


onDraw方法,主要是用Canvas和Paint类,来绘制相关的View图像。其中,Paint类相当于是画笔,当你每次进行绘制前,需要先对画笔进行修改和描述。而Canvas类,相当于你对画笔进行的动作,如画圆、画矩形、书写文字等等。而这相配合,便可以绘制出想要的图形。具体代码如下:

protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
mPaint.setColor(codecolor);
canvas.drawRoundRect(rectf,length/10,length/10,mPaint);
mPaint.setColor(textcolor);
mPaint.setTextSize(textsize);
Log.d("Jinx",text);
canvas.drawText(text,X-length/5*2,Y-length/2+textsize,mPaint);
}

相关的Canvas的绘制方法,在网上可以搜索到相关数据。

GitHub地址


https://github.com/gtxjinx/Custom-View/

有需要的朋友可以自行下载。

Android学习——自定义控件(一)的更多相关文章

  1. Android学习——自定义控件(二)

    这篇文章来介绍自定义组合控件,自定义组合控件的应用场景很多,比如当你的UI如下时: 倘若不使用组合控件,则需要在XML文件中声明4个TextView和4个EditText,而使用了组合控件,则只需要四 ...

  2. Android学习路线总结,绝对干货

    title: Android学习路线总结,绝对干货 tags: Android学习路线,Android学习资料,怎么学习android grammar_cjkRuby: true --- 一.前言 不 ...

  3. Android学习随笔--ListView的分页功能

    第一次写博客,可能格式,排版什么的会非常不美观,不过我主要是为了记录自己的Android学习之路,为了以后能有些东西回顾.既然是为了学习,那我肯定会吸收各位大大们的知道经验,有不足的地方请指出. 通过 ...

  4. (转)Android学习路线总结,绝对干货

    一.前言 不知不觉自己已经做了几年开发了,由记得刚出来工作的时候感觉自己能牛逼,现在回想起来感觉好无知.懂的越多的时候你才会发现懂的越少. 如果你的知识是一个圆,当你的圆越大时,圆外面的世界也就越大. ...

  5. Android学习路线总结,绝对干货(转)

    转自:https://www.cnblogs.com/yishaochu/p/5436094.html 一.前言 不知不觉自己已经做了几年开发了,由记得刚出来工作的时候感觉自己能牛逼,现在回想起来感觉 ...

  6. Android学习路线总结,绝对干货(转)

    title: Android学习路线总结,绝对干货tags: Android学习路线,Android学习资料,怎么学习androidgrammar_cjkRuby: true--- 一.前言 不知不觉 ...

  7. Android 学习资源

    下面这些资源对Android开发来说是很有帮助的! 最常用的: Android开发官方网站:http://developer.android.com/index.html 这个网站应该是Android ...

  8. Android学习资料收集

    1.Android 学习之路 http://stormzhang.com/android/2014/07/07/learn-android-from-rookie/

  9. Android中自定义控件TextSize属性问题

    本文主要说明一个自定义控件添加TextSize属性的坑,刚刚从坑里面爬出来,写个随笔,记录一下: *************************************************** ...

随机推荐

  1. es第十篇:Elasticsearch for Apache Hadoop

    es for apache hadoop(elasticsearch-hadoop.jar)允许hadoop作业(mapreduce.hive.pig.cascading.spark)与es交互. A ...

  2. pandas中获取数据框的行、列数

    获取数据框的行.列数 # 获取行数 df.shape[0] # 获取行数 len(df) # 获取列数 df.shape[1]

  3. 数据备份及恢复(mongodump/mongorestore)

    说明 1.mongodump创建高保真的BSON文件,mongorestore可以用其恢复数据库.对于小型数据库的备份和恢复,这两个工具非常简单和高效,但对于大型数据库的备份并不理想.2.mongod ...

  4. Linux du与df命令的差异

    今天上午查看磁盘空间,df命令查看的时候:93%,du命令查看的时候:90%.回想起昨天在用ftp传输过程中,rm掉文件,应该是文件虽然表明上删除掉了,但是空间实际是未释放的. 由于du与df命令实施 ...

  5. Python数据类型(数字)

    文章内容参考了教程:http://www.runoob.com/python/python-basic-syntax.html#commentform Python 变量类型 变量存储在内存中的值.这 ...

  6. 关于mouseleave事件触发的bug问题

    在做下拉树搜索功能的时候,下方内容框需要一个鼠标移出时就隐藏的功能,于是使用mouseleave的方法, 但是出现了一个问题就是在点击树展开个隐藏的时候,也触发了leave事件,将下方的树进行隐藏,出 ...

  7. windows下限制Redis端口只能由本机访问

    在使用redis的时候,我只想要本机能够访问,这时可通过防火墙会阻止外界的访问 1.找到防火墙,选择高级设置2.点击"入站规则",再点击"新建规则" 3.点击& ...

  8. fetch将替代ajax?

    原谅我做一次标题党,Ajax 不会死,传统 Ajax 指的是 XMLHttpRequest(XHR),未来现在已被 Fetch 替代. 最近把阿里一个千万级 PV 的数据产品全部由 jQuery 的  ...

  9. codeforces 638B—— Making Genome in Berland——————【类似拓扑排序】

    Making Genome in Berland time limit per test 1 second memory limit per test 256 megabytes input stan ...

  10. [转]【Oracle Database 12c新特性】32k varchar2 max_string_size

    本文转自:https://blogs.oracle.com/askmaclean/entry/oracle_database_12c%E6%96%B0%E7%89%B9%E6%80%A7_32k_va ...