編輯:關於Android編程
最近由於項目需要,研究了一些統計圖的做法,開始時,看了很多博文,大部分都是引用第三方的庫,雖然簡單,
易上手,但是功能太死板,有很多要求都是不能滿足的,所以經過研究,自己使用View中的canvas重新繪圖制作
統計圖。首先上幾張的效果圖吧。
點擊這裡下載(0分下載)
一、demo的結構
一個activity中嵌套了三個fragment(v4),是用viewpager對頁面進行滑動,下面是整個項目的結構:
二、核心代碼
首先是MainActivity,這個demo中只使用到一個activity,現在一個activity中鑲嵌多個fragment很火,
很多Android應用都在使用這個布局,例如微信,QQ等。這樣做節省了空間上的浪費。
package com.example.statisticalchart; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.os.Bundle; import java.util.ArrayList; import java.util.List; public class MainActivity extends FragmentActivity { private ViewPager viewPager; private Listfragments; private FragmentPagerAdapter adapter; // 設置是否顯示動畫,為了防止在創建時就開啟動畫,用以下三個參數做了判斷,只有當看到視圖後才會顯示動畫 public static int flag1 = 2; public static int flag2 = 1; public static int flag3 = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { viewPager = (ViewPager) findViewById(R.id.record_viewpager); fragments = new ArrayList (); RecordPager1 recordPager1 = new RecordPager1(); RecordPager2 recordPager2 = new RecordPager2(); RecordPager3 recordPager3 = new RecordPager3(); fragments.add(recordPager1); fragments.add(recordPager2); fragments.add(recordPager3); adapter = new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public Fragment getItem(int position) { return fragments.get(position); } @Override public int getCount() { return fragments.size(); } }; viewPager.setAdapter(adapter); viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { if (position == 0 && flag1 == 1) { flag1 = 2; fragments.get(0).onResume(); flag1 = 3; } if (position == 1 && flag2 == 1) { flag2 = 2; fragments.get(1).onResume(); flag2 = 3; } if (position == 2 && flag3 == 1) { flag3 = 2; fragments.get(2).onResume(); flag3 = 3; } } @Override public void onPageScrollStateChanged(int state) { } }); } }
package com.example.statisticalchart; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Rect; import android.os.Looper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.Transformation; public class HistogramView extends View { private Paint xLinePaint;// 坐標軸 軸線 畫筆: private Paint hLinePaint;// 坐標軸水平內部 虛線畫筆 private Paint titlePaint;// 繪制文本的畫筆 private Paint paint;// 矩形畫筆 柱狀圖的樣式信息 private int[] progress = { 2000, 5000, 6000, 8000, 500, 6000, 9000 };// 7 // 條,顯示各個柱狀的數據 private int[] aniProgress;// 實現動畫的值 private final int TRUE = 1;// 在柱狀圖上顯示數字 private int[] text;// 設置點擊事件,顯示哪一條柱狀的信息 private Bitmap bitmap; // 坐標軸左側的數標 private String[] ySteps; // 坐標軸底部的星期數 private String[] xWeeks; private int flag;// 是否使用動畫 private HistogramAnimation ani; public HistogramView(Context context) { super(context); init(); } public HistogramView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { ySteps = new String[] { "10k", "7.5k", "5k", "2.5k", "0" }; xWeeks = new String[] { "周一", "周二", "周三", "周四", "周五", "周六", "周日" }; text = new int[] { 0, 0, 0, 0, 0, 0, 0 }; aniProgress = new int[] { 0, 0, 0, 0, 0, 0, 0 }; ani = new HistogramAnimation(); ani.setDuration(2000); xLinePaint = new Paint(); hLinePaint = new Paint(); titlePaint = new Paint(); paint = new Paint(); // 給畫筆設置顏色 xLinePaint.setColor(Color.DKGRAY); hLinePaint.setColor(Color.LTGRAY); titlePaint.setColor(Color.BLACK); // 加載畫圖 bitmap = BitmapFactory .decodeResource(getResources(), R.drawable.column); } public void start(int flag) { this.flag = flag; this.startAnimation(ani); } @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight() - dp2px(50); // 繪制底部的線條 canvas.drawLine(dp2px(30), height + dp2px(3), width - dp2px(30), height + dp2px(3), xLinePaint); int leftHeight = height - dp2px(5);// 左側外周的 需要劃分的高度: int hPerHeight = leftHeight / 4;// 分成四部分 hLinePaint.setTextAlign(Align.CENTER); // 設置四條虛線 for (int i = 0; i < 4; i++) { canvas.drawLine(dp2px(30), dp2px(10) + i * hPerHeight, width - dp2px(30), dp2px(10) + i * hPerHeight, hLinePaint); } // 繪制 Y 周坐標 titlePaint.setTextAlign(Align.RIGHT); titlePaint.setTextSize(sp2px(12)); titlePaint.setAntiAlias(true); titlePaint.setStyle(Paint.Style.FILL); // 設置左部的數字 for (int i = 0; i < ySteps.length; i++) { canvas.drawText(ySteps[i], dp2px(25), dp2px(13) + i * hPerHeight, titlePaint); } // 繪制 X 周 做坐標 int xAxisLength = width - dp2px(30); int columCount = xWeeks.length + 1; int step = xAxisLength / columCount; // 設置底部的數字 for (int i = 0; i < columCount - 1; i++) { // text, baseX, baseY, textPaint canvas.drawText(xWeeks[i], dp2px(25) + step * (i + 1), height + dp2px(20), titlePaint); } // 繪制矩形 if (aniProgress != null && aniProgress.length > 0) { for (int i = 0; i < aniProgress.length; i++) {// 循環遍歷將7條柱狀圖形畫出來 int value = aniProgress[i]; paint.setAntiAlias(true);// 抗鋸齒效果 paint.setStyle(Paint.Style.FILL); paint.setTextSize(sp2px(15));// 字體大小 paint.setColor(Color.parseColor("#6DCAEC"));// 字體顏色 Rect rect = new Rect();// 柱狀圖的形狀 rect.left = step * (i + 1); rect.right = dp2px(30) + step * (i + 1); int rh = (int) (leftHeight - leftHeight * (value / 10000.0)); rect.top = rh + dp2px(10); rect.bottom = height; canvas.drawBitmap(bitmap, null, rect, paint); // 是否顯示柱狀圖上方的數字 if (this.text[i] == TRUE) { canvas.drawText(value + "", dp2px(15) + step * (i + 1) - dp2px(15), rh + dp2px(5), paint); } } } } private int dp2px(int value) { float v = getContext().getResources().getDisplayMetrics().density; return (int) (v * value + 0.5f); } private int sp2px(int value) { float v = getContext().getResources().getDisplayMetrics().scaledDensity; return (int) (v * value + 0.5f); } /** * 設置點擊事件,是否顯示數字 */ public boolean onTouchEvent(MotionEvent event) { int step = (getWidth() - dp2px(30)) / 8; int x = (int) event.getX(); for (int i = 0; i < 7; i++) { if (x > (dp2px(15) + step * (i + 1) - dp2px(15)) && x < (dp2px(15) + step * (i + 1) + dp2px(15))) { text[i] = 1; for (int j = 0; j < 7; j++) { if (i != j) { text[j] = 0; } } if (Looper.getMainLooper() == Looper.myLooper()) { invalidate(); } else { postInvalidate(); } } } return super.onTouchEvent(event); } /** * 集成animation的一個動畫類 * * @author 李垭超 */ private class HistogramAnimation extends Animation { protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); if (interpolatedTime < 1.0f && flag == 2) { for (int i = 0; i < aniProgress.length; i++) { aniProgress[i] = (int) (progress[i] * interpolatedTime); } } else { for (int i = 0; i < aniProgress.length; i++) { aniProgress[i] = progress[i]; } } invalidate(); } } }
package com.example.statisticalchart; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.animation.Animation; import android.view.animation.Transformation; public class LineChartView extends View { private Paint rectPaint;// 設置左側為白色,顯示數表 private Paint hLinePaint;// 坐標軸水平內部 虛線畫筆 private Paint titlePaint;// 繪制文本的畫筆 private Paint linePaint; private Paint paint;// 矩形畫筆 柱狀圖的樣式信息 private int[] text;// 折線的轉折點 int x, y, preX, preY; // 坐標軸左側的數標 private Bitmap mBitmap; // 坐標軸底部的星期數 private String[] str = { "62", "72", "82", "92", "102", "112", "122", "132", "142" }; private HistogramAnimation ani; private int flag; public LineChartView(Context context) { super(context); init(context, null); } public LineChartView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub init(context, attrs); } private void init(Context context, AttributeSet attrs) { text = new int[] { 6, 5, 5, 4, 5, 3, 2, 3, 1, 1 }; ani = new HistogramAnimation(); ani.setDuration(4000); rectPaint = new Paint(); titlePaint = new Paint(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); linePaint = new Paint(); // titlePaint.setAntiAlias(true); Rect bundle1 = new Rect(); Rect bundle2 = new Rect(); hLinePaint = new Paint(); int perWidth = getWidth() / 10;// 將寬度分為10部分 int hPerHeight = getHeight() / 10;// 將高度分為10部分 rectPaint.setColor(Color.WHITE); canvas.drawRect(0, 0, dp2px(30), getHeight(), rectPaint);// 畫一塊白色區域 Path path = new Path();// 折線圖的路徑 mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas mCanvas = new Canvas(mBitmap); for (int i = 0; i < 10; i++) {// 畫x線,並在左側顯示相應的數值 hLinePaint.setTextAlign(Align.CENTER); hLinePaint.setColor(Color.WHITE); y = i * hPerHeight; if (i == 2) { hLinePaint.setStrokeWidth(4); for (int j = 0; j < 10; j++) { canvas.drawLine(dp2px(30) + j * perWidth, y, dp2px(28) + (j + 1) * perWidth, y, hLinePaint); } titlePaint.setTextSize(sp2px(20)); titlePaint.getTextBounds(str[i - 1], 0, str[i - 1].length(), bundle1); canvas.drawText(str[i - 1], dp2px(25) - bundle1.width(), i * hPerHeight + (bundle1.height() / 2), titlePaint); } else { hLinePaint.setStrokeWidth(1); canvas.drawLine(dp2px(30), y, getWidth(), y, hLinePaint); if (i != 0) { titlePaint.setTextSize(sp2px(15)); titlePaint.getTextBounds(str[i - 1], 0, str[i - 1].length(), bundle2); canvas.drawText(str[i - 1], dp2px(25) - bundle2.width(), i * hPerHeight + (bundle2.height() / 2), titlePaint); } } x = i * perWidth + dp2px(30); if (i == 0) { path.moveTo(x, text[i] * hPerHeight); } else { path.lineTo(x, text[i] * hPerHeight); } linePaint.setColor(Color.parseColor("#bb2222")); linePaint.setAntiAlias(true); paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(dp2px(1)); if (i != 0) { mCanvas.drawCircle(x, text[i] * hPerHeight, dp2px(3), linePaint); } paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); mCanvas.drawPath(path, paint); } paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); paint.setStyle(Paint.Style.FILL); mCanvas.drawRect(preX + dp2px(30), 0, getWidth(), getHeight(), paint); canvas.drawBitmap(mBitmap, 0, 0, null); // Log.i("tag", "onDraw()1111"); } private int dp2px(int value) { float v = getContext().getResources().getDisplayMetrics().density; return (int) (v * value + 0.5f); } private int sp2px(int value) { float v = getContext().getResources().getDisplayMetrics().scaledDensity; return (int) (v * value + 0.5f); } public void start(int flag) { startAnimation(ani); this.flag = flag; } /** * 集成animation的一個動畫類 * * @author */ private class HistogramAnimation extends Animation { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); if (interpolatedTime < 1.0f && flag == 2) { preX = (int) ((getWidth() - dp2px(30)) * interpolatedTime); } else { preX = getWidth(); } invalidate(); } } }
package com.example.statisticalchart; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.animation.Animation; import android.view.animation.Transformation; public class PinChart extends View { static Canvas c; private Paint[] mPaints; private RectF mBigOval; float[] mSweep = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; private int preWidth; private mAnimation ani; private int flag; private int centerX; private int centerY; int valueX; int valueY; public static float[] humidity = { 110, 60, 50, 50, 40, 30, 10, 10 }; private String str[] = { "數據24%", "數據19%", "數據21%", "其他18%", "數據3%", "數據3%", "數據4%", "數據6%" }; private final String color[] = { "#2cbae7", "#ffa500", "#ff5b3b", "#9fa0a4", "#6a71e5", "#f83f5d", "#64a300", "#64ef85" }; public PinChart(Context context) { super(context); initView(); } public PinChart(Context context, AttributeSet atr) { super(context, atr); initView(); } private void initView() { ani = new mAnimation(); ani.setDuration(2000); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.TRANSPARENT);// 設置背景顏色(透明) mPaints = new Paint[humidity.length]; for (int i = 0; i < humidity.length; i++) { mPaints[i] = new Paint(); mPaints[i].setAntiAlias(true); mPaints[i].setStyle(Paint.Style.FILL); mPaints[i].setColor(Color.parseColor(color[i])); } int cicleWidth = getWidth() - dp2px(60); centerX = getWidth() / 2; centerY = dp2px(10) + cicleWidth / 2; preWidth = (getWidth() - dp2px(40)) / 4; int half = getWidth() / 2; mBigOval = new RectF();// 餅圖的四周邊界 mBigOval.top = dp2px(10); mBigOval.left = half - cicleWidth / 2; mBigOval.bottom = dp2px(10) + cicleWidth; mBigOval.right = half + cicleWidth / 2; float start = -180; Rect bounds = new Rect(); for (int i = 0; i < humidity.length; i++) { canvas.drawArc(mBigOval, start, mSweep[i], true, mPaints[i]); if (humidity[i] > 45) { mPaints[i].setXfermode(new PorterDuffXfermode( PorterDuff.Mode.SRC_OVER)); mPaints[i].setAntiAlias(true); mPaints[i].setColor(Color.WHITE); mPaints[i].getTextBounds(str[i], 0, str[i].length(), bounds); mPaints[i].setTextSize(sp2px(15)); measureText(start + 180, humidity[i], cicleWidth / 3, i); canvas.drawText(str[i], valueX - mPaints[i].measureText(str[i]) / 2, valueY + bounds.height() / 2, mPaints[i]); } start += humidity[i]; int j = 1; int k; if (i < 4) { j = 0; k = i; } else { j = 1; k = i - 4; } mPaints[i] = new Paint(); mPaints[i].setAntiAlias(true); mPaints[i].setStyle(Paint.Style.FILL); mPaints[i].setColor(Color.parseColor(color[i])); canvas.drawRect(new RectF(dp2px(20) + preWidth * k, cicleWidth + dp2px(j * 30 + 20), dp2px(20) + preWidth * (k + 1), cicleWidth + dp2px(50 + j * 30)), mPaints[i]); mPaints[i].setXfermode(new PorterDuffXfermode( PorterDuff.Mode.SRC_OVER)); mPaints[i].setAntiAlias(true); mPaints[i].setColor(Color.WHITE); mPaints[i].getTextBounds(str[i], 0, str[i].length(), bounds); mPaints[i].setTextSize(sp2px(15)); canvas.drawText(str[i], dp2px(20) + preWidth * k + preWidth / 2 - mPaints[i].measureText(str[i]) / 2, cicleWidth + dp2px(j * 30 + 20) + (dp2px(30) / 2 + bounds.height() / 2), mPaints[i]); } } /** * 顯示相應區域字開始的x,y坐標 * * @param start * @param angle * @param radius * @param i */ private void measureText(float start, float angle, int radius, int i) { float temp = start + (angle / 2); if (temp < 90) { valueX = (int) (centerX - Math.abs(radius * Math.sin((temp / 180) * Math.PI))); valueY = (int) (centerY - Math.abs(radius * Math.cos((temp / 180) * Math.PI))); } else if (temp > 90 && temp < 180) { temp = 180 - temp; valueX = centerX + (int) Math .abs((radius * Math.cos((temp / 180) * Math.PI))); valueY = centerY - (int) Math .abs((radius * Math.sin((temp / 180) * Math.PI))); } else if (temp > 180 && temp < 270) { temp = temp - 180; valueX = centerX + (int) Math .abs((radius * Math.cos((temp / 180) * Math.PI))); valueY = centerY + (int) Math .abs((radius * Math.sin((temp / 180) * Math.PI))); } else { temp = 360 - temp; valueX = centerX - (int) Math .abs((radius * Math.cos((temp / 180) * Math.PI))); valueY = centerY + (int) Math .abs((radius * Math.sin((temp / 180) * Math.PI))); } } private int sp2px(int value) { float v = getResources().getDisplayMetrics().scaledDensity; return (int) (value * v + 0.5f); } private int dp2px(int value) { float v = getResources().getDisplayMetrics().density; return (int) (value * v + 0.5f); } public void start(int flag) { startAnimation(ani); this.flag = flag; } class mAnimation extends Animation { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); if (interpolatedTime < 1.0f && flag == 2) { for (int i = 0; i < humidity.length; i++) { mSweep[i] = humidity[i] * interpolatedTime; } } else if (flag == 1) { for (int i = 0; i < humidity.length; i++) { mSweep[i] = 0; } } else { for (int i = 0; i < humidity.length; i++) { mSweep[i] = humidity[i]; } } invalidate(); } } }
說明:RecyclerView是support-v7包中的新組件,你可以使用該組件替代ListView和CridView,從命名可以看出RecyclerView會自動回
一、資源管理器介紹現在在一些移動終端上面都會有自帶的資源管理器,其實其並非是Android系統自帶,而是手機產商與app開發商的合作而導致融合,借助第三方的開發軟件預裝在
Android 開源項目第一篇——個性化控件(View)篇包括ListView、ActionBar、Menu、ViewPager、Gallery、
在朋友圈,總能看到好友分享的音樂,自己也想來試試,微信怎麼分享音樂?小編將給大家介紹一下微信朋友圈的音樂是怎麼分享的,不懂的朋友可以了解下這裡我們以蘋果iP