Creating Custom Views--自定义View

虽然Android为我们提供了很多的内置控件,但是有时候我们的独特的业务需求可能这些内置控件仍然无法满足,那么此时,就需要我们自定义控件。这系列文章将向你展示如何创建一个功能强大且可重复的View控件。


目录:


自定义View

一个良好的自定义View应该能和Android内置的View控件一样,具有良好的表现:具有自己的属性能与用户进行交互占据有效的内存空间。因此一个自定义的View的Class应该具备以下几个特征:

  1. 继承自View或者是View的子类,在Android中所有的View控件比如:Button,TextView都是View的子类。
  2. 符合Android的标准
  3. 通过xml文件提供自定义的属性
  4. 能与用户交互,能够响应事件,传递事件
  5. 兼容Android多个平台和版本

有了以上的标准,那么接下来就学习如何构造一个功能完整的自定义View吧:
大体上分为以下若干步骤:

  • 继承一个View或者View子类
  • 在res/values/attrs.xml文件中自定义属性
  • 应用我们的自定义属性
  • 添加属性和事件
  • 供外界调用

  1. 在Android的所有View体系中,所有的控件都是继承自View的,所以我们自定义控件的时候也应该是继承View或者是View的子类,比如Button,TextView。需要注意的是:大部分时候,我们自定义View是为了想在布局文件中xml调用,此时,我们的自定义View需要覆盖如下的构造方法来能在xml中被实例化,否则会报错。
    <pre>
    class PieChart extends View {
    public PieChart(Context context, AttributeSet attrs) {//必须有此构造函数才能在xml文件中被实例化
    super(context, attrs); //调用父类的构造函数
    }
    }
    </pre>
    另外,我们之所以自定义View大多数是因为想实现业务需求的View,此时基本是需要在重写onDraw()方法来绘制我们的View。
    <pre>
    @Override
    protected void onDraw(Canvas canvas) { //自定义View中都会重写此onDraw()方法
    super.onDraw(canvas); //调用父类的onDraw();
    // Draw the shadow
    canvas.drawOval(mShadowBounds, mShadowPaint);

// Draw the label text
if (getShowText()) {
canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);
}
// If the API level is less than 11, we can't rely on the view animation system to
// do the scrolling animation. Need to tick it here and call postInvalidate() until the scrolling is done.
if (Build.VERSION.SDK_INT < 11) {
tickScrollAnimation();
if (!mScroller.isFinished()) {
postInvalidate();
}
}
}
</pre>

  1. 自定义属性
    自定义属性后就能像Android内置的View控件一样通过属性标签来控制,我们自定义属性的时候需要在res/values/attrs.xml文件下以<declare-styleable>为根标签属性名和类型以键值对的方式存储。
    <pre>
    <resources>
    <declare-styleable name="PieChart">
    <attr name="showText" format="boolean" />
    <attr name="labelPosition" format="enum">
        <enum name="left" value="0"/>
       <enum name="right" value="1"/>
    </attr>
    </declare-styleable>
    </resources>
    </pre>
    需要注意的地方是:我们在布局文件中使用自定义属性的时候需要加上命名空间,命名空间可以加在根标签也可以加在自定义View标签内,一般直接加在根标签里。命名空间的格式如下:
    http://schemas.android.com/apk/res/[yourpackagename],比如:
    xmlns:custom="http://schemas.android.com/apk/res/com.example.android.customviews"
    还有特殊的情况:在你自定义的View中有一个内部的自定义innerView,如果希望在xml文件中使用innerView的属性,比如PieChart内有个PieView的内部类,那么应该这样:
    com.example.customviews.charting.PieChart$PieView。在xml引用自定义View布局需要使用全名,代码如下:
    <pre>
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews"> //自定义属性命名空间
    <com.example.customviews.charting.PieChart
    custom:showText="true"
    custom:labelPosition="left"
    />
    </LinearLayout>
    </pre>

  2. 在代码中应用我们自定义的属性,使xml中的属性能够产生效果
    当一个view从xml布局中创建后,其使用的所有属性都会读取并传递到view 构造器函数的AttributeSet中。但是倘若直接使用AttributeSet会有一些缺点比如:styles不能生效,引用的资源不能解析。这时,我们使用另外一种方法会好很多,使用[obtainStyledAttributes()](http://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int))将在xml定义的属性封装到 TypedArray中去。代码如下:
    <pre>
    public PieChart(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PieChart, 0, 0);
    try {
    mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
    mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
    } finally {
    a.recycle(); //释放资源
    }
    }
    </pre>

  3. 添加属性事件
    添加属性事件暴露方法,使view可以动态改变
    <pre>
    public boolean isShowText() {
    return mShowText;
    }
    public void setShowText(boolean showText) {
    mShowText = showText;
    invalidate();//重绘
    requestLayout();
    }
    </pre>
    注意:invalidate()和requestLayout
    requestLayout:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。
    特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。invalidate:View本身调用迫使view重画。
    另外:一个好的自定义View应该暴露接口,提供对业务有用的部分监听机制供自己调用。比如:OnCurrentItemChanged(),onColorChanged(),onSizeChanged().

小结:

  • 对于自定义View需要注意的大概就是这些,但是在实际自己写的时候仍然会有很多的问题,需要多实践,多学习别人的代码,再结合自己的业务区自定义。当然如果有第三方的控件可以满足我们的业务需求,我们亦可以引用。

Google给出的一个完整的自定义view的例子:点击下载Customview.zip

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容