android繪制曲線和折線圖的方法
本文實例為大家分享了android繪制曲線和折線圖的具體代碼,供大家參考,具體內(nèi)容如下
(曲線)
(折線)
1.CurveView.java
package com.package; ? import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Shader; import android.util.AttributeSet; import android.util.Pair; import android.util.TypedValue; import android.view.View; import android.view.WindowManager; ? import androidx.annotation.Nullable; ? import java.util.ArrayList; import java.util.List; ? public class CurveView extends View { ? ? private Paint mBrokenLinePaint; ? ? private Paint mInCirclePaint; ? ? private Paint mFillCirclePaint; ? ? private Paint mOutCirclePaint; ? ? private Paint mBottomLinePaint; ? ? private Paint mXTextPaint; ? ? private Paint mYLinePaint; ? ? private Paint mYTextPaint; ? ? private boolean isCurve; ? ? /** ? ? ?* X軸的數(shù)量,按需調(diào)整 ? ? ?*/ ? ? private int mLength = 7; ? ? /** ? ? ?* 獲取屏幕的寬度 ? ? ?*/ ? ? private final int mScreenWidth = getScreenWidth(getContext()); ? ? /** ? ? ?* 獲取實際屏幕的寬度 ? ? ?*/ ? ? private int cScreenWidth = getScreenWidth(getContext()); ? ? /** ? ? ?* 整個折線圖的高度=屏幕高度-頂部的狀態(tài)欄高度 ? ? ?*/ ? ? private final int mHeight = getScreenHeight(getContext()) - dp2px(getContext(), 190); ? ? /** ? ? ?* 節(jié)點外圓的半徑 ? ? ?*/ ? ? private final int outRadius = dp2px(getContext(), 6); ? ? /** ? ? ?* 節(jié)點內(nèi)圓的半徑 ? ? ?*/ ? ? private final int inRadius = dp2px(getContext(), 3); ? ? /** ? ? ?* 左右兩邊距邊緣的距離 ? ? ?*/ ? ? private final float mSideLength = dp2px(getContext(), 20); ? ? /** ? ? ?* X軸底部文字的高度 ? ? ?*/ ? ? private final int mXTextHeight = dp2px(getContext(), 30); ? ? ? /** ? ? ?* 距離上邊距的高度 ? ? ?*/ ? ? private final int mPaddingTop = dp2px(getContext(), 10); ? ? ? /** ? ? ?* 獲取間隔距離 ? ? ?*/ ? ? private int mSpaceLength; ? ? /** ? ? ?* 用戶設(shè)置的數(shù)據(jù) ? ? ?*/ ? ? private final List<Pair<String, Float>> dataValue = new ArrayList<>(); ? ? /** ? ? ?* 節(jié)點數(shù)據(jù) ? ? ?*/ ? ? private final List<Pair<Float, Float>> nodeValue = new ArrayList<>(); ? ? /** ? ? ?* 節(jié)點上標(biāo)注的文字 ? ? ?*/ ? ? private final List<String> yValue = new ArrayList<>(); ? ? private Paint mShadowPaint; ? ? ? public CurveView(Context context) { ? ? ? ? this(context, null); ? ? } ? ? ? public CurveView(Context context, @Nullable AttributeSet attrs) { ? ? ? ? this(context, attrs, 0); ? ? } ? ? ? public CurveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { ? ? ? ? super(context, attrs, defStyleAttr); ? ? ? ? init(); ? ? } ? ? ? /** ? ? ?* dp轉(zhuǎn)px ? ? ?*/ ? ? public static int dp2px(Context context, float dp) { ? ? ? ? float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()); ? ? ? ? return Math.round(px); ? ? } ? ? ? /** ? ? ?* 獲取屏幕寬度 ? ? ?*/ ? ? public static int getScreenWidth(Context context) { ? ? ? ? WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); ? ? ? ? Point size = new Point(); ? ? ? ? wm.getDefaultDisplay().getSize(size); ? ? ? ? return size.x; ? ? } ? ? ? /*設(shè)置實際內(nèi)容寬度*/ ? ? public void setContentWidth(int size) { ? ? ? ? mLength = size + 1; ? ? ? ? cScreenWidth = (mScreenWidth / 7) * size; ? ? } ? ? ? /** ? ? ?* 獲取屏幕高度 ? ? ?*/ ? ? public static int getScreenHeight(Context context) { ? ? ? ? WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); ? ? ? ? Point size = new Point(); ? ? ? ? wm.getDefaultDisplay().getSize(size); ? ? ? ? return size.y; ? ? } ? ? ? /** ? ? ?* 設(shè)置數(shù)據(jù) ? ? ?*/ ? ? public void setData(List<String> xAxis, List<String> yAxis) { ? ? ? ? dataValue.clear(); ? ? ? ? yValue.clear(); ? ? ? ? float yHeight = mHeight - mXTextHeight; ? ? ? ? for (int i = 0; i < xAxis.size(); i++) { ? ? ? ? ? ? yValue.add(yAxis.get(i)); ? ? ? ? ? ? float value = Float.parseFloat(yAxis.get(i)); ? ? ? ? ? ? dataValue.add(new Pair<>(xAxis.get(i), (yHeight - value / 200f * yHeight))); ? ? ? ? } ? ? ? ? invalidate(); ? ? ? ? requestLayout(); ? ? } ? ? ? private void init() { ? ? ? ? getSpaceLength();//獲取間隔距離 ? ? ? ? initYLine();//初始化豎直方向的線條 ? ? ? ? initBottomLine();//初始化底部橫線paint ? ? ? ? initInCircle();//初始化節(jié)點內(nèi)圓 ? ? ? ? initBrokenLine();//初始化曲線、折線 ? ? ? ? initXtext();//初始化X軸標(biāo)簽 ? ? ? ? initYtext();//初始化Y軸上數(shù)值 ? ? ? ? initShadowPaint();//初始化陰影 ? ? } ? ? ? /** ? ? ?* 獲取間隔距離 ? ? ?* (屏幕寬度-兩邊的間距)/(x軸數(shù)量-1) ? ? ?*/ ? ? private void getSpaceLength() { ? ? ? ? mSpaceLength = (int) (mScreenWidth - mSideLength * 2) / (mLength - 1); ? ? } ? ? ? /** ? ? ?* 初始化豎直方向的線條 ? ? ?*/ ? ? private void initYLine() { ? ? ? ? mYLinePaint = new Paint(); ? ? ? ? mYLinePaint.setColor(Color.GRAY); ? ? ? ? mYLinePaint.setStrokeWidth(1); ? ? ? ? mYLinePaint.setStyle(Paint.Style.STROKE); ? ? ? ? mYLinePaint.setAntiAlias(true); ? ? } ? ? ? /** ? ? ?* 初始化底部橫線paint ? ? ?*/ ? ? private void initBottomLine() { ? ? ? ? mBottomLinePaint = new Paint(); ? ? ? ? mBottomLinePaint.setColor(Color.GRAY); ? ? ? ? mBottomLinePaint.setStrokeWidth(dp2px(getContext(), 0.5f)); ? ? ? ? mBottomLinePaint.setStyle(Paint.Style.STROKE); ? ? ? ? mBottomLinePaint.setAntiAlias(true); ? ? } ? ? ? /** ? ? ?* 初始化X軸標(biāo)簽 ? ? ?*/ ? ? private void initXtext() { ? ? ? ? mXTextPaint = new Paint(); ? ? ? ? mXTextPaint.setColor(Color.GRAY); ? ? ? ? mXTextPaint.setTextSize(dp2px(getContext(), 12)); ? ? ? ? mXTextPaint.setStyle(Paint.Style.FILL); ? ? ? ? mXTextPaint.setAntiAlias(true); ? ? } ? ? ? /** ? ? ?* 初始化Y軸上數(shù)值 ? ? ?*/ ? ? private void initYtext() { ? ? ? ? mYTextPaint = new Paint(); ? ? ? ? mYTextPaint.setColor(Color.GRAY); ? ? ? ? mYTextPaint.setTextSize(dp2px(getContext(), 12)); ? ? ? ? mYTextPaint.setStyle(Paint.Style.FILL); ? ? ? ? mYTextPaint.setAntiAlias(true); ? ? } ? ? ? /** ? ? ?* 初始化節(jié)點內(nèi)圓 ? ? ?*/ ? ? private void initInCircle() { ? ? ? ? //初始化外圓 ? ? ? ? mOutCirclePaint = new Paint(); ? ? ? ? mOutCirclePaint.setColor(Color.GREEN); ? ? ? ? mOutCirclePaint.setStyle(Paint.Style.FILL); ? ? ? ? mOutCirclePaint.setAntiAlias(true); ? ? ? ? //內(nèi)框 ? ? ? ? mInCirclePaint = new Paint(); ? ? ? ? mInCirclePaint.setColor(Color.GRAY); ? ? ? ? mInCirclePaint.setStyle(Paint.Style.STROKE); ? ? ? ? mInCirclePaint.setStrokeWidth(dp2px(getContext(), 2)); ? ? ? ? mInCirclePaint.setAntiAlias(true); ? ? ? ? //內(nèi)圓 ? ? ? ? mFillCirclePaint = new Paint(); ? ? ? ? mFillCirclePaint.setColor(Color.GRAY); ? ? ? ? mFillCirclePaint.setStyle(Paint.Style.FILL); ? ? ? ? mFillCirclePaint.setAntiAlias(true); ? ? } ? ? ? /** ? ? ?* 初始化曲線、折線 ? ? ?*/ ? ? private void initBrokenLine() { ? ? ? ? mBrokenLinePaint = new Paint();//折線 ? ? ? ? mBrokenLinePaint.setColor(Color.GRAY); ? ? ? ? mBrokenLinePaint.setStrokeWidth(dp2px(getContext(), 2f)); ? ? ? ? mBrokenLinePaint.setStyle(Paint.Style.STROKE); ? ? ? ? mBrokenLinePaint.setStrokeCap(Paint.Cap.ROUND); ? ? ? ? mBrokenLinePaint.setAntiAlias(true); ? ? } ? ? ? /** ? ? ?* 初始化陰影 ? ? ?*/ ? ? private void initShadowPaint() { ? ? ? ? mShadowPaint = new Paint(); ? ? ? ? mShadowPaint.setStyle(Paint.Style.FILL); ? ? ? ? mShadowPaint.setAntiAlias(true); ? ? ? ? Shader shader = new LinearGradient(getWidth() / 2f, getHeight(), getWidth() / 2f, 0, ? ? ? ? ? ? ? ? Color.parseColor("#3300FF00"), Color.parseColor("#3300FF00"), Shader.TileMode.MIRROR); ? ? ? ? mShadowPaint.setShader(shader); ? ? } ? ? ? /** ? ? ?* 繪制 ? ? ?*/ ? ? @Override ? ? protected void onDraw(Canvas canvas) { ? ? ? ? super.onDraw(canvas); ? ? ? ? drawYLine(canvas); ? ? ? ? // 繪制底部的X軸線 ? ? ? ? drawBottomLine(canvas); ? ? ? ? // 繪制節(jié)點和折線圖 ? ? ? ? drawCircleLine(canvas); ? ? ? ? // 繪制Y軸上的數(shù)據(jù)和背景 ? ? ? ? drawYtext(canvas); ? ? ? ? // 繪制X軸標(biāo)簽文字 ? ? ? ? drawBottomText(canvas); ? ? ? ? //陰影 ? ? ? ? drawShadow(canvas); ? ? } ? ? ? /** ? ? ?* 繪制豎直方向上的線 ? ? ?*/ ? ? private void drawYLine(Canvas canvas) { ? ? ? ? for (int i = 0; i < dataValue.size(); i++) { ? ? ? ? ? ? canvas.drawLine(mSideLength + mSpaceLength * i, mPaddingTop, mSideLength + mSpaceLength * i, mHeight - mXTextHeight, mYLinePaint); ? ? ? ? } ? ? } ? ? ? /** ? ? ?* 繪制節(jié)點和曲線或者折線圖 ? ? ?*/ ? ? private void drawCircleLine(Canvas canvas) { ? ? ? ? nodeValue.clear(); ? ? ? ? for (int i = 0; i < dataValue.size(); i++) { ? ? ? ? ? ? Pair<String, Float> pair = dataValue.get(i); ? ? ? ? ? ? // 繪制節(jié)點外圓 ? ? ? ? ? ? canvas.drawCircle(mSideLength + mSpaceLength * i, pair.second, outRadius, mOutCirclePaint); ? ? ? ? ? ? // 繪制節(jié)點內(nèi)框 ? ? ? ? ? ? canvas.drawCircle(mSideLength + mSpaceLength * i, pair.second, inRadius, mFillCirclePaint); ? ? ? ? ? ? // 繪制節(jié)點內(nèi)圓 ? ? ? ? ? ? canvas.drawCircle(mSideLength + mSpaceLength * i, pair.second, inRadius, mInCirclePaint); ? ? ? ? ? ? // 保存圓心坐標(biāo) ? ? ? ? ? ? Pair<Float, Float> pairs = new Pair<>(mSideLength + mSpaceLength * i, pair.second); ? ? ? ? ? ? nodeValue.add(pairs); ? ? ? ? } ? ? ? ? drawScrollLine(canvas);//曲線 ? ? ? ? // drawLine(canvas);//折線 ? ? } ? ? ? /** ? ? ?* 繪制曲線圖 ? ? ?*/ ? ? private void drawScrollLine(Canvas canvas) { ? ? ? ? isCurve = true; ? ? ? ? PointF pStart, pEnd; ? ? ? ? List<PointF> points = getPoints(); ? ? ? ? Path path = new Path(); ? ? ? ? for (int i = 0; i < points.size() - 1; i++) { ? ? ? ? ? ? pStart = points.get(i); ? ? ? ? ? ? pEnd = points.get(i + 1); ? ? ? ? ? ? PointF point3 = new PointF(); ? ? ? ? ? ? PointF point4 = new PointF(); ? ? ? ? ? ? float wd = (pStart.x + pEnd.x) / 2; ? ? ? ? ? ? point3.x = wd; ? ? ? ? ? ? point3.y = pStart.y; ? ? ? ? ? ? point4.x = wd; ? ? ? ? ? ? point4.y = pEnd.y; ? ? ? ? ? ? path.moveTo(pStart.x, pStart.y); ? ? ? ? ? ? path.cubicTo(point3.x, point3.y, point4.x, point4.y, pEnd.x, pEnd.y); ? ? ? ? ? ? canvas.drawPath(path, mBrokenLinePaint); ? ? ? ? } ? ? } ? ? ? /** ? ? ?* 繪制折線 ? ? ?*/ ? ? private void drawLine(Canvas canvas) { ? ? ? ? isCurve = false; ? ? ? ? for (int i = 0; i < nodeValue.size(); i++) { ? ? ? ? ? ? if (i != nodeValue.size() - 1) { ? ? ? ? ? ? ? ? canvas.drawLine((float) nodeValue.get(i).first, ? ? ? ? ? ? ? ? ? ? ? ? (float) nodeValue.get(i).second, ? ? ? ? ? ? ? ? ? ? ? ? (float) nodeValue.get(i + 1).first, ? ? ? ? ? ? ? ? ? ? ? ? (float) nodeValue.get(i + 1).second, mBrokenLinePaint); ? ? ? ? ? ? } ? ? ? ? } ? ? } ? ? ? /** ? ? ?* 繪制陰影 ? ? ?*/ ? ? private void drawShadow(Canvas canvas) { ? ? ? ? List<PointF> points = getPoints(); ? ? ? ? if (isCurve) {//曲線 ? ? ? ? ? ? PointF pStart, pEnd; ? ? ? ? ? ? Path path = new Path(); ? ? ? ? ? ? for (int i = 0; i < points.size() - 1; i++) { ? ? ? ? ? ? ? ? pStart = points.get(i); ? ? ? ? ? ? ? ? pEnd = points.get(i + 1); ? ? ? ? ? ? ? ? PointF point3 = new PointF(); ? ? ? ? ? ? ? ? PointF point4 = new PointF(); ? ? ? ? ? ? ? ? float wd = (pStart.x + pEnd.x) / 2; ? ? ? ? ? ? ? ? point3.x = wd; ? ? ? ? ? ? ? ? point3.y = pStart.y; ? ? ? ? ? ? ? ? point4.x = wd; ? ? ? ? ? ? ? ? point4.y = pEnd.y; ? ? ? ? ? ? ? ? path.moveTo(pStart.x, pStart.y); ? ? ? ? ? ? ? ? path.cubicTo(point3.x, point3.y, point4.x, point4.y, pEnd.x, pEnd.y); ? ? ? ? ? ? ? ? //減去文字和指示標(biāo)的高度 ? ? ? ? ? ? ? ? path.lineTo(pEnd.x, getHeight() - mXTextHeight); ? ? ? ? ? ? ? ? path.lineTo(pStart.x, getHeight() - mXTextHeight); ? ? ? ? ? ? } ? ? ? ? ? ? path.close(); ? ? ? ? ? ? canvas.drawPath(path, mShadowPaint); ? ? ? ? } else { ? ? ? ? ? ? Path path = new Path(); ? ? ? ? ? ? path.moveTo(points.get(0).x, points.get(0).y); ? ? ? ? ? ? for (int i = 1; i < points.size(); i++) { ? ? ? ? ? ? ? ? path.lineTo(points.get(i).x, points.get(i).y); ? ? ? ? ? ? } ? ? ? ? ? ? //鏈接最后兩個點 ? ? ? ? ? ? int index = points.size() - 1; ? ? ? ? ? ? path.lineTo(points.get(index).x, getHeight() - mXTextHeight); ? ? ? ? ? ? path.lineTo(points.get(0).x, getHeight() - mXTextHeight); ? ? ? ? ? ? path.close(); ? ? ? ? ? ? canvas.drawPath(path, mShadowPaint); ? ? ? ? } ? ? } ? ? ? /** ? ? ?* 獲取坐標(biāo)點 ? ? ?*/ ? ? private List<PointF> getPoints() { ? ? ? ? ArrayList<PointF> points = new ArrayList<>(); ? ? ? ? for (Pair<Float, Float> pair : nodeValue) { ? ? ? ? ? ? points.add(new PointF((float) pair.first, (float) pair.second)); ? ? ? ? } ? ? ? ? return points; ? ? } ? ? ? /** ? ? ?* 繪制底部的X軸線 ? ? ?*/ ? ? private void drawBottomLine(Canvas canvas) { ? ? ? ? canvas.drawLine(0, mHeight - mXTextHeight, cScreenWidth, mHeight - mXTextHeight, mBottomLinePaint); ? ? } ? ? ? /** ? ? ?* 繪制X軸標(biāo)簽文字 ? ? ?*/ ? ? private void drawBottomText(Canvas canvas) { ? ? ? ? for (int i = 0; i < dataValue.size(); i++) { ? ? ? ? ? ? String xValue = dataValue.get(i).first; ? ? ? ? ? ? // 獲取Text內(nèi)容寬度 ? ? ? ? ? ? Rect bounds = new Rect(); ? ? ? ? ? ? mXTextPaint.getTextBounds(xValue, 0, xValue.length(), bounds); ? ? ? ? ? ? int width = bounds.right - bounds.left; ? ? ? ? ? ? canvas.drawText(xValue, mSideLength - width / 2f + mSpaceLength * i, mHeight - mXTextHeight / 2f, mXTextPaint); ? ? ? ? } ? ? } ? ? ? /** ? ? ?* 繪制Y軸上的數(shù)據(jù)和背景 ? ? ?*/ ? ? private void drawYtext(Canvas canvas) { ? ? ? ? for (int i = 0; i < dataValue.size(); i++) { ? ? ? ? ? ? Pair<String, Float> pair = dataValue.get(i); ? ? ? ? ? ? // 用Rect計算Text內(nèi)容寬度 ? ? ? ? ? ? Rect bounds = new Rect(); ? ? ? ? ? ? mYTextPaint.getTextBounds(pair.first, 0, pair.first.length(), bounds); ? ? ? ? ? ? int textWidth = bounds.right - bounds.left; ? ? ? ? ? ? // 繪制節(jié)點上的文字 ? ? ? ? ? ? canvas.drawText(yValue.get(i), mSideLength + mSpaceLength * i - textWidth / 2f, pair.second - 25, mYTextPaint); ? ? ? ? } ? ? } }
2.xml里引入
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" ? ? xmlns:app="http://schemas.android.com/apk/res-auto" ? ? xmlns:tools="http://schemas.android.com/tools" ? ? android:layout_width="match_parent" ? ? android:layout_height="match_parent" ? ? tools:context=".MainActivity"> ? ? ? <com.package.line.CurveView ? ? ? ? android:id="@+id/curveView" ? ? ? ? android:layout_width="match_parent" ? ? ? ? android:layout_height="match_parent" ? ? ? ? android:background="#f2f2f2"/> ? </androidx.constraintlayout.widget.ConstraintLayout>
3.activity中使用
package com.package; ? import androidx.appcompat.app.AppCompatActivity; ? import android.os.Bundle; ? import com.package.line.CurveView; ? import java.util.Arrays; import java.util.List; ? public class MainActivity extends AppCompatActivity { ? ? ? @Override ? ? protected void onCreate(Bundle savedInstanceState) { ? ? ? ? super.onCreate(savedInstanceState); ? ? ? ? setContentView(R.layout.activity_main); ? ? ? ? CurveView curveView = findViewById(R.id.curveView); ? ? ? ? ? List<String> xList = Arrays.asList("1","2","3","4","5","6","7"); ? ? ? ? List<String> yList = Arrays.asList("0","50","55","51","53","56","59"); ? ? ? ? curveView.setData(xList, yList); ? ? } }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android編程使用android-support-design實現(xiàn)MD風(fēng)格對話框功能示例
這篇文章主要介紹了Android編程使用android-support-design實現(xiàn)MD風(fēng)格對話框功能,涉及Android對話框、視圖、布局相關(guān)操作技巧,需要的朋友可以參考下2017-01-01Android組合式自定義控件實現(xiàn)購物車加減商品操作
這篇文章主要介紹了Android組合式自定義控件實現(xiàn)購物車加減商品操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-11-11Android開發(fā)TextView內(nèi)的文字實現(xiàn)自動換行
這篇文章主要為大家介紹了Android開發(fā)TextView內(nèi)的文字實現(xiàn)自動換行,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android Messenger實現(xiàn)進(jìn)程間通信及其原理
這篇文章主要為大家詳細(xì)介紹了Android Messenger實現(xiàn)進(jìn)程間通信及其原理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-05-05Android 中ActionBar+fragment實現(xiàn)頁面導(dǎo)航的實例
這篇文章主要介紹了Android 中ActionBar+fragment實現(xiàn)頁面導(dǎo)航的實例的相關(guān)資料,希望通過本文能幫助到大家實現(xiàn)這樣的功能,需要的朋友可以參考下2017-09-09Android開發(fā)5:應(yīng)用程序窗口小部件App Widgets的實現(xiàn)(附demo)
本篇文章主要介紹了android應(yīng)用程序窗口小部件App Widgets的實現(xiàn),具有一定的參考價值,有需要的可以了解一下。2016-11-11Android使用自定義view在指定時間內(nèi)勻速畫一條直線的實例代碼
這篇文章主要介紹了Android使用自定義view在指定時間內(nèi)勻速畫一條直線的實例代碼,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05Flutter學(xué)習(xí)之構(gòu)建、布局及繪制三部曲
這篇文章主要給大家介紹了關(guān)于Flutter學(xué)習(xí)之構(gòu)建、布局及繪制三部曲的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04