国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 系統(tǒng) > Android > 正文

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

2019-10-22 18:20:52
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

前言

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

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

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

      3.path動(dòng)畫繪制的實(shí)現(xiàn)

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

效果圖

android,線性布局,百分比,圖表

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

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

由于屏幕的寬度有限,所以我們一屏經(jīng)過計(jì)算,最好顯示的7個(gè)點(diǎn),所以我們首先需要對(duì)我們的view寬度進(jìn)行計(jì)算,首先拿到屏幕的寬度,然后再進(jìn)行/7,得到每個(gè)間隔的寬度,然后乘以我們x的坐標(biāo)點(diǎn)的個(gè)數(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;//用來(lái)對(duì)childView進(jìn)行計(jì)算的 int resultHeightMode = MeasureSpec.EXACTLY; int paddingWidth = getPaddingLeft() + getPaddingRight(); int paddingHeight = getPaddingTop() + getPaddingBottom(); ViewGroup.LayoutParams thisLp = getLayoutParams(); switch (widthParentMeasureMode) {  //父類不加限制給子類  case MeasureSpec.UNSPECIFIED:   //這個(gè)代表在布局寫死了寬度   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:   //這個(gè)代表在布局寫死了寬度   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:   //這個(gè)代表在布局寫死了寬度   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:   //這個(gè)代表在布局寫死了寬度   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:   //這個(gè)代表在布局寫死了寬度   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的時(shí)候,就依次繪制橫線和豎線,在繪制橫線的時(shí)候,將Y坐標(biāo)的數(shù)字一起繪制上去,同理繪制豎線的時(shí)候,把x坐標(biāo)的數(shù)字繪制上去,折線的畫根據(jù)數(shù)字計(jì)算出坐標(biāo)點(diǎn),然后創(chuàng)建一個(gè)path,首先moveTo(firstX,firstY) ,然后lineTo下面的點(diǎn)就可以了,最后繪制上path,然而這樣的話,我們?cè)诨瑒?dòng)的時(shí)候,會(huì)發(fā)現(xiàn)這個(gè)view都會(huì)跟著一起滾動(dòng)了,那么我們?cè)鯓硬拍軐?shí)現(xiàn)view的部分pinned呢?在這個(gè)時(shí)候,我們就需要先創(chuàng)建一個(gè)bitmap,將需要滑動(dòng)的部分繪制到這個(gè)bitmap上去,然后bitmap在繪制到這個(gè)canvas上的時(shí)候,保持固定的位置就行了,好了再說(shuō)就懵逼了,還是上代碼吧:

 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);//這段代碼就是來(lái)實(shí)現(xiàn)滑動(dòng)的操作 //繪制橫線 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)//是否需要開啟動(dòng)畫繪制,這個(gè)后面會(huì)解釋實(shí)現(xiàn)方式  mYNumCanvas.drawPath(mLineDrawPath, mLinePaint); else  mYNumCanvas.drawPath(mLinePath, mLinePaint); canvas.drawBitmap(mBitmap, tempTableLeftPadding, getYMaxTextHeight() / 2, null);

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

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

然而繪制了,我們感覺還缺少了什么,嗯,沒錯(cuò)就是動(dòng)畫效果,這里我們用到通過的path繪制實(shí)現(xiàn)動(dòng)畫的方案,就是先通過PathMeasure得到path的長(zhǎng)度,然后根據(jù)動(dòng)畫時(shí)間,通過ValueAnimator計(jì)算它在某個(gè)時(shí)刻的坐標(biāo),然后重新進(jìn)行繪制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();   // 獲取當(dāng)前點(diǎn)坐標(biāo)封裝到mCurrentPosition   mPathMeasure.getPosTan(value, mCurrentPosition, null);   mLineDrawPath.lineTo(mCurrentPosition[0], mCurrentPosition[1]);   invalidate();  } }); valueAnimator.start();}

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

android,線性布局,百分比,圖表

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

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); }} @Overrideprotected 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; }}

使用方式:

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

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

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)VEVB武林網(wǎng)的支持。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到Android開發(fā)頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 隆安县| 天镇县| 穆棱市| 囊谦县| 唐河县| 双峰县| 洛扎县| 镇雄县| 丰都县| 壶关县| 纳雍县| 鲁甸县| 巴马| 潜山县| 酒泉市| 灌云县| 来宾市| 民勤县| 敦化市| 漳州市| 饶平县| 赫章县| 海丰县| 宜章县| 淮滨县| 通河县| 通榆县| 阿拉尔市| 手机| 什邡市| 柏乡县| 婺源县| 本溪市| 攀枝花市| 巢湖市| 那曲县| 治多县| 施秉县| 常德市| 青阳县| 巴林右旗|