Android開發之自訂View專題(二):自訂餅圖

來源:互聯網
上載者:User

標籤:android開發   圖形   餅圖   自訂   報表   

在圖表裡面,常用的表徵圖一般為折線圖、直條圖和餅圖,上周,博主已經將直條圖分享。在博主的項目裡面其實還用到了餅圖,但沒用到折線圖。其實學會了其中一個,再去寫其他的,應該都是知道該怎麼寫的,原理都是自己繪製圖形,然後擷取觸摸位置判定點擊事件。好了,廢話不多說,直接上今天的餅圖的


這次也是博主從項目裡面抽離出來的,這次的代碼注釋會比上次的直條圖更加的詳細,更加便於有興趣的朋友一起學習。圖中的那個圓形指向箭頭不屬於餅圖的部分,是在布局檔案中為了美化另外添加進去的,有興趣的朋友可以下載完整的項目下來研究學習。

:http://download.csdn.net/detail/victorfreedom/8322639

本來想上傳到github的,但是網路不給力,過幾天再上傳吧。


代碼部分就直接貼出自訂餅圖部分,支援xml檔案寫入構造,也支援new方法構造。

package com.freedom.piegraph;import android.annotation.SuppressLint;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.os.Handler;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;/** * @ClassName: PiegraphView * @author victor_freedom ([email protected]) * @createddate 2015年1月3日 下午4:30:10 * @Description: 自訂餅狀圖 */@SuppressLint({ "DrawAllocation" })public class PiegraphView extends View implements Runnable {// 動畫速度private float moveSpeed = 3.0F;// 總數值private double total;// 各餅塊對應的數值private Double[] itemValuesTemp;// 各餅塊對應的數值private Double[] itemsValues;// 各餅塊對應的顏色private String[] itemColors;// 各餅塊的角度private float[] itemsAngle;// 各餅塊的起始角度private float[] itemsStartAngle;// 各餅塊的佔比private float[] itemsPercent;// 旋轉起始角度private float rotateStartAng = 0.0F;// 旋轉結束角度private float rotateEndAng = 0.0F;// 正轉還是反轉private boolean isClockWise;// 正在旋轉private boolean isRotating;// 是否開啟動畫private boolean isAnimEnabled = true;// 邊緣圓環的顏色private String loopStrokeColor;// 邊緣圓環的寬度private float strokeWidth = 0.0F;// 餅圖半徑,不包括圓環private float radius;// 當前item的位置private int itemPostion = -1;// 固定位置private int stopPosition = 0;// 固定位置public static final int TO_RIGHT = 0;public static final int TO_BOTTOM = 1;public static final int TO_LEFT = 2;public static final int TO_TOP = 3;// 顏色值private final String[] DEFAULT_ITEMS_COLORS = { "#FF0000", "#FFFF01","#FF9933", "#9967CC", "#00CCCC", "#00CC33", "#0066CC", "#FF6799","#99FF01", "#FF67FF", "#4876FF", "#FF00FF", "#FF83FA", "#0000FF","#363636", "#FFDAB9", "#90EE90", "#8B008B", "#00BFFF", "#FFFF00","#00FF00", "#006400", "#00FFFF", "#00FFFF", "#668B8B", "#000080","#008B8B" };// 訊息接收器private Handler piegraphHandler = new Handler();// 監聽器集合private OnPiegraphItemSelectedListener itemSelectedListener;public PiegraphView(Context context, String[] itemColors,Double[] itemSizes, float total, int radius, int strokeWidth,String strokeColor, int stopPosition, int separateDistence) {super(context);this.stopPosition = stopPosition;if ((itemSizes != null) && (itemSizes.length > 0)) {itemValuesTemp = itemSizes;this.total = total;// 重設總值reSetTotal();// 重設各個模組的值refreshItemsAngs();}if (radius < 0)// 預設半徑設定為100this.radius = 100.0F;else {this.radius = radius;}// 預設圓環寬度設定為2if (strokeWidth < 0)strokeWidth = 2;else {this.strokeWidth = strokeWidth;}loopStrokeColor = strokeColor;if (itemColors == null) {// 如果沒有設定顏色,則使用預設顏色值setDefaultColor();} else if (itemColors.length < itemSizes.length) {this.itemColors = itemColors;// 如果設定的顏色值和設定的集合大小不一樣,那麼需要充預設顏色值集合裡面補充顏色,一般是不會出現這種情況。setDifferentColor();} else {this.itemColors = itemColors;}invalidate();}public PiegraphView(Context context, AttributeSet attrs) {super(context, attrs);loopStrokeColor = "#000000";// 把我們自訂的屬性,放在attrs的屬性集合裡面TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.PiegraphView);radius = ScreenUtil.dip2px(getContext(),a.getFloat(R.styleable.PiegraphView_radius, 100));strokeWidth = ScreenUtil.dip2px(getContext(),a.getFloat(R.styleable.PiegraphView_strokeWidth, 2));moveSpeed = a.getFloat(R.styleable.PiegraphView_moveSpeed, 5);if (moveSpeed < 1F) {moveSpeed = 1F;}if (moveSpeed > 5.0F) {moveSpeed = 5.0F;}invalidate();a.recycle();}/** * @Title: setRaduis * @Description: 設定半徑 * @param radius * @throws */public void setRaduis(float radius) {if (radius < 0)this.radius = 100.0F;else {this.radius = radius;}invalidate();}public float getRaduis() {return radius;}/** * @Title: setStrokeWidth * @Description: 設定圓環寬度 * @param strokeWidth * @throws */public void setStrokeWidth(int strokeWidth) {if (strokeWidth < 0)strokeWidth = 2;else {this.strokeWidth = strokeWidth;}invalidate();}public float getStrokeWidth() {return strokeWidth;}/** * @Title: setStrokeColor * @Description: 設定圓環顏色 * @param strokeColor * @throws */public void setStrokeColor(String strokeColor) {loopStrokeColor = strokeColor;invalidate();}public String getStrokeColor() {return loopStrokeColor;}/** * @Title: setitemColors * @Description: 設定個餅塊的顏色 * @param colors * @throws */public void setitemColors(String[] colors) {if ((itemsValues != null) && (itemsValues.length > 0)) {// 如果傳入值未null,則使用預設的顏色if (colors == null) {setDefaultColor();} else if (colors.length < itemsValues.length) {// 如果傳入顏色不夠,則從預設顏色中填補itemColors = colors;setDifferentColor();} else {itemColors = colors;}}invalidate();}public String[] getitemColors() {return itemColors;}/** * @Title: setitemsValues * @Description: 設定各餅塊資料 * @param items * @throws */public void setitemsValues(Double[] items) {if ((items != null) && (items.length > 0)) {itemValuesTemp = items;// 重設總值,預設為所有值的和reSetTotal();refreshItemsAngs();setitemColors(itemColors);}invalidate();}public Double[] getitemsValues() {return itemValuesTemp;}public void setTotal(int total) {this.total = total;reSetTotal();invalidate();}public double getTotal() {return total;}/** * @Title: setAnimEnabled * @Description: 設定是否開啟旋轉動畫 * @param isAnimEnabled * @throws */public void setAnimEnabled(boolean isAnimEnabled) {this.isAnimEnabled = isAnimEnabled;invalidate();}public boolean isAnimEnabled() {return isAnimEnabled;}public void setmoveSpeed(float moveSpeed) {if (moveSpeed < 1F) {moveSpeed = 1F;}if (moveSpeed > 5.0F) {moveSpeed = 5.0F;}this.moveSpeed = moveSpeed;}public float getmoveSpeed() {if (isAnimEnabled()) {return moveSpeed;}return 0.0F;}/** * @Title: setShowItem * @Description: 旋轉到指定位置的item * @param position *            位置 * @param anim *            是否動畫 * @param listen *            是否設定監聽器 * @throws */public void setShowItem(int position, boolean anim) {if ((itemsValues != null) && (position < itemsValues.length)&& (position >= 0)) {// 拿到需要旋轉的角度rotateEndAng = getLastrotateStartAngle(position);itemPostion = position;if (anim) {rotateStartAng = 0.0F;if (rotateEndAng > 0.0F) {// 如果旋轉角度大於零,則順時針旋轉isClockWise = true;} else {// 如果小於零則逆時針旋轉isClockWise = false;}// 開始旋轉isRotating = true;} else {rotateStartAng = rotateEndAng;}// 如果有監聽器if (null != itemSelectedListener) {itemSelectedListener.onPieChartItemSelected(position,itemColors[position], itemsValues[position],itemsPercent[position],getAnimTime(Math.abs(rotateEndAng - rotateStartAng)));}// 開始旋轉piegraphHandler.postDelayed(this, 1L);}}private float getLastrotateStartAngle(int position) {float result = 0.0F;// 拿到旋轉角度,根據固定位置進行修正result = itemsStartAngle[position] + itemsAngle[position] / 2.0F+ getstopPositionAngle();if (result >= 360.0F) {result -= 360.0F;}if (result <= 180.0F)result = -result;else {result = 360.0F - result;}return result;}/** * @Title: getstopPositionAngle * @Description: 根據固定位置修正旋轉角度 * @return * @throws */private float getstopPositionAngle() {float resultAngle = 0.0F;switch (stopPosition) {case TO_RIGHT:resultAngle = 0.0F;break;case TO_LEFT:resultAngle = 180.0F;break;case TO_TOP:resultAngle = 90.0F;break;case TO_BOTTOM:resultAngle = 270.0F;break;}return resultAngle;}public int getShowItem() {return itemPostion;}public void setstopPosition(int stopPosition) {this.stopPosition = stopPosition;}public int getstopPosition() {return stopPosition;}/** * @Title: refreshItemsAngs * @Description: 初始化各個角度 * @throws */private void refreshItemsAngs() {if ((itemValuesTemp != null) && (itemValuesTemp.length > 0)) {// 如果出現總值比設定的集合的總值還大,那麼我們自動的增加一個模組出來(幾乎不會出現這種情況)if (getTotal() > getAllSizes()) {itemsValues = new Double[itemValuesTemp.length + 1];for (int i = 0; i < itemValuesTemp.length; i++) {itemsValues[i] = itemValuesTemp[i];}itemsValues[(itemsValues.length - 1)] = (getTotal() - getAllSizes());} else {itemsValues = new Double[itemValuesTemp.length];itemsValues = itemValuesTemp;}// 開始給各模組賦值itemsPercent = new float[itemsValues.length];itemsStartAngle = new float[itemsValues.length];itemsAngle = new float[itemsValues.length];float startAngle = 0.0F;for (int i = 0; i < itemsValues.length; i++) {itemsPercent[i] = ((float) (itemsValues[i] * 1.0D / getTotal() * 1.0D));}for (int i = 0; i < itemsPercent.length; i++) {itemsAngle[i] = (360.0F * itemsPercent[i]);if (i != 0) {itemsStartAngle[i] = startAngle + itemsAngle[i - 1];startAngle = 360.0F * itemsPercent[(i - 1)] + startAngle;} else {// Android預設起始位置設定是右側水平,初始化預設固定位置也在右邊。有興趣的同學可以根據自己的喜好修改itemsStartAngle[i] = -itemsAngle[i] / 2;startAngle = itemsStartAngle[i];}}}}/** * 繪圖 */protected void onDraw(Canvas canvas) {super.onDraw(canvas);// 餅圖半徑加圓環半徑float realRadius = radius + strokeWidth;Paint paint = new Paint();paint.setAntiAlias(true);float lineLength = 2.0F * radius + strokeWidth;if (strokeWidth != 0.0F) {// 空心的畫筆,先畫外層圓環paint.setStyle(Paint.Style.STROKE);paint.setColor(Color.parseColor(loopStrokeColor));paint.setStrokeWidth(strokeWidth);canvas.drawCircle(realRadius, realRadius, realRadius - 5, paint);}if ((itemsAngle != null) && (itemsStartAngle != null)) {// 旋轉角度canvas.rotate(rotateStartAng, realRadius, realRadius);// 設定餅圖矩形RectF oval = new RectF(strokeWidth, strokeWidth, lineLength,lineLength);// 開始畫各個扇形for (int i = 0; i < itemsAngle.length; i++) {oval = new RectF(strokeWidth, strokeWidth, lineLength,lineLength);// 先畫實體paint.setStyle(Paint.Style.FILL);paint.setColor(Color.parseColor(itemColors[i]));canvas.drawArc(oval, itemsStartAngle[i], itemsAngle[i], true,paint);// 再畫空心體描邊paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(strokeWidth / 2);paint.setColor(Color.WHITE);canvas.drawArc(oval, itemsStartAngle[i], itemsAngle[i], true,paint);}}// 畫中心的小圓paint.setStyle(Paint.Style.FILL);paint.setColor(Color.LTGRAY);canvas.drawCircle(realRadius, realRadius,ScreenUtil.dip2px(getContext(), 40), paint);// 描邊paint.setStyle(Paint.Style.STROKE);paint.setColor(Color.WHITE);paint.setStrokeWidth(strokeWidth);canvas.drawCircle(realRadius, realRadius,ScreenUtil.dip2px(getContext(), 40), paint);}/** * 觸摸事件 */public boolean onTouchEvent(MotionEvent event) {if ((!isRotating) && (itemsValues != null) && (itemsValues.length > 0)) {float x1 = 0.0F;float y1 = 0.0F;switch (event.getAction()) {// 按下case MotionEvent.ACTION_DOWN:x1 = event.getX();y1 = event.getY();float r = radius + strokeWidth;if ((x1 - r) * (x1 - r) + (y1 - r) * (y1 - r) - r * r <= 0.0F) {// 拿到位置int position = getShowItem(getTouchedPointAngle(r, r, x1,y1));// 旋轉到指定位置setShowItem(position, isAnimEnabled());}break;}}return super.onTouchEvent(event);}/** * @Title: getTouchedPointAngle * @Description: 計算觸摸角度 * @param radiusX *            圓心 * @param radiusY *            圓心 * @param x1 *            觸摸點 * @param y1 *            觸摸點 * @return * @throws */private float getTouchedPointAngle(float radiusX, float radiusY, float x1,float y1) {float differentX = x1 - radiusX;float differentY = y1 - radiusY;double a = 0.0D;double t = differentY/ Math.sqrt(differentX * differentX + differentY * differentY);if (differentX > 0.0F) {// 0~90if (differentY > 0.0F)a = 6.283185307179586D - Math.asin(t);else// 270~360a = -Math.asin(t);} else if (differentY > 0.0F)// 90~180a = 3.141592653589793D + Math.asin(t);else {// 180~270a = 3.141592653589793D + Math.asin(t);}return (float) (360.0D - a * 180.0D / 3.141592653589793D % 360.0D);}/** * @Title: getShowItem * @Description: 拿到觸摸位置 * @param touchAngle *            觸摸位置角度 * @return * @throws */private int getShowItem(float touchAngle) {int position = 0;for (int i = 0; i < itemsStartAngle.length; i++) {if (i != itemsStartAngle.length - 1) {if ((touchAngle >= itemsStartAngle[i])&& (touchAngle < itemsStartAngle[(i + 1)])) {position = i;break;}} else if ((touchAngle > itemsStartAngle[(itemsStartAngle.length - 1)])&& (touchAngle < itemsStartAngle[0])) {position = itemsValues.length - 1;} else {// 如果觸摸位置不對,則旋轉到最大值得位置position = getPointItem(itemsStartAngle);}}return position;}private int getPointItem(float[] startAngle) {int item = 0;float temp = startAngle[0];for (int i = 0; i < startAngle.length - 1; i++) {if (startAngle[(i + 1)] - temp > 0.0F)temp = startAngle[i];else {return i;}}return item;}protected void onDetachedFromWindow() {super.onDetachedFromWindow();piegraphHandler.removeCallbacks(this);}protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);float widthHeight = 2.0F * (radius + strokeWidth + 1.0F);// 重設view的寬高setMeasuredDimension((int) widthHeight, (int) widthHeight);}/** * 旋轉動作 */public void run() {if (isClockWise) {// 順時針旋轉rotateStartAng += moveSpeed;invalidate();piegraphHandler.postDelayed(this, 10L);if (rotateStartAng - rotateEndAng >= 0.0F) {rotateStartAng = 0.0F;// 如果已經轉到指定位置,則停止動畫piegraphHandler.removeCallbacks(this);// 重設各模組起始角度值resetStartAngle(rotateEndAng);isRotating = false;}} else {// 逆時針旋轉rotateStartAng -= moveSpeed;invalidate();piegraphHandler.postDelayed(this, 10L);if (rotateStartAng - rotateEndAng <= 0.0F) {rotateStartAng = 0.0F;piegraphHandler.removeCallbacks(this);resetStartAngle(rotateEndAng);isRotating = false;}}}private float getAnimTime(float ang) {return (int) Math.floor(ang / getmoveSpeed() * 10.0F);}/** * @Title: resetStartAngle * @Description: 重設個模組角度 * @param angle * @throws */private void resetStartAngle(float angle) {for (int i = 0; i < itemsStartAngle.length; i++) {float newStartAngle = itemsStartAngle[i] + angle;if (newStartAngle < 0.0F)itemsStartAngle[i] = (newStartAngle + 360.0F);else if (newStartAngle > 360.0F)itemsStartAngle[i] = (newStartAngle - 360.0F);elseitemsStartAngle[i] = newStartAngle;}}/** * @Title: setDefaultColor * @Description: 設定預設顏色 * @throws */private void setDefaultColor() {if ((itemsValues != null) && (itemsValues.length > 0)&& (itemColors == null)) {itemColors = new String[itemsValues.length];if (itemColors.length <= DEFAULT_ITEMS_COLORS.length) {System.arraycopy(DEFAULT_ITEMS_COLORS, 0, itemColors, 0,itemColors.length);} else {int multiple = itemColors.length / DEFAULT_ITEMS_COLORS.length;int difference = itemColors.length% DEFAULT_ITEMS_COLORS.length;for (int a = 0; a < multiple; a++) {System.arraycopy(DEFAULT_ITEMS_COLORS, 0, itemColors, a* DEFAULT_ITEMS_COLORS.length,DEFAULT_ITEMS_COLORS.length);}if (difference > 0)System.arraycopy(DEFAULT_ITEMS_COLORS, 0, itemColors,multiple * DEFAULT_ITEMS_COLORS.length, difference);}}}/** * @Title: setDifferentColor * @Description: 補差顏色 * @throws */private void setDifferentColor() {if ((itemsValues != null) && (itemsValues.length > itemColors.length)) {String[] preitemColors = new String[itemColors.length];preitemColors = itemColors;int leftall = itemsValues.length - itemColors.length;itemColors = new String[itemsValues.length];System.arraycopy(preitemColors, 0, itemColors, 0,preitemColors.length);if (leftall <= DEFAULT_ITEMS_COLORS.length) {System.arraycopy(DEFAULT_ITEMS_COLORS, 0, itemColors,preitemColors.length, leftall);} else {int multiple = leftall / DEFAULT_ITEMS_COLORS.length;int left = leftall % DEFAULT_ITEMS_COLORS.length;for (int a = 0; a < multiple; a++) {System.arraycopy(DEFAULT_ITEMS_COLORS, 0, itemColors, a* DEFAULT_ITEMS_COLORS.length,DEFAULT_ITEMS_COLORS.length);}if (left > 0) {System.arraycopy(DEFAULT_ITEMS_COLORS, 0, itemColors,multiple * DEFAULT_ITEMS_COLORS.length, left);}}preitemColors = null;}}/** * @Title: reSetTotal * @Description: 重設總值 * @throws */private void reSetTotal() {double totalSizes = getAllSizes();if (getTotal() < totalSizes)total = totalSizes;}private double getAllSizes() {float tempAll = 0.0F;if ((itemValuesTemp != null) && (itemValuesTemp.length > 0)) {for (double itemsize : itemValuesTemp) {tempAll += itemsize;}}return tempAll;}public void setItemSelectedListener(OnPiegraphItemSelectedListener itemSelectedListener) {this.itemSelectedListener = itemSelectedListener;}}

自訂View專題報表類的view到此就講完了。博主沒有寫過自訂的折線圖。但是學會了這兩個圖形的話再去自己寫折線圖我想也是不難的。

後續還有2期的自訂view的專題。一期是關於自訂gridView的(可以拖動gridView,但是不是和網上其他的那種拖動item,而是將item裡面的內容拖動切換位置),一期是關於自訂viewGroup(類似線性布局,相對布局那種,可以往裡面添加控制項的)。希望能夠協助到看到此篇文章的人。









Android開發之自訂View專題(二):自訂餅圖

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.