Android之自定义控件
创建一个全新的视图将赋予你从根本上决定应用程序的样式以及观感能力,通过创建自己的控件,可以创建出满足你的需求的独特的UI。这一点也是android平台最伟大的地方之一了。
1、要在一个空的画布上创建新的控件,就需要对View类或者SurfaceView类进行扩展,View类提供了一个Canvas对象,和一系列绘制方法及Paint类,因此,使用它们可以运用位图和光栅图像创建出一个可视化的界面,之后可以通过重写屏幕触摸或者按键按下这样的用户事件以提供交互。View类对于一般的应用程序界面就足够了,SurfaceView一般是为游戏画面提供支持的。
2、今天我做的是一个颜色选择的控件,形状嘛和seekBar差不多,如图
因为View类呈现出的是一个100X100像素的空白正方形,要改变控件的大小并呈现出一个更吸引人的可视化界面,就必须对onMeasure和onDraw方法进行重写。
在onMeasure方法中,新的视图将会计算出它在一系列给定的边界条件下占据的高度和宽度。然后你就可以再onDraw方法中绘制了。
3、自定义控件首先就需要继承View类,我的颜色选择控件类如下。
public class ColorPicker extends
/**
* 构造函数,在代码中进行创建时必须要有的构造函数
*
* @param context
*/
public
super(context);
init(null, 0);
}
/**
* 构造函数,在布局文件中引用时必须要有的构造函数
*
* @param context
*/
public
super(context, attrs);
init(attrs, 0);
}
/**
* 构造函数,在布局文件中引用时必须要有的构造函数
*
* @param context
*/
public ColorPicker(Context context, AttributeSet attrs, int
super(context, attrs, defStyle);
init(attrs, defStyle);
}
}
这个地方需要特别注意一下,构造函数还是非常重要的。
接下来就是根据需要重写onMeasure和onDraw方法了。这两个方法是必须的,至于怎么用,那就要更具自己的实际情况来看了。
4,接下来就是处理用户交互事件了。Android提供了多个虚拟实际处理程序,可以对用户的输入作出反应。如下:
onKeyDown:当任何设备的按键被按下时,就会调用它。包括键盘,挂断,通话,返回和摄像头按键
onKeyUp:当用户释放一个按键时调用
onTrackballEvent:当设备的轨迹球(就相当于功能机上下左右中键)移动时调用
onTouchEvent:当触摸屏被按下或释放时调用,或者当检测到运动是调用,自定义控件的事件处理程序中,这个方法也是用的最多的。非常重要。
下面是我的这个控件的完整代码。
package com.rdtd.ui;
import android.R.color;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.rdtd.crm.R;
/**
* @author jeck
*
*/
public class ColorPicker extends View {
颜色条上的颜色 */
private static final int[] COLORS = new int[] { 0xFFFFFFFF, 0xFFC2FD2F,
0xFF7DE014, 0xFF00B500, 0xFF04D778, 0xFF01F6D5, 0xFF00B7ED,
0xFF0076F1, 0xFF0235C4, 0xFF002089, 0xFF514CFF, 0xFF9659FE,
0xFFD282FF, 0xFFFF9DF8, 0xFFFF6C96, 0xFFEC3C51, 0xFFBB0200,
0xFFEA442E, 0xFFFF6D3F, 0xFFFCBC38, 0xFFFFFF20, 0xFFFFFF9E,
0xFF5C608D, 0xFF9A99A1, 0xFF3C3C3C, 0xFF000000 };
颜色条上的颜色 */
private static final int[] COLORS_ARRAY = new int[] { 0xFFFFFFFF,
0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFFFFF00, 0xFFFF00FF,
0xFF00FFFF, 0xFF000000 };
颜色条上的颜色的个数 */
private static final int COLORS_ARRAY_SIZE = COLORS.length;
用来定义水平方向 */
private static final boolean ORIENTATION_HORIZONTAL = true;
用来定义垂直方向 */
private static final boolean ORIENTATION_VERTICAL = false;
默认水平方向 */
private static final boolean ORIENTATION_DEFAULT = ORIENTATION_HORIZONTAL;
颜色条的高度 */
private int mBarThickness;
颜色条的长度 */
private int mBarLength;
颜色条的推荐(默认)长度 */
private int mPreferredBarLength;
颜色条上那个可以拖到的小圆圈的半径 */
private int mBarPointerRadius;
小圆圈上光环的半径 */
private int mBarPointerHaloRadius;
小圆圈的位置 */
private int mBarPointerPosition;
颜色条的画笔 */
private Paint mBarPaint;
小圆圈的画笔 */
private Paint mBarPointerPaint;
小圆圈光晕的画笔 */
private Paint mBarPointHaloPaint;
颜色条的矩形区域 */
private RectF mBarRectF = new RectF();
用于填充画笔的阴影 */
private Shader shader;
/**
如果用户点击小圆圈并开始移动 <br>
如果用于停止点击屏幕
*
* @see #onTouchEvent(android.view.MotionEvent)
*/
private boolean mIsMovingPointer;
/**
当前所选颜色的ARGB值
*/
private int mColor;
/**
* Factor used to calculate the Opacity to the postion on the bar.
*/
private float mOpacityToPositionFactor;
/**
内部接口
*/
private OnColorChangedListener onColorChangedListener;
提供一个接口,供选择颜色使用 */
public interface OnColorChangedListener {
public void onOpacityChanged();
}
/**
用于在水平和垂直方向进行切换
*/
private boolean mOrientation;
public OnColorChangedListener getOnOpacityChangedListener() {
return onColorChangedListener;
}
public void setOnColorChangedListener(
OnColorChangedListener onColorChangedListener) {
this.onColorChangedListener = onColorChangedListener;
}
/**
构造函数
*
* @param context
*/
public ColorPicker(Context context) {
super(context);
init(null, 0);
}
public ColorPicker(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public ColorPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
/**
初始化控件
*
* @param attr
* @param defStyle
*/
private void init(AttributeSet set, int defStyle) {
// 加载定义好的属性,设置自定义控件的默认属性值
final TypedArray typedArray = getContext().obtainStyledAttributes(set,
R.styleable.ColorBars, defStyle, 0);
// 取得资源
final Resources resources = getContext().getResources();
// 设置颜色条的高度属性
mBarThickness = typedArray.getDimensionPixelSize(
R.styleable.ColorBars_bar_thickness,
resources.getDimensionPixelSize(R.dimen.bar_thickness));
// 设置颜色条的长度属性
mBarLength = typedArray.getDimensionPixelSize(
R.styleable.ColorBars_bar_length,
resources.getDimensionPixelSize(R.dimen.bar_length));
// 颜色条的默认长度
mPreferredBarLength = mBarLength;
// 小圆圈的半径
mBarPointerRadius = typedArray.getDimensionPixelSize(
R.styleable.ColorBars_bar_pointer_radius,
resources.getDimensionPixelSize(R.dimen.bar_pointer_radius));
// 小圆圈光环的半径
mBarPointerHaloRadius = typedArray
.getDimensionPixelSize(
R.styleable.ColorBars_bar_pointer_halo_radius,
resources
.getDimensionPixelSize(R.dimen.bar_pointer_halo_radius));
// 设置默认方向为水平方向
mOrientation = typedArray.getBoolean(
R.styleable.ColorBars_bar_orientation_horizontal,
ORIENTATION_HORIZONTAL);
// 这句是什么意思
typedArray.recycle();
// 初始化颜色条画笔, 要抗锯齿的
mBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//
mBarPaint.setShader(shader);
// 小圆圈的位置, 为什么是颜色条的长度+小圆圈上光环的半径
mBarPointerPosition = mBarLength + mBarPointerHaloRadius;
// 小圆圈光环画笔,要抗锯齿,黑色半透明
mBarPointHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBarPointHaloPaint.setColor(color.black);
mBarPointHaloPaint.setAlpha(0x50);
// 小圆圈的画笔,不知道是什么颜色
mBarPointerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBarPointerPaint.setColor(0xff81ff00);
// 因子
mOpacityToPositionFactor = ((float) mBarLength) / 0xff;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 固定的大小 = 颜色条长度+光环直径
final int intrinsicSize = mPreferredBarLength
+ (mBarPointerHaloRadius * 2);
// 方向变量
int measureSpec;
// 如果默认的是水平方向
if (mOrientation = ORIENTATION_HORIZONTAL) {
measureSpec = widthMeasureSpec;
} else {
measureSpec = heightMeasureSpec;
}
// 这是什么意思?
int lengthMode = MeasureSpec.getMode(measureSpec);
int lengthSize = MeasureSpec.getSize(measureSpec);
int length;
if (lengthMode == MeasureSpec.EXACTLY) {
length = lengthSize;
} else if (lengthMode == MeasureSpec.AT_MOST) {
length = Math.min(intrinsicSize, lengthSize);
} else {
length = intrinsicSize;
}
int mBarPointerHaloRadius_2 = mBarPointerHaloRadius * 2;
mBarLength = length - mBarPointerHaloRadius_2;
// 如果是水平方向的
if (mOrientation == ORIENTATION_HORIZONTAL) {
setMeasuredDimension(mBarLength + mBarPointerHaloRadius_2,
mBarPointerHaloRadius_2);
} else {
setMeasuredDimension(mBarPointerHaloRadius_2,
mBarPointerHaloRadius_2 + mBarLength);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int x1;
int y1;
// 基于水平方向开始填充颜色条
if (mOrientation = ORIENTATION_HORIZONTAL) {
水平长度
x1 = (mBarLength + mBarPointerHaloRadius);
垂直高度
y1 = mBarThickness;
颜色条的长度是整个控件的长度-两端的圆环半径
mBarLength = w - (mBarPointerHaloRadius * 2);
颜色条的区域 左=半径, 上=半径-高度的一半
mBarRectF.set(mBarPointerHaloRadius,
(mBarPointerHaloRadius - (mBarThickness / 2)),
(mBarPointerHaloRadius + mBarLength),
(mBarPointerHaloRadius + (mBarThickness / 2)));
} else {
基于垂直方向填充颜色条
x1 = mBarThickness;
y1 = (mBarLength + mBarPointerHaloRadius);
mBarLength = h - (mBarPointerHaloRadius * 2);
mBarRectF.set((mBarPointerHaloRadius - (mBarThickness / 2)),
mBarPointerHaloRadius,
(mBarPointerHaloRadius + (mBarThickness / 2)),
(mBarPointerHaloRadius + mBarLength));
}
// 基于颜色条的长度来更新颜色值
if (!isInEditMode()) {
shader = new LinearGradient(mBarPointerHaloRadius, 0, x1, y1,
COLORS, null, Shader.TileMode.CLAMP);
} else {
shader = new LinearGradient(mBarPointerHaloRadius, 0, x1, y1,
COLORS_ARRAY, null, Shader.TileMode.CLAMP);
}
mBarPaint.setShader(shader);
mOpacityToPositionFactor = ((float) mBarLength) / 0xFF;
if (!isInEditMode()) {
mBarPointerPosition = Math.round((mOpacityToPositionFactor * Color
.alpha(mColor)) + mBarPointerHaloRadius);
} else {
mBarPointerPosition = mBarLength + mBarPointerHaloRadius;
}
}
@Override
protected void onDraw(Canvas canvas) {
// 画颜色条
canvas.drawRect(mBarRectF, mBarPaint);
// 圆心的x坐标
int cX;
// 圆心的y坐标
int cY;
// 计算小圆圈的位置
if (mOrientation = ORIENTATION_HORIZONTAL) {
cX = mBarPointerPosition;
cY = mBarPointerHaloRadius;
} else {
cX = mBarPointerHaloRadius;
cY = mBarPointerPosition;
}
// 画小圆圈的外边缘(光环)
canvas.drawCircle(cX, cY, mBarPointerHaloRadius, mBarPointHaloPaint);
// 画小圆圈的内部
canvas.drawCircle(cX, cY, mBarPointerRadius, mBarPointerPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
getParent().requestDisallowInterceptTouchEvent(false);
// coordinates 坐标转换
float dimen;
if (mOrientation == ORIENTATION_HORIZONTAL) {
dimen = event.getX();
} else {
dimen = event.getY();
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
首先将mIsMovingPointer设置成true;
mIsMovingPointer = true;
检测是不是按在了小圆圈上
if ((dimen >= mBarPointerHaloRadius)
&& (dimen <= mBarPointerHaloRadius + mBarLength)) {
// 确定当前小圆圈的位置
mBarPointerPosition = Math.round(dimen);
// 计算当前的颜色值
calculateColor(Math.round(dimen));
// 设置小圆圈的画笔的颜色
mBarPointerPaint.setColor(mColor);
// 刷新界面
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsMovingPointer) {
// 在颜色条上移动小圆圈 检测是不是按在了小圆圈上
if ((dimen >= mBarPointerHaloRadius)
&& (dimen <= mBarPointerHaloRadius + mBarLength)) {
确定当前小圆圈的位置
mBarPointerPosition = Math.round(dimen);
计算当前的颜色值
calculateColor(Math.round(dimen));
设置小圆圈的画笔的颜色
mBarPointerPaint.setColor(mColor);
刷新界面
invalidate();
} else if (dimen < mBarPointerHaloRadius) {
先确定位置
mBarPointerPosition = mBarPointerHaloRadius;
在设置颜色,这是在最左边的时候的颜色
mColor = COLORS[0];
设置画笔颜色
mBarPointerPaint.setColor(mColor);
刷新界面
invalidate();
} else if (dimen > mBarPointerHaloRadius + mBarLength) {
先确定位置
mBarPointerPosition = mBarPointerHaloRadius + mBarLength;
在设置颜色,这是在最右边的时候的颜色
mColor = COLORS[COLORS_ARRAY_SIZE - 1];
设置画笔颜色
mBarPointerPaint.setColor(mColor);
刷新界面
invalidate();
}
}
调用接口,做你想做的事情
if (onColorChangedListener != null) {
onColorChangedListener.onOpacityChanged();
}
break;
case MotionEvent.ACTION_UP:
设置mIsMovingPointer为false
mIsMovingPointer = false;
break;
}
return true;
}
/**
计算颜色值,让小圆圈显示当前所选的颜色
*
* @param coord
*/
private void calculateColor(int coord) {
coord = coord - mBarPointerHaloRadius;
if (coord < 0) {
coord = 0;
mColor = COLORS[0];
} else if (coord > mBarLength) {
coord = mBarLength;
mColor = COLORS[COLORS_ARRAY_SIZE - 1];
} else {
通过比例换算成颜色数组范围之内的值
int pos = (int) (COLORS_ARRAY_SIZE * coord / mBarLength);
if (pos >= COLORS_ARRAY_SIZE) {
pos -= 1;
}
mColor = COLORS[pos];
}
}
/**
上面写了这么多,目的就是为了取这个颜色值
*
* @return
*/
public int getColor() {
if (mColor == 0) {
return COLORS[0];
} else {
return mColor;
}
}
}
接下来是布局文件中的代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.rdtd.ui.ColorPicker
android:id="@+id/cp_color_picker"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_test_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Android"
android:textSize="20sp" />
<ImageView
android:id="@+id/iv_test_image"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="20dp" />
</LinearLayout>
最后是Activity里面的代码
package com.rdtd.crm;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import com.rdtd.ui.ColorPicker;
import com.rdtd.ui.ColorPicker.OnColorChangedListener;
public class TestColorPickerActivity extends Activity {
private ColorPicker mColorPicker;
private TextView mTextView;
private ImageView mImageView;
private int mColorValue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_color_select_layout);
initLayout();
}
private void initLayout() {
// 颜色选择控件
mColorPicker = (ColorPicker) findViewById(R.id.cp_color_picker);
// imagview控件
mImageView = (ImageView) findViewById(R.id.iv_test_image);
// text控件
mTextView = (TextView) findViewById(R.id.tv_test_textview);
// 颜色选择接口
mColorPicker.setOnColorChangedListener(new OnColorChangedListener() {
@Override
public void onOpacityChanged() {
mColorValue = mColorPicker.getColor();
mTextView.setTextColor(mColorValue);
mImageView.setBackgroundColor(mColorValue);
}
});
}
}
在使用的时候,一定要注意调用
// 颜色选择接口
mColorPicker.setOnColorChangedListener这个函数。
最后是效果图