├── .gitignore
├── .idea
├── codeStyles
│ └── Project.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── Screenshot_2019-11-29-15-29-11-886_com.amusia.linecharttest.png
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── amusia
│ │ └── linecharttest
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── amusia
│ │ │ └── linecharttest
│ │ │ ├── DisplayUtils.java
│ │ │ ├── MainActivity.java
│ │ │ ├── PieEntry.java
│ │ │ ├── PreservationLineChart.java
│ │ │ ├── SimpleHistogram.java
│ │ │ ├── SimpleLineChart.java
│ │ │ └── SimplePie.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── amusia
│ └── linecharttest
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | xmlns:android
11 |
12 | ^$
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | xmlns:.*
22 |
23 | ^$
24 |
25 |
26 | BY_NAME
27 |
28 |
29 |
30 |
31 |
32 |
33 | .*:id
34 |
35 | http://schemas.android.com/apk/res/android
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | .*:name
45 |
46 | http://schemas.android.com/apk/res/android
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | name
56 |
57 | ^$
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | style
67 |
68 | ^$
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | .*
78 |
79 | ^$
80 |
81 |
82 | BY_NAME
83 |
84 |
85 |
86 |
87 |
88 |
89 | .*
90 |
91 | http://schemas.android.com/apk/res/android
92 |
93 |
94 | ANDROID_ATTRIBUTE_ORDER
95 |
96 |
97 |
98 |
99 |
100 |
101 | .*
102 |
103 | .*
104 |
105 |
106 | BY_NAME
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LineChartTest
2 | 项目里面要用到图表,第三方库都太大,自己简单的写了一个柱状图和折线图
3 | 有需要的朋友可以参考,有帮助的话求给个star
4 |
5 | 
6 |
--------------------------------------------------------------------------------
/Screenshot_2019-11-29-15-29-11-886_com.amusia.linecharttest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/Screenshot_2019-11-29-15-29-11-886_com.amusia.linecharttest.png
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.amusia.linecharttest"
7 | minSdkVersion 23
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation 'com.android.support:appcompat-v7:28.0.0'
24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
28 | }
29 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/amusia/linecharttest/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.amusia.linecharttest;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 |
24 | assertEquals("com.amusia.linecharttest", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/amusia/linecharttest/DisplayUtils.java:
--------------------------------------------------------------------------------
1 | package com.amusia.linecharttest;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.util.DisplayMetrics;
6 | import android.util.TypedValue;
7 |
8 | public class DisplayUtils {
9 |
10 | /**
11 | * 获取屏幕高度
12 | *
13 | * @param context
14 | * @return
15 | */
16 | public static int getScreenHeight(Context context) {
17 | DisplayMetrics displayMetrics = new DisplayMetrics();
18 | ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
19 | return displayMetrics.heightPixels;
20 | }
21 |
22 | /**
23 | * 获取屏幕宽度
24 | *
25 | * @param context
26 | * @return
27 | */
28 | public static int getScreenWidth(Context context) {
29 | DisplayMetrics displayMetrics = new DisplayMetrics();
30 | ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
31 | return displayMetrics.widthPixels;
32 | }
33 |
34 | /**
35 | * 将sp值转换为px值,保证文字大小不变
36 | *
37 | * @param spValue (DisplayMetrics类中属性scaledDensity)
38 | * @return
39 | */
40 | public static int sp2px(Context context, float spValue) {
41 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
42 | return (int) (spValue * fontScale + 0.5f);
43 | }
44 |
45 | /**
46 | * dp转换成px
47 | *
48 | * @param context
49 | * @param dpVale
50 | * @return
51 | */
52 | public static int dip2px(Context context, float dpVale) {
53 | final float scale = context.getResources().getDisplayMetrics().density;
54 | return (int) (dpVale * scale + 0.5f);
55 | }
56 |
57 | /**
58 | * sp转换成px
59 | *
60 | * @param context
61 | * @param sp
62 | * @return
63 | */
64 | public static int dip2sp(Context context, float sp) {
65 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics());
66 | }
67 |
68 | /**
69 | * px转换成dp
70 | *
71 | * @param context
72 | * @param pxValue
73 | * @return
74 | */
75 | public static int px2dip(Context context, float pxValue) {
76 | final float scale = context.getResources().getDisplayMetrics().density;
77 | return (int) (pxValue / scale + 0.5f);
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/java/com/amusia/linecharttest/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.amusia.linecharttest;
2 |
3 | import android.graphics.Color;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.os.Bundle;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class MainActivity extends AppCompatActivity {
11 | SimpleLineChart slc;
12 | SimpleHistogram sh;
13 | PreservationLineChart plc;
14 | SimplePie sp;
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_main);
20 |
21 | float[] data = {90f, 78.8f, 78.8f, 76.8f, 65.8f, 53.8f, 48.8f, 31.8f};
22 | slc = findViewById(R.id.slc);
23 | slc.setMode(SimpleLineChart.MODE_ONE);
24 | slc.setData(data, new String[]{"2021", "2022", "2023", "2024", "2021", "2022", "2023", "2024"});
25 |
26 | sh = findViewById(R.id.sh);
27 | sh.setData(data, new String[]{"湖南", "广东", "上海", "重庆", "浙江", "江西", "湖北", "沈阳"});
28 |
29 | plc = findViewById(R.id.plc);
30 | plc.setData(data, new String[]{"2021", "2022", "2023", "2024", "2021", "2022", "2023", "2024"});
31 |
32 |
33 | sp = findViewById(R.id.sp);
34 | List spData = new ArrayList<>();
35 | spData.add(new PieEntry(20, "你猜", Color.parseColor("#00C11B")));
36 | spData.add(new PieEntry(45, "你猜", Color.parseColor("#FFD006")));
37 | spData.add(new PieEntry(25, "你猜", Color.parseColor("#1B3CFF")));
38 | spData.add(new PieEntry(10, "你猜", Color.parseColor("#FF3721")));
39 |
40 | sp.setData(spData);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/amusia/linecharttest/PieEntry.java:
--------------------------------------------------------------------------------
1 | package com.amusia.linecharttest;
2 |
3 |
4 | public class PieEntry {
5 |
6 | //颜色
7 | private int color;
8 | //线条颜色
9 | private int lineColor;
10 | //比分比
11 | private float percentage;
12 | //条目名
13 | private String label;
14 | //扇区起始角度
15 | private float currentStartAngle;
16 | //扇区总角度
17 | private float sweepAngle;
18 |
19 | public PieEntry(float percentage, String label) {
20 | this.percentage = percentage;
21 | this.label = label;
22 | }
23 |
24 | public PieEntry(float percentage, String label, int color, int lineColor) {
25 | this.percentage = percentage;
26 | this.label = label;
27 | this.color = color;
28 | this.lineColor = lineColor;
29 | }
30 |
31 | public PieEntry(float percentage, String label, int color) {
32 | this.percentage = percentage;
33 | this.label = label;
34 | this.color = color;
35 | this.lineColor = lineColor;
36 | }
37 |
38 |
39 | public int getLineColor() {
40 | return lineColor;
41 | }
42 |
43 | public void setLineColor(int lineColor) {
44 | this.lineColor = lineColor;
45 | }
46 |
47 | public int getColor() {
48 | return color;
49 | }
50 |
51 | public void setColor(int color) {
52 | this.color = color;
53 | }
54 |
55 | public float getPercentage() {
56 | return percentage;
57 | }
58 |
59 | public void setPercentage(float percentage) {
60 | this.percentage = percentage;
61 | }
62 |
63 | public String getLabel() {
64 | return label;
65 | }
66 |
67 | public void setLabel(String label) {
68 | this.label = label;
69 | }
70 |
71 | public float getSweepAngle() {
72 | return sweepAngle;
73 | }
74 |
75 | public void setSweepAngle(float sweepAngle) {
76 | this.sweepAngle = sweepAngle;
77 | }
78 |
79 | public float getCurrentStartAngle() {
80 | return currentStartAngle;
81 | }
82 |
83 | public void setCurrentStartAngle(float currentStartAngle) {
84 | this.currentStartAngle = currentStartAngle;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/amusia/linecharttest/PreservationLineChart.java:
--------------------------------------------------------------------------------
1 | package com.amusia.linecharttest;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.LinearGradient;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.Point;
10 | import android.graphics.PointF;
11 | import android.graphics.PorterDuff;
12 | import android.graphics.Rect;
13 | import android.graphics.Shader;
14 | import android.support.annotation.Nullable;
15 | import android.util.AttributeSet;
16 | import android.util.Log;
17 | import android.view.View;
18 |
19 | import java.util.List;
20 |
21 | /**
22 | * @作者 huzhuoren
23 | * @创建日期 2019-11-25 10:04
24 | * @描述: 保值率折线图
25 | */
26 | public class PreservationLineChart extends View {
27 |
28 |
29 | private float mSelfWidth;
30 | private float mSelfHeight;
31 | private Paint.FontMetrics mFontMetrics;
32 | private Paint mPaint;
33 | private Paint mWhitePaint;
34 | private Paint mBgPaint;
35 | //最高90%
36 | private float weight = 8;
37 | //底部小竖条的高度
38 | private float bottomVerticalLineHeight = 20;
39 | //底部竖条和文字的间隔
40 | private float lineWithTextSpace = 30;
41 | //底部右间距
42 | private float bottomPaddingLeft = 10;
43 | //底部文字高度
44 | float textH;
45 | //底部高度
46 | float bottomHeight;
47 |
48 |
49 | float circleRadius = 10;
50 |
51 | private String[] bottomText = {"1年", "2年", "3年", "4年", "5年", "6年", "7年", "8年", "9年", "10年"};
52 |
53 | private float[] mData = {78.56f, 85, 75, 60, 50, 67.74f, 44.78f, 28.00f, 35, 25};
54 |
55 | public PreservationLineChart(Context context) {
56 | this(context, null);
57 | }
58 |
59 | public PreservationLineChart(Context context, @Nullable AttributeSet attrs) {
60 | this(context, attrs, 0);
61 | }
62 |
63 | public PreservationLineChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
64 | super(context, attrs, defStyleAttr);
65 | // init();
66 | }
67 |
68 | private void init() {
69 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
70 | mPaint.setTextSize(DisplayUtils.sp2px(getContext(), 11));
71 | mFontMetrics = mPaint.getFontMetrics();
72 | mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
73 | mWhitePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
74 | mWhitePaint.setColor(Color.parseColor("#FFFFFF"));
75 | mWhitePaint.setStyle(Paint.Style.FILL);
76 | mWhitePaint.setStrokeWidth(5);
77 |
78 | }
79 |
80 | @Override
81 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
82 | super.onSizeChanged(w, h, oldw, oldh);
83 | mSelfWidth = w;
84 | mSelfHeight = h;
85 | }
86 |
87 | @Override
88 | protected void onDraw(Canvas canvas) {
89 | super.onDraw(canvas);
90 |
91 | init();
92 | drawBottom(canvas);
93 | drawLeftAndHorizontalLine(canvas);
94 | drawBrokenLine(canvas);
95 |
96 | }
97 |
98 | private void drawBottom(Canvas canvas) {
99 | Rect rect = new Rect();
100 | mPaint.getTextBounds(bottomText[0], 0, bottomText[0].length(), rect);
101 | textH = mFontMetrics.bottom - mFontMetrics.top;
102 | float textW = rect.width();
103 |
104 | float y = mSelfHeight / weight;
105 | float x = mSelfWidth / (bottomText.length + 2);
106 |
107 |
108 | float currentX = (2 * x) + (((x / 2) - (textW / 2)) / 2);
109 | float currentY = mSelfHeight - 10;
110 |
111 | //绘制文字
112 | mPaint.setColor(Color.parseColor("#333333"));
113 | for (int i = 0; i < bottomText.length; i++) {
114 | canvas.drawText(bottomText[i], currentX - 10, currentY, mPaint);
115 | currentX = currentX + x;
116 | }
117 |
118 | //绘制底部横线
119 | float startX = (2 * x) - bottomPaddingLeft;
120 | float startY = mSelfHeight - (textH + lineWithTextSpace + bottomVerticalLineHeight);
121 | float stopX = mSelfWidth - bottomPaddingLeft;
122 | mPaint.setColor(Color.parseColor("#FF602A"));
123 | canvas.drawLine(startX, startY, stopX, startY, mPaint);
124 | Log.e("startX:", "" + startX);
125 |
126 | //绘制底部竖条
127 | Log.e("startX", startX + "");
128 | for (int i = 0; i < bottomText.length + 1; i++) {
129 | canvas.drawLine(startX, startY, startX, startY + bottomVerticalLineHeight, mPaint);
130 | startX += x;
131 | }
132 |
133 | }
134 |
135 | private void drawLeftAndHorizontalLine(Canvas canvas) {
136 |
137 | bottomHeight = textH + lineWithTextSpace + bottomVerticalLineHeight + textH / 2;
138 |
139 | Rect rect = new Rect();
140 | mPaint.getTextBounds("90%", 0, "90%".length(), rect);
141 | float functionTextH = rect.height();
142 |
143 | float y = (mSelfHeight - bottomHeight) / weight;
144 | float x = (mSelfWidth / (bottomText.length + 2));
145 |
146 | float currentX = x / 2;
147 | float currentY = mSelfHeight - bottomHeight;
148 |
149 | for (int i = 0; i < weight; i++) {
150 | //绘制文字
151 | mPaint.setColor(Color.parseColor("#333333"));
152 | canvas.drawText(((i + 2) * 10) + "%", currentX, currentY + functionTextH / 2, mPaint);
153 | //绘制横线
154 | if (i != 0) {
155 | mPaint.setColor(Color.parseColor("#FF602A"));
156 | float startX = 2 * x - bottomPaddingLeft;
157 | float stopX = mSelfWidth - bottomPaddingLeft;
158 | canvas.drawLine(startX, currentY, stopX, currentY, mPaint);
159 | }
160 | currentY -= y;
161 | }
162 | }
163 |
164 | //画折线图
165 | private void drawBrokenLine(Canvas canvas) {
166 | PointF lines[] = new PointF[mData.length];
167 | Rect rect = new Rect();
168 | mPaint.getTextBounds("90%", 0, "90%".length(), rect);
169 |
170 | float x = (mSelfWidth / (bottomText.length + 2));
171 | float currentX = 2 * x + x / 2;
172 | //因为算百分比的区域只占整个view的80%
173 | double height = (mSelfHeight - bottomHeight) / 0.8;
174 |
175 |
176 | for (int i = 0; i < mData.length; i++) {
177 | double currentY = height - ((height * mData[i] / 100));
178 | PointF pointF = new PointF(currentX, (float) currentY);
179 | lines[i] = pointF;
180 | currentX += x;
181 | }
182 |
183 |
184 | //绘制填充色
185 | mBgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
186 | Path pathArea = new Path();
187 | pathArea.moveTo(2 * x + x / 2, (float) (height - ((height * 20 / 100))));
188 | for (int i = 0; i < lines.length; i++) {
189 | pathArea.lineTo(lines[i].x, lines[i].y);
190 | }
191 | pathArea.lineTo(lines[lines.length - 1].x, (float) (height - ((height * 20 / 100))));
192 | pathArea.close();
193 |
194 | Shader shader = new LinearGradient(2 * x + x / 2,
195 | (float) (height - ((height * 0.8))),
196 | 2 * x + x / 2,
197 | (float) (height - ((height * 0.2))),
198 | Color.parseColor("#3BFFB59C"), Color.parseColor("#3BFFFFFF"), Shader.TileMode.CLAMP);
199 | mBgPaint.setShader(shader);
200 | canvas.drawPath(pathArea, mBgPaint);
201 |
202 |
203 | //绘制折线
204 | mPaint.setStyle(Paint.Style.STROKE);
205 | mPaint.setColor(Color.parseColor("#FF602A"));
206 | mPaint.setStrokeWidth(5);//设置画笔粗细
207 | Path linePath = new Path();
208 | for (int i = 0; i < lines.length; i++) {
209 | if (i == 0) {
210 | linePath.moveTo(lines[i].x, lines[i].y);
211 | }
212 | linePath.lineTo(lines[i].x, lines[i].y);
213 | }
214 | canvas.drawPath(linePath, mPaint);
215 |
216 |
217 | //画白色实心圆圈
218 | currentX = 2 * x + x / 2;
219 | for (int i = 0; i < mData.length; i++) {
220 | double currentY = height - ((height * mData[i] / 100));
221 | canvas.drawCircle(currentX, (float) currentY, circleRadius, mWhitePaint);
222 | PointF pointF = new PointF(currentX, (float) currentY);
223 | lines[i] = pointF;
224 | currentX += x;
225 | }
226 |
227 |
228 | //画橙色空心圆圈
229 | currentX = 2 * x + x / 2;
230 | for (int i = 0; i < mData.length; i++) {
231 | double currentY = height - ((height * mData[i] / 100));
232 | canvas.drawCircle(currentX, (float) currentY, circleRadius, mPaint);
233 | PointF pointF = new PointF(currentX, (float) currentY);
234 | lines[i] = pointF;
235 | currentX += x;
236 | }
237 |
238 | }
239 |
240 | public void setData(float[] d, String[] s) {
241 | mData = d;
242 | bottomText = s;
243 | invalidate();
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/app/src/main/java/com/amusia/linecharttest/SimpleHistogram.java:
--------------------------------------------------------------------------------
1 | package com.amusia.linecharttest;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Rect;
8 | import android.graphics.RectF;
9 | import android.support.annotation.Nullable;
10 | import android.util.AttributeSet;
11 | import android.util.Log;
12 | import android.view.View;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * @作者 huzhuoren
19 | * @创建日期 2019-11-28 16:22
20 | * @描述: 简单的直方图
21 | */
22 | public class SimpleHistogram extends View {
23 |
24 | private float mSelfWidth;
25 | private float mSelfHeight;
26 |
27 | private List mRectF;
28 | private Paint mPaint;
29 |
30 | //列宽度
31 | private float columnWidth = 80f;
32 | //间距宽度
33 | private float columnSpace = 0;
34 | //底部文字和图形的距离
35 | private float bottomTextSpace = 20;
36 | //顶部文字和图形的距离
37 | private float topTextSpace = 50;
38 | //文字的宽度高度
39 | private float textH;
40 | private float textW;
41 |
42 | Paint.FontMetrics mFontMetrics;
43 |
44 |
45 | private String mProvince[] = {"内蒙古", "福建", "湖南", "上海", "广州"};
46 | private float mPrice[] = {2.58f, 3.02f, 2.23f, 3.02f, 2.23f};
47 |
48 |
49 | public SimpleHistogram(Context context) {
50 | super(context);
51 | }
52 |
53 | public SimpleHistogram(Context context, @Nullable AttributeSet attrs) {
54 | super(context, attrs);
55 | }
56 |
57 | public SimpleHistogram(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
58 | super(context, attrs, defStyleAttr);
59 | }
60 |
61 |
62 | private void init() {
63 | mRectF = new ArrayList<>();
64 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
65 | //间距宽度 (总宽度 - 列宽度*列数 )/(列数+1)
66 | columnSpace = (mSelfWidth - columnWidth * mPrice.length) / (mPrice.length + 1);
67 | Rect rect = new Rect();
68 |
69 |
70 | mPaint.setTextSize(DisplayUtils.sp2px(getContext(), 13));
71 | mPaint.setColor(Color.parseColor("#333333"));
72 | mPaint.setTextAlign(Paint.Align.CENTER);
73 | mFontMetrics = mPaint.getFontMetrics();
74 | mPaint.getTextBounds(mProvince[0], 0, mProvince[0].length(), rect);
75 | textW = rect.width();
76 | textH = mFontMetrics.bottom - mFontMetrics.top;
77 | }
78 |
79 | @Override
80 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
81 | super.onSizeChanged(w, h, oldw, oldh);
82 | mSelfHeight = h;
83 | mSelfWidth = w;
84 | }
85 |
86 | @Override
87 | protected void onDraw(Canvas canvas) {
88 | super.onDraw(canvas);
89 | init();
90 | initRectF();
91 | drawBottomText(canvas);
92 | drawLine(canvas);
93 | drawRectF(canvas);
94 | drawTopText(canvas);
95 | }
96 |
97 | private void drawTopText(Canvas canvas) {
98 | mPaint.setColor(Color.parseColor("#333333"));
99 | for (int i = 0; i < mRectF.size(); i++) {
100 | RectF rectF = mRectF.get(i);
101 | String str = String.valueOf(mPrice[i]);
102 | float x = rectF.left + rectF.width() / 2;
103 | float y = mSelfHeight - (rectF.height() + topTextSpace + textH);
104 | canvas.drawText(str, x, y, mPaint);
105 | }
106 | }
107 |
108 | private void drawRectF(Canvas canvas) {
109 | mPaint.setColor(Color.parseColor("#FF561D"));
110 | for (int i = 0; i < mRectF.size(); i++) {
111 | canvas.drawRect(mRectF.get(i), mPaint);
112 | }
113 | }
114 |
115 | private void drawLine(Canvas canvas) {
116 | mPaint.setColor(Color.parseColor("#FF561D"));
117 | canvas.drawLine(0, mSelfHeight - textH - bottomTextSpace, mSelfWidth, mSelfHeight - textH - bottomTextSpace, mPaint);
118 | }
119 |
120 | private void drawBottomText(Canvas canvas) {
121 | mPaint.setColor(Color.parseColor("#333333"));
122 | mPaint.setTextAlign(Paint.Align.CENTER);
123 | Rect textRect = new Rect();
124 | for (int i = 0; i < mRectF.size(); i++) {
125 | RectF rectF = mRectF.get(i);
126 | String str = mProvince[i];
127 | mPaint.getTextBounds(str, 0, str.length(), textRect);
128 | float x = rectF.left + (rectF.width() / 2);
129 | float y = mSelfHeight - mFontMetrics.bottom;
130 | canvas.drawText(str, x, y, mPaint);
131 | }
132 | }
133 |
134 | private void initRectF() {
135 | float proportionHeight = (mSelfHeight - bottomTextSpace - topTextSpace - 2 * textH) / getMaxData();
136 | Log.e("Histogram", "proportionHeight:" + proportionHeight);
137 | for (int i = 0; i < mPrice.length; i++) {
138 |
139 | float left = columnSpace * (i + 1) + columnWidth * i;
140 | float right = left + columnWidth;
141 | float bottom = mSelfHeight - textH - bottomTextSpace;
142 | float top = bottom - mPrice[i] * proportionHeight;
143 |
144 | RectF rectF = new RectF();
145 | rectF.top = top;
146 | rectF.left = left;
147 | rectF.right = right;
148 | rectF.bottom = bottom;
149 | mRectF.add(rectF);
150 | }
151 | }
152 |
153 | private float getMaxData() {
154 | float max = 0;
155 | for (int i = 0; i < mPrice.length; i++) {
156 | max = Math.max(max, mPrice[i]);
157 | }
158 | return max;
159 | }
160 |
161 | public void setData(float[] f, String[] s) {
162 | if (f.length != s.length) {
163 | throw new RuntimeException("数据不匹配");
164 | }
165 | mPrice = f;
166 | mProvince = s;
167 | invalidate();
168 | }
169 |
170 | }
171 |
--------------------------------------------------------------------------------
/app/src/main/java/com/amusia/linecharttest/SimpleLineChart.java:
--------------------------------------------------------------------------------
1 | package com.amusia.linecharttest;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.LinearGradient;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.PointF;
10 | import android.graphics.Rect;
11 | import android.graphics.Shader;
12 | import android.support.annotation.Nullable;
13 | import android.util.AttributeSet;
14 | import android.view.View;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | public class SimpleLineChart extends View {
20 |
21 | public static int MODE_ONE = 1;
22 | public static int MODE_TWO = 2;
23 | //自身宽高
24 | private float mSelfWidth;
25 | private float mSelfHeight;
26 | //画笔
27 | private Paint mPaint;
28 | //背景画笔
29 | private Paint mBgPaint;
30 | //价格
31 | private float mData[] = {8.01f, 7.35f, 6.78f};
32 | //年份
33 | private String mYears[] = {"2019", "2020", "2021"};
34 | //底部文字宽高
35 | float bottomTextW;
36 | float bottomTextH;
37 | //水平垂直方向边距
38 | float horizontalSpace = 10F;
39 | float verticalSpace = 10F;
40 | //底部竖条高度
41 | float bottomVerticalLineHeight = 20F;
42 | //底部文字和底部线条间距
43 | float textWithLineSpace = bottomVerticalLineHeight + 20F;
44 | //每个年份的宽度
45 | float proportionWidth;
46 | //底部的高度
47 | float bottomHeight;
48 | // 上方文字高度
49 | float topTextHeight = 120F;
50 | //里面圆的半径
51 | float insideRadius = 6;
52 | //外面面圆的半径
53 | float outsideRadius = 12;
54 | //底部横线的宽度
55 | float lineWidth = 1F;
56 | //画折线图的path
57 | Path linePath;
58 | //画背景的path
59 | Path bgPath;
60 | //点的集合
61 | List points = new ArrayList<>();
62 | //顶部文字距离折线的高度
63 | float topTextSpace = 20f;
64 | //模式 MODE_ONE 底部有竖线 MODE_TWO 无竖线
65 | private int mode = MODE_ONE;
66 |
67 | public SimpleLineChart(Context context) {
68 | this(context, null);
69 | }
70 |
71 | public SimpleLineChart(Context context, @Nullable AttributeSet attrs) {
72 | this(context, attrs, 0);
73 | }
74 |
75 | public SimpleLineChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
76 | super(context, attrs, defStyleAttr);
77 | }
78 |
79 | private void init() {
80 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
81 | mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
82 | linePath = new Path();
83 | bgPath = new Path();
84 | Rect rect = new Rect();
85 | mPaint.getTextBounds(mYears[0], 0, mYears[0].length(), rect);
86 | bottomTextW = rect.width();
87 | bottomTextH = rect.height();
88 | //每个年份的宽度
89 | proportionWidth = (mSelfWidth - 2 * horizontalSpace) / mYears.length;
90 | }
91 |
92 | @Override
93 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
94 | super.onSizeChanged(w, h, oldw, oldh);
95 | mSelfWidth = w;
96 | mSelfHeight = h;
97 | }
98 |
99 | @Override
100 | protected void onDraw(Canvas canvas) {
101 | super.onDraw(canvas);
102 | //初始化一些参数
103 | init();
104 | //将所有的坐标点转成PointF,并放入list
105 | initPoint();
106 | //绘制底部文字
107 | drawBottomText(canvas);
108 | //绘制底部横线
109 | drawBottomLine(canvas);
110 | //绘制折线图背景
111 | drawBg(canvas);
112 | //绘制折线图
113 | drawBrokenLine(canvas);
114 | //绘制折线图上的文字
115 | drawTopText(canvas);
116 | }
117 |
118 | private void initPoint() {
119 | //折线图的高度 = view的高度 - 上面文字高度 - 下面文字高度
120 | float brokenLineHeight = mSelfHeight - topTextHeight - bottomHeight;
121 | //单位折线图高度 = 折线图的高度 / 数据集中最大的值
122 | float proportionHeight = brokenLineHeight / getMaxData();
123 | //初始x点坐标 = 水平方向间距 + 每个点的宽度/2
124 | float circleCenterX = horizontalSpace + proportionWidth / 2;
125 | //初始y点坐标 = 0
126 | float circleCenterY;
127 | //使用之前clear一下,有可能执行多次onDraw
128 | points.clear();
129 | //将点放入到集合
130 | for (int i = 0; i < mData.length; i++) {
131 | float currentProportionHeight = mData[i] * proportionHeight;
132 | circleCenterY = mSelfHeight - bottomHeight - currentProportionHeight;
133 | points.add(new PointF(circleCenterX, circleCenterY));
134 | circleCenterX += proportionWidth;
135 | }
136 | }
137 |
138 | /**
139 | * 画底部文字
140 | *
141 | * @param canvas
142 | */
143 | private void drawBottomText(Canvas canvas) {
144 | //文字初始点x坐标
145 | float currentTextX = 0;
146 | //文字初始点y坐标
147 | float currentTextY = 0;
148 | currentTextX = proportionWidth / 2 + horizontalSpace;
149 | currentTextY = mSelfHeight - verticalSpace;
150 | mPaint.setTextSize(DisplayUtils.sp2px(getContext(), 13));
151 | mPaint.setColor(Color.parseColor("#666666"));
152 | mPaint.setTextAlign(Paint.Align.CENTER);
153 | for (int i = 0; i < mYears.length; i++) {
154 | //currentTextX左边起始点,currentTextY文字基线坐标
155 | canvas.drawText(mYears[i], currentTextX, currentTextY, mPaint);
156 | currentTextX += proportionWidth;
157 | }
158 | }
159 |
160 | /**
161 | * 画底部线条
162 | *
163 | * @param canvas
164 | */
165 | private void drawBottomLine(Canvas canvas) {
166 | float bottomLineStartX = horizontalSpace;
167 | float bottomLineStopX = mSelfWidth - horizontalSpace;
168 | float bottomLineStartY = mSelfHeight - bottomTextH - textWithLineSpace - verticalSpace;
169 | float bottomLineStopY = bottomLineStartY;
170 |
171 | mPaint.setColor(Color.parseColor("#FF602A"));
172 | mPaint.setStrokeWidth(lineWidth);
173 |
174 | canvas.drawLine(bottomLineStartX, bottomLineStartY, bottomLineStopX, bottomLineStopY, mPaint);
175 | //起点X: 水平间距
176 | float verticalLineStartX = horizontalSpace;
177 | //起点Y:高度 - 文字高度 - 文字和横线间距 - 垂直间距
178 | float verticalLineStartY = mSelfHeight - bottomTextH - textWithLineSpace - verticalSpace;
179 | //终点X:不变
180 | float verticalLineStopX = verticalLineStartX;
181 | //终点Y:起点Y + 线长
182 | float verticalLineStopY = verticalLineStartY + bottomVerticalLineHeight;
183 |
184 | if (mode == MODE_ONE) {
185 | for (int i = 0; i < mYears.length + 1; i++) {
186 | canvas.drawLine(verticalLineStartX, verticalLineStartY, verticalLineStopX, verticalLineStopY, mPaint);
187 | verticalLineStartX = verticalLineStartX + proportionWidth;
188 | verticalLineStopX = verticalLineStartX;
189 | }
190 | }
191 |
192 | //底部的高度就是总体高度 - 竖线的Y起始点
193 | bottomHeight = mSelfHeight - verticalLineStartY;
194 | }
195 |
196 | /**
197 | * 画折线
198 | *
199 | * @param canvas
200 | */
201 | private void drawBrokenLine(Canvas canvas) {
202 | //画点
203 | for (int i = 0; i < points.size(); i++) {
204 | if (mode == MODE_ONE) {
205 | mPaint.setColor(Color.parseColor("#FFC5B2"));
206 | canvas.drawCircle(points.get(i).x, points.get(i).y, outsideRadius, mPaint);
207 | }
208 | mPaint.setColor(Color.parseColor("#FF602A"));
209 | canvas.drawCircle(points.get(i).x, points.get(i).y, insideRadius, mPaint);
210 | }
211 |
212 | //连线
213 | linePath.reset();
214 | for (int i = 0; i < points.size(); i++) {
215 | if (i == 0) {
216 | linePath.moveTo(points.get(i).x, points.get(i).y);
217 | } else {
218 | linePath.lineTo(points.get(i).x, points.get(i).y);
219 | }
220 | }
221 | mPaint.setStyle(Paint.Style.STROKE);
222 | mPaint.setStrokeWidth(lineWidth);
223 | mPaint.setColor(Color.parseColor("#FF602A"));
224 | canvas.drawPath(linePath, mPaint);
225 | }
226 |
227 | /**
228 | * 画渐变背景
229 | */
230 | private void drawBg(Canvas canvas) {
231 | //折线图的高度
232 | float brokenLineHeight = mSelfHeight - topTextHeight - bottomHeight;
233 | //单位折线图高度
234 | float proportionHeight = brokenLineHeight / getMaxData();
235 | bgPath.reset();
236 | bgPath.moveTo(horizontalSpace + proportionWidth / 2, mSelfHeight - bottomHeight);
237 | for (int i = 0; i < points.size(); i++) {
238 | bgPath.lineTo(points.get(i).x, points.get(i).y);
239 | }
240 | float bgPathEndX = points.get(points.size() - 1).x;
241 | bgPath.lineTo(bgPathEndX, mSelfHeight - bottomHeight);
242 | bgPath.close();
243 | mBgPaint.setStyle(Paint.Style.FILL);
244 |
245 |
246 | float shaderStartX = horizontalSpace + proportionWidth / 2;
247 | float shaderStartY = mSelfHeight - bottomHeight - getMaxData() * proportionHeight;
248 |
249 | float shaderStopX = shaderStartX;
250 | float shaderStopY = mSelfHeight - bottomHeight - topTextHeight;
251 |
252 | Shader shader = new LinearGradient(shaderStartX, shaderStartY, shaderStopX, shaderStopY,
253 | Color.parseColor("#4DFFB59C"), Color.parseColor("#4DFFF5F1"), Shader.TileMode.CLAMP);
254 |
255 | mBgPaint.setShader(shader);
256 | canvas.drawPath(bgPath, mBgPaint);
257 | }
258 |
259 | private void drawTopText(Canvas canvas) {
260 | Rect rect = new Rect();
261 | mPaint.setStyle(Paint.Style.FILL);
262 | mPaint.setColor(Color.parseColor("#333333"));
263 | mPaint.setTextAlign(Paint.Align.CENTER);
264 | for (int i = 0; i < mData.length; i++) {
265 | float textX = points.get(i).x;
266 | float textY = points.get(i).y - topTextSpace;
267 | canvas.drawText(String.valueOf(mData[i]), textX, textY, mPaint);
268 | }
269 |
270 |
271 | }
272 |
273 |
274 | private float getMaxData() {
275 | float max = 0;
276 | for (int i = 0; i < mData.length; i++) {
277 | max = Math.max(max, mData[i]);
278 | }
279 | return max;
280 | }
281 |
282 | public void setData(float[] data, String[] years) {
283 | if (data.length != years.length) {
284 | throw new RuntimeException("数据不匹配");
285 | }
286 | mData = data;
287 | mYears = years;
288 | invalidate();
289 | }
290 |
291 | public void setMode(int mode) {
292 | this.mode = mode;
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/app/src/main/java/com/amusia/linecharttest/SimplePie.java:
--------------------------------------------------------------------------------
1 | package com.amusia.linecharttest;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.RectF;
8 | import android.support.annotation.Nullable;
9 | import android.util.AttributeSet;
10 | import android.util.Log;
11 | import android.view.View;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | public class SimplePie extends View {
17 |
18 | //画笔
19 | private Paint mPaint;
20 |
21 | //圆弧 半径
22 | private float mRadius;
23 | //白色中心圆半径
24 | private float mCenterRadius;
25 |
26 | //正方形边长
27 | private float mShortSide;
28 |
29 | //整个view的宽高
30 | private float mWidth;
31 | private float mHeight;
32 |
33 | //圆心坐标
34 | private float mCenterX;
35 | private float mCenterY;
36 |
37 | //RectF的宽高
38 | private float mRectFWidth;
39 | private float mRectFHeight;
40 |
41 | //画圆弧的矩形
42 | private RectF mRectF;
43 |
44 | //饼图初始绘制角度
45 | private float startAngle = -90;
46 |
47 | //圆的padding
48 | private float circlePaddingSpace = 60;
49 |
50 | //说明的padding
51 | private float explainPaddingSpace = 60;
52 |
53 | //说明的高度
54 | private float explainHeight = 60;
55 |
56 | //文字高度
57 | private float textHeight;
58 |
59 | private List mData = new ArrayList<>();
60 |
61 | public SimplePie(Context context) {
62 | this(context, null);
63 | }
64 |
65 | public SimplePie(Context context, @Nullable AttributeSet attrs) {
66 | this(context, attrs, 0);
67 | }
68 |
69 | public SimplePie(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
70 | super(context, attrs, defStyleAttr);
71 | initPaint();
72 | }
73 |
74 | @Override
75 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
76 | super.onSizeChanged(w, h, oldw, oldh);
77 |
78 | mWidth = w;
79 | mHeight = h;
80 |
81 | mRectFWidth = 2 * (w / 3);
82 | Log.e("mRectFWidth", ">>>" + mRectFWidth);
83 | mRectFWidth = (w / 2);
84 | Log.e("mRectFWidth", ">>>" + mRectFWidth);
85 | mRectFHeight = h;
86 |
87 |
88 | }
89 |
90 | private void initPaint() {
91 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
92 | mPaint.setColor(Color.BLUE);
93 | mPaint.setTextSize(DisplayUtils.sp2px(getContext(), 12));
94 |
95 | Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
96 | textHeight = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom; // fontMetrics.bottom - fontMetrics.top;
97 | }
98 |
99 | private void initRectF() {
100 | mShortSide = Math.min(mRectFWidth, mRectFHeight);
101 | mRadius = (mShortSide - (2 * circlePaddingSpace)) / 2;
102 | mCenterX = mShortSide / 2;
103 | mCenterY = mShortSide / 2;
104 | mCenterRadius = (float) (mRadius * 0.4);
105 | mRectF = new RectF(circlePaddingSpace, circlePaddingSpace, mShortSide - circlePaddingSpace, mShortSide - circlePaddingSpace);
106 | }
107 |
108 | private void initData() {
109 | //当前起始角度
110 | float currentStartAngle = startAngle;
111 | for (int i = 0; i < mData.size(); i++) {
112 | PieEntry pie = mData.get(i);
113 | if (pie.getPercentage() == 0)
114 | continue;
115 | pie.setCurrentStartAngle(currentStartAngle);
116 | //每个数据百分比对应的角度
117 | float sweepAngle = pie.getPercentage() / 100 * 360;
118 | pie.setSweepAngle(sweepAngle);
119 | //起始角度不断增加
120 | currentStartAngle += sweepAngle;
121 | }
122 | }
123 |
124 | @Override
125 | protected void onDraw(Canvas canvas) {
126 | super.onDraw(canvas);
127 | if (mData.size() == 0) {
128 | return;
129 | }
130 | initRectF();
131 | // canvas.drawRect(mRectF,mPaint);
132 | drawPie(canvas);
133 | drawLeft(canvas);
134 |
135 |
136 | }
137 |
138 | public void setData(List data) {
139 | mData.clear();
140 | this.mData.addAll(data);
141 | for (PieEntry pieEntry : mData) {
142 | if (pieEntry.getPercentage() == 0) {
143 | mData.remove(pieEntry);
144 | }
145 | }
146 |
147 | initData();
148 | }
149 |
150 | //绘制饼图
151 | private void drawPie(Canvas canvas) {
152 | //当前起始角度
153 | for (PieEntry pie : mData) {
154 | if (pie.getSweepAngle() == 0)
155 | continue;
156 | mPaint.setColor(pie.getColor());
157 | canvas.drawArc(mRectF,
158 | pie.getCurrentStartAngle(),
159 | pie.getSweepAngle(),
160 | true, mPaint);
161 | }
162 | mPaint.setColor(Color.WHITE);
163 | canvas.drawCircle(mCenterX, mCenterY, mCenterRadius, mPaint);
164 | }
165 |
166 | private void drawLeft(Canvas canvas) {
167 | explainHeight = mRadius * 2 / mData.size();
168 |
169 | float centerX = mWidth - mRectFWidth + explainPaddingSpace;
170 | float currentHeightCenter = circlePaddingSpace + explainHeight / 2;
171 | float radius = 8;
172 | for (int i = 0; i < mData.size(); i++) {
173 | mPaint.setColor(mData.get(i).getColor());
174 | canvas.drawCircle(centerX, currentHeightCenter, radius, mPaint);
175 |
176 | mPaint.setColor(Color.parseColor("#333333"));
177 |
178 | float textY = currentHeightCenter + textHeight;
179 | canvas.drawText(mData.get(i).getLabel() + "(" + ((int) mData.get(i).getPercentage()) + "%)", centerX + explainPaddingSpace, textY, mPaint);
180 | currentHeightCenter += explainHeight;
181 |
182 | }
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
17 |
22 |
23 |
29 |
30 |
34 |
35 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | LineChartTest
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/amusia/linecharttest/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.amusia.linecharttest;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.0'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 |
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amusiaHzr/LineChartTest/748824de601731733d68560f9b18bc97729f2351/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Nov 25 10:43:25 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='LineChartTest'
3 |
--------------------------------------------------------------------------------