<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本文範例為大家分享了Android自定義圓角柱狀圖的具體程式碼,供大家參考,具體內容如下
畫一個圓角柱狀圖,顯示12個月的資料,Y軸資料動態分割,如果是當前月,就畫出當前月圖片;點選柱狀圖變色,並顯示虛線彈出當前月資訊,滑動時彈框和虛線消失,柱狀圖重新整理到最初。
1.HistogramRound
package com.broker.liming.widget; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import com.broker.liming.R; import com.broker.liming.bean.ChartData; import com.broker.liming.utils.ArithUtil; import com.broker.liming.utils.LogUtil2; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; /** * @author chenhuirong * @Date 2018/12/10 * @Description zhu */ public class HistogramRound extends ChartYdate { private int[] rect_color; private int rect_text_color;//矩形文字顏色 private int rect_big_text_color;//矩形最大文字顏色 private int rect_text_size;//矩形文字尺寸 private getNumberListener listener; private int selectIndex = -1; private int yHeightSelect=0; private int leftSelector=0; private boolean isScrollow;//true 滑動 false點選 private List<Integer> selectIndexRoles = new ArrayList<>(); private List<Object> objects=new ArrayList<>(); public HistogramRound(Context context) { super(context); } public HistogramRound(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initStyle(context,attrs); } public HistogramRound(Context context, AttributeSet attrs) { super(context, attrs); initStyle(context,attrs); } /* * 初始化樣式屬性 */ private void initStyle(Context context,AttributeSet attrs) { TypedArray types = context.obtainStyledAttributes(attrs, R.styleable.zqxchart_histogram); coordinates_color = types.getColor(R.styleable.zqxchart_histogram_hCoordinatesColor, Color.RED); rect_text_color = types.getColor(R.styleable.zqxchart_histogram_rectTextColor,Color.BLACK); rect_big_text_color=getResources().getColor(R.color.text_yellow); rect_text_size = types.getInteger(R.styleable.zqxchart_histogram_rectTextSize,28); x_text_color = types.getColor(R.styleable.zqxchart_histogram_hxTextColor,Color.BLACK); y_text_color = types.getColor(R.styleable.zqxchart_histogram_hyTextColor,Color.BLACK); x_text_size = types.getInteger(R.styleable.zqxchart_histogram_hxTextSize,30); y_text_size = types.getInteger(R.styleable.zqxchart_histogram_hyTextSize,28); xpCount = types.getInteger(R.styleable.zqxchart_histogram_hxPointCount,12); ypCount = types.getInteger(R.styleable.zqxchart_histogram_hyPointCount,12); rect_color = getResources().getIntArray(R.array.histogram_color); animType = types.getInteger(R.styleable.zqxchart_histogram_hanimType, Anim.ANIM_NONE); types.recycle(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (xData!=null&&yData!=null){ drawHistogramRound(canvas); } } /* * 畫柱狀圖 */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void drawHistogramRound(Canvas canvas) { //如果沒有設定x軸資料 if (xData == null){ throw new NullPointerException("x軸資料來源不能為空!"); } //如果沒有設定y軸資料 if (yData == null){ throw new NullPointerException("y軸資料來源不能為空!"); } Paint histogramPaint = new Paint(); // histogramPaint.setAntiAlias(true); histogramPaint.setStyle(Paint.Style.FILL); histogramPaint.setStrokeWidth((float) 4.0); histogramPaint.setStrokeCap(Paint.Cap.ROUND); //矩形上具體資料畫筆 Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // tv_mudi.setTextSize(TypedValue.COMPLEX_UNIT_PX,getResources().getDimension(R.dimen.font_13)); // textPaint.setTextSize(rect_text_size); Paint mPaintCircle = new Paint(Paint.ANTI_ALIAS_FLAG); // mPaintCircle.setStyle(Paint.Style.ANTI_ALIAS_FLAG); // mPaintCircle.setStrokeWidth(2); mPaintCircle.setColor(Color.parseColor("#ff5932")); mPaintCircle.setPathEffect(new DashPathEffect(new float[]{4, 4}, 0)); mPaintCircle.setAntiAlias(true); textPaint.setTextSize(getResources().getDimension(R.dimen.font_9)); DecimalFormat formater = new DecimalFormat("0.000"); for (int i=0; i<xpCount; i++){ try { histogramPaint.setColor(rect_color == null ? getResources().getColor(R.color.colorPrimary) : rect_color[i]); }catch (ArrayIndexOutOfBoundsException e){ histogramPaint.setColor(getResources().getColor(R.color.colorPrimary)); } int alpha = anims[i].getAlpha(); textPaint.setAlpha(alpha); histogramPaint.setAlpha(alpha); //計算執行動畫當前y座標 float top = anims[i].getCurrentY(); float left = oX+xCoordinates[i]-xSpacing/5; float right = oX+xCoordinates[i]+xSpacing/3; String a = formater.format(yData[i]); String ydata=a.substring(0,a.indexOf(".")); int[] textSize = getTextSize(xData[i],textPaint); float textY = top - textSize[1]/2; LogUtil2.log("yMax-------"+yMax+"--ydata--"+ydata); if (Float.valueOf(ydata)>=yMax){ textPaint.setColor(rect_big_text_color); }else { textPaint.setColor(rect_text_color); } LogUtil2.log("yData--------------->ydataY軸上的資料"+ydata); //畫矩形上文字 // canvas.drawText(ydata,left+20,textY,textPaint); //計算每條柱狀圖的寬度 // double tempBarWidth = (((int) mXinterval) >> 1) / mCategoryList.size(); // mBarPaint.setStrokeWidth((float) tempBarWidth); if (selectIndex==i){ histogramPaint.setColor(ContextCompat.getColor(getContext(), R.color.name_ffa646)); //draw x 座標 yHeightSelect= (int) top/*-getPaddingTop() */- getPaddingBottom()-xTextSurplus-x_text_size; yHeightSelect= (int) (/*(oY+75)-(*/top/*+getPaddingTop()/)*/); leftSelector= (int) (left+(right-left)/2); // canvas.drawLine(oX,top, oX,top-yHeight,mPaintCircle); Path path = new Path(); path.moveTo(oX, oX+xWidth+40); path.lineTo(oX, oX+xWidth+40); // canvas.drawPath(path, mPaintCircle); // canvas.drawLine(oX,top,oX+xWidth+40,top,mPaintCircle); if (!isScrollow){ listener.getNumber(i, (int) oY, leftSelector,yHeightSelect, (int) oX); } LogUtil2.log("setSelect--畫:--getHeight"+getHeight()+"--left--"+left+"--height--"+yHeightSelect+"--top--"+top+"--oy--"+oY); }else { histogramPaint.setColor(ContextCompat.getColor(getContext(), R.color.color_1FA6FB)); } //畫每一個矩形 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ //畫圓角柱只在5.0以上版本適用,以下版本會閃退 canvas.drawRoundRect(left,top,right,oY,30,30,histogramPaint); }else { canvas.drawRect(left,top,right,oY,histogramPaint); } objects.add(histogramPaint); } } public void setPublicRefresh(){ selectIndex=-1; invalidate(); for (int i=0;i<objects.size();i++){ Paint paint= (Paint) objects.get(i); paint.setColor(ContextCompat.getColor(getContext(), R.color.color_1FA6FB)); LogUtil2.log("柱子--"+paint); } } // public void isScrollow(boolean isScrollowt){ // this.isScrollow=isScrollowt; // }; /* * 設定資料 */ @Override public void setChartYdateData(ChartData chartData) { super.setChartYdateData(chartData); HistogramData histogramData = (HistogramData) chartData; this.rect_color = histogramData.getRectColor() != null ? histogramData.getRectColor() : this.rect_color; this.rect_text_size = getFinalValue(this.rect_text_size,histogramData.getRectTextSize()); this.rect_text_color = getFinalValue(this.rect_text_color,histogramData.getRectTextColor()); } @Override public void setOnLongClickListener(@Nullable OnLongClickListener l) { super.setOnLongClickListener(l); LogUtil2.log("onDraw--getX---彈框的長按事件:" + isScrollow); } @Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { super.onOverScrolled(scrollX, scrollY, clampedX, clampedY); LogUtil2.log("onDraw--getX---彈框的滑動事件:" + isScrollow); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); LogUtil2.log("onDraw--getX---彈框的滑動改變事件:" + isScrollow); } @Override public boolean onTouchEvent(MotionEvent ev) { int x =0; int y =0; int left = getPaddingLeft()+yTextSurplus; int top = 0; int right = xWidth/ 12+getPaddingLeft()+yTextSurplus; int bottom = yHeight; // int right = getWidth()/ 12; // int bottom = yHeight; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: x = (int) ev.getX(); y = (int) ev.getY(); LogUtil2.log("setSelect--666down--x" + x + "--left--" + y + "ev--x" + ev.getX() + "ev--y" + ev.getY()); // if (isScrollow) { /* for (int i = 0; i < 12; i++) { Rect rect = new Rect(left, top, right, bottom); left += xWidth / 12; right += xWidth / 12; if (rect.contains(x, y)) { if (listener != null) { LogUtil2.log("setSelect--666down--x" + x + "--left--" + y + "ev--x" + ev.getX() + "ev--y" + ev.getY()); //擡起座標減去放下座標絕對值小於20代表點選,否則代表滑動 // if (Math.abs(ArithUtil.sub(ev.getX(), x)) <= 20 && Math.abs(ArithUtil.sub(ev.getY(), y)) <= 20) { selectIndex = i; selectIndexRoles.clear(); selectIndexRoles.add(selectIndex); invalidate(); // } } } }*/ // } break; case MotionEvent.ACTION_MOVE: isScrollow=true; LogUtil2.log("setSelect--666Move--x" + x + "--left--" + y + "ev--x" + ev.getX() + "ev--y" + ev.getY()); break; case MotionEvent.ACTION_UP: isScrollow=false; x = (int) ev.getX(); y = (int) ev.getY(); LogUtil2.log("setSelect--666UP--x" + x + "--left--" + y + "ev--x" + ev.getX() + "ev--y" + ev.getY()); for (int i = 0; i < 12; i++) { Rect rect = new Rect(left, top, right, bottom); left += xWidth / 12; right += xWidth / 12; if (rect.contains(x, y)) { if (listener != null) { //擡起座標減去放下座標絕對值小於20代表點選,否則代表滑動 // if (Math.abs(ArithUtil.sub(ev.getX(), x)) <= 20 && Math.abs(ArithUtil.sub(ev.getY(), y)) <= 20) { selectIndex = i; selectIndexRoles.clear(); selectIndexRoles.add(selectIndex); invalidate(); // } } } } break; } return true; } public void setListener(getNumberListener listener) { this.listener = listener; } public interface getNumberListener { void getNumber(int number, int x, int y,int HeightSelect,int ox); } }
2.ChartYdate
package com.broker.liming.widget; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import com.broker.liming.R; import com.broker.liming.bean.ChartData; import com.broker.liming.utils.DensityUtil; import com.broker.liming.utils.DoubleUtils; import com.broker.liming.utils.LogUtil2; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * @author chenhuirong * @Date 2018/12/10 * @Description 統計圖 * * */ public class ChartYdate extends View { protected final int xSurplus = 0;//x軸留餘 protected final int ySurplus = 20;//y軸留餘 protected final int xTextSurplus = 150;//x軸文字留餘 protected final int yTextSurplus = 100;//y軸文字留餘 protected float oX;//原點x protected float oY;//原點y protected int xWidth;//x軸寬度 protected int yHeight;//y軸高度 protected int xSpacing;//x軸座標間距 protected int ySpacing;//y軸座標間距 protected int xpCount = 7;//x軸座標點數 protected int ypCount = 7;//y軸座標點數 protected int[] xCoordinates;//x軸座標點 protected int[] yCoordinates;//y軸座標點 protected float yMax = 0f;//y軸最大刻度值 protected String[] xData;//x軸資料 protected float[] yData;//y軸資料 protected int coordinates_color;//座標系顏色 protected int x_text_size;//x軸文字尺寸 protected int y_text_size;//y軸文字尺寸 protected int x_text_color;//x軸文字顏色 protected int y_text_color;//y軸文字顏色 protected boolean isAnim = true; protected Anim[] anims;//動畫陣列 protected long interval = 100;//動畫執行間隔 protected int animType = -2;//動畫 private Paint mTableValuePaint; // private getNumberListener listener; // private int selectIndex = -1; // private List<Integer> selectIndexRoles = new ArrayList<>(); public ChartYdate(Context context) { super(context); } public ChartYdate(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public ChartYdate(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { LogUtil2.log("yData--------------->onDraw畫布"+ypCount); initChartYdateProp(); initCoordinateSystem(canvas); //開始動畫 if (isAnim){ initAnims(); post(animator); isAnim = false; } } /* * 初始化統計圖屬性 */ private void initChartYdateProp() { //x軸寬度 xWidth = getWidth() - getPaddingLeft() - getPaddingRight() - yTextSurplus; //y軸寬度 yHeight = getHeight() - getPaddingTop() - getPaddingBottom() - xTextSurplus; //x軸每個刻度的間距 xSpacing = (xWidth-xSurplus-xWidth%xpCount)/xpCount; //y軸每個刻度的間距 ySpacing = (yHeight-ySurplus-yHeight%ypCount)/ypCount; //座標系原點x oX = getPaddingLeft()+yTextSurplus; //座標系原點y 減70為了畫出當月圖片 oY = getPaddingTop()+yHeight+xTextSurplus/2-70; //x軸各刻度點位置 xCoordinates = new int[xpCount]; //y軸各刻度點位置 yCoordinates = new int[ypCount]; //記錄x軸刻度點位置 修改柱形圖離座標原點的距離 for (int i=0; i<xpCount; i++){ xCoordinates[i] = (i+1) * xSpacing- 30; } LogUtil2.log("yData--------------->ypCount記錄y軸刻度點位置"+ypCount); //記錄y軸刻度點位置 for (int j=0; j<ypCount; j++){ yCoordinates[j] = (j+1) * ySpacing; LogUtil2.log("yData--------------->henghe"+yCoordinates[j]); } LogUtil2.log("yData--------------->yCoordinates記錄y軸刻度點位置"+yCoordinates.length); } /* * 初始化座標系 */ private void initCoordinateSystem(Canvas canvas){ Paint mainPaint = new Paint(); mainPaint.setColor(coordinates_color); mainPaint.setAntiAlias(true); //draw x 座標 // canvas.drawLine(oX,oY, oX,oY-yHeight,mainPaint); //draw y 座標 // canvas.drawLine(oX,oY,oX+xWidth+40,oY,mainPaint); //刻度值畫筆 Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setColor(getResources().getColor(R.color.transparent)); mTableValuePaint = new Paint(); mTableValuePaint.setStrokeWidth(DensityUtil.dip2px(getContext(), 0.04f)); mTableValuePaint.setStyle(Paint.Style.FILL); mTableValuePaint.setColor(ContextCompat.getColor(getContext(), R.color.text_666666)); mTableValuePaint.setAntiAlias(true); mTableValuePaint.setTextSize(DensityUtil.dip2px(getContext(), 13)); //draw x 刻度 for (int i=0; i<xCoordinates.length; i++){ //更改文字大小 // textPaint.setTextSize(x_text_size); textPaint.setTextSize(getResources().getDimension(R.dimen.font_9)); //更改文字顏色 textPaint.setColor(x_text_color); //x軸刻度線 // canvas.drawLine(oX+xCoordinates[i],oY,oX+xCoordinates[i],oY-5,mainPaint); //獲取x軸文字寬高 if (xData!=null&&xData.length>0){ int[] textSize = getTextSize(xData[i],textPaint); //計算x軸文字x,y座標偏移量 int textX = textSize[0]/2; int textY = xTextSurplus/2 - textSize[1]/2; //畫x軸文字 canvas.drawText(xData[i],oX+xCoordinates[i]-textX,oY+textY,textPaint); String[] strNow = new SimpleDateFormat("yyyy-MM-dd").format(new Date()).toString().split("-"); Integer year = Integer.parseInt(strNow[0]); Integer month = Integer.parseInt(strNow[1]); //畫當前月圖片 + DensityUtil.dip2px(mContext, 6) if (month == Integer.parseInt(xData[i])) { Bitmap arrow = BitmapFactory.decodeResource(getResources(), R.mipmap.current_month); RectF rectF = new RectF((float) (oX+xCoordinates[i]-textX - DensityUtil.dip2px(getContext(), 10)), (float) (oY+textY+60 - DensityUtil.dip2px(getContext(), 15)), (float) (oX+xCoordinates[i]-textX + DensityUtil.dip2px(getContext(), 20)), (float) (oY+textY+70 - DensityUtil.dip2px(getContext(), 3))); canvas.drawBitmap(arrow, null, rectF, textPaint); } } } if(yMax == 0f){ yMax = getYMax(); } if(getYMax()==0){ yMax=1.0f; } //draw y 刻度 for (int j=0; j<ypCount; j++){ //更改文字大小 textPaint.setTextSize(y_text_size); //y軸刻度線 // canvas.drawLine(oX,oY-yCoordinates[j],oX+5,oY-yCoordinates[j],mainPaint); //y軸刻度值 int iYMax = (int)yMax; String datay; if (yMax - iYMax == 0){ datay = (int)(yMax/ypCount*(j+1))+""; }else { datay = new DecimalFormat("0.000").format(yMax/ypCount*(j+1)); } //平均分成4份 // Double mMaxDiv = DoubleUtils.getLargerInterger(yMax, 7); // datay= String.valueOf((new BigDecimal((j + 1) * mMaxDiv).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue())); int att=(int) Math.floor(yMax); String mBigst=String.valueOf(att); // String mBigst=String.valueOf(yMax).substring(0, String.valueOf(yMax).indexOf(".")); int maxLength=mBigst.length(); int baseMax= att+1; if (maxLength>=2){ baseMax= Integer.valueOf(mBigst.substring(0, 2))+1; } boolean isBreak=false; int margCount=0; String ii="";//畫y軸最大值 String aa="";//間隔 for (int i=baseMax;i<baseMax+100;i++){ for (int a=100;a>0;a--){ if (i%a==0&&i/a<=6&&i/a>=4){ margCount=i/a; ii= String.valueOf(i); aa=String .valueOf(a); isBreak=true; break; } } if (isBreak) break; } for (int y=0;y<maxLength-2;y++){ aa=aa+"0"; ii=ii+"0"; } // double mm=formatTosepara2(Double.valueOf(aa)); // int mm+=Integer.parseInt(aa)*j; //String.valueOf(Integer.parseInt(aa)*(j+1)) datay= formatTosepara2(Double.valueOf(Integer.parseInt(aa)*(j+1))); // LogUtil2.log("ydate---+++"+datay+"---"+ii); //獲取文字寬高 int[] textSize = getTextSize(datay,textPaint); //計算畫文字的座標偏移量 int textX = yTextSurplus/2 + textSize[0]/2; int textY = textSize[1]/2; //畫y軸文字 textPaint.setColor(y_text_color); if (j==0){ canvas.drawText("0",oX-textX,oY,textPaint); } canvas.drawText(datay,oX-textX,oY-yCoordinates[j]+textY,textPaint); } LogUtil2.log("ydate---+++"+yData.toString()); } public String formatTosepara2(double data) { int length = String.valueOf(data).split("\.")[0].length(); if (length < 4) { return String.format("%.2f", data); } DecimalFormat df = new DecimalFormat("##,###"); return df.format(data); } /* * 準備動畫 */ private void initAnims() { anims = new Anim[xpCount]; // switch (animType){ // case Anim.ANIM_TRANSLATE: // for (int i=0;i<xpCount;i++){ // float dataX = oX+xCoordinates[i]; // float dataY = oY-yData[i]/yMax*yCoordinates[yCoordinates.length-1]; // Anim anim = new Anim(dataX,dataY,dataX,oY); // anim.setAnimation(new TranslateAnim()); // anim.setVelocity(interval*2); // anims[i] = anim; // } // break; // case Anim.ANIM_ALPHA: // for (int i=0;i<xpCount;i++){ // float dataX = oX+xCoordinates[i]; // float dataY = oY-yData[i]/yMax*yCoordinates[yCoordinates.length-1]; // Anim anim = new Anim(dataX,dataY,dataX,dataY); // anim.setAnimation(new AlphaAnim()); // anim.setAlpha(0); // anim.setVelocity(interval * 3/2); // anims[i] = anim; // } // break; // default: // for (int i=0;i<xpCount;i++){ // float dataX = oX+xCoordinates[i]; // float dataY = oY-yData[i]/yMax*yCoordinates[yCoordinates.length-1]; // Anim anim = new Anim(dataX,dataY,dataX,dataY); // anim.setAnimation(new TranslateAnim()); // anim.setVelocity(interval); // anims[i] = anim; // } for (int i=0;i<xpCount;i++){ float dataX = oX+xCoordinates[i]; LogUtil2.log("yData----------dataX"+oX+xCoordinates[i]); LogUtil2.log("yData----------yData"+yData.length); LogUtil2.log("yData----------xpCount"+xpCount); LogUtil2.log("yData----------yCoordinates"+yCoordinates.length); //平均分成4份 Double mMaxDi = DoubleUtils.getLargerInterger(yMax, 7); //設定Y軸分值 float total=0; // float total= (float) new BigDecimal((7) * mMaxDi).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue(); LogUtil2.log("最大值--"+yMax); int att=(int) Math.floor(yMax); String mBigst=String.valueOf(att); // String mBigst=String.valueOf((int) Math.floor(yMax)); LogUtil2.log("最大值--"+yMax+"---"+mBigst); // String mBigst=String.valueOf(yMax).substring(0, String.valueOf(yMax).indexOf(".")); int maxLength=mBigst.length(); int baseMax= att+1; if (maxLength>=2){ baseMax= Integer.valueOf(mBigst.substring(0, 2))+1; } boolean isBreak=false; String aa="";//間隔值 String ii="";//y軸最大 int margCount=0;//分多少份,動態設定 for (int j=baseMax;j<baseMax+100;j++){ for (int a=100;a>0;a--){ if (j%a==0&&j/a<=6&&j/a>=4){ margCount=j/a; aa=String.valueOf(a); ii=String.valueOf(j); isBreak=true; break; } } if (isBreak) break; } for (int y=0;y<maxLength-2;y++){ aa=aa+"0"; ii=ii+"0"; // total= Float.parseFloat(String.valueOf(baseMax)+"0"); } LogUtil2.log("total"+total); total= Float.parseFloat(ii); LogUtil2.log("aa--"+aa+"--total"+total); float dataY = oY-yData[i]/total*yCoordinates[yCoordinates.length-1]; LogUtil2.log("yData----------dataY"+dataY); Anim anim = new Anim(dataX,dataY,dataX,dataY); anim.setAnimation(new TranslateAnim()); anim.setVelocity(interval); anims[i] = anim; LogUtil2.log("yData----------anims"+anims); } // break; // } } /* * 獲取y軸最大值 */ protected float getYMax(){ if (yData == null || yData.length == 0){ yData = new float[ypCount]; } float max =0; for (int i=0; i<yData.length; i++){ if (max < yData[i]){ max = yData[i]; } } return max; } /* * 獲取文字寬高 */ protected int[] getTextSize(String str,Paint paint){ //計算文字所在矩形,可以得到寬高 Rect rect = new Rect(); paint.getTextBounds(str, 0, str.length(), rect); int w = rect.width(); int h = rect.height(); return new int[]{w,h}; } /* * 設定統計圖屬性 */ public void setChartYdateData(ChartData chartData) { if (chartData == null){ throw new NullPointerException("折線圖資料不能為空!"); } this.xData = chartData.getXdata(); this.yData = chartData.getYdata(); this.xpCount = getFinalValue(this.xpCount,chartData.getXpCount()); this.ypCount = getFinalValue(this.ypCount,chartData.getYpCount()); this.coordinates_color = getFinalValue(this.coordinates_color,chartData.getCoordinatesColor()); this.x_text_size = getFinalValue(this.x_text_size,chartData.getxTextSize()); this.y_text_size = getFinalValue(this.y_text_size,chartData.getyTextSize()); this.x_text_color = getFinalValue(this.x_text_color,chartData.getxTextColor()); this.y_text_color = getFinalValue(this.y_text_color,chartData.getyTextColor()); this.interval = getFinalValue((int) this.interval,chartData.getInterval()); // this.animType = chartData.getAnimType() != -2 ? chartData.getAnimType() // : this.animType; this.yMax = chartData.getyMax() != 0f ? chartData.getyMax() : this.yMax; } /* * 設定正確屬性值 */ protected int getFinalValue(int old,int news){ old = news != 0 ? news : old; return old; } /* * 執行動畫runnable */ private Runnable animator = new Runnable() { @Override public void run() { LogUtil2.log("yData----------anims迴圈"+anims); LogUtil2.log("yData----------anims長多"+anims.length); for(Anim a : anims){ LogUtil2.log("yData----------anims迴圈2"+a.isOver()); if(!a.isOver()){ a.refresh(); postDelayed(this, interval); invalidate(); return; } } } }; /* * 更新資料並重繪 */ public void update(ChartData chartData){ yMax = 0; setChartYdateData(chartData); isAnim = true; invalidate(); } /* @Override public boolean onTouchEvent(MotionEvent ev) { int x = (int) ev.getX(); int y = (int) ev.getY(); int left = 0; int top = 0; int right = xWidth/ 12; int bottom = yHeight; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: for (int i = 0; i < 12; i++) { Rect rect = new Rect(left, top, right, bottom); left += xWidth / 12; right += xWidth / 12; if (rect.contains(x, y)) { if (listener != null) { listener.getNumber(i, x, y); // isSelect=true; // number = i; selectIndex = i; selectIndexRoles.clear(); selectIndexRoles.add(selectIndex); invalidate(); } } } break; case MotionEvent.ACTION_UP: break; } return true; } public void setListener(getNumberListener listener) { this.listener = listener; } public interface getNumberListener { void getNumber(int number, int x, int y); }*/ }
3.佈局 activity_family_status巢狀在Scrollowview中
<android.support.v4.widget.NestedScrollView android:id="@+id/scrollowview" android:layout_width="match_parent" android:layout_height="wrap_content" android:fillViewport="true" android:layout_marginBottom="@dimen/lay_10" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <com.broker.liming.widget.HistogramRound android:id="@+id/histogramchart" android:layout_width="@dimen/lay_320" android:layout_height="220dp" android:layout_marginLeft="@dimen/lay_12" chart:hCoordinatesColor="@color/text_999999" chart:hxTextColor="@color/color_6a6a6a" chart:hyTextColor="@color/color_1FA6FB" /> <LinearLayout/> <android.support.v4.widget.NestedScrollView/>
4.運用FamilyStatusActivity
package com.broker.liming.activity; import android.content.Context; import android.content.Intent; import android.support.annotation.RequiresApi; import android.support.v4.widget.NestedScrollView; import android.support.v7.widget.LinearLayoutManager; import com.broker.liming.utils.ArithUtil; import com.broker.liming.utils.BarUtils; import com.broker.liming.utils.GsonUtils; import com.broker.liming.utils.LogUtil2; import com.broker.liming.utils.UIHelper; import com.broker.liming.widget.HistogramData; import com.broker.liming.widget.HistogramRound; import com.broker.liming.widget.TitleBar; import org.json.JSONException; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import butterknife.BindView; import static com.sobot.chat.utils.ScreenUtils.dip2px; import static com.sobot.chat.utils.ScreenUtils.px2dip; /** * @Descpription:家庭現狀 * @Date: 2019/7/17. * @author:chenhuirong */ public class FamilyStatusActivity extends BaseActivity{ @BindView(R.id.histogramchart) HistogramRound histogramChart; @BindView(R.id.scrollowview) NestedScrollView scrollowview; private FamilyStatusAdapter adapter; private FamilyStatusRateAdapter rateAdapter; private HistogramData histogramData; //柱狀圖 // @BindView(R.id.my_single_chart_view) // SingleView mMySingleChartView; @BindView(R.id.rl_single) RelativeLayout rlSingle; private List<Float> singlelist; private LinearLayout llSingle; private double mMax = 44; private List<FamilyPremiumArrayBean> listData = new ArrayList<>(); private String monthBack; float[] ydata = new float[12]; private String[] xdata= new String[12] ; private String[] xdataShow= new String[12] ;//僅為顯示 private boolean isScrollow=true; private boolean isFresh=false; int margCount=0;//共分多少份 private LinearLayout line; private String name; public static void actionStart(Context context, String familyUuid,String names) { Intent intent = new Intent(context, FamilyStatusActivity.class); intent.putExtra("familyUuid", familyUuid); intent.putExtra("name",names); context.startActivity(intent); } @Override protected void initTitleBar() { TitleBar titleBar = (TitleBar) findViewById(R.id.title_bar); assert titleBar != null; /*titleBar.setLeftBackground(R.mipmap.back);*/ titleBar.setLeftBackground(R.mipmap.order_back); titleBar.setTitle(name+"的家庭資料分析"); titleBar.setTitleColor(R.color.text_000000); titleBar.setTitleBarBg(getResources().getColor(R.color.white)); titleBar.setLeftClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); } @Override protected int getContentView() { return R.layout.activity_family_status; } @Override protected boolean initBundle(Bundle bundle) { } @Override public void initData() { super.initData(); } @Override protected void onResume() { super.onResume(); getPieDate(familyUuid); } public void getHomeData() { try { UserBean query = UserBeanDao.query(); requestServerPostForm(false, 100, HttpParams.getFamilyStatus(query, familyUuid)); } catch (JSONException e) { e.printStackTrace(); } } @Override public void initView() { xdata=new String[]{"1","2","3","4","5","6","7","8","9","10","11","12"}; xdataShow=new String[]{"01","02","03","04","05","06","07","08","09","10","11","12"}; initSingle(); scrollowview.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { LogUtil2.log("onDraw--getX---isScrollow" +isScrollow); float x=0; float y=0; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: x = event.getX(); y = event.getY(); LogUtil2.log("onDraw--getX---down" + event.getX() + "---" + y); break; case MotionEvent.ACTION_MOVE: LogUtil2.log("onDraw--getX---move:" + event.getX() + "---"); // isFresh=true; //在此重新整理柱狀圖,防止多次重新整理,多次重繪 if (isScrollow){ rlSingle.removeView(llSingle); rlSingle.removeView(line); histogramChart.setPublicRefresh(); // histogramChart.isScrollow(isFresh); isScrollow=false; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // //允許ScrollView截斷點選事件,ScrollView可滑動 LogUtil2.log("onDraw--getX---up" + event.getX() + "mLastWownY:" + event.getY()+"x差值:"+Math.abs(ArithUtil.sub(event.getX(), x))+"--y差值:"+ Math.abs(ArithUtil.sub(event.getY(), y)) ); //擡起座標減去放下座標絕對值小於20代表點選,否則代表滑動 /* if (Math.abs(ArithUtil.sub(event.getX(), x)) <= 20 && Math.abs(ArithUtil.sub(event.getY(), y)) <= 20) { // isScrollow=false; isCheck=true; }else { // isScrollow=true; isCheck=false; histogramChart.isScrollow(false); rlSingle.removeView(llSingle); rlSingle.removeView(line); if (isScrollow){ histogramChart.setPublicRefresh(); isScrollow=false; } }*/ break; } return false; } }); changeStatusBarTextColor(true); BarUtils.setColorNoTranslucent(this, getResources().getColor(R.color.white)); } /** * 彈出彈框 */ private void initSingle(){ rlSingle = (RelativeLayout) findViewById(R.id.rl_single); singlelist = new ArrayList<>(); rlSingle.removeView(llSingle); rlSingle.removeView(line); histogramChart.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { LogUtil2.log("onDraw--getX---彈框的長按事件:" + isScrollow); return false; } }); histogramChart.setListener(new HistogramRound.getNumberListener() { @Override public void getNumber(int index, int height, int left,int selectorHeight,int ox) { // rlSingle.removeAllViews(); LogUtil2.log("onDraw--getX---彈框:" + isScrollow); isScrollow=true; // if(!isFresh){ rlSingle.removeView(llSingle); rlSingle.removeView(line); line= (LinearLayout) LayoutInflater.from(FamilyStatusActivity.this).inflate(R.layout.layout_dash_line, null); // TextView view=new TextView(mContext); // view.setBackground(mContext.getResources().getDrawable(R.drawable.shape_dash_line)); RelativeLayout.LayoutParams viewParam=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);viewParam. topMargin=height-selectorHeight; viewParam.topMargin=selectorHeight-5; viewParam.leftMargin=dip2px(mContext,40); viewParam.rightMargin=dip2px(mContext,30); line.setLayoutParams(viewParam); rlSingle.addView(line); llSingle = (LinearLayout) LayoutInflater.from(FamilyStatusActivity.this).inflate(R.layout.layout_pro_expense, null); LinearLayout ll_back=(LinearLayout)llSingle.findViewById(R.id.ll_back); TextView tvMoney = (TextView) llSingle.findViewById(R.id.tv_month); TextView tv_money=(TextView) llSingle.findViewById(R.id.tv_money); if (singlelist!=null&&singlelist.size()>0){ tv_money.setText(formatTosepara2(singlelist.get(index))+""); }else { tv_money.setText("0"); } String[] strNow = new SimpleDateFormat("yyyy-MM-dd").format(new Date()).toString().split("-"); String year = strNow[0]; tvMoney.setText(year+"-"+xdataShow[index]); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); LogUtil2.log("setSelect--callback--"+height+"--left---"+left+"---"+selectorHeight+"彈框高--"+dip2px(mContext,60)+"--差值--"+(height-(height-selectorHeight/*+px2dip(mContext,70)*/)-dip2px(mContext,60))); LogUtil2.log("----寬-----+++"+params.width); LogUtil2.log("setSelect--轉換"+px2dip(mContext,height)+"--left---"+px2dip(mContext,left)+"---"+dip2px(mContext,selectorHeight)); int topmargin=0; int leftmargin=0; LogUtil2.log("setSelect--差距"+px2dip(mContext,height-selectorHeight)); if (index+1>6){ if (height-(height-selectorHeight)-dip2px(mContext,60)-dip2px(mContext,5)>0){ ll_back.setBackground(mContext.getResources().getDrawable(R.mipmap.right_bottom)); topmargin=height-(height-selectorHeight)-dip2px(mContext,60)-dip2px(mContext,5)/*+dip2px(mContext,70)*/; }else { topmargin= height-(height-selectorHeight)+dip2px(mContext,5)/*+dip2px(mContext,70)*/; ll_back.setBackground(mContext.getResources().getDrawable(R.mipmap.right_top)); } // leftmargin=left-dip2px(mContext,150)+dip2px(mContext,40); }else { if (height-(height-selectorHeight)-dip2px(mContext,60)-dip2px(mContext,5)>0){ ll_back.setBackground(mContext.getResources().getDrawable(R.mipmap.left_bottom)); topmargin=height-(height-selectorHeight)-dip2px(mContext,60)-dip2px(mContext,5); }else { ll_back.setBackground(mContext.getResources().getDrawable(R.mipmap.left_top)); topmargin=height-(height-selectorHeight)+dip2px(mContext,5); } leftmargin=left; } params.leftMargin = leftmargin; //相當於總佈局的上方,需要減去家庭保費和保費距離 params.topMargin=topmargin/* selectorHeight+px2dip(mContext,64)*/; // LogUtil2.log("setSelect--callback--"+histogramChart.getHeight()+"---"+selectorHeight+"---left"+ params.topMargin); // if (x - 100 < 0) { // params.leftMargin = 0; // } else if (x - 100 > relativeLayout.getWidth() - llSingle.getMeasuredWidth()) { // params.leftMargin = relativeLayout.getWidth() - llSingle.getMeasuredWidth(); // } llSingle.setLayoutParams(params); rlSingle.addView(llSingle); // } } }); } private void changeStatusBarTextColor(boolean isBlack) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { if (isBlack) { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);//設定狀態列黑色字型 }else { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);//恢復狀態列白色字型 } } } private void setDate(FamilyYearCheakBean yearCheakBean) { if (yearCheakBean != null) { if (yearCheakBean.familyPremiumTotal != null && !yearCheakBean.familyPremiumTotal.equals("")) { tv_total_money.setText("合計年繳"+formatTosepara(Double.valueOf(String.valueOf(yearCheakBean.familyPremiumTotal))) + "元"); }else { tv_total_money.setText("合計年繳0.00元"); } String[] strNow = new SimpleDateFormat("yyyy-MM-dd").format(new Date()).toString().split("-"); Integer year = Integer.parseInt(strNow[0]); Integer month = Integer.parseInt(strNow[1]); listData = yearCheakBean.familyPremiumArray; List<Double> list = new ArrayList<>(); ArrayList<DotVo> temp = new ArrayList(); for (int i = 0; i < listData.size(); i++) { FamilyPremiumArrayBean bean = listData.get(i); String yearback = bean.MONTH.substring(0, bean.MONTH.indexOf("-")); monthBack = bean.MONTH.substring(bean.MONTH.lastIndexOf("-") + 1); // mXdots[i] = yearback + "年n" + monthBack + "月"; list.add(Double.valueOf(bean.premium)); double premium = 0; if (bean.premium != null && !bean.premium.equals("")) { premium = Double.valueOf(bean.premium); list.add(Double.valueOf(bean.premium)); singlelist.add(Float.valueOf(bean.premium)); ydata[i]=Float.valueOf(bean.premium); } DotVo tempDotVo = new DotVo(monthBack, premium); temp.add(tempDotVo); } Double[] toBeStored = list.toArray(new Double[list.size()]); mMax = checkMax(toBeStored); } //向下取整 int att=(int) Math.floor(mMax); String mBigst=String.valueOf(att); int maxLength=mBigst.length(); int baseMax= att+1; if (maxLength>=2){ baseMax= Integer.valueOf(mBigst.substring(0, 2))+1; } boolean isBreak=false; for (int i=baseMax;i<baseMax+100;i++){ for (int a=100;a>0;a--){ if (i%a==0&&i/a<=6&&i/a>=4){ margCount=i/a; isBreak=true; break; } } if (isBreak) break; } for (int i=0;i<maxLength-2;i++){ } LogUtil2.log("ydata---設定資料"+ydata.toString()+"--最大分數--"+margCount); histogramData = HistogramData.builder() .setXdata(xdata) .setYdata(ydata) .setYpCount(margCount) // .setAnimType(Anim.ANIM_ALPHA) .build(); histogramChart.setChartYdateData(histogramData); histogramChart.update(histogramData); } public double checkMax(Double[] args) { double max = 0; for (int i = 0; i < args.length; i++) { if (args[i] > max) { max = args[i]; } } return max; } @Override protected void onSuccess(String response, int id) { super.onSuccess(response, id); switch (id){ case 200: yearCheakBean= GsonUtils.toObject(response,FamilyYearCheakBean.class); setDate(yearCheakBean); break; } } /** * 將float型別的資料轉換成以4位元逗號隔開的字串,並且保留兩位有效數位 */ public String formatTosepara(double data) { int length = String.valueOf(data).split("\.")[0].length(); if (length < 4) { return String.format("%.2f", data); } DecimalFormat df = new DecimalFormat("#,####.00"); return df.format(data); } public String formatTosepara2(double data) { int length = String.valueOf(data).split("\.")[0].length(); if (length < 4) { return String.format("%.2f", data); } DecimalFormat df = new DecimalFormat("##,###"); return df.format(data); } }
5.HistogramData
package com.broker.liming.widget; import com.broker.liming.bean.ChartData; /** * @author chenhuirong * @Date 2018/12/10 * @Description 柱狀圖s */ public class HistogramData extends ChartData { private int[] rectColor; private int rectTextColor; private int rectTextSize; private HistogramData(Builder builder) { setXdata(builder.xdata); setYdata(builder.ydata); setXpCount(builder.xpCount); setYpCount(builder.ypCount); setCoordinatesColor(builder.coordinatesColor); setxTextSize(builder.xTextSize); setyTextSize(builder.yTextSize); setxTextColor(builder.xTextColor); setyTextColor(builder.yTextColor); setyMax(builder.yMax); setInterval(builder.interval); setAnimType(builder.animType); setRectColor(builder.rectColor); setRectTextColor(builder.rectTextColor); setRectTextSize(builder.rectTextSize); } public static Builder builder() { return new Builder(); } public int[] getRectColor() { return rectColor; } public void setRectColor(int[] rectColor) { this.rectColor = rectColor; } public int getRectTextColor() { return rectTextColor; } public void setRectTextColor(int rectTextColor) { this.rectTextColor = rectTextColor; } public int getRectTextSize() { return rectTextSize; } public void setRectTextSize(int rectTextSize) { this.rectTextSize = rectTextSize; } public static final class Builder{ private int rectTextSize; private int rectTextColor; private int[] rectColor; private int animType; private int interval; private int yMax; private int yTextColor; private int xTextColor; private int yTextSize; private int xTextSize; private int coordinatesColor; private int ypCount; private int xpCount; private float[] ydata; private String[] xdata; private Builder(){} public Builder setRectTextSize(int val) { rectTextSize = val; return this; } public Builder setRectTextColor(int val) { rectTextColor = val; return this; } public Builder setRectColor(int[] val) { rectColor = val; return this; } public Builder setAnimType(int val) { animType = val; return this; } public Builder setInterval(int val) { interval = val; return this; } public Builder setYMax(int val) { yMax = val; return this; } public Builder setYTextColor(int val) { yTextColor = val; return this; } public Builder setXTextColor(int val) { xTextColor = val; return this; } public Builder setYTextSize(int val) { yTextSize = val; return this; } public Builder setXTextSize(int val) { xTextSize = val; return this; } public Builder setCoordinatesColor(int val) { coordinatesColor = val; return this; } public Builder setYpCount(int val) { ypCount = val; return this; } public Builder setXpCount(int val) { xpCount = val; return this; } public Builder setYdata(float[] val) { ydata = val; return this; } public Builder setXdata(String[] val) { xdata = val; return this; } public HistogramData build() { return new HistogramData(this); } } }
6.ChartData
package com.broker.liming.bean; /** * 統計圖父類別資料類 * Created by zqx on 16/6/27. */ public class ChartData { protected String[] xdata; protected float[] ydata; protected int xpCount; protected int ypCount; protected int coordinatesColor; protected int xTextSize; protected int yTextSize; protected int xTextColor; protected int yTextColor; protected int yMax; protected int interval; protected int animType = -2; protected ChartData(){} public String[] getXdata() { return xdata; } public void setXdata(String[] xdata) { this.xdata = xdata; } public float[] getYdata() { return ydata; } public void setYdata(float[] ydata) { this.ydata = ydata; } public int getXpCount() { return xpCount; } public void setXpCount(int xpCount) { this.xpCount = xpCount; } public int getYpCount() { return ypCount; } public void setYpCount(int ypCount) { this.ypCount = ypCount; } public int getCoordinatesColor() { return coordinatesColor; } public void setCoordinatesColor(int coordinatesColor) { this.coordinatesColor = coordinatesColor; } public int getxTextSize() { return xTextSize; } public void setxTextSize(int xTextSize) { this.xTextSize = xTextSize; } public int getyTextSize() { return yTextSize; } public void setyTextSize(int yTextSize) { this.yTextSize = yTextSize; } public int getxTextColor() { return xTextColor; } public void setxTextColor(int xTextColor) { this.xTextColor = xTextColor; } public int getyTextColor() { return yTextColor; } public void setyTextColor(int yTextColor) { this.yTextColor = yTextColor; } public int getyMax() { return yMax; } public void setyMax(int yMax) { this.yMax = yMax; } public int getInterval() { return interval; } public void setInterval(int interval) { this.interval = interval; } public int getAnimType() { return animType; } public void setAnimType(int animType) { this.animType = animType; } }
注:參考demo時間太久我忘記了,網上可找到
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45