2117浏览
查看: 2117|回复: 2

[项目] 【脑洞大赛】基于NB-IoT的智慧路灯监控系统(手机应用开发)

[复制链接]
    通过与华为云平台进行数据对接及联动控制,为此开发智慧路灯APP控制系统。
    1、系统总体描述
    本系统共分为九个模块:系统导航、用户登录、扫码绑定、设备定位、设备状态、历史查询、设备控制、画像分析和系统设置。每个模块对应其各自的功能,通过设备的定位、设备的实时状态及设备控制能够全方位监控路灯的耗能量及使用情况。画像分析也可对某地方或某用户进行大数据AI分析得到监测数据,并且能够实时向用户推送用电情况,并为其用户进行合理的用电安排及方案。
    2、使用技术
    2.1、基础技术
  •      系统总体使用java语言进行开发;
  •     在界面设计及展示部分使用HTML搭配CSS技术使其界面美观大方;
  •     框架设计使用MVP模式进行设计使其系统结构清晰明了;
  •     数据对接使用HTTPOkHttp3协议,大大降低数据处理难度;且提高了数据的完整性和实时性。
     2.2、核心技术
  •     登录界面使用视屏背景技术将登录界面进行高度美化;
  •     在设备定位模块中使用第三方高德地图SDK进行开发;
  •     在云平台对接时使用华为云平台相关模块接口进行开发;
  •     使用Clendar相关类进行日期选择设计;
  •     使用Zxing二维码扫描分析技术进行扫码分析;
  •     使用Echart技术进行数据实时显示图表分析;
  •     在画像分析模块使用AI大数据分析获取数据实例。
     2.3技术亮点
  •     对第三方技术的合理运用;
  •     对MVP开发框架的组合设计;
  •     对API接口的清晰掌握;
  •     对各种相关工具类的开发及调用;
  •     结合大数据AI分析进行功能设计。
    3、开发软件
  •     系统环境:Windows 10
  •     开发环境:Android Studio 3.0JDK 8.0
  •     运行环境:Android 5.0级以上
  •     接口测试软件:Postman 6.5版本
  •     控制软件:Git
  •     打包发布软件:Android Studio Generate Signed  APK
  •   软件签名:iot_project.jks   
   4、功能概述
   4.1、系统导航
   首次进入APP当进入导航界面,导航界面中介绍APPLogo、简单描述、路灯模型、路灯功能分类及路灯运行方式。


    4.2、用户登录
  用户登录界面使用视屏作为页面背景,通过输入用户名及密码进行系统登录。系统的用户名及密码在系统后台统一进行注册。
    4.3、扫码绑定
    用户登录成功后将自动跳转至扫码界面,跳转界面后会对该移动设备进行权限访问,用户需要同意所有权限才能正常使用该系统。授权后进行二维码扫描。此时需要对路灯上的二维码进行扫码,通过扫码得到该路灯的设备信息,从而在主界面中可查看该路灯的其他信息。
    4.4、设备定位
   此模块中将对该扫描设备进行设备定位,观察其设备所在的具体位置,并能够查看当前地方的天气环境。此处的设计也是为后来的管理方便,对每一个路灯设备能够全方位的进行查看。
    4.5、设备状态
   此模块将对所在设备的所有信息进行实时查看,有电压、电流、功率、功率因子、总耗电量、光照度、路灯开光状态及路灯耗能所产生的二氧化碳量。
     4.6、历史查询
   此模块是对该路灯所有数据的历史查询,通过对历史数据的查询可分析出该设备在本周、本月及本年的所有用电量情况。这样就能够合理的对路灯用电量进行管理。
     4.7、设备控制
   此模块是对路灯的远程控制,共分为三个模式分别为:终端联控模式、分段定时模式及自动调光模式。三种模式分别对应三种不同的路灯控制,可远程也可自动,充分达到了用电量的控制。
     4.8、画像分析
   此模块涉及了大数据AI分析功能,将华为云机器学习服务分析的数据结果下发至该系统,系统对其数据进行图文的可视化展示,清晰的可以查看到城市区域日常用电情况及地方用电情况(由于数据量较少,所以设计的是本人七月份的个人用电画像)。
     4.9、系统设置
   系统设置功能共分为以下几点:系统设置、修改密码、关于我们、系统更新及退出登录。
    5、核心代码
    数据获取核心代码如下:
  1. <div align="left"><font style="font-size: 12pt">public class NetConnectHeaderDataJSON {
  2.    public static final String REQUEST_TYPE_GET = "GET";
  3.    public static final String REQUEST_TYPE_POST = "POST";
  4.    public static final String REQUEST_TYPE_PUT = "PUT";
  5.    /**
  6.     * 请求URL并返回内容
  7.     *
  8.     * @param method
  9.     *            方式get post
  10.     * @param url
  11.     *            地址
  12.     * @param param
  13.     *            参数
  14.     * @return json
  15.     */
  16.    public static String request(Context context, String method, String url, String app_id, String token,
  17.                                  List<NameValuePair> param) throws Exception {
  18.       HttpResponse response;
  19.       String result_https="";
  20.       if (method.equals(REQUEST_TYPE_GET)){
  21.          String ps ="";
  22.          if (param != null) {
  23.             List<NameValuePair> param2 = new ArrayList<NameValuePair>();
  24.             for (int i = 0; i < param.size(); i++) {
  25.                if (i>0) ps+="&";
  26.                String key = param.get(i).getName();
  27.                String value =param.get(i)
  28.                      .getValue();
  29.                ps+=key+"="+value;
  30.             }
  31.              ps = URLEncodedUtils.format(param2, HTTP.UTF_8);
  32.             // 通过url创建对象
  33.             if (url.indexOf("?") > 0) {
  34.                url += "&" + ps;
  35.             } else {
  36.                url += "?" + ps;
  37.             }
  38.          }
  39.          SSLSocketFactory ssl=DataManager.GETSSLinitHttpClientBook(context);
  40.          HttpClient httpClient = new DefaultHttpClient();
  41.          if (ssl != null) {
  42.             Scheme sch = new Scheme("https", ssl, 443);
  43.          httpClient.getConnectionManager().getSchemeRegistry().register(sch);
  44.          }
  45.          HttpGet request = new HttpGet(url);
  46.          request.setHeader("app_key",app_id);
  47.          request.setHeader("Authorization","Bearer "+token);
  48.          request.setHeader("Content-Type","application/json");
  49. //       request.setEntity(new UrlEncodedFormEntity(param));
  50.          // 发起请求,获取回应,自封装接口,详见附录
  51.           response = httpClient.execute(request);
  52.          HttpEntity httpEntity = response.getEntity();
  53.          // 得到一些数据
  54.          // 通过EntityUtils并指定编码方式取到返回的数据
  55.          StatusLine statusLine = response.getStatusLine();
  56.          statusLine.getProtocolVersion();
  57.          int statusCode = statusLine.getStatusCode();
  58.          if (statusCode == 200) {
  59.             result_https = (EntityUtils.toString(httpEntity, "utf-8"));
  60.          }else{
  61.             result_https=""+statusCode;
  62.          }
  63.       }else if(method.equals(REQUEST_TYPE_POST)){
  64.          SSLSocketFactory ssl=DataManager.GETSSLinitHttpClientBook(context);
  65.          HttpClient httpClient = new DefaultHttpClient();
  66.          if (ssl != null) {
  67.             Scheme sch = new Scheme("https", ssl, 443);
  68.           httpClient.getConnectionManager().getSchemeRegistry().register(sch);
  69.          }
  70.          HttpPost request = new HttpPost(url);
  71.          if (param != null) {
  72.             String JsonData="";
  73.             String startdata="{";
  74.             String enddata="}";
  75.             for (int i = 0; i < param.size(); i++) {
  76.                String key = param.get(i).getName();
  77.                String values = param.get(i).getValue();
  78. //             param2.add(new BasicNameValuePair(key, values));
  79.                if (i>0)
  80.                   JsonData+=",";
  81.                if (key.equals("timeout"))
  82.                   JsonData+="""+key+"":"+values;
  83.                else
  84.                   JsonData+="""+key+"":""+values+""";
  85.             }
  86.             //传入的是json格式的数据
  87.             JsonData=startdata+JsonData+enddata;
  88.             request.setEntity(new StringEntity(JsonData, HTTP.UTF_8));
  89.          }
  90.          request.setHeader("app_key",app_id);
  91.          request.setHeader("Authorization","Bearer "+token);
  92.          request.setHeader("Content-Type","application/json");
  93.          response = httpClient.execute(request);
  94.          HttpEntity httpEntity = response.getEntity();
  95.          // 通过EntityUtils并指定编码方式取到返回的数据
  96.          StatusLine statusLine = response.getStatusLine();
  97.          int statusCode = statusLine.getStatusCode();
  98.          if (statusCode == 200) {
  99.             result_https = (EntityUtils.toString(httpEntity, "utf-8"));
  100.          }else{
  101.             result_https=""+statusCode;
  102.          }
  103.       }else if(method.equals(REQUEST_TYPE_PUT)){
  104.          SSLSocketFactory ssl=DataManager.GETSSLinitHttpClientBook(context);
  105.          HttpClient httpClient = new DefaultHttpClient();
  106.          if (ssl != null) {
  107.             Scheme sch = new Scheme("https", ssl, 443);
  108.          httpClient.getConnectionManager().getSchemeRegistry().register(sch);
  109.          }
  110.          HttpPut request = new HttpPut(url);
  111.          if (param != null) {
  112.             String JsonData="";
  113.             String startdata="{";
  114.             String enddata="}";
  115.             for (int i = 0; i < param.size(); i++) {
  116.                String key = param.get(i).getName();
  117.                String values = param.get(i).getValue();
  118.                if (i>0)
  119.                   JsonData+=",";
  120.                if (key.equals("timeout"))
  121.                   JsonData+="""+key+"":"+values;
  122.                else
  123.                   JsonData+="""+key+"":""+values+""";
  124.             }
  125.             //传入的是json格式的数据
  126.             JsonData=startdata+JsonData+enddata;
  127.             request.setEntity(new StringEntity(JsonData, HTTP.UTF_8));
  128.          }
  129.          //
  130.          request.setHeader("app_key",app_id);
  131.          request.setHeader("Authorization","Bearer "+token);
  132.          request.setHeader("Content-Type","application/json");
  133.          response = httpClient.execute(request);
  134.          HttpEntity httpEntity = response.getEntity();
  135.          // 通过EntityUtils并指定编码方式取到返回的数据
  136.          StatusLine statusLine = response.getStatusLine();
  137.          int statusCode = statusLine.getStatusCode();
  138.          if (statusCode == 204) {
  139.             result_https = (EntityUtils.toString(httpEntity, "utf-8"));
  140.          }else{
  141.             result_https=""+statusCode;
  142.          }
  143.       }
  144.       else{
  145.          Log.i("method==", ".....");
  146.       }
  147.       return result_https;
  148.    }
  149. }[/mw_shl_code]</font></div><div align="left">    SDK调用核心代码如下:</div><div align="left">[mw_shl_code=java,true]/**
  150. * 方法必须重写
  151. */
  152. @Override
  153. public void onResume() {
  154.     super.onResume();
  155.     mapView.onResume();
  156. }
  157. /**
  158. * 方法必须重写
  159. */
  160. @Override
  161. public void onPause() {
  162.     super.onPause();
  163.     mapView.onPause();
  164. }
  165. /**
  166. * 方法必须重写
  167. */
  168. @Override
  169. public void onSaveInstanceState(Bundle outState) {
  170.     super.onSaveInstanceState(outState);
  171.     mapView.onSaveInstanceState(outState);
  172. }
  173. /**
  174. * 方法必须重写
  175. */
  176. @Override
  177. public void onDestroy() {
  178.     super.onDestroy();
  179.     mapView.onDestroy();
  180.     if (mTimerTask != null) {
  181.         mTimerTask.cancel();
  182.         mTimerTask = null;
  183.     }
  184.     try {
  185.         mTimer.cancel();
  186.     } catch (Throwable e) {
  187.         e.printStackTrace();
  188.     }
  189.     deactivate();
  190. }
  191. @Override
  192. public void onMapClick(LatLng latLng) {
  193. }
  194. @Override
  195. public void onMapLoaded() {
  196.     aMap.moveCamera(CameraUpdateFactory.zoomTo(zoomLevel));
  197. }
  198. @Override
  199. public void activate(OnLocationChangedListener onLocationChangedListener) {
  200.     mListener = onLocationChangedListener;
  201.     startlocation();
  202. }
  203. @Override
  204. public void deactivate() {
  205.     mListener = null;
  206.     if (mLocationClient != null) {
  207.         mLocationClient.stopLocation();
  208.         mLocationClient.onDestroy();
  209.     }
  210.     mLocationClient = null;
  211. }
  212. /**
  213. * 开始定位。
  214. */
  215. private void startlocation() {
  216.     if (mLocationClient == null) {
  217.         mLocationClient = new AMapLocationClient(getActivity());
  218.         mLocationOption = new AMapLocationClientOption();
  219.         // 设置定位监听
  220.         mLocationClient.setLocationListener(this);
  221.         // 设置为高精度定位模式
  222.   mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);
  223.         //设置为单次定位
  224.         mLocationOption.setNeedAddress(true);
  225.         mLocationOption.setOnceLocation(true);
  226.         // 设置定位参数
  227.         mLocationClient.setLocationOption(mLocationOption);
  228.         mLocationClient.startLocation();
  229.     } else {
  230.         mLocationClient.startLocation();
  231.     }
  232. }
  233. @Override
  234. public void onLocationChanged(AMapLocation aMapLocation) {
  235.     if (mListener != null && aMapLocation != null) {
  236.         if (mTimerTask != null) {
  237.             mTimerTask.cancel();
  238.             mTimerTask = null;
  239.         }
  240.         if (aMapLocation != null && aMapLocation.getErrorCode() == 0) {
  241.             LatLng mylocation = new LatLng(Absoult.Latitude, Absoult.Longitude);
  242.             changeCamera(CameraUpdateFactory.newLatLngZoom(mylocation,zoomLevel),null);
  243.             SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  244.             Date date = new Date(aMapLocation.getTime());
  245.             mTextViewCity.setText(aMapLocation.getCountry()+aMapLocation.getProvince()+aMapLocation.getCity());
  246.             mTextViewAddress.setText(aMapLocation.getAddress());
  247.             cityName = aMapLocation.getCity().substring(0,aMapLocation.getCity().length()-1);
  248.             initWaetherData(cityName);
  249.             aMapLocation.getAddress();//地址,如果option中设置isNeedAddress为false,则没有此结果,网络定位结果中会有地址信息,GPS定位不返回地址信息。
  250.             aMapLocation.getCountry();//国家信息
  251.             aMapLocation.getProvince();//省信息
  252.             aMapLocation.getCity();//城市信息
  253.             aMapLocation.getDistrict();//城区信息
  254.             aMapLocation.getStreet();//街道信息
  255.             aMapLocation.getStreetNum();//街道门牌号信息
  256.             aMapLocation.getCityCode();//城市编码
  257.             aMapLocation.getAdCode();//地区编码
  258.             df.format(date);//定位时间
  259.             if (locMarker==null){
  260.                 addLocationMarker(mylocation);
  261.             }else {
  262.                 locMarker.setPosition(mylocation);
  263.             }
  264.             if (ac!=null){
  265.                 ac.setCenter(mylocation);
  266.             }
  267.             if (c!=null){
  268.                 c.setCenter(mylocation);
  269.             }
  270.             if (d!=null){
  271.                 d.setCenter(mylocation);
  272.             }
  273.         } else {
  274.             String errText = "定位失败," + aMapLocation.getErrorCode() + ": "
  275.                     + aMapLocation.getErrorInfo();
  276.             Log.e("AmapErr", errText);
  277.         }
  278.     }
  279. }
  280. /**
  281. * 添加坐标点,这里可以添加任意坐标点位置
  282. * @param mylocation
  283. */
  284. private void addLocationMarker(LatLng mylocation) {
  285.     float accuracy = (float) ((mylocation.longitude/mylocation.latitude ));
  286.     if (locMarker == null) {
  287.         locMarker = addMarker(mylocation);
  288.         if (ac==null){
  289.             ac = aMap.addCircle(new CircleOptions().center(mylocation)
  290.                     .fillColor(Color.argb(0, 98 ,189, 255)).radius(accuracy)
  291.                     .strokeColor(Color.argb(0, 98, 198, 255)).strokeWidth(0));
  292.         }
  293.         if (c==null){
  294.             c = aMap.addCircle(new CircleOptions().center(mylocation)
  295.                     .fillColor(Color.argb(0, 98, 198, 255))
  296.                     .radius(accuracy).strokeColor(Color.argb(0,98, 198, 255))
  297.                     .strokeWidth(0));
  298.         }
  299.         if (d==null){
  300.             d = aMap.addCircle(new CircleOptions().center(mylocation)
  301.                     .fillColor(Color.argb(0, 98, 198, 255))
  302.                     .radius(accuracy).strokeColor(Color.argb(0,98, 198, 255))
  303.                     .strokeWidth(0));
  304.         }
  305.     } else {
  306.         locMarker.setPosition(mylocation);
  307.         ac.setCenter(mylocation);
  308.         ac.setRadius(accuracy);
  309.         c.setCenter(mylocation);
  310.         c.setRadius(accuracy);
  311.         d.setCenter(mylocation);
  312.         d.setRadius(accuracy);
  313.     }
  314.     handle.postDelayed(rb,0);
  315.     handle1.postDelayed(rb1,800);
  316.     handle2.postDelayed(rb2,1600);
  317. }
  318. /**
  319. * 位置波纹扩散动画
  320. * @param ac
  321. */
  322. private void Scalecircle1(final Circle ac) {
  323.     ValueAnimator vm = ValueAnimator.ofFloat(0,(float)ac.getRadius());
  324.     vm.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  325.         @Override
  326.         public void onAnimationUpdate(ValueAnimator animation) {
  327.             float curent = (float) animation.getAnimatedValue();
  328.             ac.setRadius(curent);
  329.             aMap.invalidate();
  330.         }
  331.     });
  332.     ValueAnimator vm1 = ValueAnimator.ofInt(160,0);
  333.     vm1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  334.         @Override
  335.         public void onAnimationUpdate(ValueAnimator animation) {
  336.             int color = (int) animation.getAnimatedValue();
  337.             ac.setFillColor(Color.argb(color, 98, 198, 255));
  338.             aMap.invalidate();
  339.         }
  340.     });
  341.     vm.setRepeatCount(Integer.MAX_VALUE);
  342.     vm.setRepeatMode(ValueAnimator.RESTART);
  343.     vm1.setRepeatCount(Integer.MAX_VALUE);
  344.     vm1.setRepeatMode(ValueAnimator.RESTART);
  345.     AnimatorSet set = new AnimatorSet();
  346.     set.play(vm).with(vm1);
  347.     set.setDuration(2500);
  348.     set.setInterpolator(interpolator1);
  349.     set.start();
  350. }
  351. private final Interpolator interpolator1 = new LinearInterpolator();
  352. Runnable rb = new Runnable() {
  353.     @Override
  354.     public void run() {
  355.         Scalecircle1(ac);
  356.     }
  357. };
  358. Handler handle =new Handler();
  359. Runnable rb1 = new Runnable() {
  360.     @Override
  361.     public void run() {
  362.         Scalecircle1(c);
  363.     }
  364. };
  365. Handler handle1 =new Handler();
  366. Runnable rb2 = new Runnable() {
  367.     @Override
  368.     public void run() {
  369.         Scalecircle1(d);
  370.     }
  371. };
  372. Handler handle2 =new Handler();[/mw_shl_code]</div><div align="left">    图表数据展示核心代码如下:</div><div align="left">[mw_shl_code=java,true]public class ChartService {
  373.    private GraphicalView mGraphicalView;
  374.    private XYMultipleSeriesDataset multipleSeriesDataset;// 数据集容器
  375.    private XYMultipleSeriesRenderer multipleSeriesRenderer;// 渲染器容器
  376.    private XYSeries mSeries;// 单条曲线数据集
  377.    private XYSeriesRenderer mRenderer;// 单条曲线渲染器
  378.    private Context context;
  379.    private Double xx ;
  380.    int aa = 100;
  381.    public ChartService(Context context) {
  382.       this.context = context;
  383.    }
  384.    /**
  385.     * 获取图表
  386.     *
  387.     * @return
  388.     */
  389.    public GraphicalView getGraphicalView() {
  390.       mGraphicalView = ChartFactory.getCubeLineChartView(context,
  391.             multipleSeriesDataset, multipleSeriesRenderer, 0.1f);
  392.       return mGraphicalView;
  393.    }
  394.    /**
  395.     * 获取数据集,及xy坐标的集合
  396.     *
  397.     * @param curveTitle
  398.     */
  399.    public void setXYMultipleSeriesDataset(String curveTitle) {
  400.       multipleSeriesDataset = new XYMultipleSeriesDataset();
  401.       mSeries = new XYSeries(curveTitle);
  402.       multipleSeriesDataset.addSeries(mSeries);
  403.    }
  404.    /**
  405.     * 获取渲染器
  406.     *
  407.     * @param maxX
  408.     *            x轴最大值
  409.     * @param maxY
  410.     *            y轴最大值
  411.     * @param chartTitle
  412.     *            曲线的标题
  413.     * @param xTitle
  414.     *            x轴标题
  415.     * @param yTitle
  416.     *            y轴标题
  417.     * @param axeColor
  418.     *            坐标轴颜色
  419.     * @param labelColor
  420.     *            标题颜色
  421.     * @param curveColor
  422.     *            曲线颜色
  423.     * @param gridColor
  424.     *            网格颜色
  425.     */
  426.    public void setXYMultipleSeriesRenderer(double maxX, double maxY,
  427.          String chartTitle, String xTitle, String yTitle, int axeColor,
  428.          int labelColor, int curveColor, int gridColor) {
  429.       multipleSeriesRenderer = new XYMultipleSeriesRenderer();
  430.       if (chartTitle != null) {
  431.          multipleSeriesRenderer.setChartTitle(chartTitle);
  432.       }
  433.       multipleSeriesRenderer.setXTitle(xTitle);
  434.       multipleSeriesRenderer.setYTitle(yTitle);
  435.       multipleSeriesRenderer.setRange(new double[] { 0, maxX, 0, maxY });//xy轴的范围
  436.       multipleSeriesRenderer.setLabelsColor(labelColor);
  437.       multipleSeriesRenderer.setXLabels(5);
  438.       multipleSeriesRenderer.setYLabels(10);
  439.       multipleSeriesRenderer.setXLabelsAlign(Align.RIGHT);
  440.       multipleSeriesRenderer.setYLabelsAlign(Align.RIGHT);
  441.       multipleSeriesRenderer.setAxisTitleTextSize(35);
  442.       multipleSeriesRenderer.setChartTitleTextSize(35);
  443.       multipleSeriesRenderer.setLabelsTextSize(35);
  444.       multipleSeriesRenderer.setLegendTextSize(35);
  445.       multipleSeriesRenderer.setPointSize(5f);//曲线描点尺寸
  446.       multipleSeriesRenderer.setFitLegend(true);
  447.       multipleSeriesRenderer.setMargins(new int[] { 80, 80, 80, 80 });
  448.       multipleSeriesRenderer.setShowGrid(true);
  449.       multipleSeriesRenderer.setPanEnabled(false, false);//允许X轴可拉动
  450.       multipleSeriesRenderer.setZoomEnabled(false, false);
  451.       multipleSeriesRenderer.setAxesColor(axeColor);
  452.       multipleSeriesRenderer.setGridColor(gridColor);
  453.       multipleSeriesRenderer.setBackgroundColor(Color.WHITE);//背景色
  454.       multipleSeriesRenderer.setMarginsColor(Color.WHITE);//边距背景色,默认背景色为黑色,这里修改为白色
  455.       mRenderer = new XYSeriesRenderer();
  456.       mRenderer.setLineWidth(3f);
  457.       mRenderer.setColor(curveColor);
  458.       mRenderer.setPointStrokeWidth(5f);
  459.       mRenderer.setPointStyle(PointStyle.CIRCLE);//描点风格,可以为圆点,方形点等等
  460.       multipleSeriesRenderer.addSeriesRenderer(mRenderer);
  461.    }
  462.    /**
  463.     * 根据新加的数据,更新曲线,只能运行在主线程
  464.     *
  465.     * @param x
  466.     *            新加点的x坐标
  467.     * @param y
  468.     *            新加点的y坐标
  469.     */
  470.    public void updateChart(double x, double y) {
  471.       mSeries.add(x, y);
  472.       if (x>aa) {
  473.          aa += 1;
  474.          multipleSeriesRenderer.setXAxisMax(aa);// 设置X最大值
  475.          multipleSeriesRenderer.setXAxisMin(aa - 100);// 设置X最小值
  476.       }
  477.       mGraphicalView.repaint();//此处也可以调用invalidate()
  478.    }
  479.    /**
  480.     * 添加新的数据,多组,更新曲线,只能运行在主线程
  481.     * @param xList
  482.     * @param yList
  483.     */
  484.    public void updateChart(List<Double> xList, List<Double> yList) {
  485.       for (int i = 0; i < xList.size(); i++) {
  486.          xx = xList.get(i);
  487.          mSeries.add(xx, yList.get(i));
  488.       }
  489.       mGraphicalView.repaint();//此处也可以调用invalidate()
  490.    }
  491. }[/mw_shl_code]</div><div align="left">    日历数据查询核心代码:</div><div align="left">[mw_shl_code=java,true]public abstract class NCalendar extends FrameLayout implements NestedScrollingParent, OnCalendarStateChangedListener, OnDateChangedListener, OnMonthAnimatorListener {
  492.     protected WeekCalendar weekCalendar;
  493.     protected MonthCalendar monthCalendar;
  494.     protected int weekHeight;//周日历的高度
  495.     protected int monthHeight;//月日历的高度,是日历整个的高
  496.     protected int childLayoutLayoutTop;//onLayout中,定位的高度,
  497.     protected int STATE;//默认月
  498.     private int lastSate;//防止状态监听重复回调
  499.     private OnCalendarChangedListener onCalendarChangedListener;
  500.     //NCalendar内部包含的直接子view,直接子view并不一定是NestScrillChild
  501.     protected ChildLayout childLayout;    protected Rect monthRect;//月日历大小的矩形
  502.     protected Rect weekRect;//周日历大小的矩形 ,用于判断点击事件是否在日历的范
  503.     private boolean isWeekHold;//是否需要周状态定住
  504.     public NCalendar(@NonNull Context context) {
  505.         this(context, null);
  506.     }
  507.     public NCalendar(@NonNull Context context, @Nullable AttributeSet attrs) {
  508.         this(context, attrs, 0);
  509.     }
  510.     public NCalendar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  511.         super(context, attrs, defStyleAttr);
  512.         setMotionEventSplittingEnabled(false);
  513.         Attrs attrss = AttrsUtil.getAttrs(context, attrs);
  514.         int duration = attrss.duration;
  515.         monthHeight = attrss.monthCalendarHeight;
  516.         STATE = attrss.defaultCalendar;
  517.         weekHeight = monthHeight / 5;
  518.         isWeekHold = attrss.isWeekHold;
  519.         weekCalendar = new WeekCalendar(context, attrss);
  520.         monthCalendar = new MonthCalendar(context, attrss, duration, this);
  521.         childLayout = new ChildLayout(getContext(), attrs, monthHeight, duration, this);
  522.         monthCalendar.setOnDateChangedListener(this);
  523.         weekCalendar.setOnDateChangedListener(this);
  524.         childLayout.setBackgroundColor(attrss.bgChildColor);
  525.         setCalenadrState(STATE);
  526.         childLayoutLayoutTop = STATE == Attrs.WEEK ? weekHeight : monthHeight;
  527.         post(new Runnable() {
  528.             @Override
  529.             public void run() {
  530.                 monthRect = new Rect(0, 0, monthCalendar.getWidth(), monthCalendar.getHeight());
  531.                 weekRect = new Rect(0, 0, weekCalendar.getWidth(), weekCalendar.getHeight());
  532.                 monthCalendar.setY(STATE == Attrs.MONTH ? 0 : getMonthYOnWeekState());
  533.                 childLayout.setY(STATE == Attrs.MONTH ? monthHeight : weekHeight);
  534.             }
  535.         });
  536.     }
  537.     /**
  538.      * 根据ChildLayout的自动滑动结束的状态来设置月周日历的状态
  539.      * 依据ChildLayout的状态来设置日历的状态
  540.      *
  541.      * @param isMonthState
  542.      */
  543.     @Override
  544.     public void onCalendarStateChanged(boolean isMonthState) {
  545.         if (isMonthState) {
  546.             setCalenadrState(Attrs.MONTH);
  547.         } else {
  548.             setCalenadrState(Attrs.WEEK);
  549.         }
  550.     }
  551.     /**
  552.      * xml文件加载结束,添加月,周日历和child到NCalendar中
  553.      */
  554.     @Override
  555.     protected void onFinishInflate() {
  556.         super.onFinishInflate();
  557.         if (getChildCount() != 1) {
  558.             throw new RuntimeException("NCalendar中的只能有一个直接子view");
  559.         }
  560.         childLayout.addView(getChildAt(0), new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
  561.         addView(monthCalendar, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, monthHeight));
  562.         addView(weekCalendar, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, weekHeight));
  563.         addView(childLayout, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
  564.     }
  565.     @Override
  566.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  567.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  568.         ViewGroup.LayoutParams childLayoutLayoutParams = childLayout.getLayoutParams();
  569.         childLayoutLayoutParams.height = getMeasuredHeight() - weekHeight;
  570.         //需要再调一次父类的方法?真机不调用首次高度不对,为何?
  571.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  572.     }
  573.     @Override
  574.     protected void onLayout(boolean changed, int l, int t, int r, int b) {
  575.         //super.onLayout(changed, l, t, r, b); //调用父类的该方法会造成 快速滑动月日历同时快速上滑recyclerview造成月日历的残影
  576.         int measuredWidth = getMeasuredWidth();
  577.         weekCalendar.layout(0, 0, measuredWidth, weekHeight);
  578.         monthCalendar.layout(0, 0, measuredWidth, monthHeight);
  579.         childLayout.layout(0, childLayoutLayoutTop, measuredWidth, childLayout.getMeasuredHeight() + childLayoutLayoutTop);
  580.     }
  581.     /**
  582.      * 根据条件设置日历的月周状态,并回调状态变化
  583.      *
  584.      * @param state
  585.      */
  586.     private void setCalenadrState(int state) {
  587.         if (state == Attrs.WEEK) {
  588.             STATE = Attrs.WEEK;
  589.             weekCalendar.setVisibility(VISIBLE);
  590.         } else {
  591.             STATE = Attrs.MONTH;
  592.             weekCalendar.setVisibility(INVISIBLE);
  593.         }
  594.         if (onCalendarChangedListener != null && lastSate != state) {
  595.             onCalendarChangedListener.onCalendarStateChanged(STATE == Attrs.MONTH);
  596.         }
  597.         lastSate = state;
  598.     }
  599.     /**
  600.      * 自动滑动到适当的位置
  601.      */
  602.     private void autoScroll() {
  603.         float childLayoutY = childLayout.getY();
  604.         if (STATE == Attrs.MONTH && monthHeight - childLayoutY < weekHeight) {
  605.             onAutoToMonthState();
  606.         } else if (STATE == Attrs.MONTH && monthHeight - childLayoutY >= weekHeight) {
  607.             onAutoToWeekState();
  608.         } else if (STATE == Attrs.WEEK && childLayoutY < weekHeight * 2) {
  609.             onAutoToWeekState();
  610.         } else if (STATE == Attrs.WEEK && childLayoutY >= weekHeight * 2) {
  611.             onAutoToMonthState();
  612.         }
  613.     }
  614.     /**
  615.      * 月日历和周日历的日期变化回调,每次日期变化都会回调,用于不同状态下,设置另一个日历的日期
  616.      *
  617.      * @param baseCalendar 日历本身
  618.      * @param localDate    当前选中的时间
  619.      * @param isDraw       是否绘制 此处选择都绘制,默认不选中,不适用鱼月周切换
  620.      */
  621.     @Override
  622.     public void onDateChanged(BaseCalendar baseCalendar, LocalDate localDate, boolean isDraw) {
  623.         if (baseCalendar instanceof MonthCalendar && STATE == Attrs.MONTH) {
  624.             //月日历变化,改变周的选中
  625.             weekCalendar.jumpDate(localDate, true);
  626.             if (onCalendarChangedListener != null) {
  627.                 onCalendarChangedListener.onCalendarDateChanged(Util.getNDate(localDate));
  628.             }
  629.         } else if (baseCalendar instanceof WeekCalendar && STATE == Attrs.WEEK) {
  630.             //周日历变化,改变月的选中
  631.             monthCalendar.jumpDate(localDate, true);
  632.             post(new Runnable() {
  633.                 @Override
  634.                 public void run() {
  635.                     //此时需要根据月日历的选中日期调整Y值
  636.                     // post是因为在前面得到当前view是再post中完成,如果不这样直接获取位置信息,会出现老的数据,不能获取正确的数据
  637.                     monthCalendar.setY(getMonthYOnWeekState());
  638.                 }
  639.             });
  640.             if (onCalendarChangedListener != null) {
  641.                 onCalendarChangedListener.onCalendarDateChanged(Util.getNDate(localDate));
  642.             }
  643.         }
  644.     }
  645.     @Override
  646.     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
  647.         return true;
  648.     }
  649.     @Override
  650.     public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
  651.         //跟随手势滑动
  652.         gestureMove(dy, consumed);
  653.     }
  654.     @Override
  655.     public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
  656.         //只有都在都在周状态下,才允许子View Fling滑动
  657.         return !(childLayout.isWeekState() && monthCalendar.isWeekState());
  658.     }
  659.     @Override
  660.     public void onStopNestedScroll(View target) {
  661.         //该方法手指抬起的时候回调,此时根据此刻的位置,自动滑动到相应的状态,
  662.         //如果已经在对应的位置上,则不执行动画,
  663.         if (monthCalendar.isMonthState() && childLayout.isMonthState() && STATE == Attrs.WEEK) {
  664.             setCalenadrState(Attrs.MONTH);
  665.         } else if (monthCalendar.isWeekState() && childLayout.isWeekState() && STATE == Attrs.MONTH) {
  666.             setCalenadrState(Attrs.WEEK);
  667.         } else if (!childLayout.isMonthState() && !childLayout.isWeekState()) {
  668.             //不是周状态也不是月状态时,自动滑动
  669.             autoScroll();
  670.         }
  671.     }
  672.     /**
  673.      * 手势滑动的逻辑,做了简单处理,2种状态,都以ChildLayout滑动的状态判断
  674.      * 1、向上滑动未到周状态
  675.      * 2、向下滑动未到月状态
  676.      *
  677.      * @param dy
  678.      * @param consumed
  679.      */
  680.     protected void gestureMove(int dy, int[] consumed) {
  681.         float monthCalendarY = monthCalendar.getY();
  682.         float childLayoutY = childLayout.getY();
  683.         if (dy > 0 && !childLayout.isWeekState()) {
  684.             monthCalendar.setY(-getGestureMonthUpOffset(dy) + monthCalendarY);
  685.             childLayout.setY(-getGestureChildUpOffset(dy) + childLayoutY);
  686.             if (consumed != null) consumed[1] = dy;
  687.         } else if (dy < 0 && isWeekHold && childLayout.isWeekState()) {
  688.             //不操作,
  689.         } else if
  690. (dy < 0 && !childLayout.isMonthState() && !childLayout.canScrollVertically(-1)) {
  691.             monthCalendar.setY(getGestureMonthDownOffset(dy) + monthCalendarY);
  692.             childLayout.setY(getGestureChildDownOffset(dy) + childLayoutY);
  693.             if (consumed != null) consumed[1] = dy;
  694.         }
  695.         onSetWeekVisible(dy);
  696.     }
  697.     /**
  698.      * 月日历执行自动滑动动画的回调
  699.      * 用来控制周日历的显示还是隐藏
  700.      *
  701.      * @param offset
  702.      */
  703.     @Override
  704.     public void onMonthAnimatorChanged(int offset) {
  705.         onSetWeekVisible(offset);
  706.     }
  707.     private int dowmY;
  708.     private int downX;
  709.     private int lastY;//上次的y
  710.     private int verticalY = 50;//竖直方向上滑动的临界值,大于这个值认为是竖直滑动
  711.     private boolean isFirstScroll = true; //第一次手势滑动,因为第一次滑动的偏移量大于verticalY,会出现猛的一划,这里只对第一次滑动做处理
  712.     @Override
  713.     public boolean onInterceptTouchEvent(MotionEvent ev) {
  714.         switch (ev.getAction()) {
  715.             case MotionEvent.ACTION_DOWN:
  716.                 dowmY = (int) ev.getY();
  717.                 downX = (int) ev.getX();
  718.                 lastY = dowmY;
  719.                 break;
  720.             case MotionEvent.ACTION_MOVE:
  721.                 int y = (int) ev.getY();
  722.                 int absY = Math.abs(dowmY - y);
  723.                 boolean inCalendar = isInCalendar(downX, dowmY);
  724.                 if (absY > verticalY && inCalendar) {
  725.                     //onInterceptTouchEvent返回true,触摸事件交给当前的onTouchEvent处理
  726.                     return true;
  727.                 }
  728.                 break;
  729.         }
  730.         return super.onInterceptTouchEvent(ev);
  731.     }
  732.     @Override
  733.     public boolean onTouchEvent(MotionEvent event) {
  734.         switch (event.getAction()) {
  735.             case MotionEvent.ACTION_MOVE:
  736.                 int y = (int) event.getY();
  737.                 int dy = lastY - y;
  738.                 if (isFirstScroll) {
  739.                     // 防止第一次的偏移量过大
  740.                     if (dy > verticalY) {
  741.                         dy = dy - verticalY;
  742.                     } else if (dy < -verticalY) {
  743.                         dy = dy + verticalY;
  744.                     }
  745.                     isFirstScroll = false;
  746.                 }
  747.                 // 跟随手势滑动
  748.                 gestureMove(dy, null);
  749.                 lastY = y;
  750.                 break;
  751.             case MotionEvent.ACTION_UP:
  752.             case MotionEvent.ACTION_CANCEL:
  753.                 isFirstScroll = true;
  754.                 autoScroll();
  755.                 break;
  756.         }
  757.         return true;
  758.     }
  759.     /**
  760.      * 点击事件是否在日历的范围内
  761.      *
  762.      * @param x
  763.      * @param y
  764.      * @return
  765.      */
  766.     private boolean isInCalendar(int x, int y) {
  767.         if (STATE == Attrs.MONTH) {
  768.             return monthRect.contains(x, y);
  769.         } else {
  770.             return weekRect.contains(x, y);
  771.         }
  772.     }
  773.     /**
  774.      * 滑动过界处理 ,如果大于最大距离就返回最大距离
  775.      *
  776.      * @param offset    当前滑动的距离
  777.      * @param maxOffset 当前滑动的最大距离
  778.      * @return
  779.      */
  780.     protected float getOffset(float offset, float maxOffset) {
  781.         if (offset > maxOffset) {
  782.             return maxOffset;
  783.         }
  784.         return offset;
  785.     }
  786.     /**
  787.      * 自动回到月的状态 包括月日历和chilayout
  788.      */
  789.     protected abstract void onAutoToMonthState();
  790.     /**
  791.      * 自动回到周的状态 包括月日历和chilayout
  792.      */
  793.     protected abstract void onAutoToWeekState();
  794.     /**
  795.      * 设置weekCalendar的显示隐藏,该方法会在手势滑动和自动滑动的的时候一直回调
  796.      */
  797.     protected abstract void onSetWeekVisible(int dy);
  798.     /**
  799.      * 周状态下 月日历的getY 是个负值
  800.      * 用于在 周状态下日期改变设置正确的y值
  801.      *
  802.      * @return
  803.      */
  804.     protected abstract float getMonthYOnWeekState();
  805.     /**
  806.      * 月日历根据手势向上移动的距离
  807.      *
  808.      * @param dy 当前滑动的距离 dy>0向上滑动,dy<0向下滑动
  809.      * @return 根据不同日历的交互,计算不同的滑动值
  810.      */
  811.     protected abstract float getGestureMonthUpOffset(int dy);
  812.     /**
  813.      * Child根据手势向上移动的距离
  814.      *
  815.      * @param dy 当前滑动的距离 dy>0向上滑动,dy<0向下滑动
  816.      * @return 根据不同日历的交互,计算不同的滑动值
  817.      */
  818.     protected abstract float getGestureChildUpOffset(int dy);
  819.     /**
  820.      * 月日历根据手势向下移动的距离
  821.      *
  822.      * @param dy 当前滑动的距离 dy>0向上滑动,dy<0向下滑动
  823.      * @return 根据不同日历的交互,计算不同的滑动值
  824.      */
  825.     protected abstract float getGestureMonthDownOffset(int dy);
  826.     /**
  827.      * Child根据手势向下移动的距离
  828.      *
  829.      * @param dy 当前滑动的距离 dy>0向上滑动,dy<0向下滑动
  830.      * @return 根据不同日历的交互,计算不同的滑动值
  831.      */
  832.     protected abstract float getGestureChildDownOffset(int dy);
  833.     /**
  834.      * 跳转日期
  835.      *
  836.      * @param formatDate
  837.      */
  838.     public void jumpDate( String formatDate) {
  839.         if (STATE == Attrs.MONTH) {
  840.             monthCalendar.jumpDate(formatDate);
  841.         } else {
  842.             weekCalendar.jumpDate(formatDate);
  843.         }
  844.     }
  845.     /**
  846.      * 日历初始化的日期
  847.      * @param formatDate
  848.      */
  849.     public void setInitializeDate(String formatDate) {
  850.         monthCalendar.setInitializeDate(formatDate);
  851.         weekCalendar.setInitializeDate(formatDate);
  852.     }
  853.     /**
  854.      * 回到今天
  855.      */
  856.     public void toToday() {
  857.         if (STATE == Attrs.MONTH) {
  858.             monthCalendar.toToday();
  859.         } else {
  860.             weekCalendar.toToday();
  861.         }
  862.     }
  863.     /**
  864.      * 自动滑动到周视图
  865.      */
  866.     public void toWeek() {
  867.         if (STATE == Attrs.MONTH) {
  868.             onAutoToWeekState();
  869.         }
  870.     }
  871.     /**
  872.      * 自动滑动到月视图
  873.      */
  874.     public void toMonth() {
  875.         if (STATE == Attrs.WEEK) {
  876.             onAutoToMonthState();
  877.         }
  878.     }
  879.     /**
  880.      * 设置小圆点
  881.      *
  882.      * @param pointList
  883.      */
  884.     public void setPointList(List<String> pointList) {
  885.         weekCalendar.setPointList(pointList);
  886.         monthCalendar.setPointList(pointList);
  887.     }
  888.     /**
  889.      * 获取当前日历的状态
  890.      * Attrs.MONTH==月视图    Attrs.WEEK==周视图
  891.      *
  892.      * @return
  893.      */
  894.     public int getState() {
  895.         return STATE;
  896.     }
  897.     /**
  898.      * 下一页
  899.      */
  900.     public void toNextPager() {
  901.         if (STATE == Attrs.MONTH) {
  902.             monthCalendar.toNextPager();
  903.         } else {
  904.             weekCalendar.toNextPager();
  905.         }
  906.     }
  907.     /**
  908.      * 上一页
  909.      */
  910.     public void toLastPager() {
  911.         if (STATE == Attrs.MONTH) {
  912.             monthCalendar.toLastPager();
  913.         } else {
  914.             weekCalendar.toLastPager();
  915.         }
  916.     }
  917.     /**
  918.      * 设置日期区间
  919.      *
  920.      * @param startFormatDate
  921.      * @param endFormatDate
  922.      */
  923.     public void setDateInterval(String startFormatDate, String endFormatDate) {
  924.         monthCalendar.setDateInterval(startFormatDate, endFormatDate);
  925.         weekCalendar.setDateInterval(startFormatDate, endFormatDate);
  926.     }
  927.     /**
  928.      * 日期、状态回调
  929.      *
  930.      * @param onCalendarChangedListener
  931.      */
  932.     public void setOnCalendarChangedListener(OnCalendarChangedListener onCalendarChangedListener) {
  933.         this.onCalendarChangedListener = onCalendarChangedListener;
  934.     }
  935.     /**
  936.      * 点击不可用的日期回调
  937.      *
  938.      * @param onClickDisableDateListener
  939.      */
  940.     public void setOnClickDisableDateListener(OnClickDisableDateListener onClickDisableDateListener) {
  941.         monthCalendar.setOnClickDisableDateListener(onClickDisableDateListener);
  942.         weekCalendar.setOnClickDisableDateListener(onClickDisableDateListener);
  943.     }
  944. }</div>
复制代码


rzegkly  版主

发表于 2021-4-1 08:42:15

电路图发一下?
回复

使用道具 举报

星辰大海666  见习技师

发表于 2021-4-7 10:38:08

能否加个v?请教一些问题
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail