Android實現(xiàn)輕量線性與百分比圖表的方法

前言

成都創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),運河企業(yè)網(wǎng)站建設(shè),運河品牌網(wǎng)站建設(shè),網(wǎng)站定制,運河網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,運河網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力。可充分滿足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學習、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。

經(jīng)常要用到圖表統(tǒng)計數(shù)據(jù),在WEB開發(fā)中圖表繪制是一件簡單的事情,因為有比較多的開源方案。但在Android中開源方案并不多。但目前github上有多個關(guān)于圖表的框架,比如MPAndroidChart很好,但是很大,沒必要因為一個小的圖標讓工程項目擴大很多,另外有些輕量級的框架,但是個人感覺都很難滿足自己的需求,再者就算很好的框架,那也是別人的,只有自己動手寫起來,了解前前后后的坑,自己才能成長,而且在寫的過程,我們能發(fā)現(xiàn)更多的細節(jié),比如繪制的時候內(nèi)存分配的問題,Canvas直接繪制和通過Bitmap繪制等等,所以這篇文章的目的:

      1.是給大家提供自定義view繪制的思路

      2.滑動自定義view的部分區(qū)域怎么實現(xiàn)

      3.path動畫繪制的實現(xiàn)

      4.熟悉canvas的api,總之能直接動手了,那就自定義view就通關(guān)了,所以就寫這篇文章主要是鼓勵大家多去實現(xiàn)。

效果圖

Android實現(xiàn)輕量線性與百分比圖表的方法

線性圖表實現(xiàn)的思路:

線性表是最基本、最簡單、也是最常用的一種數(shù)據(jù)結(jié)構(gòu)。線性表中數(shù)據(jù)元素之間的關(guān)系是一對一的關(guān)系,即除了第一個和最后一個數(shù)據(jù)元素之外,其它數(shù)據(jù)元素都是首尾相接的,注意,這句話只適用大部分線性表,而不是全部。

由于屏幕的寬度有限,所以我們一屏經(jīng)過計算,最好顯示的7個點,所以我們首先需要對我們的view寬度進行計算,首先拿到屏幕的寬度,然后再進行/7,得到每個間隔的寬度,然后乘以我們x的坐標點的個數(shù),其中的onMeasure的方法:

 int widthParentMeasureMode = MeasureSpec.getMode(widthMeasureSpec);
 int widthParentMeasureSize = MeasureSpec.getSize(widthMeasureSpec);
 int heightParentMeasureMode = MeasureSpec.getMode(heightMeasureSpec);
 int heightParentMeasureSize = MeasureSpec.getSize(heightMeasureSpec);
 int resultWidthSize = 0;
 int resultHeightSize = 0;
 int resultWidthMode = MeasureSpec.EXACTLY;//用來對childView進行計算的
 int resultHeightMode = MeasureSpec.EXACTLY;
 int paddingWidth = getPaddingLeft() + getPaddingRight();
 int paddingHeight = getPaddingTop() + getPaddingBottom();
 ViewGroup.LayoutParams thisLp = getLayoutParams();
 switch (widthParentMeasureMode) {
  //父類不加限制給子類
  case MeasureSpec.UNSPECIFIED:
   //這個代表在布局寫死了寬度
   if (thisLp.width > 0) {
    resultWidthSize = thisLp.width;
    resultWidthMode = MeasureSpec.EXACTLY;
   } else {
    resultWidthSize = (int) (getYMaxTextWidth() + mXinterval * mXdots.length);
    resultWidthMode = MeasureSpec.UNSPECIFIED;
   }
   break;
  case MeasureSpec.AT_MOST:
   //這個代表在布局寫死了寬度
   if (thisLp.width > 0) {
    resultWidthSize = thisLp.width;
    resultWidthMode = MeasureSpec.EXACTLY;
   } else if (thisLp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
    resultWidthSize = Math.max(0, widthParentMeasureSize - paddingWidth);
    resultWidthMode = MeasureSpec.AT_MOST;
   } else if (thisLp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
    resultWidthSize = (int) (getYMaxTextWidth() + mXinterval * mXdots.length);
    resultWidthMode = MeasureSpec.AT_MOST;
   }
   break;
  case MeasureSpec.EXACTLY:
   //這個代表在布局寫死了寬度
   if (thisLp.width > 0) {
    resultWidthSize = Math.min(widthParentMeasureSize, thisLp.width);
    resultWidthMode = MeasureSpec.EXACTLY;
   } else if (thisLp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
    resultWidthSize = widthParentMeasureSize;
    resultWidthMode = MeasureSpec.EXACTLY;
   } else if (thisLp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
    resultWidthSize = (int) (getYMaxTextWidth() + mXinterval * mXdots.length);
    resultWidthMode = MeasureSpec.AT_MOST;
   }
   break;
 }
 switch (heightParentMeasureMode) {
  //父view不加限制
  case MeasureSpec.UNSPECIFIED:
   //這個代表在布局寫死了寬度
   if (thisLp.height > 0) {
    resultHeightSize = thisLp.height;
    resultHeightMode = MeasureSpec.EXACTLY;
   } else {
    resultHeightSize = (int) (getYMaxTextHeight() + mYvisibleNum * mYinterval + getXMaxTextHeight());
    resultHeightMode = MeasureSpec.UNSPECIFIED;
   }
   break;
  case MeasureSpec.AT_MOST:
   if (thisLp.height > 0) {
    resultHeightSize = heightParentMeasureSize;
    resultHeightMode = MeasureSpec.EXACTLY;
   } else if (thisLp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
    resultHeightSize = Math.max(0, heightParentMeasureSize - paddingHeight);
    resultHeightMode = MeasureSpec.AT_MOST;
   } else if (thisLp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
    resultHeightSize = (int) (getYMaxTextHeight() + mYvisibleNum * mYinterval + getXMaxTextHeight());
    resultHeightMode = MeasureSpec.UNSPECIFIED;
   }
   break;
  case MeasureSpec.EXACTLY:
   //這個代表在布局寫死了寬度
   if (thisLp.height > 0) {
    resultHeightSize = Math.min(heightParentMeasureSize, getMeasuredWidth());
    resultHeightMode = MeasureSpec.EXACTLY;
   } else if (thisLp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
    resultHeightSize = heightParentMeasureSize;
    resultHeightMode = MeasureSpec.EXACTLY;
   } else if (thisLp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
    resultHeightSize = (int) (getYMaxTextHeight() + mYvisibleNum * mYinterval + getXMaxTextHeight());
    resultHeightMode = MeasureSpec.AT_MOST;
   }
   break;
 }
 setMeasuredDimension(MeasureSpec.makeMeasureSpec(resultWidthSize, resultWidthMode),
   MeasureSpec.makeMeasureSpec(resultHeightSize, resultHeightMode));

設(shè)置好了尺寸,我們就可以繪制界面,這里我們onDraw的時候,就依次繪制橫線和豎線,在繪制橫線的時候,將Y坐標的數(shù)字一起繪制上去,同理繪制豎線的時候,把x坐標的數(shù)字繪制上去,折線的畫根據(jù)數(shù)字計算出坐標點,然后創(chuàng)建一個path,首先moveTo(firstX,firstY) ,然后lineTo下面的點就可以了,最后繪制上path,然而這樣的話,我們在滑動的時候,會發(fā)現(xiàn)這個view都會跟著一起滾動了,那么我們怎樣才能實現(xiàn)view的部分pinned呢?在這個時候,我們就需要先創(chuàng)建一個bitmap,將需要滑動的部分繪制到這個bitmap上去,然后bitmap在繪制到這個canvas上的時候,保持固定的位置就行了,好了再說就懵逼了,還是上代碼吧:

 float tempTableLeftPadding = getYMaxTextWidth();
 if (mBitmap == null || mYNumCanvas == null) {
  mBitmap = Bitmap.createBitmap((int) (getMeasuredWidth() - getYMaxTextWidth()), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
  mYNumCanvas = new Canvas(mBitmap);
 }
 mYNumCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
 mYNumCanvas.translate(mScrollPosX,0);//這段代碼就是來實現(xiàn)滑動的操作

 //繪制橫線
 for (int y = 0, size = mYdots.length; y < size; y++) {
  String tempText = String.valueOf(mYdots[mYdots.length - 1 - y]);
  mYNumCanvas.drawLine(0, (float) (mYinterval * y), (float) (mXdots.length * mXinterval), (float) (mYinterval * y), mXlinePaint);
  canvas.drawText(tempText, getYMaxTextWidth() - mYNumPaint.measureText(tempText), getYMaxTextHeight() + (float) (mYinterval * y), mYNumPaint);
 }
 //繪制豎線
 for (int x = 0, size = mXdots.length; x <= size; x++) {
  mYNumCanvas.drawLine((float) (mXinterval * x), 0, (float) (mXinterval * x), (float) (mYinterval * mYvisibleNum), mXlinePaint);
  if (x >= 1) {
   String tempText = mXdots[x - 1];
   mYNumCanvas.drawText(tempText, (float) (mXinterval * x) - mYNumPaint.measureText(tempText) / 2, (float) (mYvisibleNum * mYinterval + getYMaxTextHeight()), mYNumPaint);
  }
 }
 if (isAnimationOpen)//是否需要開啟動畫繪制,這個后面會解釋實現(xiàn)方式
  mYNumCanvas.drawPath(mLineDrawPath, mLinePaint);
 else
  mYNumCanvas.drawPath(mLinePath, mLinePaint);
 canvas.drawBitmap(mBitmap, tempTableLeftPadding, getYMaxTextHeight() / 2, null);

上面的mScrollPosX是根據(jù)手勢監(jiān)聽類GestureDetector來獲取的:

@Override
public boolean onTouchEvent(MotionEvent event) {
 if (!isAnimationOpen || isDrawOver)
  return mGestureDetector.onTouchEvent(event);
 return super.onTouchEvent(event);
}

然而繪制了,我們感覺還缺少了什么,嗯,沒錯就是動畫效果,這里我們用到通過的path繪制實現(xiàn)動畫的方案,就是先通過PathMeasure得到path的長度,然后根據(jù)動畫時間,通過ValueAnimator計算它在某個時刻的坐標,然后重新進行繪制path路徑:

private void startPathAnim(long duration) {
 ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mLineLength);
 valueAnimator.setDuration(duration);
 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
   float value = (Float) animation.getAnimatedValue();
   // 獲取當前點坐標封裝到mCurrentPosition
   mPathMeasure.getPosTan(value, mCurrentPosition, null);
   mLineDrawPath.lineTo(mCurrentPosition[0], mCurrentPosition[1]);
   invalidate();
  }
 });
 valueAnimator.start();
}

百分比圓形圖表實現(xiàn)

Android實現(xiàn)輕量線性與百分比圖表的方法

其實這個的實現(xiàn),相比上一個少了很多,大多是集中在onDraw方法里面,關(guān)鍵點是在百分比的數(shù)字,怎么橫向顯示在扇形區(qū)域,這里我就主要這個計算規(guī)則提出來:

private void drawText(Canvas canvas, float sweepAngle, float startAngle, ArcVo temp) {
 float middleAngle;
 middleAngle = startAngle + sweepAngle / 2;
 float startX;
 float startY;
 float endX;
 float endY;
 String drawText = temp.getPercentInCircle() * 100 + "%";
 if (middleAngle <= 90) {
  //在第四象限
  double angle = middleAngle;
  angle = Math.toRadians(angle);
  startY = endY = (float) (Math.sin(angle) * mRaduis + mRaduis);
  endX = (float) (mRaduis + Math.cos(angle) * mRaduis);
  startX = endX - UiUtils.getTextWidth(drawText, mTextPaint);
 } else if (middleAngle <= 180) {
  //在第三象限
  double angle = 180 - middleAngle;
  angle = Math.toRadians(angle);
  startY = endY = (float) (Math.sin(angle) * mRaduis + mRaduis);
  startX = (float) (mRaduis - Math.cos(angle) * mRaduis);
  endX = startX + UiUtils.getTextWidth(drawText, mTextPaint);
 } else if (middleAngle <= 270) {
  //在第二象限
  double angle = 270 - middleAngle;
  angle = Math.toRadians(angle);
  startY = endY = (float) (mRaduis - Math.cos(angle) * mRaduis);
  startX = (float) (mRaduis - Math.sin(angle) * mRaduis);
  endX = startX + UiUtils.getTextWidth(drawText, mTextPaint);
 } else {
  //在第一象限
  double angle = 360 - middleAngle;
  angle = Math.toRadians(angle);
  startY = endY = (float) (mRaduis - Math.sin(angle) * mRaduis);
  endX = (float) (mRaduis + Math.cos(angle) * mRaduis);
  startX = endX - UiUtils.getTextWidth(drawText, mTextPaint);
 }

 mTextPath.reset();
 mTextPath.moveTo(startX, startY);
 mTextPath.lineTo(endX, endY);
 if (middleAngle > 180) {
  canvas.drawTextOnPath(drawText, mTextPath, 0, UiUtils.getTextHeight(drawText, mTextPaint), mTextPaint);
 } else {
  canvas.drawTextOnPath(drawText, mTextPath, 0, -UiUtils.getTextHeight(drawText, mTextPaint), mTextPaint);

 }
}
 @Override
protected void onDraw(Canvas canvas) {
 if (!canDraw()) return;
 float sweepAngle;
 float startAngle = 0;
 for (int i = 0, size = mDisArcList.size(); i < size; i++) {
  ArcVo temp = mDisArcList.get(i);
  mArcPaint.setColor(temp.getScanColor());
  sweepAngle = temp.getPercentInCircle() * 360;
  canvas.drawArc(mDrawCircleRect, startAngle, sweepAngle, true, mArcPaint);
  drawText(canvas, sweepAngle, startAngle, temp);
  startAngle = startAngle + sweepAngle;
 }
}

使用方式:

如果你覺得你們的項目正好要用到類似的圖標,在項目的gradle文件中,增加compile 'wellijohn.org.simplelinechart:linechart:0.0.2'具體的方法,歡迎移步到github上去看,已經(jīng)封裝成庫上傳至jcenter,上面有具體的使用方法(圖表地址),目前暴露的方法不多,可以留言增加

github地址:https://github.com/WelliJohn/LineChart

本地下載:http://xiazai.jb51.net/201712/yuanma/LineChart(jb51.net).rar

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對創(chuàng)新互聯(lián)的支持。

文章題目:Android實現(xiàn)輕量線性與百分比圖表的方法
文章轉(zhuǎn)載:http://muchs.cn/article34/jpigpe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導航Google、定制開發(fā)、微信公眾號、網(wǎng)站排名網(wǎng)站改版

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作