当前位置:   article > 正文

android自定义圆弧进度条,可拖拽的progressBar

android自定义圆弧进度条,可拖拽的progressBar

android自定义圆弧进度条,可拖拽的progressBar,更多可查看相关文章

  1. <com.pandacredit.administrator.customview.ArcShapeView4Top
  2. android:visibility="visible"
  3. android:layout_width="260dp"
  4. ArcShapeView:storkeWidth="10dp"
  5. ArcShapeView:sweepAngle="240"
  6. ArcShapeView:startAngle="150"
  7. ArcShapeView:storkeColor="#f36523"
  8. ArcShapeView:isArc="false"
  9. android:layout_centerHorizontal="true"
  10. android:layout_height="300dp"
  11. android:id="@+id/arcShapeView1"
  12. />

自定义view 

  1. package com.pandacredit.administrator.customview;
  2. import android.content.Context;
  3. import android.content.res.TypedArray;
  4. import android.graphics.Bitmap;
  5. import android.graphics.BitmapFactory;
  6. import android.graphics.Canvas;
  7. import android.graphics.Color;
  8. import android.graphics.Paint;
  9. import android.graphics.RectF;
  10. import android.util.AttributeSet;
  11. import android.util.Log;
  12. import android.view.MotionEvent;
  13. import android.view.View;
  14. /**
  15. * Created by Administrator on 2016/10/26.
  16. */
  17. public class ArcShapeView4Top extends View {
  18. //startAngle:圆弧的起始角度。 sweepAngle:圆弧的角度。
  19. private float left;
  20. private float top;
  21. private float right;
  22. private float bottom;
  23. private float storkeWidth;
  24. private float startAngle;
  25. private float sweepAngle;
  26. private float startAngle2;
  27. private float sweepAngle2;
  28. private int storkColor;
  29. private boolean isArc;
  30. private TypedArray arr;
  31. private float width;
  32. private float height;
  33. private float cx;
  34. private float cy;
  35. private RectF rect;
  36. /**
  37. * 上半弧进度改变是调用该接口方法
  38. */
  39. private OnPregressChangedDown pregressDown;
  40. /**
  41. * 下半弧进度改变是调用该接口方法
  42. */
  43. private OnPregressChangedUp pregressUp;
  44. /**
  45. * The radius of the inner circle
  46. */
  47. private float innerRadius;
  48. /**
  49. * The X coordinate for 12 O'Clock
  50. */
  51. private float startPointX;
  52. /**
  53. * The Y coordinate for 12 O'Clock
  54. */
  55. private float startPointY;
  56. /**
  57. * The X coordinate for 12 O'Clock
  58. */
  59. private float startPointX2;
  60. /**
  61. * The Y coordinate for 12 O'Clock
  62. */
  63. private float startPointY2;
  64. /**
  65. * The X coordinate for the current position of the marker, pre adjustment
  66. * to center
  67. */
  68. private float markPointX;
  69. /**
  70. * The Y coordinate for the current position of the marker, pre adjustment
  71. * to center
  72. */
  73. private float markPointY;
  74. /**
  75. * The X coordinate for the current position of the marker, pre adjustment
  76. * to center
  77. */
  78. private float markPointX2;
  79. /**
  80. * The Y coordinate for the current position of the marker, pre adjustment
  81. * to center
  82. */
  83. private float markPointY2;
  84. /**
  85. * The adjustment factor. This adds an adjustment of the specified size to
  86. * both sides of the progress bar, allowing touch events to be processed
  87. * more user friendlily (yes, I know that's not a word)
  88. */
  89. private float adjustmentFactor = 4;
  90. /**
  91. * The progress mark when the view isn't being progress modified
  92. */
  93. private Bitmap progressMark;
  94. /**
  95. * The flag to see if the setProgress() method was called from our own
  96. * View's setAngle() method, or externally by a user.
  97. */
  98. private boolean CALLED_FROM_ANGLE = false;
  99. /**
  100. * The X coordinate for the top left corner of the marking drawable
  101. */
  102. private float dx;
  103. /**
  104. * The Y coordinate for the top left corner of the marking drawable
  105. */
  106. private float dy;
  107. /**
  108. * The X coordinate for the top left corner of the marking drawable
  109. */
  110. private float dx2;
  111. /**
  112. * The Y coordinate for the top left corner of the marking drawable
  113. */
  114. private float dy2;
  115. /**
  116. * The maximum progress amount
  117. */
  118. private int maxProgress = 100;
  119. /**
  120. * The current progress
  121. */
  122. private int progress;
  123. /**
  124. * The progress percent
  125. */
  126. private int progressPercent;
  127. private float padding = 20;
  128. /**
  129. * The radius of the outer circle
  130. */
  131. private float outerRadius;
  132. /**
  133. * The listener to listen for changes
  134. */
  135. private ArcShapeView4Top.OnSeekChangeListener mListener;
  136. /**
  137. * The progress circle ring background
  138. */
  139. private Paint circleRing;
  140. /**
  141. * The angle of progress
  142. */
  143. private int angle = 120;
  144. /**
  145. * The angle of progress
  146. */
  147. private int angle2 = -30;
  148. private Context context;
  149. public ArcShapeView4Top(Context context, AttributeSet attrs) {
  150. super(context, attrs);
  151. this.context = context;
  152. arr = context.obtainStyledAttributes(attrs, R.styleable.ArcShapeView);
  153. inits();
  154. }
  155. private void inits() {
  156. storkeWidth = arr.getDimension(R.styleable.ArcShapeView_storkeWidth, 1);
  157. startAngle = arr.getInt(R.styleable.ArcShapeView_startAngle, 90);
  158. sweepAngle = arr.getInt(R.styleable.ArcShapeView_sweepAngle, 90);
  159. startAngle2 = 120;
  160. sweepAngle2 = -60;
  161. storkColor = arr.getColor(R.styleable.ArcShapeView_storkeColor, Color.BLACK);
  162. isArc = arr.getBoolean(R.styleable.ArcShapeView_isArc, true);
  163. rect = new RectF();
  164. progressMark = BitmapFactory.decodeResource(context.getResources(), R.drawable.a30_03);
  165. mListener = new OnSeekChangeListener() {
  166. @Override
  167. public void onProgressChange(ArcShapeView4Top view, int newProgress) {
  168. }
  169. };
  170. }
  171. protected void onDraw(Canvas canvas) {
  172. dx = getXFromAngle(markPointX);
  173. dy = getYFromAngle(markPointY);
  174. dx2 = getXFromAngle(markPointX2);
  175. dy2 = getYFromAngle(markPointY2);
  176. circleRing = new Paint();
  177. circleRing.setAntiAlias(true); //设置画笔为无锯齿
  178. circleRing.setColor(storkColor); //设置画笔颜色
  179. // canvas.drawColor(storkColor); //白色背景
  180. circleRing.setStrokeWidth(storkeWidth); //线宽
  181. circleRing.setStyle(Paint.Style.STROKE);
  182. canvas.drawArc(rect, startAngle2, angle2, isArc, circleRing); //绘制圆弧
  183. canvas.drawArc(rect, startAngle, angle, isArc, circleRing); //绘制圆弧
  184. circleRing.setColor(Color.GRAY);
  185. canvas.drawArc(rect, startAngle2 + angle2, sweepAngle2 - angle2, isArc, circleRing); //绘制圆弧
  186. canvas.drawArc(rect, startAngle + angle, sweepAngle - angle, isArc, circleRing); //绘制圆弧
  187. canvas.drawBitmap(progressMark, dx, dy, null);
  188. canvas.drawBitmap(progressMark, dx2, dy2, null);
  189. super.onDraw(canvas);
  190. }
  191. @Override
  192. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  193. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  194. width = getWidth(); // Get View Width
  195. height = getHeight();// Get View Height
  196. float size = (width > height) ? height : width; // Choose the smaller
  197. // between width and
  198. // height to make a
  199. // square
  200. cx = width / 2; // Center X for circle
  201. cy = height / 2; // Center Y for circle
  202. outerRadius = size / 2 - padding; // Radius of the outer circle
  203. innerRadius = outerRadius - storkeWidth; // Radius of the inner circle
  204. left = cx - outerRadius; // Calculate left bound of our rect
  205. right = cx + outerRadius;// Calculate right bound of our rect
  206. top = cy - outerRadius;// Calculate top bound of our rect
  207. bottom = cy + outerRadius;// Calculate bottom bound of our rect
  208. // startPointX = (float) (1-(float)Math.cos((180-startAngle)*Math.PI/180))*(outerRadius-storkeWidth/2);
  209. // startPointX =(1-(float)Math.sqrt(3)/2)*(outerRadius+storkeWidth+padding) ; // 12 O'clock X coordinate
  210. // startPointY = (float) (1+(float)Math.sin((180-startAngle)*Math.PI/180))*(outerRadius);
  211. // startPointY =(float) 1.5*(outerRadius+storkeWidth/2);// 12 O'clock Y coordinate
  212. startPointX = cx; // 12 O'clock X coordinate
  213. startPointY = cy - outerRadius;// 12 O'clock Y coordinate
  214. startPointX2 = cx; // 12 O'clock X coordinate
  215. startPointY2 = cy + outerRadius;// 12 O'clock Y coordinate
  216. markPointX = startPointX;// Initial locatino of the marker X coordinate
  217. markPointY = startPointY;// Initial locatino of the marker Y coordinate
  218. markPointX2 = startPointX2;// Initial locatino of the marker X coordinate
  219. markPointY2 = startPointY2;// Initial locatino of the marker Y coordinate
  220. rect.set(left, top, right, bottom); // assign size to rect
  221. }
  222. //float hudu = (float) Math.abs(Math.PI * currentDegreeFlag / 180)
  223. // /*
  224. // * (non-Javadoc)
  225. // *
  226. // * @see android.view.View#onDraw(android.graphics.Canvas)
  227. // */
  228. // @Override
  229. // protected void onDraw(Canvas canvas) {
  230. // dx = getXFromAngle();
  231. // dy = getYFromAngle();
  232. //
  233. // canvas.drawCircle(cx, cy, outerRadius, circleRing);
  234. // canvas.drawArc(rect, startAngle, angle, true, circleColor);
  235. // canvas.drawCircle(cx, cy, innerRadius, innerColor);
  236. //
  237. //
  238. // super.onDraw(canvas);
  239. // }
  240. // /**
  241. // * Draw marker at the current progress point onto the given canvas.
  242. // *
  243. // * @param canvas
  244. // * the canvas
  245. // */
  246. // public void drawMarkerAtProgress(Canvas canvas) {
  247. // if (IS_PRESSED) {
  248. // canvas.drawBitmap(progressMarkPressed, dx, dy, null);
  249. // } else {
  250. // canvas.drawBitmap(progressMark, dx, dy, null);
  251. // }
  252. // }
  253. /**
  254. * Gets the X coordinate of the arc's end arm's point of intersection with
  255. * the circle
  256. *
  257. * @return the X coordinate
  258. */
  259. public float getXFromAngle(float markPointX) {
  260. int size1 = progressMark.getWidth();
  261. float x = markPointX - (size1 / 2);
  262. return x;
  263. }
  264. /**
  265. * Gets the Y coordinate of the arc's end arm's point of intersection with
  266. * the circle
  267. *
  268. * @return the Y coordinate
  269. */
  270. public float getYFromAngle(float markPointY) {
  271. int size1 = progressMark.getHeight();
  272. float y = markPointY - (size1 / 2);
  273. return y;
  274. }
  275. /**
  276. * Get the angle.
  277. *
  278. * @return the angle
  279. */
  280. public int getAngle() {
  281. return angle;
  282. }
  283. /**
  284. * Set the angle.
  285. *
  286. * @param angle the new angle
  287. */
  288. public void setAngle(int angle) {
  289. this.angle = angle;
  290. float donePercent = (((float) this.angle) / 360) * 100;
  291. float progress = (donePercent / 100) * getMaxProgress();
  292. setProgressPercent(Math.round(donePercent));
  293. CALLED_FROM_ANGLE = true;
  294. setProgress(Math.round(progress));
  295. }
  296. /**
  297. * Get the angle.
  298. *
  299. * @return the angle
  300. */
  301. public int getAngle2() {
  302. return angle2;
  303. }
  304. /**
  305. * Set the angle.
  306. *
  307. * @param angle the new angle
  308. */
  309. public void setAngle2(int angle) {
  310. this.angle2 = angle;
  311. float donePercent = (((float) this.angle2) / 360) * 100;
  312. float progress = (donePercent / 100) * getMaxProgress();
  313. setProgressPercent(Math.round(donePercent));
  314. CALLED_FROM_ANGLE = true;
  315. setProgress(Math.round(progress));
  316. }
  317. /**
  318. * Sets the seek bar change listener.
  319. *
  320. * @param listener the new seek bar change listener
  321. */
  322. public void setSeekBarChangeListener(ArcShapeView4Top.OnSeekChangeListener listener) {
  323. mListener = listener;
  324. }
  325. /**
  326. * Gets the seek bar change listener.
  327. *
  328. * @return the seek bar change listener
  329. */
  330. public ArcShapeView4Top.OnSeekChangeListener getSeekBarChangeListener() {
  331. return mListener;
  332. }
  333. // /**
  334. // * Gets the bar width.
  335. // *
  336. // * @return the bar width
  337. // */
  338. // public int getBarWidth() {
  339. // return barWidth;
  340. // }
  341. //
  342. // /**
  343. // * Sets the bar width.
  344. // *
  345. // * @param barWidth
  346. // * the new bar width
  347. // */
  348. // public void setBarWidth(int barWidth) {
  349. // this.barWidth = barWidth;
  350. // }
  351. /**
  352. * The listener interface for receiving onSeekChange events. The class that
  353. * is interested in processing a onSeekChange event implements this
  354. * interface, and the object created with that class is registered with a
  355. * component using the component's
  356. * <code>setSeekBarChangeListener(OnSeekChangeListener)<code> method. When
  357. * the onSeekChange event occurs, that object's appropriate
  358. * method is invoked.
  359. */
  360. public interface OnSeekChangeListener {
  361. /**
  362. * On progress change.
  363. *
  364. * @param view the view
  365. * @param newProgress the new progress
  366. */
  367. public void onProgressChange(ArcShapeView4Top view, int newProgress);
  368. }
  369. /**
  370. * Gets the max progress.
  371. *
  372. * @return the max progress
  373. */
  374. public int getMaxProgress() {
  375. return maxProgress;
  376. }
  377. /**
  378. * Sets the max progress.
  379. *
  380. * @param maxProgress the new max progress
  381. */
  382. public void setMaxProgress(int maxProgress) {
  383. this.maxProgress = maxProgress;
  384. }
  385. /**
  386. * Gets the progress.
  387. *
  388. * @return the progress
  389. */
  390. public int getProgress() {
  391. return progress;
  392. }
  393. /**
  394. * Sets the progress.
  395. *
  396. * @param progress the new progress
  397. */
  398. public void setProgress(int progress) {
  399. if (this.progress != progress) {
  400. this.progress = progress;
  401. if (!CALLED_FROM_ANGLE) {
  402. int newPercent = (this.progress / this.maxProgress) * 100;
  403. int newAngle = (newPercent / 100) * 360;
  404. this.setAngle(newAngle);
  405. this.setProgressPercent(newPercent);
  406. }
  407. mListener.onProgressChange(this, this.getProgress());
  408. CALLED_FROM_ANGLE = false;
  409. }
  410. }
  411. /**
  412. * Gets the progress percent.
  413. *
  414. * @return the progress percent
  415. */
  416. public int getProgressPercent() {
  417. return progressPercent;
  418. }
  419. /**
  420. * Sets the progress percent.
  421. *
  422. * @param progressPercent the new progress percent
  423. */
  424. public void setProgressPercent(int progressPercent) {
  425. this.progressPercent = progressPercent;
  426. }
  427. /*
  428. * (non-Javadoc)
  429. *
  430. * @see android.view.View#onTouchEvent(android.view.MotionEvent)
  431. */
  432. @Override
  433. public boolean onTouchEvent(MotionEvent event) {
  434. float x = event.getX();
  435. float y = event.getY();
  436. boolean up = false;
  437. switch (event.getAction()) {
  438. case MotionEvent.ACTION_DOWN:
  439. moved(x, y, up);
  440. break;
  441. case MotionEvent.ACTION_MOVE:
  442. moved(x, y, up);
  443. break;
  444. case MotionEvent.ACTION_UP:
  445. up = true;
  446. moved(x, y, up);
  447. break;
  448. }
  449. return true;
  450. }
  451. /**
  452. * Moved.
  453. *
  454. * @param x the x
  455. * @param y the y
  456. * @param up the up
  457. */
  458. private void moved(float x, float y, boolean up) {
  459. float distance = (float) Math.sqrt(Math.pow((x - cx), 2) + Math.pow((y - cy), 2));
  460. if (distance < outerRadius + DisplayUtil.dip2px(context, adjustmentFactor) && distance > innerRadius - DisplayUtil.dip2px(context, adjustmentFactor) && !up) {
  461. // Log.d("120", "moved:calculate4Y(120)========== "+calculate4Y(120)+":cx===="+cx);
  462. // Log.d("150", "moved:calculate4Y(150)========== "+calculate4Y(150)+":cy===="+cy);
  463. // Log.d("y,x", "moved: y=========="+y+":x============"+x);
  464. if (y >= calculate4Y(120)) {
  465. float degrees1 = 0.0f;
  466. // degrees = (float) ((float) ((Math.toDegrees(Math.atan2(x - cx, cy - y)) + 270 - startAngle)) % 360.0);
  467. if (x >= cx) {
  468. degrees1 = ((float) (30 + 180 * (Math.atan2(x - cx, y - cy)) / Math.PI));
  469. // Log.d("degrees2", "moved: degrees2="+degrees1);
  470. // Log.d("x,y", "moved:x - cx= "+(x - cx)+":y-cy="+(y-cy)+":atan="+Math.atan2(x - cx, y-cy)*180/3.14);
  471. }
  472. if (x < cx) {
  473. degrees1 = ((float) (30 - 180 * (Math.atan2(cx - x, y - cy)) / Math.PI));
  474. // Log.d("degrees1", "moved: degrees1="+degrees1);
  475. // Log.d("x,y", "moved:cx-x= "+(cx-x)+":y-cy="+(y-cy)+":atan="+Math.atan2(cx-x, y-cy)*180/3.14);
  476. }
  477. markPointX2 = calculate4X(startAngle2 - degrees1);
  478. markPointY2 = calculate4Y(startAngle2 - degrees1);
  479. setAngle2((int) (0 - degrees1));
  480. if (pregressDown != null) {
  481. pregressDown.onPregressChangedDown(Math.abs(degrees1 / sweepAngle2));
  482. }
  483. // if (degrees>0 ) {
  484. // degrees -= 2 * Math.PI;
  485. //
  486. // }
  487. // angle2=Math.round(degrees);
  488. } else if (y <= calculate4Y(150)) {
  489. // degrees = (float) ((float) ((Math.toDegrees(Math.atan2(x - cx, cy - y)) + 270 - startAngle)) % 360.0);
  490. float degrees = 0.0f;
  491. if (y >= cy && x < cx) {
  492. degrees = ((float) (180 * (Math.atan2(cx - x, y - cy)) / Math.PI) - 60);
  493. // Log.d("degrees1", "moved: degrees1="+degrees);
  494. // Log.d("x,y", "moved:cx-x= "+(cx-x)+":y-cy="+(y-cy)+":atan="+Math.atan2(cx-x, y-cy)*180/3.14);
  495. }
  496. if (y >= cy && x >= cx) {
  497. degrees = ((float) (300 - 180 * (Math.atan2(x - cx, y - cy)) / Math.PI));
  498. // Log.d("degrees2", "moved: degrees2="+degrees);
  499. // Log.d("x,y", "moved:x - cx= "+(x - cx)+":y-cy="+(y-cy)+":atan="+Math.atan2(x - cx, y-cy));
  500. }
  501. if (y < cy && x >= cx) {
  502. degrees = ((float) (180 * (Math.atan2(x - cx, cy - y)) / Math.PI + 120));
  503. // Log.d("degrees3", "moved: degrees3="+degrees);
  504. // Log.d("x,y", "moved:x - cx= "+(x - cx)+":cy-y="+(cy-y)+":atan="+Math.atan2(x - cx, cy-y));
  505. }
  506. if (y < cy && x < cx) {
  507. degrees = ((float) (90 - 180 * (Math.atan2(cx - x, cy - y)) / Math.PI + 30));
  508. // Log.d("degrees4", "moved: degrees4="+degrees);
  509. // Log.d("x,y", "moved:cx-x= "+(cx-x)+":cy-y="+(cy-y)+":atan="+Math.atan2(cx-x, cy-y));
  510. }
  511. markPointX = calculate4X(startAngle + degrees);
  512. markPointY = calculate4Y(startAngle + degrees);
  513. setAngle((int)(degrees));
  514. if (pregressUp != null) {
  515. pregressUp.onPregressChangedUp(degrees / sweepAngle);
  516. }
  517. }
  518. invalidate();
  519. } else
  520. {
  521. invalidate();
  522. }
  523. }
  524. /**
  525. * Gets the adjustment factor.
  526. *
  527. * @return the adjustment factor
  528. */
  529. public float getAdjustmentFactor() {
  530. return adjustmentFactor;
  531. }
  532. /**
  533. * Sets the adjustment factor.
  534. *
  535. * @param adjustmentFactor the new adjustment factor
  536. */
  537. public void setAdjustmentFactor(float adjustmentFactor) {
  538. this.adjustmentFactor = adjustmentFactor;
  539. }
  540. /**
  541. * 根据半径和角度计算x坐标
  542. */
  543. private float calculateX(float r, double angle) {
  544. angle = angle * ((2 * Math.PI) / 360);
  545. double x = r * Math.sin(angle);
  546. double xFinal = cx + x;
  547. return (float) xFinal;
  548. }
  549. /**
  550. * 根据半径和角度计算y坐标
  551. */
  552. private float calculateY(float r, double angle) {
  553. angle = angle * ((2 * Math.PI) / 360);
  554. double y = r * Math.cos(angle);
  555. double yFinal = cy - y;
  556. return (float) yFinal;
  557. }
  558. /**
  559. * 根据半径和角度计算x坐标
  560. */
  561. private float calculate4X(double angle) {
  562. angle = angle * Math.PI / 180;
  563. double x = (innerRadius + storkeWidth) * Math.cos(angle);
  564. double xFinal = cx + x;
  565. return (float) xFinal;
  566. }
  567. /**
  568. * 根据半径和角度计算y坐标
  569. */
  570. private float calculate4Y(double angle) {
  571. angle = angle * Math.PI / 180;
  572. double y = (innerRadius + storkeWidth) * Math.sin(angle);
  573. double yFinal = cy + y;
  574. return (float) yFinal;
  575. }
  576. public void setLeft(float left) {
  577. this.left = left;
  578. }
  579. /**
  580. * 但进度发送改变的时候调用该方法(下半弧)
  581. *
  582. * @param pregressDown (0.0~1.0)
  583. */
  584. public void setPregressDown(OnPregressChangedDown pregressDown) {
  585. this.pregressDown = pregressDown;
  586. }
  587. /**
  588. * 但进度发送改变的时候调用该方法(上半弧)
  589. *
  590. * @param pregressUp (0.0~1.0)
  591. */
  592. public void setPregressUp(OnPregressChangedUp pregressUp) {
  593. this.pregressUp = pregressUp;
  594. }
  595. public interface OnPregressChangedUp {
  596. /**
  597. * 但进度发送改变的时候调用该方法(上半弧)
  598. *
  599. * @param progress (0.0~1.0)
  600. */
  601. public void onPregressChangedUp(double progress);
  602. }
  603. public interface OnPregressChangedDown {
  604. /**
  605. * 但进度发送改变的时候调用该方法(下半弧)
  606. *
  607. * @param progress (0.0~1.0)
  608. */
  609. public void onPregressChangedDown(double progress);
  610. }
  611. }

自定义属性

  1. <declare-styleable name="ArcShapeView">
  2. <attr name="startX" format="float"></attr>
  3. <attr name="startY" format="float"></attr>
  4. <attr name="endX" format="float"></attr>
  5. <attr name="endY" format="float"></attr>
  6. <attr name="storkeWidth" format="dimension"></attr>
  7. <attr name="isArc" format="boolean"></attr>
  8. <attr name="storkeColor" format="color"></attr>
  9. <attr name="startAngle" format="integer"></attr>
  10. <attr name="sweepAngle" format="integer"></attr>
  11. </declare-styleable>



声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/474146
推荐阅读
相关标签
  

闽ICP备14008679号