android自定义View实现圆环颜色选择器
作者:zxc123e 发布时间:2023-11-07 19:16:02
标签:android,颜色选择器
最近工作需要,自定了一个颜色选择器,效果图如下:
颜色种类是固定的,圆环上有个指示器,指示选中的颜色,这个定义起来应该是很简单了,直接上代码。
public class MyColorPicker extends View {
private int mThumbHeight;
private int mThumbWidth;
private String[] colors ;
private int sections;
//每个小块的度数
private int sectionAngle;
private Paint mPaint;
private int ringWidth;
private RectF mRectF;
private Drawable mThumbDrawable = null;
private float mThumbLeft;
private float mThumbTop;
private double mViewCenterX, mViewCenterY;
private double mViewRadisu;
//起始角度
private int mStartDegree = -90;
//当前view的尺寸
private int mViewSize;
private int textColor;
private String text="";
private Paint textPaint;
private Rect mBounds;
private float textSize;
private int colorType;
private int default_size = 100;
public MyColorPicker(Context context) {
this(context, null);
}
public MyColorPicker(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyColorPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray localTypedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleColorPicker);
mThumbDrawable = localTypedArray.getDrawable(R.styleable.CircleColorPicker_thumb);
ringWidth = (int) localTypedArray.getDimension(R.styleable.CircleColorPicker_ring_span, 30);
colorType = localTypedArray.getInt(R.styleable.CircleColorPicker_color_type, 0);
textColor = localTypedArray.getColor(R.styleable.CircleColorPicker_text_color, Color.BLACK);
text = localTypedArray.getString(R.styleable.CircleColorPicker_text);
textSize = localTypedArray.getDimension(R.styleable.CircleColorPicker_text_size, 20);
localTypedArray.recycle();
default_size = SystemUtils.dip2px(context, 260);
init();
}
private void init() {
colors = colorType == 1 ? ColorUtils.getMacaroon():ColorUtils.getAllColors();
sections = colors.length;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(ringWidth);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(textColor);
textPaint.setTextSize(textSize);
mThumbWidth = this.mThumbDrawable.getIntrinsicWidth();
mThumbHeight = this.mThumbDrawable.getIntrinsicHeight();
sectionAngle = 360/sections;
mBounds = new Rect();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredLength(widthMeasureSpec, true), getMeasuredLength(heightMeasureSpec, false));
int circleX = getMeasuredWidth();
int circleY = getMeasuredHeight();
if (circleY < circleX)
{
circleX = circleY;
}
mViewSize = circleX;
mViewCenterX = circleX/2;
mViewCenterY = circleY/2;
mViewRadisu = circleX/2 - mThumbWidth / 2;
setThumbPosition(Math.toRadians(mStartDegree));
}
private int getMeasuredLength(int length, boolean isWidth) {
int specMode = MeasureSpec.getMode(length);
int specSize = MeasureSpec.getSize(length);
int size;
int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
if (specMode == MeasureSpec.EXACTLY) {
size = specSize;
} else {
size = default_size + padding;
if (specMode == MeasureSpec.AT_MOST) {
size = Math.min(size, specSize);
}
}
return size;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mRectF = new RectF(0+mThumbWidth/2, 0+mThumbWidth/2, mViewSize-mThumbWidth/2, mViewSize-mThumbWidth/2);
for (int i = 0; i < colors.length; i++)
{
mPaint.setColor(Color.parseColor(colors[i]));
canvas.drawArc(mRectF, i*sectionAngle-90, sectionAngle+1,false, mPaint);
}
mThumbDrawable.setBounds((int) mThumbLeft, (int) mThumbTop,
(int) (mThumbLeft + mThumbWidth), (int) (mThumbTop + mThumbHeight));
mThumbDrawable.draw(canvas);
textPaint.getTextBounds(text, 0, text.length(), mBounds);
float textWidth = mBounds.width();
float textHeight = mBounds.height();
float textLeft = (float) (mViewCenterX - textWidth/2);
float textTop = (float)(mViewCenterY + textHeight/2);
canvas.drawText(text, 0, text.length(), textLeft, textTop, textPaint);
}
private void setThumbPosition(double radian) {
double x = mViewCenterX + mViewRadisu * Math.cos(radian);
double y = mViewCenterY + mViewRadisu * Math.sin(radian);
mThumbLeft = (float) (x - mThumbWidth / 2);
mThumbTop = (float) (y - mThumbHeight / 2);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
seekTo(eventX, eventY, false);
break ;
case MotionEvent.ACTION_MOVE:
seekTo(eventX, eventY, false);
break ;
case MotionEvent.ACTION_UP:
// seekTo(eventX, eventY, true);
float part = sectionAngle / 4.0f;
for (int i = 0; i < sections; i++) {
if ( mSweepDegree > (i-1)*sectionAngle+part*3 && mSweepDegree < i *sectionAngle + part)
{
if (mSweepDegree < i*sectionAngle)
{
setThumbPosition(Math.toRadians((i-1)*sectionAngle+part*2));
}else {
setThumbPosition(Math.toRadians(i*sectionAngle+part*2));
}
}
}
if (mSweepDegree > ((sections-1)*sectionAngle)+part*3)
{
setThumbPosition(Math.toRadians((sections-1)*sectionAngle+part*2));
}
invalidate();
break ;
}
return true;
}
private int preColor;
private float mSweepDegree;
private void seekTo(float eventX, float eventY, boolean isUp) {
if (true == isPointOnThumb(eventX, eventY) && false == isUp) {
// mThumbDrawable.setState(mThumbPressed);
double radian = Math.atan2(eventY - mViewCenterY, eventX - mViewCenterX);
/*
* 由于atan2返回的值为[-pi,pi]
* 因此需要将弧度值转换一下,使得区间为[0,2*pi]
*/
if (radian < 0){
radian = radian + 2*Math.PI;
}
setThumbPosition(radian);
mSweepDegree = (float) Math.round(Math.toDegrees(radian));
int currentColor = getColor(mSweepDegree);
if (currentColor != preColor)
{
preColor = currentColor;
if (onColorChangeListener != null)
{
onColorChangeListener.colorChange(preColor);
}
}
invalidate();
}else{
// mThumbDrawable.setState(mThumbNormal);
invalidate();
}
}
private int getColor(float mSweepDegree) {
int tempIndex = (int) (mSweepDegree/sectionAngle);
int num = 90 / sectionAngle;
if (tempIndex ==sections)
{
tempIndex = 0;
}
int index = tempIndex;
if (tempIndex >= 0) {
index = tempIndex+num;
}
if (tempIndex >= (sections-num))
{
index = tempIndex-(sections-num);
}
return Color.parseColor(colors[index]);
}
private boolean isPointOnThumb(float eventX, float eventY) {
boolean result = false;
double distance = Math.sqrt(Math.pow(eventX - mViewCenterX, 2)
+ Math.pow(eventY - mViewCenterY, 2));
if (distance < mViewSize && distance > (mViewSize / 2 - mThumbWidth)){
result = true;
}
return result;
}
public int getCurrentColor()
{
return preColor;
}
public void setStartColor(String color)
{
for (int i = 0; i < colors.length; i++)
{
if (colors[i].equals(color))
{
preColor = Color.parseColor(colors[i]);
int sweepAngle = (i- 90 /sectionAngle)*sectionAngle+sectionAngle/2;
// postDelayed(()->{
// setThumbPosition(Math.toRadians(sweepAngle));
// invalidate();
// },200);
mStartDegree = sweepAngle;
//最好加上
invalidate();
break;
}
}
}
public void setColor(String color) {
for (int i = 0; i < colors.length; i++)
{
if (colors[i].equals(color))
{
preColor = Color.parseColor(colors[i]);
int sweepAngle = (i- 90 /sectionAngle)*sectionAngle+sectionAngle/2;
setThumbPosition(Math.toRadians(sweepAngle));
invalidate();
break;
}
}
}
public interface OnColorChangeListener
{
void colorChange(int color);
}
public void setOnColorChangeListener(OnColorChangeListener onColorChangeListener) {
this.onColorChangeListener = onColorChangeListener;
}
private OnColorChangeListener onColorChangeListener;
}
注意的几个地方:
1. 可滑动位置的判断以及如何求滑动的角度,这里还去脑补了下atan2这个三角函数
2. 设置指示器的开始的位置,外部调用setStartColor()方法时,这个View可能还没真正完成绘制。如果没有完成绘制,第几行的invalidate()方法其实是没多大作用。
上面是选择单个颜色,下面来个加强版,选择的是颜色区间,先上效果图:
区间可以自己选择,并且可以反转(低指示器在高指示器顺时针方向或逆时针方向)。
下面是代码:
public class IntervalColorPicker extends View {
private int mThumbHeight;
private int mThumbWidth;
private int mThumbLowHeight, mThumbLowWidth;
private String[] colors = ColorUtils.getAllColors();
private int sections;
//每个小块的度数
private int sectionAngle;
private Paint mPaint;
private Paint arcPaint;
private int ringWidth;
private RectF mRectF;
private Drawable mThumbHighDrawable = null;
private Drawable mThumbLowDrawable;
private float mThumbLeft;
private float mThumbTop;
private float mThumbLowLeft, mThumbLowTop;
private double mViewCenterX, mViewCenterY;
private double mViewRadisu;
//起始角度
private float mStartDegree = 270;
//当前view的尺寸
private int mViewSize;
//区间
private int interval = 7;
private boolean reverse;
private float tempStartAngle = mStartDegree;
public IntervalColorPicker(Context context) {
this(context, null);
}
public IntervalColorPicker(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IntervalColorPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray localTypedArray = context.obtainStyledAttributes(attrs, R.styleable.IntervalColorPicker);
mThumbHighDrawable = localTypedArray.getDrawable(R.styleable.IntervalColorPicker_thumbHigh);
mThumbLowDrawable = localTypedArray.getDrawable(R.styleable.IntervalColorPicker_thumbLow);
ringWidth = (int) localTypedArray.getDimension(R.styleable.IntervalColorPicker_ring_breadth, 30);
localTypedArray.recycle();
init();
}
private void init() {
sections = colors.length;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(ringWidth);
arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
arcPaint.setStyle(Paint.Style.STROKE);
arcPaint.setStrokeWidth(ringWidth + 1);
arcPaint.setColor(Color.GRAY);
mThumbWidth = this.mThumbHighDrawable.getIntrinsicWidth();
mThumbHeight = this.mThumbHighDrawable.getIntrinsicHeight();
mThumbLowHeight = mThumbLowDrawable.getIntrinsicHeight();
mThumbLowWidth = mThumbHighDrawable.getIntrinsicWidth();
sectionAngle = 360 / sections;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int circleX = getMeasuredWidth();
int circleY = getMeasuredHeight();
if (circleY < circleX) {
circleX = circleY;
}
mViewSize = circleX;
mViewCenterX = circleX / 2;
mViewCenterY = circleY / 2;
mViewRadisu = circleX / 2 - mThumbWidth / 2;
}
private float sweepAngle;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mRectF = new RectF(0 + mThumbWidth / 2, 0 + mThumbWidth / 2, mViewSize - mThumbWidth / 2, mViewSize - mThumbWidth / 2);
for (int i = 0; i < colors.length; i++) {
mPaint.setColor(Color.parseColor(colors[i]));
canvas.drawArc(mRectF, i * sectionAngle - 90, sectionAngle + 1, false, mPaint);
}
int tempAng = (int) (tempStartAngle + sweepAngle);
int intervalAngle = interval * sectionAngle;
if (reverse) {
setThumbPosition(Math.toRadians(tempAng));
setThumbLowPosition(Math.toRadians(tempAng - intervalAngle));
canvas.drawArc(mRectF, tempAng, 360 - intervalAngle, false, arcPaint);
} else {
setThumbPosition(Math.toRadians(tempAng));
setThumbLowPosition(Math.toRadians(tempAng + intervalAngle));
canvas.drawArc(mRectF, (int) (tempAng + intervalAngle), 360 - intervalAngle, false, arcPaint);
}
mThumbHighDrawable.setBounds((int) mThumbLeft, (int) mThumbTop,
(int) (mThumbLeft + mThumbWidth), (int) (mThumbTop + mThumbHeight));
mThumbLowDrawable.setBounds((int) mThumbLowLeft, (int) mThumbLowTop, (int) (mThumbLowLeft + mThumbLowWidth), (int) (mThumbLowTop + mThumbLowHeight));
mThumbHighDrawable.draw(canvas);
mThumbLowDrawable.draw(canvas);
}
private void setThumbPosition(double radian) {
double x = mViewCenterX + mViewRadisu * Math.cos(radian);
double y = mViewCenterY + mViewRadisu * Math.sin(radian);
mThumbLeft = (float) (x - mThumbWidth / 2);
mThumbTop = (float) (y - mThumbHeight / 2);
}
private void setThumbLowPosition(double radian) {
double x = mViewCenterX + mViewRadisu * Math.cos(radian);
double y = mViewCenterY + mViewRadisu * Math.sin(radian);
mThumbLowLeft = (float) (x - mThumbLowWidth / 2);
mThumbLowTop = (float) (y - mThumbLowHeight / 2);
}
private boolean isDown = true;
@Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
getEventDegree(eventX, eventY);
// seekTo(eventX, eventY, false);
break;
case MotionEvent.ACTION_MOVE:
seekTo(eventX, eventY);
break;
case MotionEvent.ACTION_UP:
postDelayed(() -> {
tempStartAngle = tempStartAngle + sweepAngle;
sweepAngle = 0;
getSelectedColor();
if (onColorChangeListener != null) {
onColorChangeListener.colorChange(selectedColors);
}
}, 100);
break;
}
return true;
}
private float downDegree;
private void getEventDegree(float eventX, float eventY) {
if (isPointOnThumb(eventX, eventY)) {
double radian = Math.atan2(eventY - mViewCenterY, eventX - mViewCenterX);
/*
* 由于atan2返回的值为[-pi,pi]
* 因此需要将弧度值转换一下,使得区间为[0,2*pi]
*/
if (radian < 0) {
radian = radian + 2 * Math.PI;
}
isDown = true;
downDegree = Math.round(Math.toDegrees(radian));
} else {
isDown = false;
}
}
private void seekTo(float eventX, float eventY) {
if (true == isPointOnThumb(eventX, eventY)) {
// mThumbHighDrawable.setState(mThumbPressed);
if (!isDown) {
getEventDegree(eventX, eventY);
isDown = true;
}
double radian = Math.atan2(eventY - mViewCenterY, eventX - mViewCenterX);
/*
* 由于atan2返回的值为[-pi,pi]
* 因此需要将弧度值转换一下,使得区间为[0,2*pi]
*/
if (radian < 0) {
radian = radian + 2 * Math.PI;
}
setThumbPosition(radian);
float mSweepDegree = (float) Math.round(Math.toDegrees(radian));
sweepAngle = mSweepDegree - downDegree;
invalidate();
}
}
//选中的颜色
private ArrayList<Integer> selectedColors = new ArrayList<>(interval);
public void getSelectedColor() {
int tempIndex = (int) (tempStartAngle / sectionAngle);
int num = 90 / sectionAngle;
if (tempIndex == sections) {
tempIndex = 0;
}
int index = tempIndex;
if (tempIndex >= 0) {
index = tempIndex + num;
}
if (tempIndex >= (sections - num)) {
index = tempIndex - (sections - num);
}
if (index>colors.length)
index = index%colors.length;
while (index<0)
{
index = colors.length+index;
}
selectedColors.clear();
int startIndex = 0;
if (reverse)
{
startIndex = index - interval -1;
while (startIndex < 0)
{
startIndex = startIndex+colors.length;
}
if (startIndex > index)
{
for (int i = startIndex+1; i < colors.length; i++) {
selectedColors.add(Color.parseColor(colors[i]));
}
for (int i = 0; i <= index; i++) {
selectedColors.add(Color.parseColor(colors[i]));
}
}else {
for (int i = startIndex+1; i <= index; i++) {
selectedColors.add(Color.parseColor(colors[i]));
}
}
}else {
startIndex = index+interval+1;
while (startIndex>colors.length)
{
startIndex = startIndex-colors.length;
}
if (startIndex < index)
{
for (int i = startIndex-1; i >= 0; i--) {
selectedColors.add(Color.parseColor(colors[i]));
}
for (int i = colors.length-1; i >= index; i--) {
selectedColors.add(Color.parseColor(colors[i]));
}
}else {
for (int i = startIndex-1; i >=index; i--) {
selectedColors.add(Color.parseColor(colors[i]));
}
}
}
}
private boolean isPointOnThumb(float eventX, float eventY) {
boolean result = false;
double distance = Math.sqrt(Math.pow(eventX - mViewCenterX, 2)
+ Math.pow(eventY - mViewCenterY, 2));
if (distance < mViewSize && distance > (mViewSize / 2 - mThumbWidth)) {
result = true;
}
return result;
}
public boolean isReverse() {
return reverse;
}
public void setReverse(boolean reverse) {
this.reverse = reverse;
invalidate();
}
public interface OnColorChangeListener {
void colorChange(ArrayList<Integer> colors);
}
public void setOnColorChangeListener(OnColorChangeListener onColorChangeListener) {
this.onColorChangeListener = onColorChangeListener;
}
private OnColorChangeListener onColorChangeListener;
}
注意的地方:
1. 手势抬起时用了一个postDelayed方法,还是避免绘制的先后问题。
2. isDown变量的作用是判断,手势按下时是否在圆环上。当手势从圆环外滑倒圆环上时,避免指示器一下弹到手指位置。
github地址:colorpicker
来源:https://blog.csdn.net/zxc123e/article/details/70810722
0
投稿
猜你喜欢
- Spring Boot 项目之热部署配置前言所谓热部署,简单来说,就是代码修改后不需重启项目就可自动加载出新的内容。注意:热部署在 debu
- 一、Stream流引入Lambda表达式,基于Lambda所带来的函数式编程,又引入了一个全新的Stream概念,用于解决集合类库既有的鼻端
- 本文实例为大家分享了C# Email发送邮件的具体代码,供大家参考,具体内容如下//回执地址 var Receipt = &q
- 先看一下java线程运行时各个阶段的运行状态线程是进程中的一个实体,是被系 * 立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运
- 背景在工控软件的开发中很多业务场景就是使用图表控件展示设备和工艺参数。如下图案例:实现思路通常简单的做法是使用图表控件实现,常用的图表控件有
- 背景项目中经常会用到消息推送功能,关于推送技术的实现,我们通常会联想到轮询、comet长连接技术,虽然这些技术能够实现,但是需要反复连接,对
- 本文实例讲述了微信js sdk invalid signature签名错误问题的解决方法。分享给大家供大家参考,具体如下:/**最近在做微信
- 本文实例为大家分享了C#实现简单打字小游戏的具体代码,供大家参考,具体内容如下using System;using System.Colle
- 一个简单的实现版本,没有去Hook键鼠等操作,事先录制好操作步骤(将鼠标移动到需要操作的位置,按下热键执行相应动作),点击运行即可。主要还是
- 已经下过好几次了,现在还是忘了。就把过程直接放上面了。下次再换电脑就直接可以看。。。0.下载之前需要把JDK安装和配置好,点这里:https
- 前言在这个 Spring Security 教程中,我很乐意与您分享如何通过在 Java Web 应用程序中为用户添加角色来实现授权&
- 先看一下效果图实现思路:变成点的控件不是TextView和EditText而是Imageview。首先写一个RelativeLayout里边
- 应用场景:在Android开发过程中,有时需要调用手机自身设备的功能,本文侧重摄像头拍照功能的调用。知识点介绍:使用权限:调用手机自身设备功
- 最近因为fastjson安全漏洞,升级jar包时,踩了一些坑。新版本FastJsonHttpMessageConverter初始化,默认设置
- 本文介绍了Android多渠道打包的方法步骤,分享给大家,具体如下:1.生成签名文件点击 Build -> Generate Sign
- 一、简介Join方法主要是用来阻塞调用线程,直到某个线程终止或经过了指定时间为止。官方的解释比较乏味,通俗的说就是创建一个子线程,给它加了这
- 有时,类或方法需要对类型变量加以约束。下面是一个典型的例子,我们要寻找数组中的最小元素:public class ArrayAlg { &n
- 我就废话不多说了,大家还是直接看代码吧~using UnityEngine;using System.Collections; public
- start方法和run方法$start()$方法用来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到$cpu$时间片,就
- 首先我们在项目中导入这个框架:implementation 'com.mcxiaoke.volley:library:1.0.19&