您的位置:首页 > 移动开发 > Android开发

Android自定义控件(一)

2016-01-05 10:15 656 查看
1、创建新视图的类型与希望达到的目标:

如果现有控件已经满足希望实现的基本功能,那么只需要对现有控件的外观和行为进行修改或扩展即可。通过重写事件处理程序和onDraw方法,但是仍然回调超类的方法,可以对视图进行定制,而不必重新实现它的功能。例如,定制一个TextView来显示指定位数的小数。
可以通过组合多个视图创建不可分割的、可重用的控件,从而使它可以综合使用多个相互关联的视图的功能。例如,通过组合一个TextView和一个Button来创建一个秒表定时器,当单击它的时候,就重置计数器。
当需要一个全新的界面,而通过修改或者组合现有控件不能是西安这个目标的时候,就可以创建一个全新的控件。
1.1 修改现有视图
在一个已有的控件基础上创建一个新视图,就需要创建一个扩展了原控件的新类。
/**
* Desc: 在TextView的基础上绘制一条横线和竖线
* Author: xss
* Time:2016/1/5 10:25
*/
public class CustomTextView extends TextView {
private Paint marginPaint;  //绘制边缘
private Paint linePaint;   //绘制页面背景
private int paperColor;    //页面颜色值
private float margin;      //边缘宽度值

public CustomTextView(Context context) {
super(context);
init();
}

public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}

private void init() {
//获得对资源表的引用
Resources mResources = getResources();

//创建将在onDraw方法中使用的画刷
marginPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
marginPaint.setColor(mResources.getColor(R.color.custom_textView_margin));

linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(mResources.getColor(R.color.custom_textView_lines));

//获得页面背景和边缘宽度
paperColor = mResources.getColor(R.color.custom_textView_paper);
margin = mResources.getDimension(R.dimen.custom_textView_margin);
}

/**
* 一旦绘制了页面图像后,就可以调用超类的onDraw方法,让它像往常一样绘制文本
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
//绘制页面的颜色
canvas.drawColor(paperColor);

//绘制边缘
canvas.drawLine(0, 0, 0, getMeasuredHeight(), linePaint); //绘制左高
canvas.drawLine(0, getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight(), linePaint); //绘制底宽
canvas.drawLine(margin, 0, margin, getMeasuredHeight(), marginPaint); //绘制margin

//移动文本,让它跨过边缘
canvas.save();
canvas.translate(margin, 0);

//使用TextView渲染文本
super.onDraw(canvas);
canvas.restore();
}
}
实现效果图:



1.2 创建组合控件视图
复合控件是指不可分割的、自包含的视图组(ViewGroup),其中包含了多个排列和连接在一起的子视图。
当创建复合控件时,必须对它包含的视图的布局、外观和交互进行定义。复合控件是通过扩展一个ViewGroup(通常是一个布局)来创建的。因此,要创建一个新的复合控件,首先需要选择一个最合适放置子控件的布局类,然后扩展该类。
我们来实现这样的一个布局效果图:



首先,创建组合控件布局文件view_custom_layout.xml;
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:padding="8dp">
<TextView
android:id="@+id/tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="label" />
<EditText
android:id="@+id/edt_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#00ff00"
android:hint="Please input"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:gravity="center_vertical"
android:focusable="true"
android:textCursorDrawable="@drawable/color_cursor"/>
<TextView
android:id="@+id/tv_extra"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="unit" />
</LinearLayout>

<LinearLayout
android:id="@+id/ll_span_line"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#bbb"
android:orientation="vertical"/>
</merge>
其次,自定义控件的一些属性,在attr.xml中定义如下:

<declare-styleable name="CustomLinearLayout">
<attr name="custom_label_text" format="string" />
<attr name="custom_label_textSize" format="dimension" />
<attr name="custom_label_textColor" format="color" />
<attr name="custom_label_padding" format="dimension" />
<attr name="custom_edt_text" format="string" />
<attr name="custom_edt_textSize" format="dimension" />
<attr name="custom_edt_textColor" format="color" />
<attr name="custom_edt_hint_text" format="string" />
<attr name="custom_edt_hint_textColor" format="color" />
<attr name="custom_edt_padding" format="dimension" />
<attr name="custom_edt_marginLeft" format="dimension" />
<attr name="custom_edt_marginRight" format="dimension" />
<attr name="custom_extra_text" format="string" />
<attr name="custom_extra_textSize" format="dimension" />
<attr name="custom_extra_textColor" format="color" />
<attr name="custom_extra_background" format="reference|color" />
<attr name="custom_line_visibility">
<enum name="VISIBLE" value="0" />
<enum name="INVISIBLE" value="1" />
<enum name="GONE" value="8" />
</attr>
<attr name="custom_edt_inputType">  <!--整形定义  format="integer"-->
<flag name="none" value="0x00000000" />
<flag name="text" value="0x00000001" />
<flag name="textCapCharacters" value="0x00001001" />
<flag name="textMultiLine" value="0x00020001" />
<flag name="textUri" value="0x00000011" />
<flag name="textEmailAddress" value="0x00000021" />
<flag name="textPersonName" value="0x00000061" />
<flag name="textPassword" value="0x00000081" />
<flag name="number" value="0x00000002" />
<flag name="numberDecimal" value="0x00002002" />
<flag name="numberPassword" value="0x00000012" />
<flag name="phone" value="0x00000003" />
<flag name="datetime" value="0x00000004" />
<flag name="date" value="0x00000014" />
<flag name="time" value="0x00000024" />
</attr>
<attr name="custom_edt_imeOptions" >
<flag name="normal" value="0x00000000" />
<flag name="actionUnspecified" value="0x00000000" />
<flag name="actionNone" value="0x00000001" />
<flag name="actionGo" value="0x00000002" />
<flag name="actionSearch" value="0x00000003" />
<flag name="actionSend" value="0x00000004" />
<flag name="actionNext" value="0x00000005" />
<flag name="act
e06e
ionDone" value="0x00000006" />
</attr>
<attr name="custom_edt_background" format="color" />
</declare-styleable>
最后,就是重写我们的布局,代码如下:
public class CustomLinearLayout extends LinearLayout {
private Context context;

private TextView tv_label;
private EditText edt_content;
private TextView tv_extra;
private LinearLayout ll_span_line;

private String labelText;
private float labelTextSize;
private int labelTextColor;
private float labelPadding;

private String contentText;
private float contentTextSize;
private int contentTextColor;
private String hintText;
private int hintTextColor;
private float contentPadding;
private float contentMarginLeft;
private float contentMarginRight;
private int edtInputType;
private int edtImeOptions;
private int edtBackground;

private String extraText;
private float extraTextSize;
private int extraTextColor;
private int extraBackground;

private int lineVisibility;

public CustomLinearLayout(Context context) {
super(context);
this.context = context;
init();
}

public CustomLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
obtainStyleAttributes(context, attrs, 0, 0);
init();
}

public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
obtainStyleAttributes(context, attrs, defStyleAttr, 0);
init();
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.context = context;
obtainStyleAttributes(context, attrs, defStyleAttr, defStyleRes);
init();
}

/**
* 获取属性值
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
private void obtainStyleAttributes(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomLinearLayout, defStyleAttr, defStyleRes);

labelText = a.getString(R.styleable.CustomLinearLayout_custom_label_text);
labelTextSize = a.getDimension(R.styleable.CustomLinearLayout_custom_label_textSize, 12f);
labelTextColor = a.getColor(R.styleable.CustomLinearLayout_custom_label_textColor, Color.parseColor("#000000"));
labelPadding = a.getDimension(R.styleable.CustomLinearLayout_custom_label_padding, 0f);

contentText = a.getString(R.styleable.CustomLinearLayout_custom_edt_text);
contentTextSize = a.getDimension(R.styleable.CustomLinearLayout_custom_edt_textSize, 12f);
contentTextColor = a.getColor(R.styleable.CustomLinearLayout_custom_edt_textColor, Color.parseColor("#000000"));
hintText = a.getString(R.styleable.CustomLinearLayout_custom_edt_hint_text);
hintTextColor = a.getColor(R.styleable.CustomLinearLayout_custom_edt_hint_textColor, Color.parseColor("#000000"));
contentPadding = a.getDimension(R.styleable.CustomLinearLayout_custom_edt_padding, 0f);
contentMarginLeft = a.getDimension(R.styleable.CustomLinearLayout_custom_edt_marginLeft, 0f);
contentMarginRight = a.getDimension(R.styleable.CustomLinearLayout_custom_edt_marginRight, 0f);
edtInputType = a.getInt(R.styleable.CustomLinearLayout_custom_edt_inputType, EditorInfo.TYPE_NULL);  //EditorInfo.TYPE_NULL
edtImeOptions = a.getInt(R.styleable.CustomLinearLayout_custom_edt_imeOptions, EditorInfo.IME_ACTION_NONE);
edtBackground = a.getColor(R.styleable.CustomLinearLayout_custom_edt_background, Color.parseColor("#ffffff"));

extraText = a.getString(R.styleable.CustomLinearLayout_custom_extra_text);
extraTextSize = a.getDimension(R.styleable.CustomLinearLayout_custom_extra_textSize, 12f);
extraTextColor = a.getColor(R.styleable.CustomLinearLayout_custom_extra_textColor, Color.parseColor("#000000"));
extraBackground = a.getResourceId(R.styleable.CustomLinearLayout_custom_extra_background, R.color.COLOR_000000);

lineVisibility = a.getInt(R.styleable.CustomLinearLayout_custom_line_visibility, View.VISIBLE);

a.recycle();
}

/**
* 获取控件对象
*/
private void init() {
setOrientation(LinearLayout.VERTICAL);
setGravity(Gravity.CENTER_VERTICAL);

LayoutInflater inflater = (LayoutInflater)this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.view_custom_layout, this, true);

final ViewGroup viewGroup = (ViewGroup)getChildAt(0);
if (viewGroup != null) {
tv_label = (TextView)viewGroup.getChildAt(0);
edt_content = (EditText)viewGroup.getChildAt(1);
tv_extra = (TextView)viewGroup.getChildAt(2);
}
ll_span_line = (LinearLayout)getChildAt(1);

//        tv_label = (TextView)view.findViewById(R.id.tv_label);
//        edt_content = (EditText)view.findViewById(R.id.edt_content);
//        tv_extra = (TextView)view.findViewById(R.id.tv_extra);
//        ll_span_line = (LinearLayout)view.findViewById(R.id.ll_span_line);

tv_label.setText(labelText);
//setTextSize函数对应的单位本身就是sp
tv_label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize);
tv_label.setTextColor(labelTextColor);
tv_label.setPadding((int) labelPadding, (int) labelPadding, (int) labelPadding, (int) labelPadding);

showKeyBoard();
edt_content.setText(contentText);
edt_content.setTextSize(TypedValue.COMPLEX_UNIT_PX, contentTextSize);
edt_content.setTextColor(contentTextColor);
edt_content.setHint(hintText);
edt_content.setHintTextColor(hintTextColor);
edt_content.setPadding((int) contentPadding, (int) contentPadding, (int) contentPadding, (int) contentPadding);
LinearLayout.LayoutParams lp = (LayoutParams) edt_content.getLayoutParams();
lp.setMargins((int) contentMarginLeft, 0, (int) contentMarginRight, 0);  //左上右下
edt_content.setImeOptions(edtImeOptions);
setEdtInputType();  //设置InputType
edt_content.setBackgroundColor(edtBackground);
if (!TextUtils.isEmpty(contentText)) {
edt_content.setKeyListener(DigitsKeyListener.getInstance(contentText));
}
edt_content.setFocusableInTouchMode(true);

tv_extra.setText(extraText);
tv_extra.setTextSize(TypedValue.COMPLEX_UNIT_PX, extraTextSize);
tv_extra.setTextColor(extraTextColor);
tv_extra.setBackgroundColor(extraBackground); //getResources().getColor(extraBackground)

if (lineVisibility == VISIBLE) {
ll_span_line.setVisibility(VISIBLE);
} else if (lineVisibility == INVISIBLE) {
ll_span_line.setVisibility(INVISIBLE);
} else if (lineVisibility == GONE) {
ll_span_line.setVisibility(GONE);
}
}

private void showKeyBoard() {
if (edt_content != null) {
edt_content.setFocusable(true);
edt_content.setFocusableInTouchMode(true);
edt_content.requestFocus();
//调用系统输入法
InputMethodManager inputMethodManager = (InputMethodManager) edt_content.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(edt_content, 0);
}
}
}
1.3 创建定制的视图
要在一个空画布上创建新的控件,就需要对View或者Surface类(3D)进行扩展。View类提供一个Canvas和一系列绘制方法以及Paint类,因此,使用它可以运用位图和光栅图像创建一个可视化的界面,之后,可以重写像触摸屏或者按键按下这样的用户事件以提供交互。
例如,要实现以下效果的自定义视图:



代码实现如下:
public class CustomView extends View {
private Paint mPaint;

//使用代码进行创建时必需的构造函数
public CustomView(Context context) {
super(context);
init();
}

//使用资源文件进行填充时必需的构造函数
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

//使用资源文件进行填充时必需的构造函数
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}

private void init() {
//为提高效率,paint对象最好在构造函数中创建,因为在onDraw方法中创建的任何对象都会在屏幕刷新的时候被创建和销毁
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
}

@Override
protected void onDraw(Canvas canvas) {
int height = getMeasuredHeight();
int width = getMeasuredWidth();

//找出控件的中心
int px = width / 2;
int py = height / 2;

String text = "Hello world !";
//计算文本字符串的宽度
float textWidth = mPaint.measureText(text);

//在控件中心绘制文本字符串
canvas.drawText(text, px - textWidth / 2, py, mPaint);
}

/**
* 调整控件大小
* @param widthMeasureSpec
* @param heightMeasureSpec
* 对参数解码,可以计算出适合的高宽值
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredWidth = measureWidth(widthMeasureSpec);
int measuredHeight = measureHeight(heightMeasureSpec);

//必须调用setMeasuredDimension,否则在布局控件的时候,会造成运行时异常
setMeasuredDimension(measuredWidth, measuredHeight);
}

/**
* 返回计算的组建的宽度
* @param measureSpec
* @return
*/
private int measureWidth(int measureSpec) {
int sepcMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

//如果不指定限制,就是默认大小
int result = 500;
if (sepcMode == MeasureSpec.AT_MOST) {
//计算控件在这个最大尺寸范围内的理想大小,如果控件填充了可用空间,则返回外边界
result = specSize;
} else if (sepcMode == MeasureSpec.EXACTLY) {
//如果控件可以放置在这个边界内,则返回该值
result = specSize;
}
return result;
}

/**
* 返回计算的组建的高度
* @param measureSpec
* @return
*/
private int measureHeight(int measureSpec) {
int sepcMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

//如果不指定限制,就是默认大小
int result = 500;
if (sepcMode == MeasureSpec.AT_MOST) {
//计算控件在这个最大尺寸范围内的理想大小,如果控件填充了可用空间,则返回外边界
result = specSize;
} else if (sepcMode == MeasureSpec.EXACTLY) {
//如果控件可以放置在这个边界内,则返回该值
result = specSize;
}
return result;
}
}
1.4 attrs.xml属性全解:http://blog.csdn.net/aldridge1/article/details/14005403
1.5 attr属性format讲解:http://www.cnblogs.com/rayray/p/3442026.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android View