Android自定義柱狀圖表效果

Linux編程

點擊右側關注,免費入門到精通!

作者丨亂世白衣

https://www.jianshu.com/p/1595ce6aa3a7

本文通過示例代碼介紹如何自定義簡單的直方圖表,此圖表並非常見的直方圖表,而是可以分組的。此文不會過多涉及原理,比較簡單,示例圖片如下(gif圖片沒有製作好,有閃爍,請見諒):

對於該示例的代碼做到,其實重點在於坐標軸、文字、直方圖的位置控制,需要隨滑動距離而動態更新。注意事項會在示例代碼中標註。下面貼出示例代碼

publicclassMultiGroupHistogramViewextendsView{privateintwidth;privateintheight;//坐標軸線寬度privateintcoordinateAxisWidth;//組名稱字體大小privateintgroupNameTextSize;//小組之間間距privateintgroupInterval;//組內子直方圖間距privateinthistogramInterval;privateinthistogramValueTextSize;//圖表數值小數點位數privateinthistogramValueDecimalCount;privateinthistogramHistogramWidth;privateintchartPaddingTop;privateinthistogramPaddingStart;privateinthistogramPaddingEnd;//各組名稱到X軸的距離privateintdistanceFormGroupNameToAxis;//直方圖上方數值到直方圖的距離privateintdistanceFromValueToHistogram;//直方圖最大高度privateintmaxHistogramHeight;//軸線畫筆privatePaintcoordinateAxisPaint;//組名畫筆privatePaintgroupNamePaint;privatePaint.FontMetricsgroupNameFontMetrics;privatePaint.FontMetricshistogramValueFontMetrics;//直方圖數值畫筆privatePainthistogramValuePaint;//直方圖畫筆privatePainthistogramPaint;//直方圖繪制區域privateRecthistogramPaintRect;//直方圖表視圖總寬度privateinthistogramContentWidth;//存儲組內直方圖shadercolor,例如,每組有3個直方圖,該SparseArray就存儲3個相對應的shadercolorprivateSparseArray<int[]>histogramShaderColorArray;privateList<MultiGroupHistogramGroupData>dataList;privateSparseArray<Float>childMaxValueArray;privateScrollerscroller;privateintminimumVelocity;privateintmaximumVelocity;privateVelocityTrackervelocityTracker;publicMultiGroupHistogramView(Contextcontext){this(context,null);}publicMultiGroupHistogramView(Contextcontext,@NullableAttributeSetattrs){this(context,attrs,0);}publicMultiGroupHistogramView(Contextcontext,@NullableAttributeSetattrs,intdefStyleAttr){super(context,attrs,defStyleAttr);init(attrs);}privatevoidinit(AttributeSetattrs){setLayerType(View.LAYER_TYPE_HARDWARE,null);TypedArraytypedArray=getContext().obtainStyledAttributes(attrs,R.styleable.MultiGroupHistogramView);coordinateAxisWidth=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_coordinateAxisWidth,DisplayUtil.dp2px(2));//坐標軸線顏色intcoordinateAxisColor=typedArray.getColor(R.styleable.MultiGroupHistogramView_coordinateAxisColor,Color.parseColor("#434343"));//底部小組名稱字體顏色intgroupNameTextColor=typedArray.getColor(R.styleable.MultiGroupHistogramView_groupNameTextColor,Color.parseColor("#CC202332"));groupNameTextSize=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_groupNameTextSize,DisplayUtil.dp2px(15));groupInterval=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_groupInterval,DisplayUtil.dp2px(30));histogramInterval=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_histogramInterval,DisplayUtil.dp2px(10));//直方圖數值文本顏色inthistogramValueTextColor=typedArray.getColor(R.styleable.MultiGroupHistogramView_histogramValueTextColor,Color.parseColor("#CC202332"));histogramValueTextSize=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_histogramValueTextSize,DisplayUtil.dp2px(12));histogramValueDecimalCount=typedArray.getInt(R.styleable.MultiGroupHistogramView_histogramValueDecimalCount,0);histogramHistogramWidth=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_histogramHistogramWidth,DisplayUtil.dp2px(20));chartPaddingTop=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_chartPaddingTop,DisplayUtil.dp2px(10));histogramPaddingStart=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_histogramPaddingStart,DisplayUtil.dp2px(15));histogramPaddingEnd=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_histogramPaddingEnd,DisplayUtil.dp2px(15));distanceFormGroupNameToAxis=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_distanceFormGroupNameToAxis,DisplayUtil.dp2px(15));distanceFromValueToHistogram=typedArray.getDimensionPixelSize(R.styleable.MultiGroupHistogramView_distanceFromValueToHistogram,DisplayUtil.dp2px(10));typedArray.recycle();coordinateAxisPaint=newPaint(Paint.ANTI_ALIAS_FLAG);coordinateAxisPaint.setStyle(Paint.Style.FILL);coordinateAxisPaint.setStrokeWidth(coordinateAxisWidth);coordinateAxisPaint.setColor(coordinateAxisColor);groupNamePaint=newPaint(Paint.ANTI_ALIAS_FLAG);groupNamePaint.setTextSize(groupNameTextSize);groupNamePaint.setColor(groupNameTextColor);groupNameFontMetrics=groupNamePaint.getFontMetrics();histogramValuePaint=newPaint(Paint.ANTI_ALIAS_FLAG);histogramValuePaint.setTextSize(histogramValueTextSize);histogramValuePaint.setColor(histogramValueTextColor);histogramValueFontMetrics=histogramValuePaint.getFontMetrics();histogramPaintRect=newRect();histogramPaint=newPaint(Paint.ANTI_ALIAS_FLAG);scroller=newScroller(getContext(),newLinearInterpolator());ViewConfigurationconfiguration=ViewConfiguration.get(getContext());minimumVelocity=configuration.getScaledMinimumFlingVelocity();maximumVelocity=configuration.getScaledMaximumFlingVelocity();}@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){super.onMeasure(widthMeasureSpec,heightMeasureSpec);width=getMeasuredWidth();height=getMeasuredHeight();maxHistogramHeight=height-groupNameTextSize-coordinateAxisWidth-distanceFormGroupNameToAxis-distanceFromValueToHistogram-histogramValueTextSize-chartPaddingTop;}/***判斷是否可以水平滑動*@paramdirection標識滑動方向正數:右滑(手指從右至左移動);負數:左滑(手指由左向右移動)*您可參考ScaollView或HorizontalScrollView理解滑動方向*/@OverridepublicbooleancanScrollHorizontally(intdirection){if(direction>0){returnhistogramContentWidth-getScrollX()-width+histogramPaddingStart+histogramPaddingEnd>0;}else{returngetScrollX()>0;}}/***根據滑動方向獲取最大可滑動距離*@paramdirection標識滑動方向正數:右滑(手指從右至左移動);負數:左滑(手指由左向右移動)*您可參考ScaollView或HorizontalScrollView理解滑動方向*/privateintgetMaxCanScrollX(intdirection){if(direction>0){returnhistogramContentWidth-getScrollX()-width+histogramPaddingStart+histogramPaddingEnd>0?histogramContentWidth-getScrollX()-width+histogramPaddingStart+histogramPaddingEnd:0;}elseif(direction<0){returngetScrollX();}return0;}privatefloatlastX;@OverridepublicbooleanonTouchEvent(MotionEventevent){initVelocityTrackerIfNotExists();velocityTracker.addMovement(event);switch(event.getAction()){caseMotionEvent.ACTION_DOWN:{if(!scroller.isFinished()){scroller.abortAnimation();}lastX=event.getX();returntrue;}caseMotionEvent.ACTION_MOVE:{intdeltaX=(int)(event.getX()-lastX);lastX=event.getX();//滑動處理if(deltaX>0&&canScrollHorizontally(-1)){scrollBy(-Math.min(getMaxCanScrollX(-1),deltaX),0);}elseif(deltaX<0&&canScrollHorizontally(1)){scrollBy(Math.min(getMaxCanScrollX(1),-deltaX),0);}break;}caseMotionEvent.ACTION_UP:{velocityTracker.computeCurrentVelocity(1000,maximumVelocity);intvelocityX=(int)velocityTracker.getXVelocity();fling(velocityX);recycleVelocityTracker();break;}caseMotionEvent.ACTION_CANCEL:{recycleVelocityTracker();break;}}returnsuper.onTouchEvent(event);}privatevoidinitVelocityTrackerIfNotExists(){if(velocityTracker==null){velocityTracker=VelocityTracker.obtain();}}privatevoidrecycleVelocityTracker(){if(velocityTracker!=null){velocityTracker.recycle();velocityTracker=null;}}//ACTION_UP事件觸發privatevoidfling(intvelocityX){if(Math.abs(velocityX)>minimumVelocity){if(Math.abs(velocityX)>maximumVelocity){velocityX=maximumVelocity*velocityX/Math.abs(velocityX);}scroller.fling(getScrollX(),getScrollY(),-velocityX,0,0,histogramContentWidth+histogramPaddingStart-width,0,0);}}@OverridepublicvoidcomputeScroll(){if(scroller.computeScrollOffset()){scrollTo(scroller.getCurrX(),0);}}publicvoidsetDataList(@NonNullList<MultiGroupHistogramGroupData>dataList){this.dataList=dataList;if(childMaxValueArray==null){childMaxValueArray=newSparseArray<>();}else{childMaxValueArray.clear();}histogramContentWidth=0;for(MultiGroupHistogramGroupDatagroupData:dataList){List<MultiGroupHistogramChildData>childDataList=groupData.getChildDataList();if(childDataList!=null&&childDataList.size()>0){for(inti=0;i<childDataList.size();i++){histogramContentWidth+=histogramHistogramWidth+histogramInterval;MultiGroupHistogramChildDatachildData=childDataList.get(i);FloatchildMaxValue=childMaxValueArray.get(i);if(childMaxValue==null||childMaxValue<childData.getValue()){childMaxValueArray.put(i,childData.getValue());}}histogramContentWidth+=groupInterval-histogramInterval;}}histogramContentWidth+=-groupInterval;postInvalidate();}/***設置組內直方圖顏色(並不是設置所有直方圖顏色,而是根據每組數據內直方圖數量設置)*/publicvoidsetHistogramColor(int[]...colors){if(colors!=null&&colors.length>0){if(histogramShaderColorArray==null){histogramShaderColorArray=newSparseArray<>();}else{histogramShaderColorArray.clear();}for(inti=0;i<colors.length;i++){histogramShaderColorArray.put(i,colors[i]);}}}@OverrideprotectedvoidonDraw(Canvascanvas){if(width==0||height==0){return;}intscrollX=getScrollX();intaxisBottom=height-groupNameTextSize-distanceFormGroupNameToAxis-coordinateAxisWidth/2;canvas.drawLine(coordinateAxisWidth/2+scrollX,0,coordinateAxisWidth/2+scrollX,axisBottom,coordinateAxisPaint);canvas.drawLine(scrollX,axisBottom,width+scrollX,axisBottom,coordinateAxisPaint);if(dataList!=null&&dataList.size()>0){intxAxisOffset=histogramPaddingStart;//每個直方圖在x軸的偏移量for(MultiGroupHistogramGroupDatagroupData:dataList){List<MultiGroupHistogramChildData>childDataList=groupData.getChildDataList();if(childDataList!=null&&childDataList.size()>0){intgroupWidth=0;for(inti=0;i<childDataList.size();i++){MultiGroupHistogramChildDatachildData=childDataList.get(i);histogramPaintRect.left=xAxisOffset;histogramPaintRect.right=histogramPaintRect.left+histogramHistogramWidth;intchildHistogramHeight;if(childData.getValue()<=0||childMaxValueArray.get(i)<=0){childHistogramHeight=0;}else{childHistogramHeight=(int)(childData.getValue()/childMaxValueArray.get(i)*maxHistogramHeight);}histogramPaintRect.top=height-childHistogramHeight-coordinateAxisWidth-distanceFormGroupNameToAxis-groupNameTextSize;histogramPaintRect.bottom=histogramPaintRect.top+childHistogramHeight;int[]histogramShaderColor=histogramShaderColorArray.get(i);LinearGradientshader=null;if(histogramShaderColor!=null&&histogramShaderColor.length>0){shader=getHistogramShader(histogramPaintRect.left,chartPaddingTop+distanceFromValueToHistogram+histogramValueTextSize,histogramPaintRect.right,histogramPaintRect.bottom,histogramShaderColor);}histogramPaint.setShader(shader);canvas.drawRect(histogramPaintRect,histogramPaint);StringchildHistogramHeightValue=StringUtil.NumericScaleByFloor(String.valueOf(childData.getValue()),histogramValueDecimalCount)+childData.getSuffix();floatvalueTextX=xAxisOffset+(histogramHistogramWidth-histogramValuePaint.measureText(childHistogramHeightValue))/2;//數值繪制Y軸位置特別處理floatvalueTextY=histogramPaintRect.top-distanceFormGroupNameToAxis+(histogramValueFontMetrics.bottom)/2;canvas.drawText(childHistogramHeightValue,valueTextX,valueTextY,histogramValuePaint);intdeltaX=i<childDataList.size()-1?histogramHistogramWidth+histogramInterval:histogramHistogramWidth;groupWidth+=deltaX;//注意此處偏移量累加xAxisOffset+=i==childDataList.size()-1?deltaX+groupInterval:deltaX;}StringgroupName=groupData.getGroupName();floatgroupNameTextWidth=groupNamePaint.measureText(groupName);floatgroupNameTextX=xAxisOffset-groupWidth-groupInterval+(groupWidth-groupNameTextWidth)/2;//組名繪制Y軸位置特別處理floatgroupNameTextY=(height-groupNameFontMetrics.bottom/2);canvas.drawText(groupName,groupNameTextX,groupNameTextY,groupNamePaint);}}}}privateLinearGradientgetHistogramShader(floatx0,floaty0,floatx1,floaty1,int[]colors){returnnewLinearGradient(x0,y0,x1,y1,colors,null,Shader.TileMode.CLAMP);}}

代碼就這一點,閱讀起來應該不難,如有疑問歡迎留言

自定義屬性如下:

<declare-styleablename="MultiGroupHistogramView"><attrname="coordinateAxisWidth"format="dimension"/><attrname="coordinateAxisColor"format="color"/><attrname="groupNameTextColor"format="color"/><attrname="groupNameTextSize"format="dimension"/><attrname="groupInterval"format="dimension"/><attrname="histogramInterval"format="dimension"/><attrname="histogramValueTextColor"format="color"/><attrname="histogramValueTextSize"format="dimension"/><attrname="histogramHistogramWidth"format="dimension"/><attrname="histogramPaddingStart"format="dimension"/><attrname="histogramPaddingEnd"format="dimension"/><attrname="chartPaddingTop"format="dimension"/><attrname="distanceFormGroupNameToAxis"format="dimension"/><attrname="distanceFromValueToHistogram"format="dimension"/><!--圖表數值小數點位數--><attrname="histogramValueDecimalCount"><enumname="ZERO"value="0"/><enumname="ONE"value="1"/><enumname="TWO"value="2"/></attr></declare-styleable>

下面貼出使用方法:

privatevoidinitMultiGroupHistogramView(){Randomrandom=newRandom();intgroupSize=random.nextInt(5)+10;List<MultiGroupHistogramGroupData>groupDataList=newArrayList<>();//生成測試數據for(inti=0;i<groupSize;i++){List<MultiGroupHistogramChildData>childDataList=newArrayList<>();MultiGroupHistogramGroupDatagroupData=newMultiGroupHistogramGroupData();groupData.setGroupName("第"+(i+1)+"組");MultiGroupHistogramChildDatachildData1=newMultiGroupHistogramChildData();childData1.setSuffix("分");childData1.setValue(random.nextInt(50)+51);childDataList.add(childData1);MultiGroupHistogramChildDatachildData2=newMultiGroupHistogramChildData();childData2.setSuffix("%");childData2.setValue(random.nextInt(50)+51);childDataList.add(childData2);groupData.setChildDataList(childDataList);groupDataList.add(groupData);}multiGroupHistogramView.setDataList(groupDataList);int[]color1=newint[]{getResources().getColor(R.color.color_orange),getResources().getColor(R.color.colorPrimary)};int[]color2=newint[]{getResources().getColor(R.color.color_supper_tip_normal),getResources().getColor(R.color.bg_supper_selected)};//設置直方圖顏色multiGroupHistogramView.setHistogramColor(color1,color2);}

完整示例:

https://github.com/670832188/TestApp

推薦↓↓↓

?16個技術公眾號】都在這里!

涵蓋:工程師大咖、源碼共讀、工程師共讀、數據結構與算法、黑客技術和網路安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、數據庫研發、幽默工程師等。

萬水千山總是情,點個 「好看」 行不行

分享到Facebook

 


不知道如何找適合的對象?歡迎加官方LINE → Line ID:@shesay
戀愛小秘書免費一對一諮詢!
✔追蹤我的YouTube:https://www.youtube.com/@datenami
✔追蹤我的TikTok:https://www.tiktok.com/@datnami

 

配對成功的關鍵:參加實體交友活動

erose主題派對與戀愛小秘書創辦人娜米表示:「透過各種有趣的實體活動,不僅能親眼真實見到異性,也能在活動進行中讓大家很輕鬆自然的認識彼此、聊天互動,能更快速的找到適合的對象。」

結合大數據用心篩選 + 客製化條件配對

戀愛小秘書團隊已經成功替4000位以上的未婚男女配對成功,這個驚人成果背後的秘密在於「高度客製化服務」,跟每位客戶深度訪談,瞭解客戶真正的特質及需求,從「契合度」提高速配率。

訪談結果結合專屬的人格分析測驗與數據配對分析,精緻化的操作,締造高速配率!

除此之外,戀愛小秘書團隊還會定期追蹤客戶的後續狀況,目的是希望協助客戶發展長期且穩定的伴侶關係。

實名認證防造假!隱私保護最安心!

採用「實名認證」的制度,不僅是把關顧客的身份,避免已婚人士或動機不單純者的加入,更對客戶資料嚴格保密,讓客戶們能在安全且有隱私的狀況下認識另一半。

多元有趣的主題活動,豐富你的社交生活

戀愛小秘書團隊每個月都會規劃豐富多元的實體活動,從戶外踏青、娛樂遊戲、手作、料理課程到桌遊活動,希望客戶們能從歡樂的氣氛中認識彼此。

透過實體活動讓大家先有初步的接觸,然後再為會員們做「客製化」的約會安排。

另外針對想提升自身魅力的客戶,也有投資理財、形象穿搭等講座可供選擇。

追求脫單,先勇敢跨出你的第一步

許多單身者為了心中理想的對象條件,在還沒認識新朋友時,就先限制了自己。建議以認識新朋友的心態,積極參與活動,並適當的設限,才能真正為自己帶來戀愛的機會!勇敢跨出第一步吧!

♡ 現在就和戀愛小秘書娜米聊聊吧Line ID:@shesay

♡ 追蹤娜米的臉書粉絲團

她來報好康

 

SheSay 專注在 兩性、愛情等領域
建立專屬女生觀點的品牌形象
堅持「在第一時間掌握男女的時事議題」
將時下最流行的話題網羅、呈現。

馬上測算你的戀愛密碼

戀愛小秘書-娜米

單身很久?一直被分手?
從生日就看出你的戀愛疑難雜症!
娜米的戀愛數字密碼來幫你了。