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

android自定义view--色彩选择器

2016-05-26 19:26 453 查看
先上图,大概就是这样的一个效果:



可以选用progressbar做,在这里我直接画了两个圆角矩形+圆

下面贴代码:

public class ColorPickerView extends View {

private static final int[] COLORS = new int[]{0xFFFF0000, 0xFFFF00FF,
0xFF0000FF, 0xFF00FFFF, 0xFF00FF00, 0xFFFFFF00, 0xFFFF0000};

private int radius;
private int fingerflag;
private int rectBeginx;
private int rectEndx;
private int firstFingerx;
private int firstRectBeginy;
private int firstRectEndy;
private int secondFingerx;
private int secondRectBeginy;
private int secondRectEndy;
private int resultColor;
private int rectHeight;
private int rectCorner;
private int firstRectCentre;
private int secondRectCentre;
private int rectLength;
private int rectDistance;
private int rectSetLength;

private int lastfirstposition;
private int lastsecondposition;
private Paint rectPaint;
private Paint circlePaint;
private Paint blankPaint;
private Paint mRectBitmapPaint;
private RectF firstRect;
private RectF secondRect;
private Shader shader;
private Bitmap mRectBitmap;

private OnColorChangedListener onColorChangedListener;
private Context context;
private int windowheight;
private int windowwidth;
private float[] mHSV = new float[3];
private boolean canMove;

public ColorPickerView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPickerView);
rectHeight = (int) a.getDimension(R.styleable.ColorPickerView_rectHeight, 8);
radius = (int) a.getDimension(R.styleable.ColorPickerView_radius, 24);
ifClickChange = a.getBoolean(R.styleable.ColorPickerView_ifClickChange, false);
rectDistance= (int) a.getDimension(R.styleable.ColorPickerView_rectDistance,3*radius);
rectSetLength= (int) a.getDimension(R.styleable.ColorPickerView_rectLength,0);
a.recycle();
}

public void setColor(int color) {
resultColor = color;
}

public void setBaseColor(int color) {
setColor(calculateHSVColor(secondFingerx));
Color.colorToHSV(color, mHSV);
}

private void calculateRectBitmap() {
for (int x = 0; x < mRectBitmap.getWidth(); x++) {
for (int y = 0; y < mRectBitmap.getHeight(); y++) {
float[] tmpHSV = new float[3];
tmpHSV[0] = mHSV[0];
tmpHSV[1] = 1;
tmpHSV[2] = (float) x / mRectBitmap.getWidth();
mRectBitmap.setPixel(x, y, Color.HSVToColor(tmpHSV));
}
}
}

private int calculateHSVColor(float x) {
float[] hsv = new float[3];
hsv[0] = mHSV[0];
hsv[1] = 1;
hsv[2] = (x - rectBeginx) / rectLength;
return Color.HSVToColor(hsv);
}

private int calculateColor(float x) {
int unitlength = rectLength / (COLORS.length - 1);
int fingerlength = (int) (x - rectBeginx);
int unit = fingerlength / unitlength;
float percent = (float) fingerlength / unitlength - unit;

int c0;
int c1;

if (unit == 6) {
return COLORS[COLORS.length - 1];
} else {
c0 = COLORS[unit];
c1 = COLORS[unit + 1];
}

int a = ave(Color.alpha(c0), Color.alpha(c1), percent);
int r = ave(Color.red(c0), Color.red(c1), percent);
int g = ave(Color.green(c0), Color.green(c1), percent);
int b = ave(Color.blue(c0), Color.blue(c1), percent);

return Color.argb(a, r, g, b);
}

private int ave(int s, int d, float p) {
return s + Math.round(p * (d - s));
}

public void setOnColorChangedListener(OnColorChangedListener listener) {
this.onColorChangedListener = listener;
}

public void init() {

WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowheight = wm.getDefaultDisplay().getHeight();
windowwidth = wm.getDefaultDisplay().getWidth();

rectCorner = rectHeight;
rectLength = rectEndx - rectBeginx;
firstRectEndy = firstRectBeginy + rectHeight;
firstFingerx = rectBeginx;
firstRectCentre = (firstRectBeginy + firstRectEndy) / 2;
secondRectEndy = secondRectBeginy + rectHeight;
secondFingerx = rectEndx;
secondRectCentre = (secondRectBeginy + secondRectEndy) / 2;

if (lastfirstposition!=0){
firstFingerx=lastfirstposition;
}
if (lastsecondposition!=0){
secondFingerx=lastsecondposition;
}

rectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint = new Paint();
circlePaint.setColor(Color.WHITE);
blankPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
blankPaint.setStyle(Paint.Style.STROKE);
blankPaint.setColor(Color.GRAY);
mRectBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

firstRect = new RectF(rectBeginx, firstRectBeginy, rectEndx, firstRectEndy);
shader = new LinearGradient(rectBeginx, 0, rectEndx, 0, COLORS, null, Shader.TileMode.MIRROR);

secondRect = new RectF(rectBeginx, secondRectBeginy, rectEndx, secondRectEndy);
mRectBitmap = Bitmap.createBitmap(100, 1, Bitmap.Config.RGB_565);

resultColor = calculateColor(firstFingerx);
Color.colorToHSV(resultColor, mHSV);

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

calculateRectBitmap();

rectPaint.setShader(shader);
canvas.drawRoundRect(firstRect, rectCorner, rectCorner, rectPaint);
canvas.drawCircle(firstFingerx, firstRectCentre, radius, circlePaint);
canvas.drawCircle(firstFingerx, firstRectCentre, radius, blankPaint);

int sc = canvas.saveLayer(0, 0, windowwidth, windowheight, null, Canvas.ALL_SAVE_FLAG);
mRectBitmapPaint.reset();
canvas.drawRoundRect(secondRect, rectCorner, rectCorner, mRectBitmapPaint);
mRectBitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mRectBitmap, null, secondRect, mRectBitmapPaint);
canvas.restoreToCount(sc);

canvas.drawCircle(secondFingerx, secondRectCentre, radius, circlePaint);
canvas.drawCircle(secondFingerx, secondRectCentre, radius, blankPaint);
}

@Override
public boolean onTouchEvent(MotionEvent event) {

float x = event.getX();
float y = event.getY();

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (x > firstFingerx - 2 * radius && x < firstFingerx + 2 * radius && y < firstRectCentre + radius) {
fingerflag = 1;
canMove = true;
}
if (x > secondFingerx - 2 * radius && x < secondFingerx + 2 * radius && y > secondRectCentre - radius) {
fingerflag = 2;
canMove = true;
}

case MotionEvent.ACTION_MOVE:
if (canMove){
if (fingerflag == 1) {
if (x > rectBeginx && x < rectEndx) {
firstFingerx = (int) x;
} else if (x < rectBeginx) {
firstFingerx = rectBeginx;
} else if (x > rectEndx) {
firstFingerx = rectEndx;
}
setBaseColor(calculateColor(firstFingerx));
if (onColorChangedListener != null) {
onColorChangedListener.onColorChanged(resultColor);
}
invalidate();
break;
} else if (fingerflag == 2) {
if (x >= rectBeginx && x <= rectEndx) {
secondFingerx = (int) x;
} else if (x < rectBeginx) {
secondFingerx = rectBeginx;
} else if (x > rectEndx) {
secondFingerx = rectEndx;
}
setColor(calculateHSVColor(secondFingerx));
if (onColorChangedListener != null) {
onColorChangedListener.onColorChanged(resultColor);
}
invalidate();
break;
}
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
canMove = false;
break;
}
return true;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(574, widthSize);
} else {
width = widthSize;
}
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(188, heightSize);
} else {
height = heightSize;
}
rectBeginx = getPaddingLeft();
if (rectSetLength != 0) {
rectEndx = rectBeginx + rectSetLength;
} else {
rectEndx = widthSize - getPaddingRight();
}

firstRectBeginy = (heightSize - 5 * radius) / 2 + radius - rectHeight / 2 + getPaddingTop();
secondRectBeginy = firstRectBeginy + rectDistance - getPaddingBottom();

setMeasuredDimension(width, height);
init();
}

public void setPosition(int firstFingerx, int secondFingerx) {
if (firstFingerx != 0 && secondFingerx != 0) {
this.lastfirstposition = firstFingerx;
this.lastsecondposition = secondFingerx;
}
invalidate();
}

public int[] getPosition() {
return new int[]{firstFingerx, secondFingerx};
}

public interface OnColorChangedListener {
void onColorChanged(int color);
}

}


一块一块说明一下:

首先,构造函数:

public ColorPickerView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPickerView);
rectHeight = (int) a.getDimension(R.styleable.ColorPickerView_rectHeight, 8);
radius = (int) a.getDimension(R.styleable.ColorPickerView_radius, 24);

rectDistance= (int) a.getDimension(R.styleable.ColorPickerView_rectDistance,3*radius);
rectSetLength= (int) a.getDimension(R.styleable.ColorPickerView_rectLength,0);
a.recycle();
}


里面使用了一些自定义属性,自定义属性是在res/values/attrs.xml里面注册的:

<declare-styleable name="ColorPickerView">
<attr name="rectHeight" format="dimension"/>
<attr name="rectLength" format="dimension"/>
<attr name="radius" format="dimension"/>
<attr name="rectDistance" format="dimension"/>
</declare-styleable>


然后在布局文件里给其赋值即可,注意使用时要声明包,比如声明了
xmlns:app="http://schemas.android.com/apk/res-auto"


,那么用时只需

app:ArrowDirection="left"

app:ArrowSize="16"
即可

最后,用完TypedArray别忘了回收(recycle)

其次,onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(574, widthSize);
} else {
width = widthSize;
}
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(188, heightSize);
} else {
height = heightSize;
}
rectBeginx = getPaddingLeft();
if (rectSetLength != 0) {
rectEndx = rectBeginx + rectSetLength;
} else {
rectEndx = widthSize - getPaddingRight();
}

firstRectBeginy = (heightSize - 5 * radius) / 2 + radius - rectHeight / 2 + getPaddingTop();
secondRectBeginy = firstRectBeginy + rectDistance - getPaddingBottom();

setMeasuredDimension(width, height);
init();
}


onMeasure方法即测量控件的大小,系统测量完布局中的大小后会以widthMeasureSpec和heightMeasureSpec作为参数传递进来,其是一个32位的int值,其中最高2位表示mode,低30位表示size。而mode又分为3类:

UNSPECIFIED(未指定),父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小,我们平时一般不用;

EXACTLY(完全),父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小,当我们设置match_parent时就是这个mode;

AT_MOST(至多),子元素至多达到指定大小的值,我们设置wrap_content或者具体的值时就是这个mode。

根据得到的值,我们就可以设置这个控件的位置了,这里我设置这个控件为竖直居中,水平依赖于布局中设置的padding值。

measure完后有init()方法,初始化一些参数的值是在这个方法里进行的。其中重要的是色彩块的初始化,我们将其和ondraw一起说明:

public void init() {

WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowheight = wm.getDefaultDisplay().getHeight();
windowwidth = wm.getDefaultDisplay().getWidth();

rectCorner = rectHeight;
rectLength = rectEndx - rectBeginx;
firstRectEndy = firstRectBeginy + rectHeight;
firstFingerx = rectBeginx;
firstRectCentre = (firstRectBeginy + firstRectEndy) / 2;
secondRectEndy = secondRectBeginy + rectHeight;
secondFingerx = rectEndx;
secondRectCentre = (secondRectBeginy + secondRectEndy) / 2;

if (lastfirstposition!=0){
firstFingerx=lastfirstposition;
}
if (lastsecondposition!=0){
secondFingerx=lastsecondposition;
}

rectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint = new Paint();
circlePaint.setColor(Color.WHITE);
blankPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
blankPaint.setStyle(Paint.Style.STROKE);
blankPaint.setColor(Color.GRAY);
mRectBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

firstRect = new RectF(rectBeginx, firstRectBeginy, rectEndx, firstRectEndy);
shader = new LinearGradient(rectBeginx, 0, rectEndx, 0, COLORS, null, Shader.TileMode.MIRROR);

secondRect = new RectF(rectBeginx, secondRectBeginy, rectEndx, secondRectEndy);
mRectBitmap = Bitmap.createBitmap(100, 1, Bitmap.Config.RGB_565);

resultColor = calculateColor(firstFingerx);
Color.colorToHSV(resultColor, mHSV);
}


protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

calculateRectBitmap();

rectPaint.setShader(shader);
canvas.drawRoundRect(firstRect, rectCorner, rectCorner, rectPaint);
canvas.drawCircle(firstFingerx, firstRectCentre, radius, circlePaint);
canvas.drawCircle(firstFingerx, firstRectCentre, radius, blankPaint);

int sc = canvas.saveLayer(0, 0, windowwidth, windowheight, null, Canvas.ALL_SAVE_FLAG);
mRectBitmapPaint.reset();
canvas.drawRoundRect(secondRect, rectCorner, rectCorner, mRectBitmapPaint);
mRectBitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mRectBitmap, null, secondRect, mRectBitmapPaint);
canvas.restoreToCount(sc);

canvas.drawCircle(secondFingerx, secondRectCentre, radius, circlePaint);
canvas.drawCircle(secondFingerx, secondRectCentre, radius, blankPaint);
}


两个圆角矩形是用不同的方法绘制的,

第一个是用颜色渲染做的:

在init中用
shader = new LinearGradient(rectBeginx, 0, rectEndx, 0, COLORS, null, Shader.TileMode.MIRROR);


设置矩形渲染,在ondraw中用
rectPaint.setShader(shader);

canvas.drawRoundRect(firstRect, rectCorner, rectCorner, rectPaint);
将这个圆角矩形画出来

第二个是用画bitmap染像素点做的:

在init中用
mRectBitmap = Bitmap.createBitmap(100, 1, Bitmap.Config.RGB_565);


创建这个bitmap块,在ondraw中首先要填充这个bitmap块的像素点,写了一个calculateRectBitmap

private void calculateRectBitmap() {
for (int x = 0; x < mRectBitmap.getWidth(); x++) {
for (int y = 0; y < mRectBitmap.getHeight(); y++) {
float[] tmpHSV = new float[3];
tmpHSV[0] = mHSV[0];
tmpHSV[1] = 1;
tmpHSV[2] = (float) x / mRectBitmap.getWidth();
mRectBitmap.setPixel(x, y, Color.HSVToColor(tmpHSV));
}
}
}


HSV 分别为色相,饱和度,明度,色相H就是我们第一个圆角矩形中选中的颜色,它的初始化是在init里进行的
Color.colorToHSV(resultColor, mHSV);
,饱和度S即表示色彩的深浅,它的范围是0-1,0为白色,明度V表示色彩的明暗,它的范围是0-1,0为黑色。在这里我们只改变明度。根据坐标的比例来填充像素点。

接下来在ondraw里画这个圆角矩形:

int sc = canvas.saveLayer(0, 0, windowwidth, windowheight, null, Canvas.ALL_SAVE_FLAG);
mRectBitmapPaint.reset();
canvas.drawRoundRect(secondRect, rectCorner, rectCorner, mRectBitmapPaint);
mRectBitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mRectBitmap, null, secondRect, mRectBitmapPaint);
canvas.restoreToCount(sc);


因为是圆角,所以我们使用了图形遮盖的方法来实现圆角bitmap,其要点就是设置画笔为PorterDuff.Mode.SRC_IN,别忘了保存canvas的状态。

接下来就是滑动选颜色了,在ontouchevent里面写:

@Override
public boolean onTouchEvent(MotionEvent event) {

float x = event.getX();
float y = event.getY();

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (x > firstFingerx - 2 * radius && x < firstFingerx + 2 * radius && y < firstRectCentre + radius) {
fingerflag = 1;
canMove = true;
}
if (x > secondFingerx - 2 * radius && x < secondFingerx + 2 * radius && y > secondRectCentre - radius) {
fingerflag = 2;
canMove = true;
}

case MotionEvent.ACTION_MOVE:
if (canMove){
if (fingerflag == 1) {
if (x > rectBeginx && x < rectEndx) {
firstFingerx = (int) x;
} else if (x < rectBeginx) {
firstFingerx = rectBeginx;
} else if (x > rectEndx) {
firstFingerx = rectEndx;
}
setBaseColor(calculateColor(firstFingerx));
if (onColorChangedListener != null) {
onColorChangedListener.onColorChanged(resultColor);
}
invalidate();
break;
} else if (fingerflag == 2) {
if (x >= rectBeginx && x <= rectEndx) {
secondFingerx = (int) x;
} else if (x < rectBeginx) {
secondFingerx = rectBeginx;
} else if (x > rectEndx) {
secondFingerx = rectEndx;
}
setColor(calculateHSVColor(secondFingerx));
if (onColorChangedListener != null) {
onColorChangedListener.onColorChanged(resultColor);
}
invalidate();
break;
}
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
canMove = false;
break;
}
return true;
}


当按下时,判断是否按在了圆形滑块处,如果是就可以拖动,如果不是就不能,滑动时,可以变颜色。这里我们设置了监听事件,可以在activity里调用,比如说我们在activity里有一个view叫colorchangeview,然后我们使用了这个colorpickerview,就可以这么写:

colorpickerView.setOnColorChangedListener(new ColorPickerView.OnColorChangedListener() {
@Override
public void onColorChanged(int color) {
colorchangeView.setBackgroundColor(color);
}
});


这样我们拖动的同时,colorchangeview就可以变颜色了。

最后,说明一下变颜色的设置:

主要有这么几个方法:

private int calculateHSVColor(float x) {
float[] hsv = new float[3];
hsv[0] = mHSV[0];
hsv[1] = 1;
hsv[2] = (x - rectBeginx) / rectLength;
return Color.HSVToColor(hsv);
}

private int calculateColor(float x) {
int unitlength = rectLength / (COLORS.length - 1);
int fingerlength = (int) (x - rectBeginx);
int unit = fingerlength / unitlength;
float percent = (float) fingerlength / unitlength - unit;

int c0;
int c1;

if (unit == 6) {
return COLORS[COLORS.length - 1];
} else {
c0 = COLORS[unit];
c1 = COLORS[unit + 1];
}

int a = ave(Color.alpha(c0), Color.alpha(c1), percent);
int r = ave(Color.red(c0), Color.red(c1), percent);
int g = ave(Color.green(c0), Color.green(c1), percent);
int b = ave(Color.blue(c0), Color.blue(c1), percent);

return Color.argb(a, r, g, b);
}

private int ave(int s, int d, float p) {
return s + Math.round(p * (d - s));
}


滑动第一个圆角矩形时,使用calculateColor()方法由于其是线性渲染出来的,所以可以根据其坐标来计算颜色的argb值,argb即透明度,红色,绿色,蓝色,对应2位16进制数,比如说0xFFFF0000,其a为ff,r为ff,g为00,b为00,我们看到的就是红色。color和其都有对应的方法,见上面,比如Color.green()就是获取color的green值,计算各颜色的比重后再用Color.argb(a, r, g, b)把数字转为颜色。

滑动第二个圆角矩形时,使用calculateHSVColor()方法,就是直接使用hsv的转换,上文已经说过,我们只计算其v即明度的值,根据坐标计算再转换即可,使用,Color.HSVToColor(hsv)就是把一个hsv数组转化为颜色。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: