服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - android自定义View实现圆环颜色选择器

android自定义View实现圆环颜色选择器

2022-02-28 15:11zxc123e Android

这篇文章主要介绍了android自定义View实现圆环颜色选择器,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

最近工作需要,自定了一个颜色选择器,效果图如下:

android自定义View实现圆环颜色选择器

颜色种类是固定的,圆环上有个指示器,指示选中的颜色,这个定义起来应该是很简单了,直接上代码。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
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()方法其实是没多大作用。

上面是选择单个颜色,下面来个加强版,选择的是颜色区间,先上效果图:

android自定义View实现圆环颜色选择器

区间可以自己选择,并且可以反转(低指示器在高指示器顺时针方向或逆时针方向)。

下面是代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
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

延伸 · 阅读

精彩推荐