Android 自定义View及其在布局文件中的使用示例
前言:
尽管Android已经为我们提供了一套丰富的控件,如:Button,ImageView,TextView,EditText等众多控件,但是,有时候在项目开发过程中,还是需要开发者自定义一些需要重复使用的控件,使之能像Android提供的其它控件一样,使用起来方便,幸好Android为我们自定义控件过程扫除了障碍,提供了一套基础的类(如:View,Canvas等)和XML标签(如下文即将提及的resources标签,declare-styleable标签,attr标签等);
创建流程:
一,在value文件夹新建以"attrs"命名的XML文件:
看一下本例中的attrs.xml文件
attrs.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomView">
<attr name="textString" format="string"></attr>
<attr name="colorValue" format="color"></attr>
<attr name="textSize" format = "dimension"></attr>
</declare-styleable>
</resources>
attrs.xml文件中,外层引入了如下标签:
<declare-styleable name="CustomView">
这个标签就是为了让我们自定义的View,拥有自身的属性,从上面的代码中,我们可以看到,该标签内包含定义了三个属性,分别取名为:"textString","colorValue","textSize",这样我们就可以方便地使用该View的这些属性,就像我们在使用系统提供的TextView时,在布局文件中设置TextView的textSize,textColor等属性。
我们给declare-styleable的name字段取名为"CustomView",这是因为,我们将在下文给自定义的View取名为"CustomView",为什么declare-styleable的名字要跟我们自定义的这个View的名字一样呢?翻阅了google文档,找到解释:
The name of the styleable entity is, by convention, the same name as the name of the class that defines the custom view. Although it's not strictly necessary to follow this convention, many popular code editors depend on this naming convention to provide statement completion.
外层的declare-styleable标签就分析到这里,我们再来仔细看一下attr标签:
<attr name="textString" format="string"></attr>
<attr name="colorValue" format="color"></attr>
<attr name="textSize" format = "dimension"></attr>
本例中,给自定义的View制定了三个属性,textString:该View显示的Text内容;colorValue:字体的颜色;textSize:字体的大小。attr标签不仅有name字段,并且给出了format字段(关于format字段都有哪些值,在附录中我们给出其具体的定义及应用示例)
二,编写布局文件,引用自定义的View
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.project.summary"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/BgColor"> <com.project.summary.customview.CustomView
android:id="@+id/customView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:colorValue="@color/textRed"
app:textString="This the Custom View!!!"
app:textSize="20sp"
/> </LinearLayout>
这里需要注意的地方有两个:
1,新增布局文件的命名空间
因为我们自定义了View,并且自定义了属性,而这些属性不再属于
http://schemas.android.com/apk/res/android
这个命名空间,而是属于
http://schemas.android.com/apk/res/[your package name].
所以我们需要增加布局文件中的命名空间,改成
xmlns:app="http://schemas.android.com/apk/res/com.project.summary"
2,自定义View在布局文件中的引用
<com.project.summary.customview.CustomView
我们需要把这个自定义类的包名都写完整;
另外:如果CustomView这个类是ParentCustomView类的内部类,那么在布局文件中的引用应该写成
<com.project.summary.customview.ParentCustomView$CustomView
即需要在类和之间增加字符$
3,自定义属性在布局文件中的引用
app:colorValue="@color/textRed"
app:textString="This the Custom View!!!"
app:textSize="20sp"
需要在自定义的属性前面加上app字段(因为app="http://schemas.android.com/apk/res/com.project.summary",在这里app就是指代新的命名空间)。
三,编写自定义View代码
google文档要求该自定义的View中,至少要有以Context和AttributeSet为参数的构造方法,原因有两个:
1.
To allow the Android Developer Tools to interact with your view, at a minimum you must provide a constructor that takes a Context and an AttributeSet object as parameters. This constructor allows the layout editor to create and edit an instance of your view.
2.
When a view is created from an XML layout, all of the attributes in the XML tag are read from the resource bundle and passed into the view's constructor as an AttributeSet. Although it's possible to read values from the AttributeSet directly, doing so has some disadvantages:
A:Resource references within attribute values are not resolved;
B:Styles are not applied;
Instead, pass the AttributeSet to obtainStyledAttributes(). This method passes back a TypedArray array of values that have already been dereferenced and styled. The Android resource compiler does a lot of work for you to make calling obtainStyledAttributes() easier. For each <declare-styleable> resource in the res directory, the generated R.java defines both an array of attribute ids and a set of constants that define the index for each attribute in the array. You use the predefined constants to read the attributes from the TypedArray.
第一个原因:为了让我们的开发工具 layout editor创建和编辑我们自定义的View;
第二个原因:这个也是最主要的原因,当我们从布局文件中创建View的时候,布局文件中的所有标签,标签中的所有属性都被读到资源包里,并且这个资源包被包装成属性集合AttributeSet传递给自定义View的构造方法;
在构造方法中,使用 obtainStyledAttributes()方法将这些属性转化成TypedArray数组,数组里包含我们自定义的属性ID和常量集合,这样,我们就可以用我们定义的常量名称很方便地从TypedArray中读取我们定义的属性。
所以我们至少先编写包含Context和AttributeSet为参数的构造方法
public class CustomView extends View {
private int color;
private String mText;
private int textSize;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CustomView);
try {
mText = a.getString(R.styleable.CustomView_textString);
color = a.getColor(R.styleable.CustomView_colorValue,
R.color.textRed);
textSize = a.getDimensionPixelOffset(
R.styleable.CustomView_textSize, 20);
} finally {
a.recycle();
}
}
从代码中可以看出,我们可以用TypedArray提供的相关方法,来取出我们在布局文件中设置的相关属性,此处还需要注意TypedArray的回收!
四,本例中,我们自定义了一个View用来实现显示文字,类似于TextView
由于本文只是讲述如何自定义View,以及其使用,自定义View的功能部分不在本文范畴,将在下一篇中具体讲述;所以,下面只贴代码,不再具体讲述。
public class CustomView extends View {
private int color;
private Paint mTextPaint;
private String mText;
private int textSize;
private int mAscent;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initLabelView();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CustomView);
try {
mText = a.getString(R.styleable.CustomView_textString);
color = a.getColor(R.styleable.CustomView_colorValue,
R.color.textRed);
if (mText != null) {
setCustomText(mText);
}
setTextColor(color);
textSize = a.getDimensionPixelOffset(
R.styleable.CustomView_textSize, 20);
if (textSize > 0) {
setTextSize(textSize);
}
} finally {
a.recycle();
}
}
/**
* Sets the text to display in this label
*
* @param text
* The text to display. This will be drawn as one line.
*/
private void setCustomText(String text) {
// TODO Auto-generated method stub
mText = text;
requestLayout();
invalidate();
}
private final void initLabelView() {
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
// Must manually scale the desired text size to match screen density
mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
mTextPaint.setColor(0xFF000000);
setPadding(3, 3, 3, 3);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
/**
* Determines the width of this view
*
* @param measureSpec
* A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
+ getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by
// measureSpec
result = Math.min(result, specSize);
}
}
return result;
}
/**
* Determines the height of this view
*
* @param measureSpec
* A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
mAscent = (int) mTextPaint.ascent();
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop()
+ getPaddingBottom();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by
// measureSpec
result = Math.min(result, specSize);
}
}
return result;
}
/**
* Sets the text size for this label
*
* @param size
* Font size
*/
public void setTextSize(int size) {
// This text size has been pre-scaled by the getDimensionPixelOffset
// method
mTextPaint.setTextSize(size);
requestLayout();
invalidate();
}
/**
* Sets the text color for this label.
*
* @param color
* ARGB value for the text
*/
public void setTextColor(int color) {
mTextPaint.setColor(color);
invalidate();
}
/**
* Render the text
*
* @see android.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent,
mTextPaint);
}
// @Override
// protected void onMeasure(final int widthMeasureSpec,
// final int heightMeasureSpec) {
// int width = MeasureSpec.getSize(widthMeasureSpec);
// // int height = (int) (width * heightScale / widthScale);
// int height = MeasureSpec.getSize(heightMeasureSpec);
// if (height == 0) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// } else {
// super.onMeasure(
// MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
// MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
// }
// }
}
附录:format的定义及应用示例:
1. reference:资源引用。
属性定义:
<attr name = "background" format = "reference" />
属性使用:
<com.lin.gw.CustomView
android:layout_width = "42dip"
android:layout_height = "42dip"
app:background = "@drawable/图片ID"
/>
2. color:颜色值。
属性定义:
<attr name = "textColor" format = "color" />
属性使用:
<com.lin.gw.CustomView
android:layout_width = "42dip"
android:layout_height = "42dip"
app:textColor = "#fff000"
/>
3. boolean:布尔值。
属性定义:
<attr name = "focusable" format = "boolean" />
属性使用:
<com.lin.gw.CustomView
android:layout_width = "42dip"
android:layout_height = "42dip"
app:focusable = "true"
/>
4. dimension:尺寸值。
属性定义:
<attr name = "customWidth" format = "dimension" />
属性使用:
<com.lin.gw.CustomView
app:customWidth = "42dip"
android:layout_height = "wrap_content"
/>
5. float:浮点值。
属性定义:
<attr name = "fromAlpha" format = "float" />
属性使用:
<com.lin.gw.CustomView
app:fromAlpha = "2.0"
/>
6. integer:整型值。
属性定义:
<attr name = "frameDuration" format="integer" />
属性使用:
<com.lin.gw.CustomView
app:frameDuration = "20"
/>
7. string:字符串。
属性定义:
<attr name="textString" format="string"></attr>
属性使用:
<com.lin.gw.CustomView
app:textString = "hello lingling!"
/>
8. fraction:百分数。
属性定义:
<attr name = "pivotX" format = "fraction" />
属性使用:
<com.lin.gw.CustomView
app:pivotX = "30%"
/>
9. enum:枚举值。
属性定义:
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
属性使用:
<com.lin.gw.CustomView
app:orientation = "vertical"
/>
10. flag:位或运算。
属性定义:
<declare-styleable name="CustomView">
<attr name="windowSoftInputMode">
<flag name = "stateUnspecified" value = "0" />
<flag name = "stateUnchanged" value = "1" />
<flag name = "stateHidden" value = "2" />
<flag name = "stateAlwaysHidden" value = "3" />
<flag name = "stateVisible" value = "4" />
<flag name = "stateAlwaysVisible" value = "5" />
<flag name = "adjustUnspecified" value = "0x00" />
<flag name = "adjustResize" value = "0x10" />
<flag name = "adjustPan" value = "0x20" />
<flag name = "adjustNothing" value = "0x30" />
</attr>
</declare-styleable>
属性使用:
app:windowSoftInputMode = "stateUnspecified | stateUnchanged | stateHidden">
转载请注明出处
http://www.cnblogs.com/crashmaker/p/3521310.html
From crash_coder linguowu
linguowu0622@gamil.com
Android 自定义View及其在布局文件中的使用示例的更多相关文章
- Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程
转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...
- Android 自定义View及其在布局文件中的使用示例(二)
转载请注明出处 http://www.cnblogs.com/crashmaker/p/3530213.html From crash_coder linguowu linguowu0622@gami ...
- Android查缺补漏(View篇)--布局文件中的“@+id”和“@id”有什么区别?
Android布局文件中的"@+id"和"@id"有什么区别? +id表示为控件指定一个id(新增一个id),如: <cn.codingblock.vie ...
- android 布局文件中xmlns:android="http://schemas.android.com/apk/res/android"
http://blog.163.com/benben_long/blog/static/199458243201411394624170/ xmlns:android="http://sch ...
- Android自定义View初步
经过上一篇的介绍,大家对于自定义View一定有了一定的认识,接下来我们就以实现一个图片下显示文字的自定义View来练习一下.废话不多说,下面进入我们的正题,首先看一下我们的思路,1.我们需要通过在va ...
- android自定义view系列:认识activity结构
标签: android 自定义view activity 开发中虽然我们调用Activity的setContentView(R.layout.activity_main)方法显示View视图,但是vi ...
- Android 自定义View修炼-Android中常见的热门标签的流式布局的实现
一.概述:在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出哈) 类似的 ...
- Android自定义View研究--View中的原点坐标和XML中布局自定义View时View触摸原点问题
这里只做个汇总~.~独一无二 文章出处:http://blog.csdn.net/djy1992/article/details/9715047 Android自定义View研究--View中的原点坐 ...
- android自定义View&&简单布局&&回调方法
一.内容描述 根据“慕课网”上的教程,实现一个自定义的View,且该View中使用自定义的属性,同时为该自定义的View定义点击事件的回调方法. 二.定义自定义的属性 在res/valus/ 文件夹下 ...
随机推荐
- 【探索】在 JavaScript 中使用 C 程序
JavaScript 是个灵活的脚本语言,能方便的处理业务逻辑.当需要传输通信时,我们大多选择 JSON 或 XML 格式. 但在数据长度非常苛刻的情况下,文本协议的效率就非常低了,这时不得不使用二进 ...
- 消息队列 Kafka 的基本知识及 .NET Core 客户端
前言 最新项目中要用到消息队列来做消息的传输,之所以选着 Kafka 是因为要配合其他 java 项目中,所以就对 Kafka 了解了一下,也算是做个笔记吧. 本篇不谈论 Kafka 和其他的一些消息 ...
- Mono为何能跨平台?聊聊CIL(MSIL)
前言: 其实小匹夫在U3D的开发中一直对U3D的跨平台能力很好奇.到底是什么原理使得U3D可以跨平台呢?后来发现了Mono的作用,并进一步了解到了CIL的存在.所以,作为一个对Unity3D跨平台能力 ...
- 路由的Resolve机制(需要了解promise)
angular的resovle机制,实际上是应用了promise,在进入特定的路由之前给我们一个做预处理的机会 1.在进入这个路由之前先懒加载对应的 .js $stateProvider .state ...
- OpenCASCADE Shape Location
OpenCASCADE Shape Location eryar@163.com Abstract. The TopLoc package of OpenCASCADE gives resources ...
- 将 instance 部署到 OVS Local Network - 每天5分钟玩转 OpenStack(130)
上一节创建了 OVS 本地网络 first_local_net,今天我们会部署一个 instance 到该网络并分析网络结构.launch 一个 instance,选择 first_local_net ...
- 9、委托、事件、Lambda
开始 关于委托,肯定是要有问题的. 第一个问题,委托用来干什么? 看.net中的表述:在.net平台下,委托类型用来定义和相应应用程序中的回调.(回调?处理内存中两个实体双向通信的一种技术.) 第 ...
- 多线程条件通行工具——CountDownLatch
CountDownLatch的作用是,线程进入等待后,需要计数器达到0才能通行. CountDownLatch(int)构造方法,指定初始计数. await()等待计数减至0. await(long, ...
- OpenDigg前端开源项目周报1219
由OpenDigg 出品的前端开源项目周报第二期来啦.我们的前端开源周报集合了OpenDigg一周来新收录的优质的前端开发方面的开源项目,方便前端开发人员便捷的找到自己需要的项目工具等.react-f ...
- /etc/ppp/chap-secrets
# Secrets for authentication using CHAP # client server secret IP addresses abc l2tpd * client:VPN 用 ...