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

定义自定义控件

2016-07-16 08:16 302 查看
[TOC]

安卓的ui元素全部都基于view或者是viewgroup。在一些app中我imenxuyao自定义view来满足我们的需求,这意味着对于现有的view的一些延伸创造view的子类以创造更加复杂的view。

自定义自己的view视图意味着扩展view或者一个存在的子类,然后能够重写view的某些行为例如
onDraw
onToutchEvent
然后在你的活动中使用。

创建完全自定义的组件

自定义组件我们主要着重于5个方面:

- Drawing: 控制市局上view的渲染,通过重写
onDraw
方法

- Interaction: 控制用户和view的交互方式通过控制
onTouchEvent
和手势

- Measurement: 控制view的内容区域通过重写
onMeasure()
方法

- Attributes: 自定义vie的XML的属性,然后使用TypedArray控制相关的行为

- Persistence: 存储或者获得相关的状态以避免失去状态,方法:
onSavedInstanceState
onRestoreInstanceState


下面我们将自定义一个试图展示不同的图形。

定义view类

为了实现一个可切换的图形选择器我们定义一个
ShapeSelectorView
继承
View
视图。

1. 建立一个
ShapeSelectorView
类继承View类,并写明构造方法

2. 在布局文件中添加我们定义的view

3. 定义了两个属性
app:shapeColor
app:displayShapeName


4. 新建
values/attr.xml
文件定义视图的两个属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ShapeSelectorView">
<attr name="shapeColor" format="color" />
<attr name="displayShapeName" format="boolean" />
</declare-styleable>
</resources>


5.提取视图的相关属性赋值给成员变量,使用
TypedArray
obtainStyledAttributes
在AttributeSet上

public class ShapeSelectorView extends View {
private int shapeColor;
private boolean displayShapeName;

public ShapeSelectorView(Context context, AttributeSet attrs) {
super(context, attrs);
setupAttributes(attrs);
}

private void setupAttributes(AttributeSet attrs) {
// Obtain a typed array of attributes
TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ShapeSelectorView, 0, 0);
// Extract custom attributes into member variables
try {
shapeColor = a.getColor(R.styleable.ShapeSelectorView_shapeColor, Color.BLACK);
displayShapeName = a.getBoolean(R.styleable.ShapeSelectorView_displayShapeName, false);
} finally {
// TypedArray objects are shared and must be recycled.
a.recycle();
}
}
}


6绘制一个形状

public class ShapeSelectorView extends View {
// ...
private int shapeWidth = 100;
private int shapeHeight = 100;
private int textXOffset = 0;
private int textYOffset = 30;
private Paint paintShape;

// ...
public ShapeSelectorView(Context context, AttributeSet attrs) {
super(context, attrs);
setupAttributes(attrs);
setupPaint();
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, shapeWidth, shapeHeight, paintShape);
if (displayShapeName) {
canvas.drawText("Square", shapeWidth + textXOffset, shapeHeight + textXOffset, paintShape);
}
}

private void setupPaint() {
paintShape = new Paint();
paintShape.setStyle(Style.FILL);
paintShape.setColor(shapeColor);
paintShape.setTextSize(30);
}
}


整体的代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tvPrompt"
>
<com.codepath.shapeselector.ShapeSelectorView

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/shapeSelector"
app:shapeColor="#7f0000"
app:displayShapeName="true"/>
</RelativeLayout>


public class ShapeSelectorView extends View {
private int shapeColor;
private boolean displayShapeName;
private int shapeWidth = 100;
private int shapeHeight = 100;
private int textXOffset = 0;
private int textYOffset = 30;
private Paint paintShape;

public boolean isDisplayShapeName() {
return displayShapeName;
}
//状态改变时需要重新绘制
public void setDisplayShapeName(boolean displayShapeName) {
this.displayShapeName = displayShapeName;
invalidate();//状态改变时,使view无效?
requestLayout();//当布局改变时调用
}

public int getShapeColor() {
return shapeColor;
}

public void setShapeColor(int shapeColor) {
this.shapeColor = shapeColor;
invalidate();
requestLayout();
}
//创建一个构造方法,包含父构造方法和属性集合和笔画的设置

public ShapeSelectorView(Context context, AttributeSet attrs) {
super(context, attrs);
//设置属性
setupAttributes(attrs);
setupPaint();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//参数分别为左上右下
canvas.drawRect(0, 0, shapeWidth, shapeHeight, paintShape);
if (displayShapeName) {
canvas.drawText("Square", textXOffset, shapeHeight + textYOffset, paintShape);
}
}
private void setupPaint() {
paintShape = new Paint();
paintShape.setStyle(Paint.Style.FILL);
paintShape.setColor(shapeColor);
paintShape.setTextSize(30);
}
//参数为一些属性的集合,可以与xml文件连接起来的属性
private void setupAttributes(AttributeSet attrs) {
//获得属性类型数组

TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ShapeSelectorView, 0, 0);
try {
//提取属性作为成员变量
shapeColor = a.getColor(R.styleable.ShapeSelectorView_shapeColor, Color.BLACK);
displayShapeName = a.getBoolean(R.styleable.ShapeSelectorView_displayShapeName, false);
} finally {
a.recycle();//回收typedArray供以后使用
}
}

}


计算大小

//测量view和它的内容来确定高度和宽度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//给名称定义额外的间距
int textPadding = 10;
int contentWidth = shapeWidth;
//根据测量的最小值和测量规格确定宽度:内容宽度 + 左边距+右边距
int minw = contentWidth + getPaddingLeft() + getPaddingRight();
int w = resolveSizeAndState(minw, widthMeasureSpec, 0);
//让view的获得足够的高度
int minh = shapeHeight + getPaddingTop() + getPaddingBottom();
if (displayShapeName) {
//如果展示名称的话要加上文字高度
minh += textYOffset + textPadding;
}
int h = resolveSizeAndState(minh,  heightMeasureSpec, 0);
//调用此方法确定测量的宽度和高度
//可以使用getMeasuredWidth和getMeasuredHeight获得值
setMeasuredDimension(w, h);
}


onMeasure
方法决定了基于内容的视图的高度和宽度,要记住计算包括了view的内间距和内容的大小,而且这个方法必须带哦用
setMeasuredDimension
MeasureSpec
包含了父布局对子布局的限制,
resolveSizeAndState
通过两方面进行比较返回一个恰当的值。

切换视图

我们想要每次按钮被点击的时候就会使图形被改变,因此需要
onTouchEvent
方法处理点击事件,每次点击都会改变下标

public class ShapeSelectorView extends View {
// ...
private String[] shapeValues = { "square", "circle", "triangle" };
private int currentShapeIndex = 0;

// Change the currentShapeIndex whenever the shape is clicked
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
currentShapeIndex =  (++currentShapeIndex) % shapeValues.length;
postInvalidate();
return true;
}
return result;
}
}


下面我们重写
onDraw
方法:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String shapeSelected = shapeValues[currentShapeIndex];
if (shapeSelected.equals("square")) {
canvas.drawRect(0, 0, shapeWidth, shapeHeight, paintShape);
textXOffset = 0;
} else if (shapeSelected.equals("circle")) {
canvas.drawCircle(shapeWidth / 2, shapeHeight / 2, shapeWidth / 2, paintShape);
textXOffset = 12;
} else if (shapeSelected.equals("triangle")) {
canvas.drawPath(getTrianglePath(), paintShape);
textXOffset = 0;
}
if (displayShapeName) {
canvas.drawText(shapeSelected, 0 + textXOffset, shapeHeight + textYOffset, paintShape);
}
}

protected Path getTrianglePath() {
Point p1 = new Point(0, shapeHeight), p2 = null, p3 = null;
p2 = new Point(p1.x + shapeWidth, p1.y);
p3 = new Point(p1.x + (shapeWidth / 2), p1.y - shapeHeight);//原点为屏幕左上角
Path path = new Path();
path.moveTo(p1.x, p1.y);
path.lineTo(p2.x, p2.y);
path.lineTo(p3.x, p3.y);
return path;
}

// ...
}


然后就是对于在主视图中加入按钮点击后,弹出Toast,

保存view相应状态

@Override
protected Parcelable onSaveInstanceState() {
//创建Bundle对象
Bundle bundle = new Bundle();
//存储基本view的状态
bundle.putParcelable("instanceState", super.onSaveInstanceState());
//存储自定义view的状态
bundle.putInt("currentShapeIndex", this.currentShapeIndex);
//如果有的话还应该存储其他的状态
return bundle;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
//检测我们是否保存了状态
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
this.currentShapeIndex = bundle.getInt("currentShapeIndex");
state = bundle.getParcelable("instanceState");
}
super.onRestoreInstanceState(state);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android