一个不错效果的调光调色开关

  先上效果图:

  不错吧,最中间那个开关是个CheckBox,中间那个蓝色的是个圆形的拖动条,可以用来显示灯的亮度,而最外面的彩色环形也是可以拖动的,可以用来控制色彩。

  彩色环形是自定义View——CirclView,onTouch事件计算如旋转的角度,然后旋转画布,那个环形是UI素材。代码如下:

  1 import android.content.Context;
  2 import android.graphics.Bitmap;
  3 import android.graphics.Canvas;
  4 import android.graphics.Color;
  5 import android.graphics.drawable.BitmapDrawable;
  6 import android.graphics.drawable.Drawable;
  7 import android.util.AttributeSet;
  8 import android.view.MotionEvent;
  9 import android.view.View;
 10 
 11 public class CircleView extends View {
 12     public static final int INVALID_ANGLE = Integer.MIN_VALUE;
 13 
 14     private float lastX;                                    //起始X坐标
 15     private float lastY;                                    //起始Y坐标
 16 
 17     private float centerX;                                    //圆心X坐标
 18     private float centerY;                                    //圆心Y坐标
 19 
 20     double oldAngle;                                        //旋转前角度
 21     double angle;                                            //旋转后角度
 22 
 23     private Drawable rotatedPicture;                        //要旋转的背景图片
 24     private Bitmap rotatedBitmap;
 25     private float ringRatio;
 26 
 27     private int maxAngle = INVALID_ANGLE;
 28     private int minAngle = INVALID_ANGLE;
 29 
 30     private OnAngleChangedListener anglelistener;
 31     private OnColorChangedListener colorListener;
 32 
 33     public CircleView(Context context) {
 34         this(context, null);
 35     }
 36 
 37     public CircleView(Context context, AttributeSet attrs) {
 38         this(context, attrs, 0);
 39     }
 40 
 41     public CircleView(Context context, AttributeSet attrs, int defStyle) {
 42         super(context, attrs, defStyle);
 43     }
 44 
 45     /**
 46      * 
 47      * @param lastPointX 一次ActionMove的起始X坐标
 48      * @param lastPointY 一次ActionMove的起始Y坐标
 49      * @param pointX 一次ActionMove的终止X坐标
 50      * @param pointY 一次ActionMove的终止Y坐标
 51      * @return 返回一次ActionMove转过的角度
 52      */
 53     private double computeAngle(float lastPointX, float lastPointY, float pointX, float pointY) {
 54         //坐标系换算,原坐标系为以屏幕左上角为原点的直角坐标系,新坐标系为以圆心位置(centerX,centerY)为原点的直角坐标系
 55         //没有delta前缀的都是原坐标系的值,delta前缀标识的为换算后新坐标系的值
 56         float deltaLastPointX = lastPointX - centerX;
 57         float deltaLastPointY = centerY - lastPointY;
 58 
 59         float deltaPointX = pointX - centerX;
 60         float deltaPointY = centerY - pointY;
 61 
 62         //gradient表示斜率,如果deltaPonitX的值为零,则gradient为无穷大,否则为deltaPointY / deltaPointX
 63         float gradient = deltaPointX != 0 ? deltaPointY / deltaPointX : Integer.MAX_VALUE;
 64         float lastGradient = deltaLastPointX != 0 ? deltaLastPointY / deltaLastPointX : Integer.MAX_VALUE;
 65         //如果斜率相同,则返回0
 66         if (gradient == lastGradient)
 67             return 0;
 68 
 69         //lastPointSidePow,pointSidePow表示对应点到圆心的距离平方
 70         float lastPointSidePow = deltaLastPointX * deltaLastPointX + deltaLastPointY * deltaLastPointY;
 71         float pointSidePow = deltaPointX * deltaPointX + deltaPointY * deltaPointY;
 72         //两点之间距离的平方
 73         float lastPointPointSidePow = (float) (Math.pow(deltaLastPointY - deltaPointY, 2) + Math.pow(deltaLastPointX
 74                 - deltaPointX, 2));
 75         //下面使用余弦定理求出一次ActionMove所经过角度的余弦值
 76         //余弦定理:cosC = (a^2 + b^2 - c^2) / (2·a·b)
 77         double cosinCentralAngle = (lastPointSidePow + pointSidePow - lastPointPointSidePow)
 78                 / (2 * Math.sqrt(lastPointSidePow) * Math.sqrt(pointSidePow));
 79         if (cosinCentralAngle > 1 || cosinCentralAngle < -1)
 80             return 0;
 81         //反余弦:把余弦值换算成对应弧度
 82         double acos = Math.acos(cosinCentralAngle);
 83         //弧度角度换算:把弧度换算成对应角度
 84         double increment = (acos / Math.PI) * 180;
 85         //isBellow判断一次ActionMove之后斜率值是否降低
 86         boolean isBellow = lastGradient > gradient;
 87         //如果斜率降低
 88         if (isBellow) {
 89             //如果ActionMove之前在第一、四象限
 90             if (deltaLastPointX >= 0) {
 91                 //如果ActionMove之后也在第一、四象限,则旋转的方向为逆时针,角度应为正值
 92                 if (deltaPointX >= 0) {
 93                     return increment;
 94                 //如果ActionMove之后在第二、三象限,则旋转的方向为顺时针,角度为负值
 95                 } else {
 96                     return -increment;
 97                 }
 98             //如果ActionMove之前在第二、三象限
 99             } else {
100                 //如果ActionMove之后也在第二、三象限,则旋转的方向为逆时针,角度应为正值
101                 if (deltaPointX <= 0) {
102                     return increment;
103                 //如果ActionMove之后在第一、四象限,则旋转的方向为顺时针,角度为负值
104                 } else {
105                     return -increment;
106                 }
107             }
108         } else {
109             if (deltaLastPointX >= 0) {
110                 if (deltaPointX >= 0) {
111                     return -increment;
112                 } else {
113                     return increment;
114                 }
115             } else {
116                 if (deltaPointX <= 0) {
117                     return -increment;
118                 } else {
119                     return increment;
120                 }
121             }
122         }
123     }
124 
125     @Override
126     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
127         centerX = w / 2.0f;
128         centerY = h / 2.0f;
129         rotatedPicture.setBounds(0, 0, w, h);
130         rotatedBitmap = ((BitmapDrawable) rotatedPicture).getBitmap();
131     }
132 
133     @Override
134     protected void onDraw(Canvas canvas) {
135         canvas.save();
136         canvas.rotate((float) angle, centerX, centerY);
137         rotatedPicture.draw(canvas);
138         canvas.restore();
139     }
140 
141     public boolean isHit(int x, int y) {
142         //getPixel(x, y)方法返回(x,y)坐标位置的像素颜色信息
143         //x,y的取值范围分别为(0...width-1)、(0...height-1),如果超出取值范围则会抛出异常
144         //方法返回值为
145         return (rotatedBitmap.getPixel(x, y) & 0xFF000000) != 0;
146     }
147 
148     @Override
149     public boolean onTouchEvent(MotionEvent event) {
150         final float nowX = event.getX();
151         final float nowY = event.getY();
152 
153         switch (event.getAction()) {
154         case MotionEvent.ACTION_DOWN:
155             if (!isHit((int) nowX, (int) nowY))
156                 return false;
157             lastX = nowX;
158             lastY = nowY;
159             oldAngle = angle;
160             break;
161         case MotionEvent.ACTION_MOVE:
162             double increment = computeAngle(lastX, lastY, nowX, nowY);
163             if (increment != 0) {
164                 angle += increment;
165                 if (maxAngle != INVALID_ANGLE && angle > maxAngle) {
166                     angle = maxAngle;
167                 } else if (minAngle != INVALID_ANGLE && angle < minAngle) {
168                     angle = minAngle;
169                 }
170                 invalidate();
171             }
172             lastX = nowX;
173             lastY = nowY;
174             break;
175         case MotionEvent.ACTION_UP:
176             if (anglelistener != null) {
177                 anglelistener.onAngleChanged(oldAngle, angle);
178             }
179             if (colorListener != null && ringRatio != 0) {
180                 int width = getWidth();
181                 int r = (int) (width / 2 - ringRatio * width / 4);
182                 int x = 0;
183                 int y = 0;
184                 double radian = (-angle % 360) * Math.PI / 180;
185                 x = (int) (width / 2 + r * Math.sin(radian));
186                 y = (int) (width / 2 - r * Math.cos(radian));
187                 int color = rotatedBitmap.getPixel(x, y);
188                 colorListener.onColorChanged(Color.red(color), Color.green(color), Color.blue(color));
189             }
190             invalidate();
191             break;
192         }
193 
194         return true;
195     }
196 
197     public static interface OnAngleChangedListener {
198         public void onAngleChanged(double oldAngle, double newAngle);
199     }
200 
201     public static interface OnColorChangedListener {
202         public void onColorChanged(int red, int green, int blue);
203     }
204 
205     public void OnAngleChangedListener(OnAngleChangedListener listener) {
206         this.anglelistener = listener;
207     }
208 
209     public void setOnColorChangedListener(OnColorChangedListener listener) {
210         this.colorListener = listener;
211     }
212 
213     public void setRotateDrawable(Drawable rotatedPicture, float ringRatio) {
214         this.rotatedPicture = rotatedPicture;
215         this.ringRatio = ringRatio;
216     }
217 
218     public void setRotateDrawable(Drawable rotatedPicture) {
219         this.rotatedPicture = rotatedPicture;
220     }
221 
222     public void setMaxRotatedAngle(int maxAngle) {
223         this.maxAngle = maxAngle;
224     }
225 
226     public void setMinRotatedAngle(int minAngle) {
227         this.minAngle = minAngle;
228     }
229 
230     public void setAngle(double angle) {
231         this.angle = angle;
232         postInvalidate();
233     }
234 
235     protected double getAngle() {
236         return angle;
237     }
238 }

  那个圆形拖动条则继承自CircleView,然后重写onDraw方法绘制进度。代码如下:

 1 import android.content.Context;
 2 import android.graphics.Canvas;
 3 import android.graphics.Paint;
 4 import android.graphics.RectF;
 5 import android.graphics.Paint.Cap;
 6 import android.graphics.Paint.Style;
 7 import android.util.AttributeSet;
 8 
 9 public class ProgressCircleView extends CircleView {
10     private int progressBackground;
11     private int progress;
12 
13     private RectF progressRectF;
14     private int progressBgWidth, progressWidth;
15     private Paint progressBgPaint;
16     private Paint progressPaint;
17 
18     public ProgressCircleView(Context context) {
19         this(context, null);
20     }
21 
22     public ProgressCircleView(Context context, AttributeSet attrs) {
23         this(context, attrs, 0);
24     }
25 
26     public ProgressCircleView(Context context, AttributeSet attrs, int defStyle) {
27         super(context, attrs, defStyle);
28         super.setMaxRotatedAngle(360);
29         super.setMinRotatedAngle(0);
30     }
31 
32     @Override
33     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
34         super.onSizeChanged(w, h, oldw, oldh);
35         int offset = progressWidth / 2 + 1;
36         progressRectF = new RectF(offset, offset, w - offset, h - offset);
37     }
38 
39     @Override
40     protected void onDraw(Canvas canvas) {
41         super.onDraw(canvas);
42         // draw bg
43         canvas.drawArc(progressRectF, 0, 360, false, progressBgPaint);
44         // draw arc
45         canvas.drawArc(progressRectF, -90, (float) getAngle(), false, progressPaint);
46     }
47 
48     public void setProgressBackground(int width, int color) {
49         int widthDp = (int) (width * getResources().getDisplayMetrics().density);
50         Paint paint = new Paint();
51         paint.setAntiAlias(true);
52         paint.setAlpha(0xff);
53         paint.setStrokeWidth(widthDp);
54         paint.setStyle(Style.STROKE);
55         paint.setColor(color);
56         progressBgWidth = widthDp;
57         progressBgPaint = paint;
58     }
59 
60     public void setProgress(int width, int color) {
61         int widthDp = (int) (width * getResources().getDisplayMetrics().density);
62         Paint paint = new Paint();
63         paint.setAntiAlias(true);
64         paint.setAlpha(0xff);
65         paint.setStrokeWidth(widthDp);
66         paint.setStyle(Style.STROKE);
67         paint.setColor(color);
68         paint.setStrokeCap(Cap.ROUND);
69         progressWidth = widthDp;
70         progressPaint = paint;
71     }
72 
73 }

  简单使用的Activity:

 1 public class MainActivity extends Activity {
 2 
 3     private double oldAngle;
 4     boolean isClicked;
 5     @Override
 6     protected void onCreate(Bundle savedInstanceState) {
 7         super.onCreate(savedInstanceState);
 8         setContentView(R.layout.color_light_control_panel);
 9 
10         CircleView cv = (CircleView) findViewById(R.id.colorCircle);
11         cv.setRotateDrawable(getResources().getDrawable(R.drawable.color_light_color_circle), 0.15f);
12         cv.setOnColorChangedListener(new OnColorChangedListener() {
13 
14             @Override
15             public void onColorChanged(int red, int green, int blue) {
16                 Log.d("onColorChanged", red + " " + green + " " + blue + " ");
17 
18             }
19         });
20         // cv.setMaxRotatedAngle(360);
21         // cv.setMinRotatedAngle(0);
22         final ProgressCircleView pcv = (ProgressCircleView) findViewById(R.id.progress);
23         pcv.setRotateDrawable(getResources().getDrawable(R.drawable.color_light_progress));
24         pcv.setProgress(10, 0xFF32CFEB);
25         pcv.setProgressBackground(10, 0xFF3A3A3A);
26 
27         pcv.OnAngleChangedListener(new OnAngleChangedListener() {
28 
29             @Override
30             public void onAngleChanged(double oldAngle, double newAngle) {
31                 Log.d("angle=", "oldAngle=" + oldAngle + " newAngle=" + newAngle);
32                 MainActivity.this.oldAngle = oldAngle;
33             }
34         });
35 
36         View btn = findViewById(R.id.switch_btn);
37         btn.setOnClickListener(new OnClickListener() {
38 
39             @Override
40             public void onClick(View v) {
41                 pcv.setAngle(oldAngle);
42             }
43         });
44     }
45 }

   最后是布局文件:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#ff000000" >
 6 
 7     <com.example.test2.CircleView
 8         android:id="@+id/colorCircle"
 9         android:layout_width="242dp"
10         android:layout_height="242dp"
11         android:layout_centerInParent="true" >
12     </com.example.test2.CircleView>
13 
14     <com.example.test2.ProgressCircleView
15         android:id="@+id/progress"
16         android:layout_width="164dp"
17         android:layout_height="164dp"
18         android:layout_centerInParent="true" >
19     </com.example.test2.ProgressCircleView>
20 
21     <View
22         android:id="@+id/switch_btn"
23         android:layout_width="61dp"
24         android:layout_height="61dp"
25         android:background="@drawable/color_light_switch_on"
26         android:layout_centerInParent="true" >
27     </View>
28 
29 </RelativeLayout>
原文地址:https://www.cnblogs.com/Couch-potato/p/3749420.html