溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android中怎么自定義可左右滑動和點擊的折線圖

發(fā)布時間:2021-06-15 14:38:37 來源:億速云 閱讀:404 作者:Leah 欄目:移動開發(fā)

這篇文章給大家介紹Android中怎么自定義可左右滑動和點擊的折線圖,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

1、自定義view所需要的屬性

確定所需要的自定義view的屬性,然后在res/values目錄下,新建一個attrs.xml文件,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <!-- xy坐標軸顏色 -->
 <attr name="xylinecolor" format="color" />
 <!-- xy坐標軸寬度 -->
 <attr name="xylinewidth" format="dimension" />
 <!-- xy坐標軸文字顏色 -->
 <attr name="xytextcolor" format="color" />
 <!-- xy坐標軸文字大小 -->
 <attr name="xytextsize" format="dimension" />
 <!-- 折線圖中折線的顏色 -->
 <attr name="linecolor" format="color" />
 <!-- x軸各個坐標點水平間距 -->
 <attr name="interval" format="dimension" />
 <!-- 背景顏色 -->
 <attr name="bgcolor" format="color" />
 <!--是否在ACTION_UP時,根據(jù)速度進行自滑動,建議關(guān)閉,過于占用GPU-->
 <attr name="isScroll" format="boolean" />
 <declare-styleable name="chartView">
  <attr name="xylinecolor" />
  <attr name="xylinewidth" />
  <attr name="xytextcolor" />
  <attr name="xytextsize" />
  <attr name="linecolor" />
  <attr name="interval" />
  <attr name="bgcolor" />
  <attr name="isScroll" />
 </declare-styleable>
</resources>

2、在自定義view的構(gòu)造方法中獲取我們的自定義屬性:

public ChartView(Context context) {
  this(context, null);
 }
 
 public ChartView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }
 
 public ChartView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init(context, attrs, defStyleAttr);
  initPaint();
 }
 
 /**
  * 初始化
  *
  * @param context
  * @param attrs
  * @param defStyleAttr
  */
 private void init(Context context, AttributeSet attrs, int defStyleAttr) {
  TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.chartView, defStyleAttr, 0);
  int count = array.getIndexCount();
  for (int i = 0; i < count; i++) {
   int attr = array.getIndex(i);
   switch (attr) {
    case R.styleable.chartView_xylinecolor://xy坐標軸顏色
     xylinecolor = array.getColor(attr, xylinecolor);
     break;
    case R.styleable.chartView_xylinewidth://xy坐標軸寬度
     xylinewidth = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xylinewidth, getResources().getDisplayMetrics()));
     break;
    case R.styleable.chartView_xytextcolor://xy坐標軸文字顏色
     xytextcolor = array.getColor(attr, xytextcolor);
     break;
    case R.styleable.chartView_xytextsize://xy坐標軸文字大小
     xytextsize = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xytextsize, getResources().getDisplayMetrics()));
     break;
    case R.styleable.chartView_linecolor://折線圖中折線的顏色
     linecolor = array.getColor(attr, linecolor);
     break;
    case R.styleable.chartView_interval://x軸各個坐標點水平間距
     interval = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, interval, getResources().getDisplayMetrics()));
     break;
    case R.styleable.chartView_bgcolor: //背景顏色
     bgcolor = array.getColor(attr, bgcolor);
     break;
    case R.styleable.chartView_isScroll://是否在ACTION_UP時,根據(jù)速度進行自滑動
     isScroll = array.getBoolean(attr, isScroll);
     break;
   }
  }
  array.recycle();
 }
 /**
  * 初始化畫筆
  */
 private void initPaint() {
  xyPaint = new Paint();
  xyPaint.setAntiAlias(true);
  xyPaint.setStrokeWidth(xylinewidth);
  xyPaint.setStrokeCap(Paint.Cap.ROUND);
  xyPaint.setColor(xylinecolor);
  xyTextPaint = new Paint();
  xyTextPaint.setAntiAlias(true);
  xyTextPaint.setTextSize(xytextsize);
  xyTextPaint.setStrokeCap(Paint.Cap.ROUND);
  xyTextPaint.setColor(xytextcolor);
  xyTextPaint.setStyle(Paint.Style.STROKE);
  linePaint = new Paint();
  linePaint.setAntiAlias(true);
  linePaint.setStrokeWidth(xylinewidth);
  linePaint.setStrokeCap(Paint.Cap.ROUND);
  linePaint.setColor(linecolor);
  linePaint.setStyle(Paint.Style.STROKE);
 }

3、獲取一寫基本點

這些基本點包括:xy軸的原點坐標,第一個點的x軸的初始化坐標值以及其最大值和最小值。這些參數(shù)可以在onLayout()方法里面獲取。

 @Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  if (changed) {
   //這里需要確定幾個基本點,只有確定了xy軸原點坐標,第一個點的X坐標值及其最大最小值
   width = getWidth();
   height = getHeight();
   //Y軸文本最大寬度
   float textYWdith = getTextBounds("000", xyTextPaint).width();
   for (int i = 0; i < yValue.size(); i++) {//求取y軸文本最大的寬度
    float temp = getTextBounds(yValue.get(i) + "", xyTextPaint).width();
    if (temp > textYWdith)
     textYWdith = temp;
   }
   int dp2 = dpToPx(2);
   int dp3 = dpToPx(3);
   xOri = (int) (dp2 + textYWdith + dp2 + xylinewidth);//dp2是y軸文本距離左邊,以及距離y軸的距離
//   //X軸文本最大高度
   xValueRect = getTextBounds("000", xyTextPaint);
   float textXHeight = xValueRect.height();
   for (int i = 0; i < xValue.size(); i++) {//求取x軸文本最大的高度
    Rect rect = getTextBounds(xValue.get(i) + "", xyTextPaint);
    if (rect.height() > textXHeight)
     textXHeight = rect.height();
    if (rect.width() > xValueRect.width())
     xValueRect = rect;
   }
   yOri = (int) (height - dp2 - textXHeight - dp3 - xylinewidth);//dp3是x軸文本距離底邊,dp2是x軸文本距離x軸的距離
   xInit = interval + xOri;
   minXInit = width - (width - xOri) * 0.1f - interval * (xValue.size() - 1);//減去0.1f是因為最后一個X周刻度距離右邊的長度為X軸可見長度的10%
   maxXInit = xInit;
  }
  super.onLayout(changed, left, top, right, bottom);
 }

4、利用ondraw()方法進行繪制

 @Override
 protected void onDraw(Canvas canvas) {
//  super.onDraw(canvas);
  canvas.drawColor(bgcolor);
  drawXY(canvas);
  drawBrokenLineAndPoint(canvas);
 }
 
 /**
  * 繪制折線和折線交點處對應(yīng)的點
  *
  * @param canvas
  */
 private void drawBrokenLineAndPoint(Canvas canvas) {
  if (xValue.size() <= 0)
   return;
  //重新開一個圖層
  int layerId = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);
  drawBrokenLine(canvas);
  drawBrokenPoint(canvas);
  // 將折線超出x軸坐標的部分截取掉
  linePaint.setStyle(Paint.Style.FILL);
  linePaint.setColor(bgcolor);
  linePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
  RectF rectF = new RectF(0, 0, xOri, height);
  canvas.drawRect(rectF, linePaint);
  linePaint.setXfermode(null);
  //保存圖層
  canvas.restoreToCount(layerId);
 }
 /**
  * 繪制折線對應(yīng)的點
  *
  * @param canvas
  */
 private void drawBrokenPoint(Canvas canvas) {
  float dp2 = dpToPx(2);
  float dp4 = dpToPx(4);
  float dp7 = dpToPx(7);
  //繪制節(jié)點對應(yīng)的原點
  for (int i = 0; i < xValue.size(); i++) {
   float x = xInit + interval * i;
   float y = yOri - yOri * (1 - 0.1f) * value.get(xValue.get(i)) / yValue.get(yValue.size() - 1);
   //繪制選中的點
   if (i == selectIndex - 1) {
    linePaint.setStyle(Paint.Style.FILL);
    linePaint.setColor(0xffd0f3f2);
    canvas.drawCircle(x, y, dp7, linePaint);
    linePaint.setColor(0xff81dddb);
    canvas.drawCircle(x, y, dp4, linePaint);
    drawFloatTextBox(canvas, x, y - dp7, value.get(xValue.get(i)));
   }
   //繪制普通的節(jié)點
   linePaint.setStyle(Paint.Style.FILL);
   linePaint.setColor(Color.WHITE);
   canvas.drawCircle(x, y, dp2, linePaint);
   linePaint.setStyle(Paint.Style.STROKE);
   linePaint.setColor(linecolor);
   canvas.drawCircle(x, y, dp2, linePaint);
  }
 }
 /**
  * 繪制顯示Y值的浮動框
  *
  * @param canvas
  * @param x
  * @param y
  * @param text
  */
 private void drawFloatTextBox(Canvas canvas, float x, float y, int text) {
  int dp6 = dpToPx(6);
  int dp18 = dpToPx(18);
  //p1
  Path path = new Path();
  path.moveTo(x, y);
  //p2
  path.lineTo(x - dp6, y - dp6);
  //p3
  path.lineTo(x - dp18, y - dp6);
  //p4
  path.lineTo(x - dp18, y - dp6 - dp18);
  //p5
  path.lineTo(x + dp18, y - dp6 - dp18);
  //p6
  path.lineTo(x + dp18, y - dp6);
  //p7
  path.lineTo(x + dp6, y - dp6);
  //p1
  path.lineTo(x, y);
  canvas.drawPath(path, linePaint);
  linePaint.setColor(Color.WHITE);
  linePaint.setTextSize(spToPx(14));
  Rect rect = getTextBounds(text + "", linePaint);
  canvas.drawText(text + "", x - rect.width() / 2, y - dp6 - (dp18 - rect.height()) / 2, linePaint);
 }
 /**
  * 繪制折線
  *
  * @param canvas
  */
 private void drawBrokenLine(Canvas canvas) {
  linePaint.setStyle(Paint.Style.STROKE);
  linePaint.setColor(linecolor);
  //繪制折線
  Path path = new Path();
  float x = xInit + interval * 0;
  float y = yOri - yOri * (1 - 0.1f) * value.get(xValue.get(0)) / yValue.get(yValue.size() - 1);
  path.moveTo(x, y);
  for (int i = 1; i < xValue.size(); i++) {
   x = xInit + interval * i;
   y = yOri - yOri * (1 - 0.1f) * value.get(xValue.get(i)) / yValue.get(yValue.size() - 1);
   path.lineTo(x, y);
  }
  canvas.drawPath(path, linePaint);
 }
 /**
  * 繪制XY坐標
  *
  * @param canvas
  */
 private void drawXY(Canvas canvas) {
  int length = dpToPx(4);//刻度的長度
  //繪制Y坐標
  canvas.drawLine(xOri - xylinewidth / 2, 0, xOri - xylinewidth / 2, yOri, xyPaint);
  //繪制y軸箭頭
  xyPaint.setStyle(Paint.Style.STROKE);
  Path path = new Path();
  path.moveTo(xOri - xylinewidth / 2 - dpToPx(5), dpToPx(12));
  path.lineTo(xOri - xylinewidth / 2, xylinewidth / 2);
  path.lineTo(xOri - xylinewidth / 2 + dpToPx(5), dpToPx(12));
  canvas.drawPath(path, xyPaint);
  //繪制y軸刻度
  int yLength = (int) (yOri * (1 - 0.1f) / (yValue.size() - 1));//y軸上面空出10%,計算出y軸刻度間距
  for (int i = 0; i < yValue.size(); i++) {
   //繪制Y軸刻度
   canvas.drawLine(xOri, yOri - yLength * i + xylinewidth / 2, xOri + length, yOri - yLength * i + xylinewidth / 2, xyPaint);
   xyTextPaint.setColor(xytextcolor);
   //繪制Y軸文本
   String text = yValue.get(i) + "";
   Rect rect = getTextBounds(text, xyTextPaint);
   canvas.drawText(text, 0, text.length(), xOri - xylinewidth - dpToPx(2) - rect.width(), yOri - yLength * i + rect.height() / 2, xyTextPaint);
  }
  //繪制X軸坐標
  canvas.drawLine(xOri, yOri + xylinewidth / 2, width, yOri + xylinewidth / 2, xyPaint);
  //繪制x軸箭頭
  xyPaint.setStyle(Paint.Style.STROKE);
  path = new Path();
  //整個X軸的長度
  float xLength = xInit + interval * (xValue.size() - 1) + (width - xOri) * 0.1f;
  if (xLength < width)
   xLength = width;
  path.moveTo(xLength - dpToPx(12), yOri + xylinewidth / 2 - dpToPx(5));
  path.lineTo(xLength - xylinewidth / 2, yOri + xylinewidth / 2);
  path.lineTo(xLength - dpToPx(12), yOri + xylinewidth / 2 + dpToPx(5));
  canvas.drawPath(path, xyPaint);
  //繪制x軸刻度
  for (int i = 0; i < xValue.size(); i++) {
   float x = xInit + interval * i;
   if (x >= xOri) {//只繪制從原點開始的區(qū)域
    xyTextPaint.setColor(xytextcolor);
    canvas.drawLine(x, yOri, x, yOri - length, xyPaint);
    //繪制X軸文本
    String text = xValue.get(i);
    Rect rect = getTextBounds(text, xyTextPaint);
    if (i == selectIndex - 1) {
     xyTextPaint.setColor(linecolor);
     canvas.drawText(text, 0, text.length(), x - rect.width() / 2, yOri + xylinewidth + dpToPx(2) + rect.height(), xyTextPaint);
     canvas.drawRoundRect(x - xValueRect.width() / 2 - dpToPx(3), yOri + xylinewidth + dpToPx(1), x + xValueRect.width() / 2 + dpToPx(3), yOri + xylinewidth + dpToPx(2) + xValueRect.height() + dpToPx(2), dpToPx(2), dpToPx(2), xyTextPaint);
    } else {
     canvas.drawText(text, 0, text.length(), x - rect.width() / 2, yOri + xylinewidth + dpToPx(2) + rect.height(), xyTextPaint);
    }
   }
  }
 }

5、點擊的處理以及左右

重寫ontouchEven()方法,來處理點擊和滑動

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  if (isScrolling)
   return super.onTouchEvent(event);
  this.getParent().requestDisallowInterceptTouchEvent(true);//當該view獲得點擊事件,就請求父控件不攔截事件
  obtainVelocityTracker(event);
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN:
    startX = event.getX();
    break;
   case MotionEvent.ACTION_MOVE:
    if (interval * xValue.size() > width - xOri) {//當期的寬度不足以呈現(xiàn)全部數(shù)據(jù)
     float dis = event.getX() - startX;
     startX = event.getX();
     if (xInit + dis < minXInit) {
      xInit = minXInit;
     } else if (xInit + dis > maxXInit) {
      xInit = maxXInit;
     } else {
      xInit = xInit + dis;
     }
     invalidate();
    }
    break;
   case MotionEvent.ACTION_UP:
    clickAction(event);
    scrollAfterActionUp();
    this.getParent().requestDisallowInterceptTouchEvent(false);
    recycleVelocityTracker();
    break;
   case MotionEvent.ACTION_CANCEL:
    this.getParent().requestDisallowInterceptTouchEvent(false);
    recycleVelocityTracker();
    break;
  }
  return true;
 }

點擊的處理是計算當前點擊的X、Y坐標范圍進行判斷點擊的是那個點

 /**
  * 點擊X軸坐標或者折線節(jié)點
  *
  * @param event
  */
 private void clickAction(MotionEvent event) {
  int dp8 = dpToPx(8);
  float eventX = event.getX();
  float eventY = event.getY();
  for (int i = 0; i < xValue.size(); i++) {
   //節(jié)點
   float x = xInit + interval * i;
   float y = yOri - yOri * (1 - 0.1f) * value.get(xValue.get(i)) / yValue.get(yValue.size() - 1);
   if (eventX >= x - dp8 && eventX <= x + dp8 &&
     eventY >= y - dp8 && eventY <= y + dp8 && selectIndex != i + 1) {//每個節(jié)點周圍8dp都是可點擊區(qū)域
    selectIndex = i + 1;
    invalidate();
    return;
   }
   //X軸刻度
   String text = xValue.get(i);
   Rect rect = getTextBounds(text, xyTextPaint);
   x = xInit + interval * i;
   y = yOri + xylinewidth + dpToPx(2);
   if (eventX >= x - rect.width() / 2 - dp8 && eventX <= x + rect.width() + dp8 / 2 &&
     eventY >= y - dp8 && eventY <= y + rect.height() + dp8 && selectIndex != i + 1) {
    selectIndex = i + 1;
    invalidate();
    return;
   }
  }
 }

處理滑動的原理,就是通過改變第一個點的X坐標,通過改變這個基本點,依次改變后面的X軸的點的坐標。

最后在布局里面應(yīng)用就可以啦,我就不貼代碼啦!

總結(jié):

項目還是有缺點的:

(1)左右滑動時,抬起手指仍然可以快速滑動;代碼里面給出了一種解決方案,但是太過于暫用資源,沒有特殊要求不建議使用,所以給出一個boolean類型的自定義屬性isScroll,true:啟動,反之亦然;還有一種解決方案就是外面再加一層橫向ScrollView,請讀者自行解決,也很簡單,只需要稍作修改即可。

(2)點擊的時候忘記添加回調(diào),只有添加了回調(diào)在可以在activity或者fragment里面獲取點擊的內(nèi)容;代碼很簡單,自行腦補。

關(guān)于Android中怎么自定義可左右滑動和點擊的折線圖就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI