Android自定义控件总结

Android自定义控件总结,第1张

为什么我们觉得自定义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) {}方法

空讲理论很难理解,我们还得用例子来说明,记得我前面来写了一篇 Android 微信61 tab栏图标和字体颜色渐变的实现 的博客,里面tab的每个item就是通过自定义View来实现的,那么接下来就通过此例子来说明问题。

我们可以把View理解为一张白纸,而自定义View就是在这张白纸上画上我们自己绘制的图案,可以在绘制任何图案,也可以在白纸的任何位置绘制,那么问题来了,白纸哪里来?图案哪里来?位置如何计算?

a)白纸好说,只要我们继承之View,在onDraw(Canvas canvas)中的canvas就是我们所说的白纸

/

Created by moonzhong on 2015/2/13

/

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 即为白纸

superonDraw(canvas);

}

}12345678910111213141516171819202122

b)图案呢?这里的图案就是有和文字组成,这个也好说,定义一个Bitmap 成员变量,和一个String的成员变量

private Bitmap mBitmap ;

private String mName ;

mName = "这里直接赋值";

mBitmap = BitmapFactorydecodeResource(getResources(),Rdrawableic_launcher) ;1234

可以通过资源文件可以拿到。

c)计算位置

所以最核心的也是我们认为最麻烦的地方就是计算绘制的位置,计算位置就得先测量自身的大小,也就是我们必须掌握的两点中的第一点:需要重写

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

{}方法

先来看一下google写的TextView的onMeasure()方法是如何实现的

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int widthMode = MeasureSpecgetMode(widthMeasureSpec);

int heightMode = MeasureSpecgetMode(heightMeasureSpec);

int widthSize = MeasureSpecgetSize(widthMeasureSpec);

int heightSize = MeasureSpecgetSize(heightMeasureSpec);

int width;

int height;

BoringLayoutMetrics boring = UNKNOWN_BORING;

BoringLayoutMetrics hintBoring = UNKNOWN_BORING;

if (mTextDir == null) {

mTextDir = getTextDirectionHeuristic();

}

int des = -1;

boolean fromexisting = false;

if (widthMode == MeasureSpecEXACTLY) {

// Parent has told us how big to be So be it

width = widthSize;

} else {

if (mLayout != null && mEllipsize == null) {

des = desired(mLayout);

}

if (des < 0) {

boring = BoringLayoutisBoring(mTransformed, mTextPaint, mTextDir, mBoring);

if (boring != null) {

mBoring = boring;

}

} else {

fromexisting = true;

}

if (boring == null || boring == UNKNOWN_BORING) {

if (des < 0) {

des = (int) FloatMathceil(LayoutgetDesiredWidth(mTransformed, mTextPaint));

}

width = des;

} else {

width = boringwidth;

}

final Drawables dr = mDrawables;

if (dr != null) {

width = Mathmax(width, drmDrawableWidthTop);

width = Mathmax(width, drmDrawableWidthBottom);

}

if (mHint != null) {

int hintDes = -1;

int hintWidth;

if (mHintLayout != null && mEllipsize == null) {

hintDes = desired(mHintLayout);

}

if (hintDes < 0) {

hintBoring = BoringLayoutisBoring(mHint, mTextPaint, mTextDir, mHintBoring);

if (hintBoring != null) {

mHintBoring = hintBoring;

}

}

if (hintBoring == null || hintBoring == UNKNOWN_BORING) {

if (hintDes < 0) {

hintDes = (int) FloatMathceil(LayoutgetDesiredWidth(mHint, mTextPaint));

}

hintWidth = hintDes;

} else {

hintWidth = hintBoringwidth;

}

if (hintWidth > width) {

width = hintWidth;

}

}

width += getCompoundPaddingLeft() + getCompoundPaddingRight();

if (mMaxWidthMode == EMS) {

width = Mathmin(width, mMaxWidth getLineHeight());

} else {

width = Mathmin(width, mMaxWidth);

}

if (mMinWidthMode == EMS) {

width = Mathmax(width, mMinWidth getLineHeight());

} else {

width = Mathmax(width, mMinWidth);

}

// Check against our minimum width

width = Mathmax(width, getSuggestedMinimumWidth());

if (widthMode == MeasureSpecAT_MOST) {

width = Mathmin(widthSize, width);

}

}

int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();

int unpaddedWidth = want;

if (mHorizontallyScrolling) want = VERY_WIDE;

int hintWant = want;

int hintWidth = (mHintLayout == null) hintWant : mHintLayoutgetWidth();

if (mLayout == null) {

makeNewLayout(want, hintWant, boring, hintBoring,

width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);

} else {

final boolean layoutChanged = (mLayoutgetWidth() != want) ||

(hintWidth != hintWant) ||

(mLayoutgetEllipsizedWidth() !=

width - getCompoundPaddingLeft() - getCompoundPaddingRight());

final boolean widthChanged = (mHint == null) &&

(mEllipsize == null) &&

(want > mLayoutgetWidth()) &&

(mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));

final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);

if (layoutChanged || maximumChanged) {

if (!maximumChanged && widthChanged) {

mLayoutincreaseWidthTo(want);

} else {

makeNewLayout(want, hintWant, boring, hintBoring,

width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);

}

} else {

// Nothing has changed

}

}

if (heightMode == MeasureSpecEXACTLY) {

// Parent has told us how big to be So be it

height = heightSize;

mDesiredHeightAtMeasure = -1;

} else {

int desired = getDesiredHeight();

height = desired;

mDesiredHeightAtMeasure = desired;

if (heightMode == MeasureSpecAT_MOST) {

height = Mathmin(desired, heightSize);

}

}

int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();

if (mMaxMode == LINES && mLayoutgetLineCount() > mMaximum) {

unpaddedHeight = Mathmin(unpaddedHeight, mLayoutgetLineTop(mMaximum));

}

/

We didn't let makeNewLayout() register to bring the cursor into view,

so do it here if there is any possibility that it is needed

/

if (mMovement != null ||

mLayoutgetWidth() > unpaddedWidth ||

mLayoutgetHeight() > unpaddedHeight) {

registerForPreDraw();

} else {

scrollTo(0, 0);

}

setMeasuredDimension(width, height);

}

哇!好长!而且方法中还嵌套方法,如果真要算下来,代码量不会低于500行,看到这么多代码,头都大了,我想这也是我们为什么在学习Android自定义View的时候觉得如此困难的原因。大多数情况下,因为我们是自定义的View,可以说是根据我们的需求定制的View,所以很多里面的功能我们完全没必要,只需要几十行代码就能搞定。看到几十行代码就能搞定,感觉顿时信心倍增(^^)

在重写这个方法之前,得先了解一个类 MeasureSpec ,如果不了解,没关系,下面就一起来了解一下这个类。先把代码贴出来,膜拜一下

1234

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;

0x3 就是 11 左移30位 ,就是补30个0;

private static final int MODE_MASK = 1100 0000 0000 0000 0000 0000 0000 0000 ;

00 左移30位

public static final int UNSPECIFIED = 0000 0000 0000 0000 0000 0000 0000 0000 ;

01 左移30位

public static final int EXACTLY = 0100 0000 0000 0000 0000 0000 0000 0000 ;

10 左移30位

public static final int AT_MOST = 1000 0000 0000 0000 0000 0000 0000 0000 ;

你就会问了,这样写有什么好处呢? 细心的人看了上面这几个方法就明白了,每个方法中都有一个 & 的 *** 作,所以我们接下来看看这集几个方法的含义是什么,先从下往上看,先易后难

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控件就是。

1、程序,把Assets中的图像显示出来

try {

BufferedInputStream bis = new BufferedInputStream(getAssets()

open("abmp"));

Bitmap bm = BitmapFactorydecodeStream(bis);

imageView01setImageBitmap(bm);

} catch (Exception e) {

// TODO Auto-generated catch block

eprintStackTrace();

Systemoutprintln("==========file not found======");

}

2、原理:Android中的资源分析

资源是Android应用程序中重要的组成部分。在应用程序中经常会使用字符串、菜单、图像、声音、视频等内容,都可以称之为资源。通过将资源放到与apk文件中与Android应用程序一同发布,在资源文件比较大的情况下,可以通过将资源作为外部文件来使用,我们将分析如何在Android应用程序中存储这些资源。

一、资源的存储

在android中,资源大多都是保存在res目录中,例如布局资源以XML文件的形式保存在res\layout目录中;图像资源保存着res\drawable目录中;菜单资源保存在res\menu目录中。ADT在生成apk文件时,这些目录中的资源都会被编译,然后保存到apk文件中。如果将资源文件放到res\raw目录中,资源将在不编译的情况下放入apk文件中。在程序运行时可以使用InputStream来读取res\raw目录中的资源。

如果使用的资源文件过大,我们可以考虑将资源文件作为外部文件单独发布。Android应用程序会从手机内存或者SD卡读取这些资源文件。

二、资源的种类

从资源文件的类型来划分,我们可以将资源文件划分为XML、图像和其它。以XML文件形式存储的资源可以放在res目录中的不同子目录里,用来表示不同种类的资源;而图像资源会放在res\drawable目录中。除此之外,可以将任意的资源嵌入Androidy应用程序中。比如音频和视频等,一般这些资源放在res\raw目录中。

表1、 Android支持的资源

目录 资源类型 描述

Res\values

XML

保存字符串、颜色、尺寸、类型、主题等资源,可以是任意文件名。对于字符串、颜色、尺寸等信息采用

Key-value形式表示,对于类型、主题等资源,采用其它形式表示

Res\layout

XML

保存布局信息。一个资源文件表示一个View或ViewGroup的布局

Res\menu

XML

保存菜单资源。一个资源文件表示一个菜单(包括子菜单)

Res\anim

XML

保存与动画相关的信息。可以定义帧(frame)动画和补间(tween)动画

Res\xml

XML

在该目录的文件可以是任意类型的XML文件,这些XML文件可以在运行时被读取。

Res\raw

任意类型

在该目录中的文件虽然也会被封装在apk文件中,但不会被编译。在该目录中可以放置任意类型的文件,例如,各种类型的文档、音频、视频文件等

Res\drawable

图像

该目录中的文件可以是多种格式的图像文件,例如,bmp、png、gif、jpg等。在该目录中的图像不需要分辨率非常高,aapt工具会优化这个目录中的图像文件。如果想按字流读取该目录下的图像文件,需要将图像文件放在res\raw目录中。

assets

任意类型

该目录中的资源与res\raw中的资源一样,也不会被编译。但不同的是该目录中的资源文件都不会生出资源ID

三、资源文件的命名

每一个资源文件或资源文件中的key-value对都会在ADT自动生成的R类(在Rjava文件中)中找到相对应的ID其中资源文件名或key-value对中的key就是R类中的java变量名。因此,资源文件名好key的命名首先要符合java变量的命名规则。

除了资源文件和key本身的命名要遵循相应的规则外,多个资源文件和key也要遵循唯一的原则。也就是说,同类资源的文件名或key不能重复。例如,两个表示字符串资源的key不能重复,就算这两个key在不同的XML文件中也不行。

由于ADT在生成ID时并不考虑资源文件的扩展名,因此,在res\drawable、res\raw等目录中不能存在文件名相同,扩展名不同的资源文件。例如在res\drawable目录不能同时放置iconjpg和iconpng文件。

四、资源使用示例

在Android SDK中不仅提供了大量的系统资源,而且还允许开发人员定制自己的资源。不管是系统资源,还是自定义的资源,一般都会将这些资源放在res目录中,然后通过R类中的相应ID来引用这些资源。接下来将针对于XML类资源的使用进行分析。

XML资源实际上就是XML格式的文本文件,这些文件必须放在res\xml目录中。可以通过ResourcesgetXml方法获得处理指定XML文件的XmlResourceParser对象。实际上,XmlResourceParser对象处理XML文件的的过程主要是针对不同的状态点处理相应的代码,比如开始分析文档、开始分析标签、分析标签完成等,XmlResourceParser通过调用next方法不断更新当前的状态。

下面的代码,则是展示如何读取res\xml目录中的XML文件的内容,先在res\xml目录中建立一个xml文件。将AndroidManifestxml文件复制到res\xml目录中,并改名为androidxml。

在准备完XML文件后,在onCreate方法中开始读取XML文件的内容,代码如下:

public void onCreate(Bundle savedInstanceState)

{

superonCreate(savedInstanceState);

setContentView(Rlayoutmain);

TextView textView=(TextView)findViewById(Ridtextview);

StringBuffer sb=new StringBuffer();

// 获得处理android。xml文件的XmlResourceParser对象

XmlResourceParser xml=getResources()getXml(Rxmlandroid);

try

{

//切换到下一个状态,并获得当前状态的类型

int eventType =xmlnext();

while(true)

{

//文档开始状态

if(eventType == XmlPullParserSTART_DOCUMENT)

{

Logd("start_document","start_document");

}

//标签开始状态

else if(eventType ==XmlPullParserSTART_TAG)

{

Logd("start_tag",xmlgetName());

//将标签名称和当前标签的深度(根节点的depth是1,第2层节点的depth是2,类推)

sbappend(xmlgetName()+"(depth:"+xmlgetDepth()" ");

//获得当前标签的属性个数

int count=xmlgetAttributeCount();

//将所有属性的名称和属性值添加到StringBuffer对象中

for(int i=0;i<count;i++)

{

sbappend(xmlgetAttributeName(i)+":

"+xmlgetAttributeValue(i)+"");

}

sbappend(")\n");

}

//标签结束状态

else if(eventType ==XmlPullParserEND_TAG)

{

Logd("end_tag",xmlgetName());

}

//读取标签内容状态

else if(eventType ==XmlPullParserTEXT)

{

Logd("text","text");

}

//文档结束状态

else if(eventType ==XmlPullParserEND_DOCUMENT)

{

Logd("end_document","end_document");

//文档分析结束后,退出while循环

break;

}

//切换到下一个状态,并获得当前状态的类型

eventType =xmlnext();

}

textViewsetText(sbtoString());

}

catch(Exception e) {}

}

二、如果想读入文件

在使用getAssets()open("anhuixml")返回输人流之后,就可以以此为参数,后面的处理跟普通的java的处理相同。

以上就是关于Android自定义控件总结全部的内容,包括:Android自定义控件总结、QQ查看图片时不能向右滑动,往左滑也不行,往左右滑怎么都滑不过来,怎么回事啊、android 怎么知道自定义view图画完了等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9747075.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-01
下一篇 2023-05-01

发表评论

登录后才能评论

评论列表(0条)

保存