前言
- 自定义View是Android开发者必须了解的基础
- 自定义View一共分为两大类,具体如下图:
使用注意点
在使用自定义View时有很多注意点(坑),希望大家要非常留意:
多线程应直接使用post方式
View的内部本身提供了post系列的方法,完全可以替代Handler的作用,使用起来更加方便、直接。
避免内存泄露
主要针对View中含有线程或动画的情况:当View退出或不可见时,记得及时停止该View包含的线程和动画,否则会造成内存泄露问题。
启动或停止线程/ 动画的方式:
启动线程/ 动画:使用view.onAttachedToWindow(),因为该方法调用的时机是当包含View的Activity启动的时刻
停止线程/ 动画:使用view.onDetachedFromWindow(),因为该方法调用的时机是当包含View的Activity退出或当前View被remove的时刻
在下面的例子中,我将讲解:
- 如何实现一个基本的自定义View(继承VIew)
- 如何自身支持wrap_content & padding属性
- 如何为自定义View提供自定义属性(如颜色等等)
- 实例说明:画一个实心圆
具体步骤
- 创建自定义View类(继承View类)
- 布局文件添加自定义View组件
- 注意点设置(支持wrap_content & padding属性自定义属性等等)
创建自定义View类(继承View类)
// 用于绘制自定义View的具体内容
// 具体绘制是在复写的onDraw()内实现
public class CircleView extends View {
// 设置画笔变量
Paint mPaint1;
// 自定义View有三个构造函数
// 如果View是在Java代码里面new的,则调用第一个构造函数
public CircleView(Context context){
super(context);
// 在构造函数里初始化画笔的操作
init();
}
// 如果View是在.xml里声明的,则调用第二个构造函数
// 自定义属性是从AttributeSet参数传进来的
public CircleView(Context context,AttributeSet attrs){
super(context, attrs);
init();
}
// 不会自动调用
// 一般是在第二个构造函数里主动调用
// 如View有style属性时
public CircleView(Context context,AttributeSet attrs,int defStyleAttr ){
super(context, attrs,defStyleAttr);
init();
}
// 画笔初始化
private void init() {
// 创建画笔
mPaint1 = new Paint ();
// 设置画笔颜色为蓝色
mPaint1.setColor(Color.BLUE);
// 设置画笔宽度为5px
mPaint1.setStrokeWidth(5f);
//设置画笔模式为填充
mPaint1.setStyle(Paint.Style.FILL);
}
// 复写onDraw()进行绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取控件的高度和宽度
int width = getWidth();
int height = getHeight();
// 设置圆的半径 = 宽,高最小值的2分之1
int r = Math.min(width, height)/2;
// 画出圆(蓝色)
// 圆心 = 控件的中央,半径 = 宽,高最小值的2分之1
canvas.drawCircle(width/2,height/2,r,mPaint1);
}
}
在布局文件中添加自定义View类的组件
activity_main.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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 注意添加自定义View组件的标签名:包名 + 自定义View类名-->
<!-- 控件背景设置为黑色-->
<com.example.learnview.CircleView
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="#000000" />
</RelativeLayout>
好了,至此,一个基本的自定义View已经实现了。接下来继续看自定义View所有应该注意的点:
- 如何手动支持wrap_content属性
- 如何手动支持padding属性
- 如何为自定义View提供自定义属性(如颜色等等)
先来看wrap_content & match_parent属性的区别
// 视图的宽和高刚好适应视图内容的最小尺寸
android:layout_width="wrap_content"
// 视图的宽和高充满整个父布局
android:layout_width="match_parent"
// 在Android API 8之前叫作"fill_parent"
padding属性用于设置控件内容相对控件边缘的边距
margin属性:边距,相对父控件的边距,控件距离父控件上下左右的边距
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 注意添加自定义View组件的标签名:包名 + 自定义View类名-->
<!-- 控件背景设置为黑色-->
<com.example.learnview.CircleView
android:layout_width="match_parent"
android:layout_height="150dp"
/** 添加Padding属性,但不会生效 **/
android:padding="20dp"
android:background="#000000" />
</RelativeLayout>
绘制时考虑传入的padding属性值(四个方向)。
// 仅看复写的onDraw()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取传入的padding值
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
// 获取绘制内容的高度和宽度(考虑了四个方向的padding值)
int width = getWidth() - paddingLeft - paddingRight ;
int height = getHeight() - paddingTop - paddingBottom ;
// 设置圆的半径 = 宽,高最小值的2分之1
int r = Math.min(width, height)/2;
// 画出圆(蓝色)
// 圆心 = 控件的中央,半径 = 宽,高最小值的2分之1
canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,r,mPaint1);
}
提供自定义属性
系统自带属性,如
// 基本是以android开头
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:padding="30dp"
- 但有些时候需要一些系统所没有的属性,称为自定义属性
- 使用步骤有如下:
1 在values目录下创建自定义属性的xml文件
2 在自定义View的构造方法中解析自定义属性的值
3 在布局文件中使用自定义属性
在values目录下创建自定义属性的xml文件
attrs_circle_view.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--自定义属性集合:CircleView-->
<!--在该集合下,设置不同的自定义属性-->
<declare-styleable name="CircleView">
<!--在attr标签下设置需要的自定义属性-->
<!--此处定义了一个设置图形的颜色:circle_color属性,格式是color,代表颜色-->
<!--格式有很多种,如资源id(reference)等等-->
<attr name="circle_color" format="color"/>
</declare-styleable>
</resources>
在自定义View的构造方法中解析自定义属性的值
// 不会自动调用
// 一般是在第二个构造函数里主动调用
// 如View有style属性时
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 加载自定义属性集合CircleView
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CircleView);
// 解析集合中的属性circle_color属性
// 该属性的id为:R.styleable.CircleView_circle_color
// 将解析的属性传入到画圆的画笔颜色变量当中(本质上是自定义画圆画笔的颜色)
// 第二个参数是默认设置颜色(即无指定circle_color情况下使用)
mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);
// 解析后释放资源
a.recycle();
init();
}
// 画笔初始化
private void init() {
// 创建画笔
mPaint1 = new Paint();
// 设置画笔颜色为蓝色
mPaint1.setColor(mColor);
// 设置画笔宽度为5px
mPaint1.setStrokeWidth(5f);
//设置画笔模式为填充
mPaint1.setStyle(Paint.Style.FILL);
}
在布局文件中使用自定义属性
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- 注意添加自定义View组件的标签名:包名 + 自定义View类名-->
<!-- 控件背景设置为黑色-->
<com.example.learnview.CircleView
android:layout_width="match_parent"
android:layout_height="150dp"
android:padding="20dp"
app:circle_color="#FF4081"
android:background="#000000" />
</RelativeLayout>
对于自定义属性类型 & 格式如下:
<-- 1. reference:使用某一资源ID -->
<declare-styleable name="名称">
<attr name="background" format="reference" />
</declare-styleable>
// 使用格式
// 1. Java代码
private int ResID;
private Drawable ResDraw;
ResID = typedArray.getResourceId(R.styleable.SuperEditText_background, R.drawable.background); // 获得资源ID
ResDraw = getResources().getDrawable(ResID); // 获得Drawble对象
// 2. xml代码
<ImageView
android:layout_width="42dip"
android:layout_height="42dip"
app:background="@drawable/图片ID" />
<-- 2. color:颜色值 -->
<declare-styleable name="名称">
<attr name="textColor" format="color" />
</declare-styleable>
// 格式使用
<TextView
android:layout_width="42dip"
android:layout_height="42dip"
android:textColor="#00FF00" />
<-- 3. boolean:布尔值 -->
<declare-styleable name="名称">
<attr name="focusable" format="boolean" />
</declare-styleable>
// 格式使用
<Button
android:layout_width="42dip"
android:layout_height="42dip"
android:focusable="true" />
<-- 4. dimension:尺寸值 -->
<declare-styleable name="名称">
<attr name="layout_width" format="dimension" />
</declare-styleable>
// 格式使用:
<Button
android:layout_width="42dip"
android:layout_height="42dip" />
<-- 5. float:浮点值 -->
<declare-styleable name="AlphaAnimation">
<attr name="fromAlpha" format="float" />
<attr name="toAlpha" format="float" />
</declare-styleable>
// 格式使用
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.7" />
<-- 6. integer:整型值 -->
<declare-styleable name="AnimatedRotateDrawable">
<attr name="frameDuration" format="integer" />
<attr name="framesCount" format="integer" />
</declare-styleable>
// 格式使用
<animated-rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:frameDuration="100"
android:framesCount="12"
/>
<-- 7. string:字符串 -->
<declare-styleable name="MapView">
<attr name="apiKey" format="string" />
</declare-styleable>
// 格式使用
<com.google.android.maps.MapView
android:apiKey="0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g" />
<-- 8. fraction:百分数 -->
<declare-styleable name="RotateDrawable">
<attr name="pivotX" format="fraction" />
<attr name="pivotY" format="fraction" />
</declare-styleable>
// 格式使用
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotX="200%"
android:pivotY="300%"
/>
<-- 9. enum:枚举值 -->
<declare-styleable name="名称">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
// 格式使用
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
<-- 10. flag:位或运算 -->
<declare-styleable name="名称">
<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>、
// 使用
<activity
android:name=".StyleAndThemeActivity"
android:label="@string/app_name"
android:windowSoftInputMode="stateUnspecified | stateUnchanged | stateHidden" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<-- 特别注意:属性定义时可以指定多种类型值 -->
<declare-styleable name="名称">
<attr name="background" format="reference|color" />
</declare-styleable>
// 使用
<ImageView
android:layout_width="42dip"
android:layout_height="42dip"
android:background="@drawable/图片ID|#00FF00" />
今天我们这篇博客就到这里,下篇博客自定义View基础
网友评论