android 自定义 View(2)
来源:互联网 发布:c语言头文件是什么意思 编辑:程序博客网 时间:2024/06/03 23:02
参考:
android 自定义 View - 参考
自定义视图(View
)是 Android
开发的一个进阶内容。随着开发的深入,肯定会出现系统提供的基础控件不符合需求的情况。一方面通过组合基础控件以形成新的布局,另一方面可以通过自定义控件的方式来更加灵活的实现需求
自定义视图涉及到 Android
系统许多方面的内容,下面根据自己的理解顺序来讲一讲如何自定义视图
主要内容
- 视图创建
- 自定义属性
- 完整代码
视图创建
可以通过代码(from code
)或者布局文件(from XML layout file
)增加视图,根据不同的载入方式,将调用不同的构造函数
构造函数
View
类提供了 4
种构造函数,其分别有 1-4
个参数:
public MyView(Context context) { super(context);}public MyView(Context context, AttributeSet attrs) { super(context, attrs);}public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);}public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes);}
单个参数:
/** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. */public View(Context context) { ...}
当视图在代码中创建时,调用此构造函数
两个参数:
/** * Constructor that is called when inflating a view from XML. This is called * when a view is being constructed from an XML file, supplying attributes * that were specified in the XML file. This version uses a default style of * 0, so the only attribute values applied are those in the Context's Theme * and the given AttributeSet. * * <p> * The method onFinishInflate() will be called after all children have been * added. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view. * @see #View(Context, AttributeSet, int) */public View(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0);}
当视图在
xml
布局文件中定义时,调用此构造函数三个参数:
/** * Perform inflation from XML and apply a class-specific base style from a * theme attribute. This constructor of View allows subclasses to use their * own base style when they are inflating. For example, a Button class's * constructor would call this version of the super class constructor and * supply <code>R.attr.buttonStyle</code> for <var>defStyleAttr</var>; this * allows the theme's button style to modify all of the base view attributes * (in particular its background) as well as the Button class's attributes. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view. * @param defStyleAttr An attribute in the current theme that contains a * reference to a style resource that supplies default values for * the view. Can be 0 to not look for defaults. * @see #View(Context, AttributeSet) */public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0);}
该构造函数同样表示当自定义视图在布局文件中定义时使用,不过它和上一个构造函数的区别是多了一个
defStyleAttr
属性。当自定义视图继承自View
子类,比如按钮(button
),调用此构造函数,属性defStyleAttr
就表示按钮的基本样式属性四个参数:
/** * Perform inflation from XML and apply a class-specific base style from a * theme attribute or style resource. This constructor of View allows * subclasses to use their own base style when they are inflating. * <p> * When determining the final value of a particular attribute, there are * four inputs that come into play: * <ol> * <li>Any attribute values in the given AttributeSet. * <li>The style resource specified in the AttributeSet (named "style"). * <li>The default style specified by <var>defStyleAttr</var>. * <li>The default style specified by <var>defStyleRes</var>. * <li>The base values in this theme. * </ol> * <p> * Each of these inputs is considered in-order, with the first listed taking * precedence over the following ones. In other words, if in the * AttributeSet you have supplied <code><Button * textColor="#ff000000"></code> * , then the button's text will <em>always</em> be black, regardless of * what is specified in any of the styles. * * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param attrs The attributes of the XML tag that is inflating the view. * @param defStyleAttr An attribute in the current theme that contains a * reference to a style resource that supplies default values for * the view. Can be 0 to not look for defaults. * @param defStyleRes A resource identifier of a style resource that * supplies default values for the view, used only if * defStyleAttr is 0 or can not be found in the theme. Can be 0 * to not look for defaults. * @see #View(Context, AttributeSet, int) */public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { ...}
最后一个构造函数是从
Android 5.0(API 21)
开始加入的,如果minSdkVersion
小于21
的话,自定义视图不需要重载它
通常的使用方式是在自定义视图中重载前 3
个构造函数:
public class MyView extends View { public MyView(Context context) {// super(context); this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) {// super(context, attrs); this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }}
构造结束判断
当自定义视图从布局文件中载入结束后,会调用方法 onFinishInflate
:
/** * Finalize inflating a view from XML. This is called as the last phase * of inflation, after all child views have been added. * * <p>Even if the subclass overrides onFinishInflate, they should always be * sure to call the super method, so that we get called. */@CallSuperprotected void onFinishInflate() {}
Note:需要调用超类方法(super method
)
测试代码如下:
public class MyView extends View { private static final String TAG = "MyView"; public MyView(Context context) {// super(context); this(context, null); Log.e(TAG, "MyView: one"); } public MyView(Context context, @Nullable AttributeSet attrs) {// super(context, attrs); this(context, attrs, 0); Log.e(TAG, "MyView: two"); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); Log.e(TAG, "MyView: three"); } @Override protected void onFinishInflate() { super.onFinishInflate(); Log.e(TAG, "onFinishInflate: "); }}
布局文件定义:
<?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-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.zj.viewtest.MainActivity"> <com.zj.viewtest.MyView android:layout_width="200dp" android:layout_height="200dp" android:background="@android:color/black" /></LinearLayout>
运行,日志如下:
由图可知,自定义视图调用顺序如下:
public MyView(Context context, @Nullable AttributeSet attrs)
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
protected void onFinishInflate()
自定义属性
当自定义视图在布局文件中定义时,可以根据需要自定义属性
自定义属性分为 4
个基本步骤:
- 使用
<declare-styleable>
资源元素定义自定义属性(Define custom attributes for your view in a <declare-styleable> resource element
) - 在布局文件中使用自定义属性(
Specify values for the attributes in your XML layout
) - 从构造函数中解析自定义属性(
Retrieve attribute values at runtime
) - 在绘图中使用自定义属性(
Apply the retrieved attribute values to your view
)
定义
自定义属性使用资源元素(resource element
)<declare-styleable>
,通常将其放置在 res/values/attrs.xml
文件中(工程如果没有此资源文件,新建一个):
元素 <declare-styleable>
作为 <resource>
元素的子元素,包含属性 name
,指明此自定义属性集:
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="CustomView" ></declare-styleable></resources>
Note:通常将属性 name
赋值为使用的自定义视图名
在元素 <declare-styleable>
下声明子元素 <attr>
,每个子元素表示一个自定义属性
元素 <attr>
需要声明两个属性:name
和 format
属性 name
表示该属性名称,属性 format
表示该属性格式,共有 10
种格式:
enum
boolean
color
dimension
flag
float
fraction
integer
reference
string
定义 3
个自定义属性,分别表示文本内容,文本大小和文本颜色,示例如下:
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="MyView"> <attr name="text" format="string" /> <attr name="textSize" format="dimension" /> <attr name="textColor" format="color" /> </declare-styleable></resources>
也可以在根元素 <resource>
下先定义好 <attr>
元素,然后在元素 <declare-stylebale>
中使用:
<?xml version="1.0" encoding="utf-8"?><resources> <attr name="text" format="string" /> <attr name="textSize" format="dimension" /> <attr name="textColor" format="color" /> <declare-styleable name="MyView"> <attr name="text" /> <attr name="textSize" /> <attr name="textColor" /> </declare-styleable></resources>
赋值
定义完成后,就可以在布局文件的自定义组件中使用这些属性
首先需要在根元素中定义自定义属性的命名空间(namespace
),使用指令 xmlns:name=value
,其中 name
为命名空间名称(自定义),value
为命名空间,值为 http://schemas.android.com/apk/res/[your package name]
,比如:
xmlns:custom="http://schemas.android.com/apk/com.zj.viewtest"
也可使用系统默认的
xmlns:app="http://schemas.android.com/apk/res-auto"
在布局文件中使用自定义视图时,需要完全限定名(the fully qualified name
),即完整的名称,比如 com.example.MyView
;如果自定义视图是内部类,也需要完整名称,比如文件 MyView.java
中的内部类 InnerView
,其名为 com.example.MyView&InnerView
自定义属性的使用方式和原生属性无异,唯一有的区别可能就是没有自动提示了:
<?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-auto" xmlns:custom="http://schemas.android.com/apk/res/com.zj.viewtest" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.zj.viewtest.MainActivity"> <com.zj.viewtest.MyView android:layout_width="200dp" android:layout_height="200dp" android:background="@color/colorAccent" custom:text="Hello World" custom:textColor="@android:color/holo_blue_bright" custom:textSize="16sp" /></LinearLayout>
获取
当自定义视图在布局文件中定义时,构造对象时使用的构造函数为:
public View(Context context, @Nullable AttributeSet attrs)
布局文件中使用的属性均放置在参数 attrs
中,Android
推荐不直接从 AttributeSet
对象中取出属性值,它列出两点原因:
- Resource references within attribute values are not resolved
- Styles are not applied
将参数 attrs
输入函数 obtainStyledAttributes
,然后从这里获取属性值
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); Log.e(TAG, "MyView: three"); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0); try { text = a.getString(R.styleable.MyView_text); textSize = a.getDimension(R.styleable.MyView_textSize, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); textColor = a.getColor(R.styleable.MyView_textColor, Color.BLACK); } finally { a.recycle(); } Log.e(TAG, "MyView: text = " + text); Log.e(TAG, "MyView: textSize = " + textSize); Log.e(TAG, "MyView: textColor = " + textColor);}
Note 1:属性值的写法 - R.styleable.自定义属性_属性名
,比如 R.styleable.MyView_text
Note 2: 在检索完属性值后,需要将 TypedArray
回收
从
Android 自定义View (一)
中看到另一种检索属性值的方式,参考一下:
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); Log.e(TAG, "MyView: three"); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0); try { int count = a.getIndexCount(); for (int i = 0; i < count; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.MyView_text: text = a.getString(attr); break; case R.styleable.MyView_textSize: textSize = a.getDimension(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); break; case R.styleable.MyView_textColor: textColor = a.getColor(attr, Color.BLACK); break; } } } finally { a.recycle(); } Log.e(TAG, "MyView: text = " + text); Log.e(TAG, "MyView: textSize = " + textSize); Log.e(TAG, "MyView: textColor = " + textColor);}
获取的属性值如下:
使用
获取自定义的属性值后,就可以在绘图操作中使用
绘图操作最重要的部分就是重载 onDraw
方法
/** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */protected void onDraw(Canvas canvas) {}
参数 canvas
是画布,另外还需要定义画笔 Paint
实现如下:
public class MyView extends View { private static final String TAG = "MyView"; private String text; private float textSize; private int textColor; private Paint paint; private Rect textBound; ... public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); Log.e(TAG, "MyView: three"); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0); try { int count = a.getIndexCount(); for (int i = 0; i < count; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.MyView_text: text = a.getString(attr); break; case R.styleable.MyView_textSize: textSize = a.getDimension(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); break; case R.styleable.MyView_textColor: textColor = a.getColor(attr, Color.BLACK); break; } } } finally { a.recycle(); } Log.e(TAG, "MyView: text = " + text); Log.e(TAG, "MyView: textSize = " + textSize); Log.e(TAG, "MyView: textColor = " + textColor); paint = new Paint(); textBound = new Rect(); } ... @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); paint.setColor(textColor); paint.setTextSize(textSize); paint.getTextBounds(text, 0, text.length(), textBound); canvas.drawText(text, width / 2 - textBound.width() / 2, height / 2 + textBound.height() / 2, paint); }}
结果:
属性封装
在应用运行过程中,可能会需要改变自定义视图的属性,可以通过 setter getter
方法,封装自定义属性
MyView.java
中增加如下代码:
public String getText() { return text;}public void setText(String text) { this.text = text; invalidate(); requestLayout();}public float getTextSize() { return textSize;}public void setTextSize(float textSize) { this.textSize = textSize; invalidate(); requestLayout();}public int getTextColor() { return textColor;}public void setTextColor(int textColor) { this.textColor = textColor; invalidate(); requestLayout();}
Note:在 setter
方法中除了改变属性值外,还需要调用方法 invalidate
和 requestLayout
,以确保通知系统重绘该视图
完整代码
attrs.xml
:
<?xml version="1.0" encoding="utf-8"?><resources> <attr name="text" format="string" /> <attr name="textSize" format="dimension" /> <attr name="textColor" format="color" /> <declare-styleable name="MyView"> <attr name="text" /> <attr name="textSize" /> <attr name="textColor" /> </declare-styleable></resources>
MyView.java
:
public class MyView extends View { private static final String TAG = "MyView"; private String text; private float textSize; private int textColor; private Paint paint; private Rect textBound; public MyView(Context context) {// super(context); this(context, null); Log.e(TAG, "MyView: one"); } public MyView(Context context, @Nullable AttributeSet attrs) {// super(context, attrs); this(context, attrs, 0); Log.e(TAG, "MyView: two"); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); Log.e(TAG, "MyView: three"); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0); try { int count = a.getIndexCount(); for (int i = 0; i < count; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.MyView_text: text = a.getString(attr); break; case R.styleable.MyView_textSize: textSize = a.getDimension(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); break; case R.styleable.MyView_textColor: textColor = a.getColor(attr, Color.BLACK); break; } } } finally { a.recycle(); } Log.e(TAG, "MyView: text = " + text); Log.e(TAG, "MyView: textSize = " + textSize); Log.e(TAG, "MyView: textColor = " + textColor); paint = new Paint(); textBound = new Rect(); } @Override protected void onFinishInflate() { super.onFinishInflate(); Log.e(TAG, "onFinishInflate: "); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); paint.setColor(textColor); paint.setTextSize(textSize); paint.getTextBounds(text, 0, text.length(), textBound); canvas.drawText(text, width / 2 - textBound.width() / 2, height / 2 + textBound.height() / 2, paint); } public String getText() { return text; } public void setText(String text) { this.text = text; invalidate(); requestLayout(); } public float getTextSize() { return textSize; } public void setTextSize(float textSize) { this.textSize = textSize; invalidate(); requestLayout(); } public int getTextColor() { return textColor; } public void setTextColor(int textColor) { this.textColor = textColor; invalidate(); requestLayout(); }}
activity_main.xml
:
<?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-auto" xmlns:custom="http://schemas.android.com/apk/res/com.zj.viewtest" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.zj.viewtest.MainActivity"> <com.zj.viewtest.MyView android:id="@+id/my_view" android:layout_width="200dp" android:layout_height="200dp" android:background="@color/colorAccent" custom:text="Hello World" custom:textColor="@android:color/holo_blue_bright" custom:textSize="16sp" /> <Button android:id="@+id/btn_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:text="TEXT" /> <Button android:id="@+id/btn_color" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="COLOR" /></LinearLayout>
MainActivity.java
:
public class MainActivity extends AppCompatActivity { private MyView myView; private Button btnText; private Button btnColor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myView = (MyView) findViewById(R.id.my_view); btnText = (Button) findViewById(R.id.btn_text); btnColor = (Button) findViewById(R.id.btn_color); final String[] randomText = getResources().getStringArray(android.R.array.phoneTypes); btnText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Random random = new Random(); myView.setText(randomText[random.nextInt(randomText.length)]); } }); final int[] randomColor = new int[]{Color.GREEN, Color.GRAY, Color.WHITE, Color.BLUE, Color.BLACK}; btnColor.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Random random = new Random(); myView.setTextColor(randomColor[random.nextInt(randomColor.length)]); } }); }}
- Android自定义view(2)
- android 自定义 View(2)
- Android 自定义View(2)
- android学习之(2)----自定义View
- Android读书笔记-----自定义View(2)实现
- Android 自定义View教程(2)
- Android的自定义View(2)
- Android Custom View (自定义 View)
- Android自定义View(三)继承View
- Android View体系(九)自定义View
- Android View体系(九)自定义View
- Android view 详解(四) 自定义view
- Android 自定义属性 view (继承View)
- android 自定义view(二),继承view
- Android自定义view之(刻度尺view)
- Android View体系(九)自定义View
- Android View---自定义View
- Android View---自定义View
- 数据驱动深度学习革命AI将彻底改变小企业的生意方式
- 深度解读
- * 24种设计模式——策略模式
- linux基本命令及操作3(man命令)
- web前端之jQuery脚本的书写
- android 自定义 View(2)
- Exception from container-launch: ExitCodeException exitCode=1:
- PHP之Linux(八、九)shell
- 在MyEclipse工具中导入JAVA WEB项目会报错的解决方法
- 解决:Error:Execution failed for task ':app:transformClassesWithJarMergingForRealD
- Jvm 运行时数据区域详解
- scrapy xpath
- HDU 1175 连连看(dfs)
- javascript将时间戳转化为Date格式