├── .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 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LineChartTest 2 | 项目里面要用到图表,第三方库都太大,自己简单的写了一个柱状图和折线图 3 | 有需要的朋友可以参考,有帮助的话求给个star 4 | 5 | ![image](https://github.com/amusiaHzr/LineChartTest/blob/master/Screenshot_2019-11-29-15-29-11-886_com.amusia.linecharttest.png?raw=false) 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 | --------------------------------------------------------------------------------