1、概述
自定义View过程可以分为三个大的部分:java代码部分,继承自某一个特定的View类,并重写其中的方法,这是必不可少的部分;自定义属性文件部分,可选;在布局xml文件中声明自定义的View并有可能使用自定义的属性,可选。
2、重写View类或View的子类
重写View或者View的子类时,可以继承某一个View类或者一个ViewGroup类。当继承ViewGroup类时,在整体上可以认为是对视图的联合,即在布局里添加视图控件。重写View类及其子类的方法可以获得需要的数据或功能:
onSizeChanged(int w,int h,int oldw,int oldh)在布局期间当视图的大小产生改变的时候调用,通过保存w,h值可以获得最终视图的宽度和高度。
onMeasure(int widthMeasureSpec, int heightMeasureSpec) 在测量视图大小的时候调用,widthMeasureSpec和heightMeasureSpec分别用于表示测量时的宽度和高度,通过MeasureSpec.getMode()和MeasureSpec.getSize()方法传入widthMeasureSpec/heightMeasureSpec参数得到返回值分别表示得到测量视图的mode和size。在onMeasure方法的末尾一定要调用setMeasuredDimension(int, int)方法来保存处理后的宽度和高度。
onDraw(Canvas canvas):控制界面的绘制的方法,如果你在原有视图的基础上进行修改,并想维持原有的绘制则在onDraw方法的末尾就应该使用super.onDraw(canvas)来调用父类的绘制。
还有很多方法...。
关于构造方法:
为了让视图在java代码中或者xml文件中都能够构造使用,我通常会对父类所有的构造方法都写相应的构造方法并简单的调用父类的构造方法。然后就是对AttributeSet进行处理了。通过TypedArray ta=context.getTheme().obtainStyledAttributes(as, R.styleable.UIView, 0, 0)方法获得属性数组,然后通过ta.getXX()方法根据设置的属性名获得属性值,然后就可以通过相关的方法将属性应用到视图中。
package com.cn.customui;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class UIView extends LinearLayout implements TextWatcher {
Context c;
public UIView(Context context) {
super(context);
this.c = context;
init_show();
View v=new View(c);
}
public UIView(Context context, AttributeSet as) {
super(context, as);
this.c = context;
init_show(as);
}
int measuredWidth, measuredHeight;
public int measureWidth(int widthMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
int result = 200;
if (mode == MeasureSpec.UNSPECIFIED) {
} else {
result = width;
}
return result;
}
int width, height;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
this.width = w;
this.height = h;
if (txt != null) {
txt_lp.width = txt_lp.height = height;
txt.setLayoutParams(txt_lp);
}
}
public int measureHeight(int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
int mode = MeasureSpec.getMode(heightMeasureSpec);
int result = 40;
if (mode == MeasureSpec.UNSPECIFIED) {
} else {
result = height;
}
return result;
}
LinearLayout.LayoutParams txt_lp;
TextView txt;
public void init_show() {
this.setOrientation(LinearLayout.HORIZONTAL);
txt = new TextView(c);
txt_lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.FILL_PARENT);
txt.setLayoutParams(txt_lp);
txt.setText("用户名:");
txt.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams edit_lp = new LinearLayout.LayoutParams(0,
LayoutParams.FILL_PARENT);
edit_lp.weight = 1;
edit_lp.rightMargin = 20;
edit_lp.bottomMargin=edit_lp.topMargin=2;
EditText edit = new EditText(c);
edit.setLayoutParams(edit_lp);
edit.setBackgroundResource(R.drawable.edit_rect);
edit.setHint("搜索");
edit.addTextChangedListener(this);
this.addView(txt);
this.addView(edit);
System.out.println("init_show" + this.toString());
System.out.println("init_show,height:" + this.height);
}
public void init_show(AttributeSet as) {
TypedArray ta=c.getTheme().obtainStyledAttributes(as, R.styleable.UIView, 0, 0);
String txt_txt=ta.getString(R.styleable.UIView_settext);
String edit_hint=ta.getString(R.styleable.UIView_sethint);
ta.recycle();
this.setOrientation(LinearLayout.HORIZONTAL);
txt = new TextView(c);
txt_lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.FILL_PARENT);
txt.setLayoutParams(txt_lp);
txt.setText(txt_txt);
txt.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams edit_lp = new LinearLayout.LayoutParams(0,
LayoutParams.FILL_PARENT);
edit_lp.weight = 1;
edit_lp.rightMargin = 20;
edit_lp.bottomMargin=5;
edit_lp.topMargin=5;
EditText edit = new EditText(c);
edit.setLayoutParams(edit_lp);
edit.setBackgroundResource(R.drawable.edit_rect);
edit.setHint(edit_hint);
edit.addTextChangedListener(this);
ImageView imageView=new ImageView(c);
imageView.setImageResource(R.drawable.img_search);
LinearLayout.LayoutParams image_lp=new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.FILL_PARENT);
imageView.setLayoutParams(image_lp);
this.addView(txt);
this.addView(edit);
this.addView(imageView);
System.out.println("init_show2" + txt_txt);
System.out.println("init_show,height:2" + this.height);
}
Editable editable;
onTextChangeListener otl;
@Override
public void afterTextChanged(Editable arg0) {}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,int arg3) {}
@Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
if (arg0 != null)
otl.getText(arg0, arg1, arg2, arg3);
}
interface onTextChangeListener {
public void getText(CharSequence arg0, int arg1, int arg2, int arg3);
}
public void setOnTextChangedListener(onTextChangeListener listener) {
this.otl = listener;
};
}
3、自定义属性文件部分
在values文件夹下新建一个attrs.xml的文件,在<declare-styleable>标签内的标签<attr>用于定义一个属性,在<declare-styleable>中name属性用于标识这个指定的自定义样式,通常这个名字与自定义的类名相同。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="UIView">
<attr name="settext" format="string"/>
<attr name="sethint" format="string"/>
</declare-styleable>
</resources>
4、布局xml文件声明自定义View的注意事项
在xml文件中声明自定义的属性时应该有命名空间为:xmlns:android="http://schemas.android.com/apk/res/[packagename]",[packagename]为应用程序的包名,在这个应用程序的R.java文件下记录了要使用的自定义属性的标识。
5、自定义属性时format的常用值:
reference:参考某一资源ID。
(1)属性定义:
<declare-styleable name = "名称">
<attr name = "background" format = "reference" />
</declare-styleable>
(2)属性使用:
<ImageView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:background = "@drawable/图片ID"
/>
2. color:颜色值。
(1)属性定义:
<declare-styleable name = "名称">
<attr name = "textColor" format = "color" />
</declare-styleable>
(2)属性使用:
<TextView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:textColor = "#00FF00"
/>
3. boolean:布尔值。
(1)属性定义:
<declare-styleable name = "名称">
<attr name = "focusable" format = "boolean" />
</declare-styleable>
(2)属性使用:
<Button
android:layout_width = "42dip"
android:layout_height = "42dip"
android:focusable = "true"
/>
4. dimension:尺寸值。
(1)属性定义:
<declare-styleable name = "名称">
<attr name = "layout_width" format = "dimension" />
</declare-styleable>
(2)属性使用:
<Button
android:layout_width = "42dip"
android:layout_height = "42dip"
/>
5. float:浮点值。
(1)属性定义:
<declare-styleable name = "AlphaAnimation">
<attr name = "fromAlpha" format = "float" />
<attr name = "toAlpha" format = "float" />
</declare-styleable>
(2)属性使用:
<alpha
android:fromAlpha = "1.0"
android:toAlpha = "0.7"
/>
6. integer:整型值。
(1)属性定义:
<declare-styleable name = "AnimatedRotateDrawable">
<attr name = "visible" />
<attr name = "frameDuration" format="integer" />
<attr name = "framesCount" format="integer" />
<attr name = "pivotX" />
<attr name = "pivotY" />
<attr name = "drawable" />
</declare-styleable>
(2)属性使用:
<animated-rotate
xmlns:android = "http://schemas.android.com/apk/res/android"
android:drawable = "@drawable/图片ID"
android:pivotX = "50%"
android:pivotY = "50%"
android:framesCount = "12"
android:frameDuration = "100"
/>
7. string:字符串。
(1)属性定义:
<declare-styleable name = "MapView">
<attr name = "apiKey" format = "string" />
</declare-styleable>
(2)属性使用:
<com.google.android.maps.MapView
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
android:apiKey = "0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g"
/>
8. fraction:百分数。
(1)属性定义:
<declare-styleable name="RotateDrawable">
<attr name = "visible" />
<attr name = "fromDegrees" format = "float" />
<attr name = "toDegrees" format = "float" />
<attr name = "pivotX" format = "fraction" />
<attr name = "pivotY" format = "fraction" />
<attr name = "drawable" />
</declare-styleable>
(2)属性使用:
<rotate
xmlns:android = "http://schemas.android.com/apk/res/android"
android:interpolator = "@anim/动画ID"
android:fromDegrees = "0"
android:toDegrees = "360"
android:pivotX = "200%"
android:pivotY = "300%"
android:duration = "5000"
android:repeatMode = "restart"
android:repeatCount = "infinite"
/>
9. enum:枚举值。
(1)属性定义:
<declare-styleable name="名称">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
(2)属性使用:
<LinearLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
android:orientation = "vertical"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
>
</LinearLayout>
10. flag:位或运算。
(1)属性定义:
<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>
(2)属性使用:
<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>
特别要注意:
属性定义时可以指定多种类型值。
(1)属性定义:
<declare-styleable name = "名称">
<attr name = "background" format = "reference|color" />
</declare-styleable>
(2)属性使用:
<ImageView
android:layout_width = "42dip"
android:layout_height = "42dip"
android:background = "@drawable/图片ID|#00FF00"
/>